[desktop] Fix watch related IPC - Part 1/x (#1463)
This commit is contained in:
commit
bb9c384a52
22 changed files with 537 additions and 542 deletions
|
@ -10,7 +10,7 @@
|
|||
|
||||
import type { FSWatcher } from "chokidar";
|
||||
import { ipcMain } from "electron/main";
|
||||
import type { ElectronFile, FILE_PATH_TYPE, WatchMapping } from "../types/ipc";
|
||||
import type { ElectronFile, FILE_PATH_TYPE, FolderWatch } from "../types/ipc";
|
||||
import {
|
||||
selectDirectory,
|
||||
showUploadDirsDialog,
|
||||
|
@ -242,13 +242,13 @@ export const attachFSWatchIPCHandlers = (watcher: FSWatcher) => {
|
|||
|
||||
ipcMain.handle(
|
||||
"updateWatchMappingSyncedFiles",
|
||||
(_, folderPath: string, files: WatchMapping["syncedFiles"]) =>
|
||||
(_, folderPath: string, files: FolderWatch["syncedFiles"]) =>
|
||||
updateWatchMappingSyncedFiles(folderPath, files),
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"updateWatchMappingIgnoredFiles",
|
||||
(_, folderPath: string, files: WatchMapping["ignoredFiles"]) =>
|
||||
(_, folderPath: string, files: FolderWatch["ignoredFiles"]) =>
|
||||
updateWatchMappingIgnoredFiles(folderPath, files),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { FSWatcher } from "chokidar";
|
||||
import ElectronLog from "electron-log";
|
||||
import { WatchMapping, WatchStoreType } from "../../types/ipc";
|
||||
import { FolderWatch, WatchStoreType } from "../../types/ipc";
|
||||
import { watchStore } from "../stores/watch.store";
|
||||
|
||||
export const addWatchMapping = async (
|
||||
|
@ -28,7 +28,7 @@ export const addWatchMapping = async (
|
|||
setWatchMappings(watchMappings);
|
||||
};
|
||||
|
||||
function isMappingPresent(watchMappings: WatchMapping[], folderPath: string) {
|
||||
function isMappingPresent(watchMappings: FolderWatch[], folderPath: string) {
|
||||
const watchMapping = watchMappings?.find(
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
|
@ -59,7 +59,7 @@ export const removeWatchMapping = async (
|
|||
|
||||
export function updateWatchMappingSyncedFiles(
|
||||
folderPath: string,
|
||||
files: WatchMapping["syncedFiles"],
|
||||
files: FolderWatch["syncedFiles"],
|
||||
): void {
|
||||
const watchMappings = getWatchMappings();
|
||||
const watchMapping = watchMappings.find(
|
||||
|
@ -76,7 +76,7 @@ export function updateWatchMappingSyncedFiles(
|
|||
|
||||
export function updateWatchMappingIgnoredFiles(
|
||||
folderPath: string,
|
||||
files: WatchMapping["ignoredFiles"],
|
||||
files: FolderWatch["ignoredFiles"],
|
||||
): void {
|
||||
const watchMappings = getWatchMappings();
|
||||
const watchMapping = watchMappings.find(
|
||||
|
|
|
@ -45,7 +45,7 @@ import type {
|
|||
AppUpdateInfo,
|
||||
ElectronFile,
|
||||
FILE_PATH_TYPE,
|
||||
WatchMapping,
|
||||
FolderWatch,
|
||||
} from "./types/ipc";
|
||||
|
||||
// - General
|
||||
|
@ -220,18 +220,18 @@ const addWatchMapping = (
|
|||
const removeWatchMapping = (folderPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("removeWatchMapping", folderPath);
|
||||
|
||||
const getWatchMappings = (): Promise<WatchMapping[]> =>
|
||||
const getWatchMappings = (): Promise<FolderWatch[]> =>
|
||||
ipcRenderer.invoke("getWatchMappings");
|
||||
|
||||
const updateWatchMappingSyncedFiles = (
|
||||
folderPath: string,
|
||||
files: WatchMapping["syncedFiles"],
|
||||
files: FolderWatch["syncedFiles"],
|
||||
): Promise<void> =>
|
||||
ipcRenderer.invoke("updateWatchMappingSyncedFiles", folderPath, files);
|
||||
|
||||
const updateWatchMappingIgnoredFiles = (
|
||||
folderPath: string,
|
||||
files: WatchMapping["ignoredFiles"],
|
||||
files: FolderWatch["ignoredFiles"],
|
||||
): Promise<void> =>
|
||||
ipcRenderer.invoke("updateWatchMappingIgnoredFiles", folderPath, files);
|
||||
|
||||
|
|
|
@ -5,6 +5,20 @@
|
|||
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
*/
|
||||
|
||||
export interface FolderWatch {
|
||||
rootFolderName: string;
|
||||
uploadStrategy: number;
|
||||
folderPath: string;
|
||||
syncedFiles: FolderWatchSyncedFile[];
|
||||
ignoredFiles: string[];
|
||||
}
|
||||
|
||||
export interface FolderWatchSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
collectionID: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors that have special semantics on the web side.
|
||||
*
|
||||
|
@ -52,22 +66,8 @@ export interface ElectronFile {
|
|||
arrayBuffer: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
interface WatchMappingSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
collectionID: number;
|
||||
}
|
||||
|
||||
export interface WatchMapping {
|
||||
rootFolderName: string;
|
||||
uploadStrategy: number;
|
||||
folderPath: string;
|
||||
syncedFiles: WatchMappingSyncedFile[];
|
||||
ignoredFiles: string[];
|
||||
}
|
||||
|
||||
export interface WatchStoreType {
|
||||
mappings: WatchMapping[];
|
||||
mappings: FolderWatch[];
|
||||
}
|
||||
|
||||
export enum FILE_PATH_TYPE {
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { getAccountsURL } from "@ente/shared/network/api";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import { EnteMenuItem } from "components/Menu/EnteMenuItem";
|
||||
import WatchFolder from "components/WatchFolder";
|
||||
import { WatchFolder } from "components/WatchFolder";
|
||||
import isElectron from "is-electron";
|
||||
import { getAccountsToken } from "services/userService";
|
||||
import { getDownloadAppMessage } from "utils/ui";
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
savePublicCollectionUploaderName,
|
||||
} from "services/publicCollectionService";
|
||||
import uploadManager from "services/upload/uploadManager";
|
||||
import watchFolderService from "services/watchFolder/watchFolderService";
|
||||
import watchFolderService from "services/watch";
|
||||
import { NotificationAttributes } from "types/Notification";
|
||||
import { Collection } from "types/collection";
|
||||
import {
|
||||
|
|
364
web/apps/photos/src/components/WatchFolder.tsx
Normal file
364
web/apps/photos/src/components/WatchFolder.tsx
Normal file
|
@ -0,0 +1,364 @@
|
|||
import {
|
||||
FlexWrapper,
|
||||
HorizontalFlex,
|
||||
SpaceBetweenFlex,
|
||||
VerticallyCentered,
|
||||
} from "@ente/shared/components/Container";
|
||||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||
import OverflowMenu from "@ente/shared/components/OverflowMenu/menu";
|
||||
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import DoNotDisturbOutlinedIcon from "@mui/icons-material/DoNotDisturbOutlined";
|
||||
import FolderCopyOutlinedIcon from "@mui/icons-material/FolderCopyOutlined";
|
||||
import FolderOpenIcon from "@mui/icons-material/FolderOpen";
|
||||
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import UploadStrategyChoiceModal from "components/Upload/UploadStrategyChoiceModal";
|
||||
import { PICKED_UPLOAD_TYPE, UPLOAD_STRATEGY } from "constants/upload";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import watchFolderService from "services/watch";
|
||||
import { WatchMapping } from "types/watchFolder";
|
||||
import { getImportSuggestion } from "utils/upload";
|
||||
|
||||
interface WatchFolderProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
||||
const [mappings, setMappings] = useState<WatchMapping[]>([]);
|
||||
const [inputFolderPath, setInputFolderPath] = useState("");
|
||||
const [choiceModalOpen, setChoiceModalOpen] = useState(false);
|
||||
const appContext = useContext(AppContext);
|
||||
|
||||
const electron = globalThis.electron;
|
||||
|
||||
useEffect(() => {
|
||||
if (!electron) return;
|
||||
watchFolderService.getWatchMappings().then((m) => setMappings(m));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
appContext.watchFolderFiles &&
|
||||
appContext.watchFolderFiles.length > 0
|
||||
) {
|
||||
handleFolderDrop(appContext.watchFolderFiles);
|
||||
appContext.setWatchFolderFiles(null);
|
||||
}
|
||||
}, [appContext.watchFolderFiles]);
|
||||
|
||||
const handleFolderDrop = async (folders: FileList) => {
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const folder: any = folders[i];
|
||||
const path = (folder.path as string).replace(/\\/g, "/");
|
||||
if (await watchFolderService.isFolder(path)) {
|
||||
await addFolderForWatching(path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addFolderForWatching = async (path: string) => {
|
||||
if (!electron) return;
|
||||
|
||||
setInputFolderPath(path);
|
||||
const files = await electron.getDirFiles(path);
|
||||
const analysisResult = getImportSuggestion(
|
||||
PICKED_UPLOAD_TYPE.FOLDERS,
|
||||
files,
|
||||
);
|
||||
if (analysisResult.hasNestedFolders) {
|
||||
setChoiceModalOpen(true);
|
||||
} else {
|
||||
handleAddWatchMapping(UPLOAD_STRATEGY.SINGLE_COLLECTION, path);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddFolderClick = async () => {
|
||||
await handleFolderSelection();
|
||||
};
|
||||
|
||||
const handleFolderSelection = async () => {
|
||||
const folderPath = await watchFolderService.selectFolder();
|
||||
if (folderPath) {
|
||||
await addFolderForWatching(folderPath);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddWatchMapping = async (
|
||||
uploadStrategy: UPLOAD_STRATEGY,
|
||||
folderPath?: string,
|
||||
) => {
|
||||
folderPath = folderPath || inputFolderPath;
|
||||
await watchFolderService.addWatchMapping(
|
||||
folderPath.substring(folderPath.lastIndexOf("/") + 1),
|
||||
folderPath,
|
||||
uploadStrategy,
|
||||
);
|
||||
setInputFolderPath("");
|
||||
setMappings(await watchFolderService.getWatchMappings());
|
||||
};
|
||||
|
||||
const handleRemoveWatchMapping = (mapping: WatchMapping) => {
|
||||
watchFolderService
|
||||
.mappingsAfterRemovingFolder(mapping.folderPath)
|
||||
.then((ms) => setMappings(ms));
|
||||
};
|
||||
|
||||
const closeChoiceModal = () => setChoiceModalOpen(false);
|
||||
|
||||
const uploadToSingleCollection = () => {
|
||||
closeChoiceModal();
|
||||
handleAddWatchMapping(UPLOAD_STRATEGY.SINGLE_COLLECTION);
|
||||
};
|
||||
|
||||
const uploadToMultipleCollection = () => {
|
||||
closeChoiceModal();
|
||||
handleAddWatchMapping(UPLOAD_STRATEGY.COLLECTION_PER_FOLDER);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
PaperProps={{ sx: { height: "448px", maxWidth: "414px" } }}
|
||||
>
|
||||
<DialogTitleWithCloseButton
|
||||
onClose={onClose}
|
||||
sx={{ "&&&": { padding: "32px 16px 16px 24px" } }}
|
||||
>
|
||||
{t("WATCHED_FOLDERS")}
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent sx={{ flex: 1 }}>
|
||||
<Stack spacing={1} p={1.5} height={"100%"}>
|
||||
<MappingList
|
||||
mappings={mappings}
|
||||
handleRemoveWatchMapping={handleRemoveWatchMapping}
|
||||
/>
|
||||
<Button
|
||||
fullWidth
|
||||
color="accent"
|
||||
onClick={handleAddFolderClick}
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: "8px",
|
||||
}}
|
||||
></span>
|
||||
{t("ADD_FOLDER")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<UploadStrategyChoiceModal
|
||||
open={choiceModalOpen}
|
||||
onClose={closeChoiceModal}
|
||||
uploadToSingleCollection={uploadToSingleCollection}
|
||||
uploadToMultipleCollection={uploadToMultipleCollection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MappingsContainer = styled(Box)(() => ({
|
||||
height: "278px",
|
||||
overflow: "auto",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
}));
|
||||
|
||||
const NoMappingsContainer = styled(VerticallyCentered)({
|
||||
textAlign: "left",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: "32px",
|
||||
});
|
||||
|
||||
const EntryContainer = styled(Box)({
|
||||
marginLeft: "12px",
|
||||
marginRight: "6px",
|
||||
marginBottom: "12px",
|
||||
});
|
||||
|
||||
interface MappingListProps {
|
||||
mappings: WatchMapping[];
|
||||
handleRemoveWatchMapping: (value: WatchMapping) => void;
|
||||
}
|
||||
|
||||
const MappingList: React.FC<MappingListProps> = ({
|
||||
mappings,
|
||||
handleRemoveWatchMapping,
|
||||
}) => {
|
||||
return mappings.length === 0 ? (
|
||||
<NoMappingsContent />
|
||||
) : (
|
||||
<MappingsContainer>
|
||||
{mappings.map((mapping) => {
|
||||
return (
|
||||
<MappingEntry
|
||||
key={mapping.rootFolderName}
|
||||
mapping={mapping}
|
||||
handleRemoveMapping={handleRemoveWatchMapping}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</MappingsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const NoMappingsContent: React.FC = () => {
|
||||
return (
|
||||
<NoMappingsContainer>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="large" fontWeight={"bold"}>
|
||||
{t("NO_FOLDERS_ADDED")}
|
||||
</Typography>
|
||||
<Typography py={0.5} variant={"small"} color="text.muted">
|
||||
{t("FOLDERS_AUTOMATICALLY_MONITORED")}
|
||||
</Typography>
|
||||
<Typography variant={"small"} color="text.muted">
|
||||
<FlexWrapper gap={1}>
|
||||
<CheckmarkIcon />
|
||||
{t("UPLOAD_NEW_FILES_TO_ENTE")}
|
||||
</FlexWrapper>
|
||||
</Typography>
|
||||
<Typography variant={"small"} color="text.muted">
|
||||
<FlexWrapper gap={1}>
|
||||
<CheckmarkIcon />
|
||||
{t("REMOVE_DELETED_FILES_FROM_ENTE")}
|
||||
</FlexWrapper>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</NoMappingsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckmarkIcon: React.FC = () => {
|
||||
return (
|
||||
<CheckIcon
|
||||
fontSize="small"
|
||||
sx={{
|
||||
display: "inline",
|
||||
fontSize: "15px",
|
||||
|
||||
color: (theme) => theme.palette.secondary.main,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface MappingEntryProps {
|
||||
mapping: WatchMapping;
|
||||
handleRemoveMapping: (mapping: WatchMapping) => void;
|
||||
}
|
||||
|
||||
const MappingEntry: React.FC<MappingEntryProps> = ({
|
||||
mapping,
|
||||
handleRemoveMapping,
|
||||
}) => {
|
||||
const appContext = React.useContext(AppContext);
|
||||
|
||||
const stopWatching = () => {
|
||||
handleRemoveMapping(mapping);
|
||||
};
|
||||
|
||||
const confirmStopWatching = () => {
|
||||
appContext.setDialogMessage({
|
||||
title: t("STOP_WATCHING_FOLDER"),
|
||||
content: t("STOP_WATCHING_DIALOG_MESSAGE"),
|
||||
close: {
|
||||
text: t("CANCEL"),
|
||||
variant: "secondary",
|
||||
},
|
||||
proceed: {
|
||||
action: stopWatching,
|
||||
text: t("YES_STOP"),
|
||||
variant: "critical",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SpaceBetweenFlex>
|
||||
<HorizontalFlex>
|
||||
{mapping &&
|
||||
mapping.uploadStrategy === UPLOAD_STRATEGY.SINGLE_COLLECTION ? (
|
||||
<Tooltip title={t("UPLOADED_TO_SINGLE_COLLECTION")}>
|
||||
<FolderOpenIcon />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title={t("UPLOADED_TO_SEPARATE_COLLECTIONS")}>
|
||||
<FolderCopyOutlinedIcon />
|
||||
</Tooltip>
|
||||
)}
|
||||
<EntryContainer>
|
||||
<EntryHeading mapping={mapping} />
|
||||
<Typography color="text.muted" variant="small">
|
||||
{mapping.folderPath}
|
||||
</Typography>
|
||||
</EntryContainer>
|
||||
</HorizontalFlex>
|
||||
<MappingEntryOptions confirmStopWatching={confirmStopWatching} />
|
||||
</SpaceBetweenFlex>
|
||||
);
|
||||
};
|
||||
|
||||
interface EntryHeadingProps {
|
||||
mapping: WatchMapping;
|
||||
}
|
||||
|
||||
const EntryHeading: React.FC<EntryHeadingProps> = ({ mapping }) => {
|
||||
const appContext = useContext(AppContext);
|
||||
return (
|
||||
<FlexWrapper gap={1}>
|
||||
<Typography>{mapping.rootFolderName}</Typography>
|
||||
{appContext.isFolderSyncRunning &&
|
||||
watchFolderService.isMappingSyncInProgress(mapping) && (
|
||||
<CircularProgress size={12} />
|
||||
)}
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
interface MappingEntryOptionsProps {
|
||||
confirmStopWatching: () => void;
|
||||
}
|
||||
|
||||
const MappingEntryOptions: React.FC<MappingEntryOptionsProps> = ({
|
||||
confirmStopWatching,
|
||||
}) => {
|
||||
return (
|
||||
<OverflowMenu
|
||||
menuPaperProps={{
|
||||
sx: {
|
||||
backgroundColor: (theme) =>
|
||||
theme.colors.background.elevated2,
|
||||
},
|
||||
}}
|
||||
ariaControls={"watch-mapping-option"}
|
||||
triggerButtonIcon={<MoreHorizIcon />}
|
||||
>
|
||||
<OverflowMenuOption
|
||||
color="critical"
|
||||
onClick={confirmStopWatching}
|
||||
startIcon={<DoNotDisturbOutlinedIcon />}
|
||||
>
|
||||
{t("STOP_WATCHING")}
|
||||
</OverflowMenuOption>
|
||||
</OverflowMenu>
|
||||
);
|
||||
};
|
|
@ -1,152 +0,0 @@
|
|||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||
import { Button, Dialog, DialogContent, Stack } from "@mui/material";
|
||||
import UploadStrategyChoiceModal from "components/Upload/UploadStrategyChoiceModal";
|
||||
import { PICKED_UPLOAD_TYPE, UPLOAD_STRATEGY } from "constants/upload";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import watchFolderService from "services/watchFolder/watchFolderService";
|
||||
import { WatchMapping } from "types/watchFolder";
|
||||
import { getImportSuggestion } from "utils/upload";
|
||||
import { MappingList } from "./mappingList";
|
||||
|
||||
interface Iprops {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function WatchFolder({ open, onClose }: Iprops) {
|
||||
const [mappings, setMappings] = useState<WatchMapping[]>([]);
|
||||
const [inputFolderPath, setInputFolderPath] = useState("");
|
||||
const [choiceModalOpen, setChoiceModalOpen] = useState(false);
|
||||
const appContext = useContext(AppContext);
|
||||
|
||||
const electron = globalThis.electron;
|
||||
|
||||
useEffect(() => {
|
||||
if (!electron) return;
|
||||
watchFolderService.getWatchMappings().then((m) => setMappings(m));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
appContext.watchFolderFiles &&
|
||||
appContext.watchFolderFiles.length > 0
|
||||
) {
|
||||
handleFolderDrop(appContext.watchFolderFiles);
|
||||
appContext.setWatchFolderFiles(null);
|
||||
}
|
||||
}, [appContext.watchFolderFiles]);
|
||||
|
||||
const handleFolderDrop = async (folders: FileList) => {
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const folder: any = folders[i];
|
||||
const path = (folder.path as string).replace(/\\/g, "/");
|
||||
if (await watchFolderService.isFolder(path)) {
|
||||
await addFolderForWatching(path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addFolderForWatching = async (path: string) => {
|
||||
if (!electron) return;
|
||||
|
||||
setInputFolderPath(path);
|
||||
const files = await electron.getDirFiles(path);
|
||||
const analysisResult = getImportSuggestion(
|
||||
PICKED_UPLOAD_TYPE.FOLDERS,
|
||||
files,
|
||||
);
|
||||
if (analysisResult.hasNestedFolders) {
|
||||
setChoiceModalOpen(true);
|
||||
} else {
|
||||
handleAddWatchMapping(UPLOAD_STRATEGY.SINGLE_COLLECTION, path);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddFolderClick = async () => {
|
||||
await handleFolderSelection();
|
||||
};
|
||||
|
||||
const handleFolderSelection = async () => {
|
||||
const folderPath = await watchFolderService.selectFolder();
|
||||
if (folderPath) {
|
||||
await addFolderForWatching(folderPath);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddWatchMapping = async (
|
||||
uploadStrategy: UPLOAD_STRATEGY,
|
||||
folderPath?: string,
|
||||
) => {
|
||||
folderPath = folderPath || inputFolderPath;
|
||||
await watchFolderService.addWatchMapping(
|
||||
folderPath.substring(folderPath.lastIndexOf("/") + 1),
|
||||
folderPath,
|
||||
uploadStrategy,
|
||||
);
|
||||
setInputFolderPath("");
|
||||
setMappings(await watchFolderService.getWatchMappings());
|
||||
};
|
||||
|
||||
const handleRemoveWatchMapping = async (mapping: WatchMapping) => {
|
||||
await watchFolderService.removeWatchMapping(mapping.folderPath);
|
||||
setMappings(await watchFolderService.getWatchMappings());
|
||||
};
|
||||
|
||||
const closeChoiceModal = () => setChoiceModalOpen(false);
|
||||
|
||||
const uploadToSingleCollection = () => {
|
||||
closeChoiceModal();
|
||||
handleAddWatchMapping(UPLOAD_STRATEGY.SINGLE_COLLECTION);
|
||||
};
|
||||
|
||||
const uploadToMultipleCollection = () => {
|
||||
closeChoiceModal();
|
||||
handleAddWatchMapping(UPLOAD_STRATEGY.COLLECTION_PER_FOLDER);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
PaperProps={{ sx: { height: "448px", maxWidth: "414px" } }}
|
||||
>
|
||||
<DialogTitleWithCloseButton
|
||||
onClose={onClose}
|
||||
sx={{ "&&&": { padding: "32px 16px 16px 24px" } }}
|
||||
>
|
||||
{t("WATCHED_FOLDERS")}
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent sx={{ flex: 1 }}>
|
||||
<Stack spacing={1} p={1.5} height={"100%"}>
|
||||
<MappingList
|
||||
mappings={mappings}
|
||||
handleRemoveWatchMapping={handleRemoveWatchMapping}
|
||||
/>
|
||||
<Button
|
||||
fullWidth
|
||||
color="accent"
|
||||
onClick={handleAddFolderClick}
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: "8px",
|
||||
}}
|
||||
></span>
|
||||
{t("ADD_FOLDER")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<UploadStrategyChoiceModal
|
||||
open={choiceModalOpen}
|
||||
onClose={closeChoiceModal}
|
||||
uploadToSingleCollection={uploadToSingleCollection}
|
||||
uploadToMultipleCollection={uploadToMultipleCollection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import { CircularProgress, Typography } from "@mui/material";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import watchFolderService from "services/watchFolder/watchFolderService";
|
||||
import { WatchMapping } from "types/watchFolder";
|
||||
|
||||
interface Iprops {
|
||||
mapping: WatchMapping;
|
||||
}
|
||||
|
||||
export function EntryHeading({ mapping }: Iprops) {
|
||||
const appContext = useContext(AppContext);
|
||||
return (
|
||||
<FlexWrapper gap={1}>
|
||||
<Typography>{mapping.rootFolderName}</Typography>
|
||||
{appContext.isFolderSyncRunning &&
|
||||
watchFolderService.isMappingSyncInProgress(mapping) && (
|
||||
<CircularProgress size={12} />
|
||||
)}
|
||||
</FlexWrapper>
|
||||
);
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
import {
|
||||
HorizontalFlex,
|
||||
SpaceBetweenFlex,
|
||||
} from "@ente/shared/components/Container";
|
||||
import FolderCopyOutlinedIcon from "@mui/icons-material/FolderCopyOutlined";
|
||||
import FolderOpenIcon from "@mui/icons-material/FolderOpen";
|
||||
import { Tooltip, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import React from "react";
|
||||
import { WatchMapping } from "types/watchFolder";
|
||||
import { EntryContainer } from "../styledComponents";
|
||||
|
||||
import { UPLOAD_STRATEGY } from "constants/upload";
|
||||
import { EntryHeading } from "./entryHeading";
|
||||
import MappingEntryOptions from "./mappingEntryOptions";
|
||||
|
||||
interface Iprops {
|
||||
mapping: WatchMapping;
|
||||
handleRemoveMapping: (mapping: WatchMapping) => void;
|
||||
}
|
||||
|
||||
export function MappingEntry({ mapping, handleRemoveMapping }: Iprops) {
|
||||
const appContext = React.useContext(AppContext);
|
||||
|
||||
const stopWatching = () => {
|
||||
handleRemoveMapping(mapping);
|
||||
};
|
||||
|
||||
const confirmStopWatching = () => {
|
||||
appContext.setDialogMessage({
|
||||
title: t("STOP_WATCHING_FOLDER"),
|
||||
content: t("STOP_WATCHING_DIALOG_MESSAGE"),
|
||||
close: {
|
||||
text: t("CANCEL"),
|
||||
variant: "secondary",
|
||||
},
|
||||
proceed: {
|
||||
action: stopWatching,
|
||||
text: t("YES_STOP"),
|
||||
variant: "critical",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SpaceBetweenFlex>
|
||||
<HorizontalFlex>
|
||||
{mapping &&
|
||||
mapping.uploadStrategy === UPLOAD_STRATEGY.SINGLE_COLLECTION ? (
|
||||
<Tooltip title={t("UPLOADED_TO_SINGLE_COLLECTION")}>
|
||||
<FolderOpenIcon />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title={t("UPLOADED_TO_SEPARATE_COLLECTIONS")}>
|
||||
<FolderCopyOutlinedIcon />
|
||||
</Tooltip>
|
||||
)}
|
||||
<EntryContainer>
|
||||
<EntryHeading mapping={mapping} />
|
||||
<Typography color="text.muted" variant="small">
|
||||
{mapping.folderPath}
|
||||
</Typography>
|
||||
</EntryContainer>
|
||||
</HorizontalFlex>
|
||||
<MappingEntryOptions confirmStopWatching={confirmStopWatching} />
|
||||
</SpaceBetweenFlex>
|
||||
);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import { t } from "i18next";
|
||||
|
||||
import OverflowMenu from "@ente/shared/components/OverflowMenu/menu";
|
||||
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
|
||||
import DoNotDisturbOutlinedIcon from "@mui/icons-material/DoNotDisturbOutlined";
|
||||
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
|
||||
|
||||
interface Iprops {
|
||||
confirmStopWatching: () => void;
|
||||
}
|
||||
|
||||
export default function MappingEntryOptions({ confirmStopWatching }: Iprops) {
|
||||
return (
|
||||
<OverflowMenu
|
||||
menuPaperProps={{
|
||||
sx: {
|
||||
backgroundColor: (theme) =>
|
||||
theme.colors.background.elevated2,
|
||||
},
|
||||
}}
|
||||
ariaControls={"watch-mapping-option"}
|
||||
triggerButtonIcon={<MoreHorizIcon />}
|
||||
>
|
||||
<OverflowMenuOption
|
||||
color="critical"
|
||||
onClick={confirmStopWatching}
|
||||
startIcon={<DoNotDisturbOutlinedIcon />}
|
||||
>
|
||||
{t("STOP_WATCHING")}
|
||||
</OverflowMenuOption>
|
||||
</OverflowMenu>
|
||||
);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { WatchMapping } from "types/watchFolder";
|
||||
import { MappingEntry } from "../mappingEntry";
|
||||
import { MappingsContainer } from "../styledComponents";
|
||||
import { NoMappingsContent } from "./noMappingsContent/noMappingsContent";
|
||||
interface Iprops {
|
||||
mappings: WatchMapping[];
|
||||
handleRemoveWatchMapping: (value: WatchMapping) => void;
|
||||
}
|
||||
|
||||
export function MappingList({ mappings, handleRemoveWatchMapping }: Iprops) {
|
||||
return mappings.length === 0 ? (
|
||||
<NoMappingsContent />
|
||||
) : (
|
||||
<MappingsContainer>
|
||||
{mappings.map((mapping) => {
|
||||
return (
|
||||
<MappingEntry
|
||||
key={mapping.rootFolderName}
|
||||
mapping={mapping}
|
||||
handleRemoveMapping={handleRemoveWatchMapping}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</MappingsContainer>
|
||||
);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import CheckIcon from "@mui/icons-material/Check";
|
||||
|
||||
export function CheckmarkIcon() {
|
||||
return (
|
||||
<CheckIcon
|
||||
fontSize="small"
|
||||
sx={{
|
||||
display: "inline",
|
||||
fontSize: "15px",
|
||||
|
||||
color: (theme) => theme.palette.secondary.main,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import { Stack, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import { NoMappingsContainer } from "../../styledComponents";
|
||||
import { CheckmarkIcon } from "./checkmarkIcon";
|
||||
|
||||
export function NoMappingsContent() {
|
||||
return (
|
||||
<NoMappingsContainer>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="large" fontWeight={"bold"}>
|
||||
{t("NO_FOLDERS_ADDED")}
|
||||
</Typography>
|
||||
<Typography py={0.5} variant={"small"} color="text.muted">
|
||||
{t("FOLDERS_AUTOMATICALLY_MONITORED")}
|
||||
</Typography>
|
||||
<Typography variant={"small"} color="text.muted">
|
||||
<FlexWrapper gap={1}>
|
||||
<CheckmarkIcon />
|
||||
{t("UPLOAD_NEW_FILES_TO_ENTE")}
|
||||
</FlexWrapper>
|
||||
</Typography>
|
||||
<Typography variant={"small"} color="text.muted">
|
||||
<FlexWrapper gap={1}>
|
||||
<CheckmarkIcon />
|
||||
{t("REMOVE_DELETED_FILES_FROM_ENTE")}
|
||||
</FlexWrapper>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</NoMappingsContainer>
|
||||
);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
import { Box } from "@mui/material";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
export const MappingsContainer = styled(Box)(() => ({
|
||||
height: "278px",
|
||||
overflow: "auto",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
}));
|
||||
|
||||
export const NoMappingsContainer = styled(VerticallyCentered)({
|
||||
textAlign: "left",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: "32px",
|
||||
});
|
||||
|
||||
export const EntryContainer = styled(Box)({
|
||||
marginLeft: "12px",
|
||||
marginRight: "6px",
|
||||
marginBottom: "12px",
|
||||
});
|
|
@ -14,7 +14,7 @@ import {
|
|||
getPublicCollectionUID,
|
||||
} from "services/publicCollectionService";
|
||||
import { getDisableCFUploadProxyFlag } from "services/userService";
|
||||
import watchFolderService from "services/watchFolder/watchFolderService";
|
||||
import watchFolderService from "services/watch";
|
||||
import { Collection } from "types/collection";
|
||||
import { EncryptedEnteFile, EnteFile } from "types/file";
|
||||
import { SetFiles } from "types/gallery";
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* @file Interface with the Node.js layer of our desktop app to provide the
|
||||
* watch folders functionality.
|
||||
*/
|
||||
|
||||
import { ensureElectron } from "@/next/electron";
|
||||
import log from "@/next/log";
|
||||
import { UPLOAD_RESULT, UPLOAD_STRATEGY } from "constants/upload";
|
||||
|
@ -12,17 +17,11 @@ import {
|
|||
WatchMappingSyncedFile,
|
||||
} from "types/watchFolder";
|
||||
import { groupFilesBasedOnCollectionID } from "utils/file";
|
||||
import { getValidFilesToUpload } from "utils/watch";
|
||||
import { removeFromCollection } from "../collectionService";
|
||||
import { getLocalFiles } from "../fileService";
|
||||
import { getParentFolderName } from "./utils";
|
||||
import {
|
||||
diskFileAddedCallback,
|
||||
diskFileRemovedCallback,
|
||||
diskFolderRemovedCallback,
|
||||
} from "./watchFolderEventHandlers";
|
||||
import { isSystemFile } from "utils/upload";
|
||||
import { removeFromCollection } from "./collectionService";
|
||||
import { getLocalFiles } from "./fileService";
|
||||
|
||||
class watchFolderService {
|
||||
class WatchFolderService {
|
||||
private eventQueue: EventQueueItem[] = [];
|
||||
private currentEvent: EventQueueItem;
|
||||
private currentlySyncedMapping: WatchMapping;
|
||||
|
@ -196,12 +195,9 @@ class watchFolderService {
|
|||
}
|
||||
}
|
||||
|
||||
async removeWatchMapping(folderPath: string) {
|
||||
try {
|
||||
await ensureElectron().removeWatchMapping(folderPath);
|
||||
} catch (e) {
|
||||
log.error("error while removing watch mapping", e);
|
||||
}
|
||||
async mappingsAfterRemovingFolder(folderPath: string) {
|
||||
await ensureElectron().removeWatchMapping(folderPath);
|
||||
return await this.getWatchMappings();
|
||||
}
|
||||
|
||||
async getWatchMappings(): Promise<WatchMapping[]> {
|
||||
|
@ -641,4 +637,104 @@ class watchFolderService {
|
|||
}
|
||||
}
|
||||
|
||||
export default new watchFolderService();
|
||||
const watchFolderService = new WatchFolderService();
|
||||
|
||||
export default watchFolderService;
|
||||
|
||||
const getParentFolderName = (filePath: string) => {
|
||||
const folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
|
||||
const folderName = folderPath.substring(folderPath.lastIndexOf("/") + 1);
|
||||
return folderName;
|
||||
};
|
||||
|
||||
async function diskFileAddedCallback(file: ElectronFile) {
|
||||
try {
|
||||
const collectionNameAndFolderPath =
|
||||
await watchFolderService.getCollectionNameAndFolderPath(file.path);
|
||||
|
||||
if (!collectionNameAndFolderPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { collectionName, folderPath } = collectionNameAndFolderPath;
|
||||
|
||||
const event: EventQueueItem = {
|
||||
type: "upload",
|
||||
collectionName,
|
||||
folderPath,
|
||||
files: [file],
|
||||
};
|
||||
watchFolderService.pushEvent(event);
|
||||
log.info(
|
||||
`added (upload) to event queue, collectionName:${event.collectionName} folderPath:${event.folderPath}, filesCount: ${event.files.length}`,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("error while calling diskFileAddedCallback", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function diskFileRemovedCallback(filePath: string) {
|
||||
try {
|
||||
const collectionNameAndFolderPath =
|
||||
await watchFolderService.getCollectionNameAndFolderPath(filePath);
|
||||
|
||||
if (!collectionNameAndFolderPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { collectionName, folderPath } = collectionNameAndFolderPath;
|
||||
|
||||
const event: EventQueueItem = {
|
||||
type: "trash",
|
||||
collectionName,
|
||||
folderPath,
|
||||
paths: [filePath],
|
||||
};
|
||||
watchFolderService.pushEvent(event);
|
||||
log.info(
|
||||
`added (trash) to event queue collectionName:${event.collectionName} folderPath:${event.folderPath} , pathsCount: ${event.paths.length}`,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("error while calling diskFileRemovedCallback", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function diskFolderRemovedCallback(folderPath: string) {
|
||||
try {
|
||||
const mappings = await watchFolderService.getWatchMappings();
|
||||
const mapping = mappings.find(
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
if (!mapping) {
|
||||
log.info(`folder not found in mappings, ${folderPath}`);
|
||||
throw Error(`Watch mapping not found`);
|
||||
}
|
||||
watchFolderService.pushTrashedDir(folderPath);
|
||||
log.info(`added trashedDir, ${folderPath}`);
|
||||
} catch (e) {
|
||||
log.error("error while calling diskFolderRemovedCallback", e);
|
||||
}
|
||||
}
|
||||
|
||||
export function getValidFilesToUpload(
|
||||
files: ElectronFile[],
|
||||
mapping: WatchMapping,
|
||||
) {
|
||||
const uniqueFilePaths = new Set<string>();
|
||||
return files.filter((file) => {
|
||||
if (!isSystemFile(file) && !isSyncedOrIgnoredFile(file, mapping)) {
|
||||
if (!uniqueFilePaths.has(file.path)) {
|
||||
uniqueFilePaths.add(file.path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function isSyncedOrIgnoredFile(file: ElectronFile, mapping: WatchMapping) {
|
||||
return (
|
||||
mapping.ignoredFiles.includes(file.path) ||
|
||||
mapping.syncedFiles.find((f) => f.path === file.path)
|
||||
);
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export const getParentFolderName = (filePath: string) => {
|
||||
const folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
|
||||
const folderName = folderPath.substring(folderPath.lastIndexOf("/") + 1);
|
||||
return folderName;
|
||||
};
|
|
@ -1,73 +0,0 @@
|
|||
import log from "@/next/log";
|
||||
import { ElectronFile } from "types/upload";
|
||||
import { EventQueueItem } from "types/watchFolder";
|
||||
import watchFolderService from "./watchFolderService";
|
||||
|
||||
export async function diskFileAddedCallback(file: ElectronFile) {
|
||||
try {
|
||||
const collectionNameAndFolderPath =
|
||||
await watchFolderService.getCollectionNameAndFolderPath(file.path);
|
||||
|
||||
if (!collectionNameAndFolderPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { collectionName, folderPath } = collectionNameAndFolderPath;
|
||||
|
||||
const event: EventQueueItem = {
|
||||
type: "upload",
|
||||
collectionName,
|
||||
folderPath,
|
||||
files: [file],
|
||||
};
|
||||
watchFolderService.pushEvent(event);
|
||||
log.info(
|
||||
`added (upload) to event queue, collectionName:${event.collectionName} folderPath:${event.folderPath}, filesCount: ${event.files.length}`,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("error while calling diskFileAddedCallback", e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function diskFileRemovedCallback(filePath: string) {
|
||||
try {
|
||||
const collectionNameAndFolderPath =
|
||||
await watchFolderService.getCollectionNameAndFolderPath(filePath);
|
||||
|
||||
if (!collectionNameAndFolderPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { collectionName, folderPath } = collectionNameAndFolderPath;
|
||||
|
||||
const event: EventQueueItem = {
|
||||
type: "trash",
|
||||
collectionName,
|
||||
folderPath,
|
||||
paths: [filePath],
|
||||
};
|
||||
watchFolderService.pushEvent(event);
|
||||
log.info(
|
||||
`added (trash) to event queue collectionName:${event.collectionName} folderPath:${event.folderPath} , pathsCount: ${event.paths.length}`,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("error while calling diskFileRemovedCallback", e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function diskFolderRemovedCallback(folderPath: string) {
|
||||
try {
|
||||
const mappings = await watchFolderService.getWatchMappings();
|
||||
const mapping = mappings.find(
|
||||
(mapping) => mapping.folderPath === folderPath,
|
||||
);
|
||||
if (!mapping) {
|
||||
log.info(`folder not found in mappings, ${folderPath}`);
|
||||
throw Error(`Watch mapping not found`);
|
||||
}
|
||||
watchFolderService.pushTrashedDir(folderPath);
|
||||
log.info(`added trashedDir, ${folderPath}`);
|
||||
} catch (e) {
|
||||
log.error("error while calling diskFolderRemovedCallback", e);
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { ElectronFile } from "types/upload";
|
||||
import { WatchMapping } from "types/watchFolder";
|
||||
import { isSystemFile } from "utils/upload";
|
||||
|
||||
function isSyncedOrIgnoredFile(file: ElectronFile, mapping: WatchMapping) {
|
||||
return (
|
||||
mapping.ignoredFiles.includes(file.path) ||
|
||||
mapping.syncedFiles.find((f) => f.path === file.path)
|
||||
);
|
||||
}
|
||||
|
||||
export function getValidFilesToUpload(
|
||||
files: ElectronFile[],
|
||||
mapping: WatchMapping,
|
||||
) {
|
||||
const uniqueFilePaths = new Set<string>();
|
||||
return files.filter((file) => {
|
||||
if (!isSystemFile(file) && !isSyncedOrIgnoredFile(file, mapping)) {
|
||||
if (!uniqueFilePaths.has(file.path)) {
|
||||
uniqueFilePaths.add(file.path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
|
@ -26,20 +26,6 @@ export interface DataStream {
|
|||
chunkCount: number;
|
||||
}
|
||||
|
||||
export interface WatchMappingSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
collectionID: number;
|
||||
}
|
||||
|
||||
export interface WatchMapping {
|
||||
rootFolderName: string;
|
||||
folderPath: string;
|
||||
uploadStrategy: UPLOAD_STRATEGY;
|
||||
syncedFiles: WatchMappingSyncedFile[];
|
||||
ignoredFiles: string[];
|
||||
}
|
||||
|
||||
export interface EventQueueItem {
|
||||
type: "upload" | "trash";
|
||||
folderPath: string;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//
|
||||
// See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
|
||||
import type { ElectronFile, WatchMapping } from "./file";
|
||||
import type { ElectronFile } from "./file";
|
||||
|
||||
export interface AppUpdateInfo {
|
||||
autoUpdatable: boolean;
|
||||
|
@ -298,16 +298,16 @@ export interface Electron {
|
|||
|
||||
removeWatchMapping: (folderPath: string) => Promise<void>;
|
||||
|
||||
getWatchMappings: () => Promise<WatchMapping[]>;
|
||||
getWatchMappings: () => Promise<FolderWatch[]>;
|
||||
|
||||
updateWatchMappingSyncedFiles: (
|
||||
folderPath: string,
|
||||
files: WatchMapping["syncedFiles"],
|
||||
files: FolderWatch["syncedFiles"],
|
||||
) => Promise<void>;
|
||||
|
||||
updateWatchMappingIgnoredFiles: (
|
||||
folderPath: string,
|
||||
files: WatchMapping["ignoredFiles"],
|
||||
files: FolderWatch["ignoredFiles"],
|
||||
) => Promise<void>;
|
||||
|
||||
// - FS legacy
|
||||
|
@ -332,3 +332,30 @@ export interface Electron {
|
|||
setToUploadCollection: (collectionName: string) => Promise<void>;
|
||||
getDirFiles: (dirPath: string) => Promise<ElectronFile[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A top level folder that was selected by the user for watching.
|
||||
*
|
||||
* The user can set up multiple such watches. Each of these can in turn be
|
||||
* syncing multiple on disk folders to one or more (dependening on the
|
||||
* {@link uploadStrategy}) Ente albums.
|
||||
*
|
||||
* This type is passed across the IPC boundary. It is persisted on the Node.js
|
||||
* side.
|
||||
*/
|
||||
export interface FolderWatch {
|
||||
rootFolderName: string;
|
||||
uploadStrategy: number;
|
||||
folderPath: string;
|
||||
syncedFiles: FolderWatchSyncedFile[];
|
||||
ignoredFiles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* An on-disk file that was synced as part of a folder watch.
|
||||
*/
|
||||
export interface FolderWatchSyncedFile {
|
||||
path: string;
|
||||
uploadedFileID: number;
|
||||
collectionID: number;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue