Cleanup
This commit is contained in:
parent
a22423d039
commit
170ea0c997
2 changed files with 115 additions and 129 deletions
|
@ -28,8 +28,8 @@ 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 watcher from "services/watch";
|
||||
import { WatchMapping as FolderWatch } from "types/watchFolder";
|
||||
import { getImportSuggestion } from "utils/upload";
|
||||
|
||||
interface WatchFolderProps {
|
||||
|
@ -38,7 +38,7 @@ interface WatchFolderProps {
|
|||
}
|
||||
|
||||
export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
||||
const [mappings, setMappings] = useState<WatchMapping[]>([]);
|
||||
const [mappings, setMappings] = useState<FolderWatch[]>([]);
|
||||
const [inputFolderPath, setInputFolderPath] = useState("");
|
||||
const [choiceModalOpen, setChoiceModalOpen] = useState(false);
|
||||
const appContext = useContext(AppContext);
|
||||
|
@ -47,7 +47,7 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (!electron) return;
|
||||
watchFolderService.getWatchMappings().then((m) => setMappings(m));
|
||||
watcher.getWatchMappings().then((m) => setMappings(m));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -64,7 +64,7 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
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)) {
|
||||
if (await watcher.isFolder(path)) {
|
||||
await addFolderForWatching(path);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
};
|
||||
|
||||
const handleFolderSelection = async () => {
|
||||
const folderPath = await watchFolderService.selectFolder();
|
||||
const folderPath = await watcher.selectFolder();
|
||||
if (folderPath) {
|
||||
await addFolderForWatching(folderPath);
|
||||
}
|
||||
|
@ -102,19 +102,18 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
folderPath?: string,
|
||||
) => {
|
||||
folderPath = folderPath || inputFolderPath;
|
||||
await watchFolderService.addWatchMapping(
|
||||
await watcher.addWatchMapping(
|
||||
folderPath.substring(folderPath.lastIndexOf("/") + 1),
|
||||
folderPath,
|
||||
uploadStrategy,
|
||||
);
|
||||
setInputFolderPath("");
|
||||
setMappings(await watchFolderService.getWatchMappings());
|
||||
setMappings(await watcher.getWatchMappings());
|
||||
};
|
||||
|
||||
const handleRemoveWatchMapping = (mapping: WatchMapping) => {
|
||||
watchFolderService
|
||||
.mappingsAfterRemovingFolder(mapping.folderPath)
|
||||
.then((ms) => setMappings(ms));
|
||||
const stopWatching = async (watch: FolderWatch) => {
|
||||
await watcher.removeWatchForFolderPath(watch.folderPath);
|
||||
setMappings(await watcher.getWatchMappings());
|
||||
};
|
||||
|
||||
const closeChoiceModal = () => setChoiceModalOpen(false);
|
||||
|
@ -144,9 +143,9 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
</DialogTitleWithCloseButton>
|
||||
<DialogContent sx={{ flex: 1 }}>
|
||||
<Stack spacing={1} p={1.5} height={"100%"}>
|
||||
<MappingList
|
||||
mappings={mappings}
|
||||
handleRemoveWatchMapping={handleRemoveWatchMapping}
|
||||
<WatchList
|
||||
watches={mappings}
|
||||
stopWatching={stopWatching}
|
||||
/>
|
||||
<Button
|
||||
fullWidth
|
||||
|
@ -174,7 +173,30 @@ export const WatchFolder: React.FC<WatchFolderProps> = ({ open, onClose }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const MappingsContainer = styled(Box)(() => ({
|
||||
interface WatchList {
|
||||
watches: FolderWatch[];
|
||||
stopWatching: (watch: FolderWatch) => void;
|
||||
}
|
||||
|
||||
const WatchList: React.FC<WatchList> = ({ watches, stopWatching }) => {
|
||||
return watches.length === 0 ? (
|
||||
<NoWatches />
|
||||
) : (
|
||||
<WatchesContainer>
|
||||
{watches.map((mapping) => {
|
||||
return (
|
||||
<WatchEntry
|
||||
key={mapping.rootFolderName}
|
||||
watch={mapping}
|
||||
stopWatching={stopWatching}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</WatchesContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const WatchesContainer = styled(Box)(() => ({
|
||||
height: "278px",
|
||||
overflow: "auto",
|
||||
"&::-webkit-scrollbar": {
|
||||
|
@ -182,45 +204,7 @@ const MappingsContainer = styled(Box)(() => ({
|
|||
},
|
||||
}));
|
||||
|
||||
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 = () => {
|
||||
const NoWatches: React.FC = () => {
|
||||
return (
|
||||
<NoMappingsContainer>
|
||||
<Stack spacing={1}>
|
||||
|
@ -247,6 +231,12 @@ const NoMappingsContent: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const NoMappingsContainer = styled(VerticallyCentered)({
|
||||
textAlign: "left",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: "32px",
|
||||
});
|
||||
|
||||
const CheckmarkIcon: React.FC = () => {
|
||||
return (
|
||||
<CheckIcon
|
||||
|
@ -254,28 +244,20 @@ const CheckmarkIcon: React.FC = () => {
|
|||
sx={{
|
||||
display: "inline",
|
||||
fontSize: "15px",
|
||||
|
||||
color: (theme) => theme.palette.secondary.main,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface MappingEntryProps {
|
||||
mapping: WatchMapping;
|
||||
handleRemoveMapping: (mapping: WatchMapping) => void;
|
||||
interface WatchEntryProps {
|
||||
watch: FolderWatch;
|
||||
stopWatching: (watch: FolderWatch) => void;
|
||||
}
|
||||
|
||||
const MappingEntry: React.FC<MappingEntryProps> = ({
|
||||
mapping,
|
||||
handleRemoveMapping,
|
||||
}) => {
|
||||
const WatchEntry: React.FC<WatchEntryProps> = ({ watch, stopWatching }) => {
|
||||
const appContext = React.useContext(AppContext);
|
||||
|
||||
const stopWatching = () => {
|
||||
handleRemoveMapping(mapping);
|
||||
};
|
||||
|
||||
const confirmStopWatching = () => {
|
||||
appContext.setDialogMessage({
|
||||
title: t("STOP_WATCHING_FOLDER"),
|
||||
|
@ -285,7 +267,7 @@ const MappingEntry: React.FC<MappingEntryProps> = ({
|
|||
variant: "secondary",
|
||||
},
|
||||
proceed: {
|
||||
action: stopWatching,
|
||||
action: () => stopWatching(watch),
|
||||
text: t("YES_STOP"),
|
||||
variant: "critical",
|
||||
},
|
||||
|
@ -295,8 +277,7 @@ const MappingEntry: React.FC<MappingEntryProps> = ({
|
|||
return (
|
||||
<SpaceBetweenFlex>
|
||||
<HorizontalFlex>
|
||||
{mapping &&
|
||||
mapping.uploadStrategy === UPLOAD_STRATEGY.SINGLE_COLLECTION ? (
|
||||
{watch.uploadStrategy === UPLOAD_STRATEGY.SINGLE_COLLECTION ? (
|
||||
<Tooltip title={t("UPLOADED_TO_SINGLE_COLLECTION")}>
|
||||
<FolderOpenIcon />
|
||||
</Tooltip>
|
||||
|
@ -306,41 +287,43 @@ const MappingEntry: React.FC<MappingEntryProps> = ({
|
|||
</Tooltip>
|
||||
)}
|
||||
<EntryContainer>
|
||||
<EntryHeading mapping={mapping} />
|
||||
<EntryHeading watch={watch} />
|
||||
<Typography color="text.muted" variant="small">
|
||||
{mapping.folderPath}
|
||||
{watch.folderPath}
|
||||
</Typography>
|
||||
</EntryContainer>
|
||||
</HorizontalFlex>
|
||||
<MappingEntryOptions confirmStopWatching={confirmStopWatching} />
|
||||
<EntryOptions {...{ confirmStopWatching }} />
|
||||
</SpaceBetweenFlex>
|
||||
);
|
||||
};
|
||||
|
||||
const EntryContainer = styled(Box)({
|
||||
marginLeft: "12px",
|
||||
marginRight: "6px",
|
||||
marginBottom: "12px",
|
||||
});
|
||||
|
||||
interface EntryHeadingProps {
|
||||
mapping: WatchMapping;
|
||||
watch: FolderWatch;
|
||||
}
|
||||
|
||||
const EntryHeading: React.FC<EntryHeadingProps> = ({ mapping }) => {
|
||||
const EntryHeading: React.FC<EntryHeadingProps> = ({ watch }) => {
|
||||
const appContext = useContext(AppContext);
|
||||
return (
|
||||
<FlexWrapper gap={1}>
|
||||
<Typography>{mapping.rootFolderName}</Typography>
|
||||
<Typography>{watch.rootFolderName}</Typography>
|
||||
{appContext.isFolderSyncRunning &&
|
||||
watchFolderService.isMappingSyncInProgress(mapping) && (
|
||||
<CircularProgress size={12} />
|
||||
)}
|
||||
watcher.isSyncingWatch(watch) && <CircularProgress size={12} />}
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
interface MappingEntryOptionsProps {
|
||||
interface EntryOptionsProps {
|
||||
confirmStopWatching: () => void;
|
||||
}
|
||||
|
||||
const MappingEntryOptions: React.FC<MappingEntryOptionsProps> = ({
|
||||
confirmStopWatching,
|
||||
}) => {
|
||||
const EntryOptions: React.FC<EntryOptionsProps> = ({ confirmStopWatching }) => {
|
||||
return (
|
||||
<OverflowMenu
|
||||
menuPaperProps={{
|
||||
|
|
|
@ -13,7 +13,7 @@ import uploadManager from "services/upload/uploadManager";
|
|||
import { Collection } from "types/collection";
|
||||
import { EncryptedEnteFile } from "types/file";
|
||||
import { ElectronFile, FileWithCollection } from "types/upload";
|
||||
import { WatchMapping, WatchMappingSyncedFile } from "types/watchFolder";
|
||||
import { WatchMappingSyncedFile } from "types/watchFolder";
|
||||
import { groupFilesBasedOnCollectionID } from "utils/file";
|
||||
import { isSystemFile } from "utils/upload";
|
||||
import { removeFromCollection } from "./collectionService";
|
||||
|
@ -50,7 +50,7 @@ interface WatchEvent {
|
|||
class WatchFolderService {
|
||||
private eventQueue: WatchEvent[] = [];
|
||||
private currentEvent: WatchEvent;
|
||||
private currentlySyncedMapping: WatchMapping;
|
||||
private currentlySyncedMapping: FolderWatch;
|
||||
private trashingDirQueue: string[] = [];
|
||||
private isEventRunning: boolean = false;
|
||||
private uploadRunning: boolean = false;
|
||||
|
@ -94,6 +94,14 @@ class WatchFolderService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are currently processing an event for the given
|
||||
* {@link watch}
|
||||
*/
|
||||
isSyncingWatch(watch: FolderWatch) {
|
||||
return this.currentEvent?.folderPath === watch.folderPath;
|
||||
}
|
||||
|
||||
private async syncWithDisk() {
|
||||
try {
|
||||
const electron = ensureElectron();
|
||||
|
@ -102,7 +110,8 @@ class WatchFolderService {
|
|||
|
||||
this.eventQueue = [];
|
||||
const { events, deletedFolderPaths } = await deduceEvents(mappings);
|
||||
this.eventQueue = [...this.eventQueue, ...events];
|
||||
log.info(`Folder watch deduced ${events.length} events`);
|
||||
this.eventQueue = this.eventQueue.concat(events);
|
||||
|
||||
for (const path of deletedFolderPaths)
|
||||
electron.removeWatchMapping(path);
|
||||
|
@ -113,13 +122,9 @@ class WatchFolderService {
|
|||
}
|
||||
}
|
||||
|
||||
isMappingSyncInProgress(mapping: WatchMapping) {
|
||||
return this.currentEvent?.folderPath === mapping.folderPath;
|
||||
}
|
||||
|
||||
private pushEvent(event: WatchEvent) {
|
||||
this.eventQueue.push(event);
|
||||
log.info("Watch event", event);
|
||||
log.info("Folder watch event", event);
|
||||
this.debouncedRunNextEvent();
|
||||
}
|
||||
|
||||
|
@ -152,12 +157,15 @@ class WatchFolderService {
|
|||
}
|
||||
}
|
||||
|
||||
async mappingsAfterRemovingFolder(folderPath: string) {
|
||||
/**
|
||||
* Remove the folder watch corresponding to the given root
|
||||
* {@link folderPath}.
|
||||
*/
|
||||
async removeWatchForFolderPath(folderPath: string) {
|
||||
await ensureElectron().removeWatchMapping(folderPath);
|
||||
return await this.getWatchMappings();
|
||||
}
|
||||
|
||||
async getWatchMappings(): Promise<WatchMapping[]> {
|
||||
async getWatchMappings(): Promise<FolderWatch[]> {
|
||||
try {
|
||||
return (await ensureElectron().getWatchMappings()) ?? [];
|
||||
} catch (e) {
|
||||
|
@ -312,8 +320,8 @@ class WatchFolderService {
|
|||
return;
|
||||
}
|
||||
|
||||
const syncedFiles: WatchMapping["syncedFiles"] = [];
|
||||
const ignoredFiles: WatchMapping["ignoredFiles"] = [];
|
||||
const syncedFiles: FolderWatch["syncedFiles"] = [];
|
||||
const ignoredFiles: FolderWatch["ignoredFiles"] = [];
|
||||
|
||||
for (const fileWithCollection of filesWithCollection) {
|
||||
this.handleUploadedFile(
|
||||
|
@ -361,8 +369,8 @@ class WatchFolderService {
|
|||
|
||||
private handleUploadedFile(
|
||||
fileWithCollection: FileWithCollection,
|
||||
syncedFiles: WatchMapping["syncedFiles"],
|
||||
ignoredFiles: WatchMapping["ignoredFiles"],
|
||||
syncedFiles: FolderWatch["syncedFiles"],
|
||||
ignoredFiles: FolderWatch["ignoredFiles"],
|
||||
) {
|
||||
if (fileWithCollection.isLivePhoto) {
|
||||
const imagePath = (
|
||||
|
@ -465,7 +473,7 @@ class WatchFolderService {
|
|||
}
|
||||
}
|
||||
|
||||
private async trashByIDs(toTrashFiles: WatchMapping["syncedFiles"]) {
|
||||
private async trashByIDs(toTrashFiles: FolderWatch["syncedFiles"]) {
|
||||
try {
|
||||
const files = await getLocalFiles();
|
||||
const toTrashFilesMap = new Map<number, WatchMappingSyncedFile>();
|
||||
|
@ -526,7 +534,7 @@ class WatchFolderService {
|
|||
}
|
||||
|
||||
return {
|
||||
collectionName: getCollectionNameForMapping(mapping, filePath),
|
||||
collectionName: collectionNameForPath(filePath, mapping),
|
||||
folderPath: mapping.folderPath,
|
||||
};
|
||||
} catch (e) {
|
||||
|
@ -642,7 +650,7 @@ async function diskFolderRemovedCallback(folderPath: string) {
|
|||
|
||||
export function getValidFilesToUpload(
|
||||
files: ElectronFile[],
|
||||
mapping: WatchMapping,
|
||||
mapping: FolderWatch,
|
||||
) {
|
||||
const uniqueFilePaths = new Set<string>();
|
||||
return files.filter((file) => {
|
||||
|
@ -656,7 +664,7 @@ export function getValidFilesToUpload(
|
|||
});
|
||||
}
|
||||
|
||||
function isSyncedOrIgnoredFile(file: ElectronFile, mapping: WatchMapping) {
|
||||
function isSyncedOrIgnoredFile(file: ElectronFile, mapping: FolderWatch) {
|
||||
return (
|
||||
mapping.ignoredFiles.includes(file.path) ||
|
||||
mapping.syncedFiles.find((f) => f.path === file.path)
|
||||
|
@ -671,24 +679,26 @@ function isSyncedOrIgnoredFile(file: ElectronFile, mapping: WatchMapping) {
|
|||
* longer any no corresponding directory on disk.
|
||||
*/
|
||||
const deduceEvents = async (
|
||||
mappings: FolderWatch[],
|
||||
watches: FolderWatch[],
|
||||
): Promise<{
|
||||
events: WatchEvent[];
|
||||
deletedFolderPaths: string[];
|
||||
}> => {
|
||||
const activeMappings = [];
|
||||
const electron = ensureElectron();
|
||||
|
||||
const activeWatches = [];
|
||||
const deletedFolderPaths: string[] = [];
|
||||
|
||||
for (const mapping of mappings) {
|
||||
const valid = await electron.fs.isDir(mapping.folderPath);
|
||||
if (!valid) deletedFolderPaths.push(mapping.folderPath);
|
||||
else activeMappings.push(mapping);
|
||||
for (const watch of watches) {
|
||||
const valid = await electron.fs.isDir(watch.folderPath);
|
||||
if (!valid) deletedFolderPaths.push(watch.folderPath);
|
||||
else activeWatches.push(watch);
|
||||
}
|
||||
|
||||
const events: WatchEvent[] = [];
|
||||
|
||||
for (const mapping of activeMappings) {
|
||||
const folderPath = mapping.folderPath;
|
||||
for (const watch of activeWatches) {
|
||||
const folderPath = watch.folderPath;
|
||||
|
||||
const paths = (await electron.watch.findFiles(folderPath))
|
||||
// Filter out hidden files (files whose names begins with a dot)
|
||||
|
@ -696,27 +706,27 @@ const deduceEvents = async (
|
|||
|
||||
// Files that are on disk but not yet synced.
|
||||
const pathsToUpload = paths.filter(
|
||||
(path) => !isSyncedOrIgnoredPath(path, mapping),
|
||||
(path) => !isSyncedOrIgnoredPath(path, watch),
|
||||
);
|
||||
|
||||
for (const path of pathsToUpload)
|
||||
events.push({
|
||||
action: "upload",
|
||||
collectionName: getCollectionNameForMapping(mapping, path),
|
||||
folderPath,
|
||||
collectionName: collectionNameForPath(path, watch),
|
||||
filePath: path,
|
||||
});
|
||||
|
||||
// Synced files that are no longer on disk
|
||||
const pathsToRemove = mapping.syncedFiles.filter(
|
||||
const pathsToRemove = watch.syncedFiles.filter(
|
||||
(file) => !paths.includes(file.path),
|
||||
);
|
||||
|
||||
for (const path of pathsToRemove)
|
||||
events.push({
|
||||
type: "trash",
|
||||
collectionName: getCollectionNameForMapping(mapping, path),
|
||||
folderPath: mapping.folderPath,
|
||||
action: "trash",
|
||||
folderPath,
|
||||
collectionName: collectionNameForPath(path, watch),
|
||||
filePath: path,
|
||||
});
|
||||
}
|
||||
|
@ -724,21 +734,14 @@ const deduceEvents = async (
|
|||
return { events, deletedFolderPaths };
|
||||
};
|
||||
|
||||
function isSyncedOrIgnoredPath(path: string, mapping: WatchMapping) {
|
||||
return (
|
||||
mapping.ignoredFiles.includes(path) ||
|
||||
mapping.syncedFiles.find((f) => f.path === path)
|
||||
);
|
||||
}
|
||||
const isSyncedOrIgnoredPath = (path: string, watch: FolderWatch) =>
|
||||
watch.ignoredFiles.includes(path) ||
|
||||
watch.syncedFiles.find((f) => f.path === path);
|
||||
|
||||
const getCollectionNameForMapping = (
|
||||
mapping: WatchMapping,
|
||||
filePath: string,
|
||||
) => {
|
||||
return mapping.uploadStrategy === UPLOAD_STRATEGY.COLLECTION_PER_FOLDER
|
||||
const collectionNameForPath = (filePath: string, watch: FolderWatch) =>
|
||||
watch.uploadStrategy === UPLOAD_STRATEGY.COLLECTION_PER_FOLDER
|
||||
? parentDirectoryName(filePath)
|
||||
: mapping.rootFolderName;
|
||||
};
|
||||
: watch.rootFolderName;
|
||||
|
||||
const parentDirectoryName = (filePath: string) => {
|
||||
const components = filePath.split("/");
|
||||
|
|
Loading…
Add table
Reference in a new issue