[desktop] Fix export related IPC - Part 3/x (#1439)
This commit is contained in:
commit
28574e516d
9 changed files with 175 additions and 200 deletions
|
@ -8,6 +8,16 @@ import { Readable } from "node:stream";
|
|||
|
||||
export const fsExists = (path: string) => existsSync(path);
|
||||
|
||||
export const fsRename = (oldPath: string, newPath: string) =>
|
||||
fs.rename(oldPath, newPath);
|
||||
|
||||
export const fsMkdirIfNeeded = (dirPath: string) =>
|
||||
fs.mkdir(dirPath, { recursive: true });
|
||||
|
||||
export const fsRmdir = (path: string) => fs.rmdir(path);
|
||||
|
||||
export const fsRm = (path: string) => fs.rm(path);
|
||||
|
||||
/**
|
||||
* Write a (web) ReadableStream to a file at the given {@link filePath}.
|
||||
*
|
||||
|
@ -73,9 +83,6 @@ const writeNodeStream = async (
|
|||
|
||||
/* TODO: Audit below this */
|
||||
|
||||
export const checkExistsAndCreateDir = (dirPath: string) =>
|
||||
fs.mkdir(dirPath, { recursive: true });
|
||||
|
||||
export const saveStreamToDisk = writeStream;
|
||||
|
||||
export const saveFileToDisk = (path: string, contents: string) =>
|
||||
|
@ -85,9 +92,6 @@ export const readTextFile = async (filePath: string) =>
|
|||
fs.readFile(filePath, "utf-8");
|
||||
|
||||
export const moveFile = async (sourcePath: string, destinationPath: string) => {
|
||||
if (!existsSync(sourcePath)) {
|
||||
throw new Error("File does not exist");
|
||||
}
|
||||
if (existsSync(destinationPath)) {
|
||||
throw new Error("Destination file already exists");
|
||||
}
|
||||
|
@ -102,32 +106,3 @@ export const isFolder = async (dirPath: string) => {
|
|||
const stats = await fs.stat(dirPath);
|
||||
return stats.isDirectory();
|
||||
};
|
||||
|
||||
export const deleteFolder = async (folderPath: string) => {
|
||||
// Ensure it is folder
|
||||
if (!isFolder(folderPath)) return;
|
||||
|
||||
// Ensure folder is empty
|
||||
const files = await fs.readdir(folderPath);
|
||||
if (files.length > 0) throw new Error("Folder is not empty");
|
||||
|
||||
// rm -rf it
|
||||
await fs.rmdir(folderPath);
|
||||
};
|
||||
|
||||
export const rename = async (oldPath: string, newPath: string) => {
|
||||
if (!existsSync(oldPath)) throw new Error("Path does not exist");
|
||||
await fs.rename(oldPath, newPath);
|
||||
};
|
||||
|
||||
export const deleteFile = async (filePath: string) => {
|
||||
// Ensure it exists
|
||||
if (!existsSync(filePath)) return;
|
||||
|
||||
// And is a file
|
||||
const stat = await fs.stat(filePath);
|
||||
if (!stat.isFile()) throw new Error("Path is not a file");
|
||||
|
||||
// rm it
|
||||
return fs.rm(filePath);
|
||||
};
|
||||
|
|
|
@ -18,14 +18,14 @@ import {
|
|||
showUploadZipDialog,
|
||||
} from "./dialogs";
|
||||
import {
|
||||
checkExistsAndCreateDir,
|
||||
deleteFile,
|
||||
deleteFolder,
|
||||
fsRm,
|
||||
fsRmdir,
|
||||
fsExists,
|
||||
fsMkdirIfNeeded,
|
||||
fsRename,
|
||||
isFolder,
|
||||
moveFile,
|
||||
readTextFile,
|
||||
rename,
|
||||
saveFileToDisk,
|
||||
saveStreamToDisk,
|
||||
} from "./fs";
|
||||
|
@ -169,12 +169,18 @@ export const attachIPCHandlers = () => {
|
|||
|
||||
ipcMain.handle("fsExists", (_, path) => fsExists(path));
|
||||
|
||||
// - FS Legacy
|
||||
|
||||
ipcMain.handle("checkExistsAndCreateDir", (_, dirPath) =>
|
||||
checkExistsAndCreateDir(dirPath),
|
||||
ipcMain.handle("fsRename", (_, oldPath: string, newPath: string) =>
|
||||
fsRename(oldPath, newPath),
|
||||
);
|
||||
|
||||
ipcMain.handle("fsMkdirIfNeeded", (_, dirPath) => fsMkdirIfNeeded(dirPath));
|
||||
|
||||
ipcMain.handle("fsRmdir", (_, path: string) => fsRmdir(path));
|
||||
|
||||
ipcMain.handle("fsRm", (_, path: string) => fsRm(path));
|
||||
|
||||
// - FS Legacy
|
||||
|
||||
ipcMain.handle(
|
||||
"saveStreamToDisk",
|
||||
(_, path: string, fileStream: ReadableStream) =>
|
||||
|
@ -193,14 +199,6 @@ export const attachIPCHandlers = () => {
|
|||
moveFile(oldPath, newPath),
|
||||
);
|
||||
|
||||
ipcMain.handle("deleteFolder", (_, path: string) => deleteFolder(path));
|
||||
|
||||
ipcMain.handle("deleteFile", (_, path: string) => deleteFile(path));
|
||||
|
||||
ipcMain.handle("rename", (_, oldPath: string, newPath: string) =>
|
||||
rename(oldPath, newPath),
|
||||
);
|
||||
|
||||
// - Upload
|
||||
|
||||
ipcMain.handle("getPendingUploads", () => getPendingUploads());
|
||||
|
|
|
@ -99,6 +99,12 @@ const skipAppUpdate = (version: string) => {
|
|||
const fsExists = (path: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke("fsExists", path);
|
||||
|
||||
const fsMkdirIfNeeded = (dirPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("fsMkdirIfNeeded", dirPath);
|
||||
|
||||
const fsRename = (oldPath: string, newPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("fsRename", oldPath, newPath);
|
||||
|
||||
// - AUDIT below this
|
||||
|
||||
// - Conversion
|
||||
|
@ -218,9 +224,6 @@ const updateWatchMappingIgnoredFiles = (
|
|||
|
||||
// - FS Legacy
|
||||
|
||||
const checkExistsAndCreateDir = (dirPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("checkExistsAndCreateDir", dirPath);
|
||||
|
||||
const saveStreamToDisk = (
|
||||
path: string,
|
||||
fileStream: ReadableStream,
|
||||
|
@ -238,14 +241,10 @@ const isFolder = (dirPath: string): Promise<boolean> =>
|
|||
const moveFile = (oldPath: string, newPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("moveFile", oldPath, newPath);
|
||||
|
||||
const deleteFolder = (path: string): Promise<void> =>
|
||||
ipcRenderer.invoke("deleteFolder", path);
|
||||
const fsRmdir = (path: string): Promise<void> =>
|
||||
ipcRenderer.invoke("fsRmdir", path);
|
||||
|
||||
const deleteFile = (path: string): Promise<void> =>
|
||||
ipcRenderer.invoke("deleteFile", path);
|
||||
|
||||
const rename = (oldPath: string, newPath: string): Promise<void> =>
|
||||
ipcRenderer.invoke("rename", oldPath, newPath);
|
||||
const fsRm = (path: string): Promise<void> => ipcRenderer.invoke("fsRm", path);
|
||||
|
||||
// - Upload
|
||||
|
||||
|
@ -348,19 +347,19 @@ contextBridge.exposeInMainWorld("electron", {
|
|||
// - FS
|
||||
fs: {
|
||||
exists: fsExists,
|
||||
rename: fsRename,
|
||||
mkdirIfNeeded: fsMkdirIfNeeded,
|
||||
rmdir: fsRmdir,
|
||||
rm: fsRm,
|
||||
},
|
||||
|
||||
// - FS legacy
|
||||
// TODO: Move these into fs + document + rename if needed
|
||||
checkExistsAndCreateDir,
|
||||
saveStreamToDisk,
|
||||
saveFileToDisk,
|
||||
readTextFile,
|
||||
isFolder,
|
||||
moveFile,
|
||||
deleteFolder,
|
||||
deleteFile,
|
||||
rename,
|
||||
|
||||
// - Upload
|
||||
|
||||
|
|
|
@ -166,13 +166,14 @@ class ExportService {
|
|||
}
|
||||
|
||||
async changeExportDirectory() {
|
||||
const electron = ensureElectron();
|
||||
try {
|
||||
const newRootDir = await ensureElectron().selectDirectory();
|
||||
const newRootDir = await electron.selectDirectory();
|
||||
if (!newRootDir) {
|
||||
throw Error(CustomError.SELECT_FOLDER_ABORTED);
|
||||
}
|
||||
const newExportDir = `${newRootDir}/${exportDirectoryName}`;
|
||||
await ensureElectron().checkExistsAndCreateDir(newExportDir);
|
||||
await electron.fs.mkdirIfNeeded(newExportDir);
|
||||
return newExportDir;
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.SELECT_FOLDER_ABORTED) {
|
||||
|
@ -485,6 +486,7 @@ class ExportService {
|
|||
renamedCollections: Collection[],
|
||||
isCanceled: CancellationStatus,
|
||||
) {
|
||||
const fs = ensureElectron().fs;
|
||||
try {
|
||||
for (const collection of renamedCollections) {
|
||||
try {
|
||||
|
@ -498,6 +500,7 @@ class ExportService {
|
|||
const newCollectionExportName = await safeDirectoryName(
|
||||
exportFolder,
|
||||
getCollectionUserFacingName(collection),
|
||||
fs.exists,
|
||||
);
|
||||
log.info(
|
||||
`renaming collection with id ${collection.id} from ${oldCollectionExportName} to ${newCollectionExportName}`,
|
||||
|
@ -513,7 +516,7 @@ class ExportService {
|
|||
newCollectionExportName,
|
||||
);
|
||||
try {
|
||||
await ensureElectron().rename(
|
||||
await fs.rename(
|
||||
oldCollectionExportPath,
|
||||
newCollectionExportPath,
|
||||
);
|
||||
|
@ -561,6 +564,7 @@ class ExportService {
|
|||
exportFolder: string,
|
||||
isCanceled: CancellationStatus,
|
||||
) {
|
||||
const fs = ensureElectron().fs;
|
||||
try {
|
||||
const exportRecord = await this.getExportRecord(exportFolder);
|
||||
const collectionIDPathMap =
|
||||
|
@ -595,13 +599,11 @@ class ExportService {
|
|||
);
|
||||
try {
|
||||
// delete the collection metadata folder
|
||||
await ensureElectron().deleteFolder(
|
||||
await fs.rmdir(
|
||||
getMetadataFolderExportPath(collectionExportPath),
|
||||
);
|
||||
// delete the collection folder
|
||||
await ensureElectron().deleteFolder(
|
||||
collectionExportPath,
|
||||
);
|
||||
await fs.rmdir(collectionExportPath);
|
||||
} catch (e) {
|
||||
await this.addCollectionExportedRecord(
|
||||
exportFolder,
|
||||
|
@ -646,6 +648,7 @@ class ExportService {
|
|||
incrementFailed: () => void,
|
||||
isCanceled: CancellationStatus,
|
||||
): Promise<void> {
|
||||
const fs = ensureElectron().fs;
|
||||
try {
|
||||
for (const file of files) {
|
||||
log.info(
|
||||
|
@ -681,10 +684,8 @@ class ExportService {
|
|||
);
|
||||
}
|
||||
const collectionExportPath = `${exportDir}/${collectionExportName}`;
|
||||
await ensureElectron().checkExistsAndCreateDir(
|
||||
collectionExportPath,
|
||||
);
|
||||
await ensureElectron().checkExistsAndCreateDir(
|
||||
await fs.mkdirIfNeeded(collectionExportPath);
|
||||
await fs.mkdirIfNeeded(
|
||||
getMetadataFolderExportPath(collectionExportPath),
|
||||
);
|
||||
await this.downloadAndSave(
|
||||
|
@ -731,6 +732,8 @@ class ExportService {
|
|||
removedFileUIDs: string[],
|
||||
isCanceled: CancellationStatus,
|
||||
): Promise<void> {
|
||||
const electron = ensureElectron();
|
||||
const fs = electron.fs;
|
||||
try {
|
||||
const exportRecord = await this.getExportRecord(exportDir);
|
||||
const fileIDExportNameMap = convertFileIDExportNameObjectToMap(
|
||||
|
@ -760,8 +763,8 @@ class ExportService {
|
|||
log.info(
|
||||
`moving image file ${imageExportPath} to trash folder`,
|
||||
);
|
||||
if (await this.exists(imageExportPath)) {
|
||||
await ensureElectron().moveFile(
|
||||
if (await fs.exists(imageExportPath)) {
|
||||
await electron.moveFile(
|
||||
imageExportPath,
|
||||
await getTrashedFileExportPath(
|
||||
exportDir,
|
||||
|
@ -773,10 +776,8 @@ class ExportService {
|
|||
const imageMetadataFileExportPath =
|
||||
getMetadataFileExportPath(imageExportPath);
|
||||
|
||||
if (
|
||||
await this.exists(imageMetadataFileExportPath)
|
||||
) {
|
||||
await ensureElectron().moveFile(
|
||||
if (await fs.exists(imageMetadataFileExportPath)) {
|
||||
await electron.moveFile(
|
||||
imageMetadataFileExportPath,
|
||||
await getTrashedFileExportPath(
|
||||
exportDir,
|
||||
|
@ -786,31 +787,7 @@ class ExportService {
|
|||
}
|
||||
|
||||
const videoExportPath = `${collectionExportPath}/${videoExportName}`;
|
||||
log.info(
|
||||
`moving video file ${videoExportPath} to trash folder`,
|
||||
);
|
||||
if (await this.exists(videoExportPath)) {
|
||||
await ensureElectron().moveFile(
|
||||
videoExportPath,
|
||||
await getTrashedFileExportPath(
|
||||
exportDir,
|
||||
videoExportPath,
|
||||
),
|
||||
);
|
||||
}
|
||||
const videoMetadataFileExportPath =
|
||||
getMetadataFileExportPath(videoExportPath);
|
||||
if (
|
||||
await this.exists(videoMetadataFileExportPath)
|
||||
) {
|
||||
await ensureElectron().moveFile(
|
||||
videoMetadataFileExportPath,
|
||||
await getTrashedFileExportPath(
|
||||
exportDir,
|
||||
videoMetadataFileExportPath,
|
||||
),
|
||||
);
|
||||
}
|
||||
await moveToTrash(exportDir, videoExportPath);
|
||||
} else {
|
||||
const fileExportPath = `${collectionExportPath}/${fileExportName}`;
|
||||
const trashedFilePath =
|
||||
|
@ -821,16 +798,16 @@ class ExportService {
|
|||
log.info(
|
||||
`moving file ${fileExportPath} to ${trashedFilePath} trash folder`,
|
||||
);
|
||||
if (await this.exists(fileExportPath)) {
|
||||
await ensureElectron().moveFile(
|
||||
if (await fs.exists(fileExportPath)) {
|
||||
await electron.moveFile(
|
||||
fileExportPath,
|
||||
trashedFilePath,
|
||||
);
|
||||
}
|
||||
const metadataFileExportPath =
|
||||
getMetadataFileExportPath(fileExportPath);
|
||||
if (await this.exists(metadataFileExportPath)) {
|
||||
await ensureElectron().moveFile(
|
||||
if (await fs.exists(metadataFileExportPath)) {
|
||||
await electron.moveFile(
|
||||
metadataFileExportPath,
|
||||
await getTrashedFileExportPath(
|
||||
exportDir,
|
||||
|
@ -984,14 +961,16 @@ class ExportService {
|
|||
}
|
||||
|
||||
async getExportRecord(folder: string, retry = true): Promise<ExportRecord> {
|
||||
const electron = ensureElectron();
|
||||
const fs = electron.fs;
|
||||
try {
|
||||
await this.verifyExportFolderExists(folder);
|
||||
const exportRecordJSONPath = `${folder}/${exportRecordFileName}`;
|
||||
if (!(await this.exists(exportRecordJSONPath))) {
|
||||
if (!(await fs.exists(exportRecordJSONPath))) {
|
||||
return this.createEmptyExportRecord(exportRecordJSONPath);
|
||||
}
|
||||
const recordFile =
|
||||
await ensureElectron().readTextFile(exportRecordJSONPath);
|
||||
await electron.readTextFile(exportRecordJSONPath);
|
||||
try {
|
||||
return JSON.parse(recordFile);
|
||||
} catch (e) {
|
||||
|
@ -1017,15 +996,17 @@ class ExportService {
|
|||
collectionID: number,
|
||||
collectionIDNameMap: Map<number, string>,
|
||||
) {
|
||||
const fs = ensureElectron().fs;
|
||||
await this.verifyExportFolderExists(exportFolder);
|
||||
const collectionName = collectionIDNameMap.get(collectionID);
|
||||
const collectionExportName = await safeDirectoryName(
|
||||
exportFolder,
|
||||
collectionName,
|
||||
fs.exists,
|
||||
);
|
||||
const collectionExportPath = `${exportFolder}/${collectionExportName}`;
|
||||
await ensureElectron().checkExistsAndCreateDir(collectionExportPath);
|
||||
await ensureElectron().checkExistsAndCreateDir(
|
||||
await fs.mkdirIfNeeded(collectionExportPath);
|
||||
await fs.mkdirIfNeeded(
|
||||
getMetadataFolderExportPath(collectionExportPath),
|
||||
);
|
||||
|
||||
|
@ -1037,6 +1018,7 @@ class ExportService {
|
|||
collectionExportPath: string,
|
||||
file: EnteFile,
|
||||
): Promise<void> {
|
||||
const electron = ensureElectron();
|
||||
try {
|
||||
const fileUID = getExportRecordFileUID(file);
|
||||
const originalFileStream = await downloadManager.getFile(file);
|
||||
|
@ -1060,6 +1042,7 @@ class ExportService {
|
|||
const fileExportName = await safeFileName(
|
||||
collectionExportPath,
|
||||
file.metadata.title,
|
||||
electron.fs.exists,
|
||||
);
|
||||
await this.addFileExportedRecord(
|
||||
exportDir,
|
||||
|
@ -1072,7 +1055,7 @@ class ExportService {
|
|||
fileExportName,
|
||||
file,
|
||||
);
|
||||
await ensureElectron().saveStreamToDisk(
|
||||
await electron.saveStreamToDisk(
|
||||
`${collectionExportPath}/${fileExportName}`,
|
||||
updatedFileStream,
|
||||
);
|
||||
|
@ -1094,15 +1077,18 @@ class ExportService {
|
|||
fileStream: ReadableStream<any>,
|
||||
file: EnteFile,
|
||||
) {
|
||||
const electron = ensureElectron();
|
||||
const fileBlob = await new Response(fileStream).blob();
|
||||
const livePhoto = await decodeLivePhoto(file, fileBlob);
|
||||
const imageExportName = await safeFileName(
|
||||
collectionExportPath,
|
||||
livePhoto.imageNameTitle,
|
||||
electron.fs.exists,
|
||||
);
|
||||
const videoExportName = await safeFileName(
|
||||
collectionExportPath,
|
||||
livePhoto.videoNameTitle,
|
||||
electron.fs.exists,
|
||||
);
|
||||
const livePhotoExportName = getLivePhotoExportName(
|
||||
imageExportName,
|
||||
|
@ -1120,7 +1106,7 @@ class ExportService {
|
|||
imageExportName,
|
||||
file,
|
||||
);
|
||||
await ensureElectron().saveStreamToDisk(
|
||||
await electron.saveStreamToDisk(
|
||||
`${collectionExportPath}/${imageExportName}`,
|
||||
imageStream,
|
||||
);
|
||||
|
@ -1132,12 +1118,12 @@ class ExportService {
|
|||
file,
|
||||
);
|
||||
try {
|
||||
await ensureElectron().saveStreamToDisk(
|
||||
await electron.saveStreamToDisk(
|
||||
`${collectionExportPath}/${videoExportName}`,
|
||||
videoStream,
|
||||
);
|
||||
} catch (e) {
|
||||
await ensureElectron().deleteFile(
|
||||
await electron.fs.rm(
|
||||
`${collectionExportPath}/${imageExportName}`,
|
||||
);
|
||||
throw e;
|
||||
|
@ -1163,20 +1149,8 @@ class ExportService {
|
|||
return this.exportInProgress;
|
||||
};
|
||||
|
||||
exists = (path: string) => {
|
||||
return ensureElectron().fs.exists(path);
|
||||
};
|
||||
|
||||
rename = (oldPath: string, newPath: string) => {
|
||||
return ensureElectron().rename(oldPath, newPath);
|
||||
};
|
||||
|
||||
checkExistsAndCreateDir = (path: string) => {
|
||||
return ensureElectron().checkExistsAndCreateDir(path);
|
||||
};
|
||||
|
||||
exportFolderExists = async (exportFolder: string) => {
|
||||
return exportFolder && (await this.exists(exportFolder));
|
||||
return exportFolder && (await ensureElectron().fs.exists(exportFolder));
|
||||
};
|
||||
|
||||
private verifyExportFolderExists = async (exportFolder: string) => {
|
||||
|
@ -1412,7 +1386,7 @@ const getTrashedFileExportPath = async (exportDir: string, path: string) => {
|
|||
const fileRelativePath = path.replace(`${exportDir}/`, "");
|
||||
let trashedFilePath = `${exportDir}/${exportTrashDirectoryName}/${fileRelativePath}`;
|
||||
let count = 1;
|
||||
while (await exportService.exists(trashedFilePath)) {
|
||||
while (await ensureElectron().fs.exists(trashedFilePath)) {
|
||||
const trashedFilePathParts = splitFilenameAndExtension(trashedFilePath);
|
||||
if (trashedFilePathParts[1]) {
|
||||
trashedFilePath = `${trashedFilePathParts[0]}(${count}).${trashedFilePathParts[1]}`;
|
||||
|
@ -1460,3 +1434,25 @@ const parseLivePhotoExportName = (
|
|||
|
||||
const isExportInProgress = (exportStage: ExportStage) =>
|
||||
exportStage > ExportStage.INIT && exportStage < ExportStage.FINISHED;
|
||||
|
||||
const moveToTrash = async (exportDir: string, videoExportPath: string) => {
|
||||
const fs = ensureElectron().fs;
|
||||
log.info(`moving video file ${videoExportPath} to trash folder`);
|
||||
if (await fs.exists(videoExportPath)) {
|
||||
await electron.moveFile(
|
||||
videoExportPath,
|
||||
await getTrashedFileExportPath(exportDir, videoExportPath),
|
||||
);
|
||||
}
|
||||
const videoMetadataFileExportPath =
|
||||
getMetadataFileExportPath(videoExportPath);
|
||||
if (await fs.exists(videoMetadataFileExportPath)) {
|
||||
await electron.moveFile(
|
||||
videoMetadataFileExportPath,
|
||||
await getTrashedFileExportPath(
|
||||
exportDir,
|
||||
videoMetadataFileExportPath,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensureElectron } from "@/next/electron";
|
||||
import log from "@/next/log";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { User } from "@ente/shared/user/types";
|
||||
|
@ -188,40 +189,29 @@ async function migrationV4ToV5(exportDir: string, exportRecord: ExportRecord) {
|
|||
await removeCollectionExportMissingMetadataFolder(exportDir, exportRecord);
|
||||
}
|
||||
|
||||
/*
|
||||
This updates the folder name of already exported folders from the earlier format of
|
||||
`collectionID_collectionName` to newer `collectionName(numbered)` format
|
||||
*/
|
||||
async function migrateCollectionFolders(
|
||||
/**
|
||||
* Update the folder name of already exported folders from the earlier format of
|
||||
* `collectionID_collectionName` to newer `collectionName(numbered)` format.
|
||||
*/
|
||||
const migrateCollectionFolders = async (
|
||||
collections: Collection[],
|
||||
exportDir: string,
|
||||
collectionIDPathMap: Map<number, string>,
|
||||
) {
|
||||
) => {
|
||||
const fs = ensureElectron().fs;
|
||||
for (const collection of collections) {
|
||||
const oldCollectionExportPath = getOldCollectionFolderPath(
|
||||
exportDir,
|
||||
collection.id,
|
||||
collection.name,
|
||||
);
|
||||
const newCollectionExportPath = await safeDirectoryName(
|
||||
const oldPath = `${exportDir}/${collection.id}_${oldSanitizeName(collection.name)}`;
|
||||
const newPath = await safeDirectoryName(
|
||||
exportDir,
|
||||
collection.name,
|
||||
fs.exists,
|
||||
);
|
||||
collectionIDPathMap.set(collection.id, newCollectionExportPath);
|
||||
if (!(await exportService.exists(oldCollectionExportPath))) {
|
||||
continue;
|
||||
}
|
||||
await exportService.rename(
|
||||
oldCollectionExportPath,
|
||||
newCollectionExportPath,
|
||||
);
|
||||
await addCollectionExportedRecordV1(
|
||||
exportDir,
|
||||
collection.id,
|
||||
newCollectionExportPath,
|
||||
);
|
||||
collectionIDPathMap.set(collection.id, newPath);
|
||||
if (!(await fs.exists(oldPath))) continue;
|
||||
await fs.rename(oldPath, newPath);
|
||||
await addCollectionExportedRecordV1(exportDir, collection.id, newPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
This updates the file name of already exported files from the earlier format of
|
||||
|
@ -231,6 +221,7 @@ async function migrateFiles(
|
|||
files: EnteFile[],
|
||||
collectionIDPathMap: Map<number, string>,
|
||||
) {
|
||||
const fs = ensureElectron().fs;
|
||||
for (const file of files) {
|
||||
const collectionPath = collectionIDPathMap.get(file.collectionID);
|
||||
const metadataPath = `${collectionPath}/${exportMetadataDirectoryName}`;
|
||||
|
@ -242,14 +233,15 @@ async function migrateFiles(
|
|||
const newFileName = await safeFileName(
|
||||
collectionPath,
|
||||
file.metadata.title,
|
||||
fs.exists,
|
||||
);
|
||||
const newFilePath = `${collectionPath}/${newFileName}`;
|
||||
const newFileMetadataPath = `${metadataPath}/${newFileName}.json`;
|
||||
|
||||
if (!(await exportService.exists(oldFilePath))) continue;
|
||||
if (!(await fs.exists(oldFilePath))) continue;
|
||||
|
||||
await exportService.rename(oldFilePath, newFilePath);
|
||||
await exportService.rename(oldFileMetadataPath, newFileMetadataPath);
|
||||
await fs.rename(oldFilePath, newFilePath);
|
||||
await fs.rename(oldFileMetadataPath, newFileMetadataPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -409,6 +401,7 @@ async function removeCollectionExportMissingMetadataFolder(
|
|||
exportDir: string,
|
||||
exportRecord: ExportRecord,
|
||||
) {
|
||||
const fs = ensureElectron().fs;
|
||||
if (!exportRecord?.collectionExportNames) {
|
||||
return;
|
||||
}
|
||||
|
@ -422,7 +415,7 @@ async function removeCollectionExportMissingMetadataFolder(
|
|||
collectionExportName,
|
||||
] of properlyExportedCollectionsAll) {
|
||||
if (
|
||||
await exportService.exists(
|
||||
await fs.exists(
|
||||
getMetadataFolderExportPath(
|
||||
`${exportDir}/${collectionExportName}`,
|
||||
),
|
||||
|
@ -493,12 +486,6 @@ const oldSanitizeName = (name: string) =>
|
|||
const getFileSavePath = (collectionFolderPath: string, fileSaveName: string) =>
|
||||
`${collectionFolderPath}/${fileSaveName}`;
|
||||
|
||||
const getOldCollectionFolderPath = (
|
||||
dir: string,
|
||||
collectionID: number,
|
||||
collectionName: string,
|
||||
) => `${dir}/${collectionID}_${oldSanitizeName(collectionName)}`;
|
||||
|
||||
const getUniqueFileExportNameForMigration = (
|
||||
collectionPath: string,
|
||||
filename: string,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ensureElectron } from "@/next/electron";
|
||||
import log from "@/next/log";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { getAlbumsURL } from "@ente/shared/network/api";
|
||||
|
@ -30,7 +31,6 @@ import {
|
|||
updatePublicCollectionMagicMetadata,
|
||||
updateSharedCollectionMagicMetadata,
|
||||
} from "services/collectionService";
|
||||
import exportService from "services/export";
|
||||
import { getAllLocalFiles, getLocalFiles } from "services/fileService";
|
||||
import {
|
||||
COLLECTION_ROLE,
|
||||
|
@ -169,12 +169,14 @@ async function createCollectionDownloadFolder(
|
|||
downloadDirPath: string,
|
||||
collectionName: string,
|
||||
) {
|
||||
const fs = ensureElectron().fs;
|
||||
const collectionDownloadName = await safeDirectoryName(
|
||||
downloadDirPath,
|
||||
collectionName,
|
||||
fs.exists,
|
||||
);
|
||||
const collectionDownloadPath = `${downloadDirPath}/${collectionDownloadName}`;
|
||||
await exportService.checkExistsAndCreateDir(collectionDownloadPath);
|
||||
await fs.mkdirIfNeeded(collectionDownloadPath);
|
||||
return collectionDownloadPath;
|
||||
}
|
||||
|
||||
|
|
|
@ -815,6 +815,7 @@ async function downloadFileDesktop(
|
|||
const imageExportName = await safeFileName(
|
||||
downloadPath,
|
||||
livePhoto.imageNameTitle,
|
||||
electron.fs.exists,
|
||||
);
|
||||
const imageStream = generateStreamFromArrayBuffer(livePhoto.image);
|
||||
await electron.saveStreamToDisk(
|
||||
|
@ -825,6 +826,7 @@ async function downloadFileDesktop(
|
|||
const videoExportName = await safeFileName(
|
||||
downloadPath,
|
||||
livePhoto.videoNameTitle,
|
||||
electron.fs.exists,
|
||||
);
|
||||
const videoStream = generateStreamFromArrayBuffer(livePhoto.video);
|
||||
await electron.saveStreamToDisk(
|
||||
|
@ -832,13 +834,14 @@ async function downloadFileDesktop(
|
|||
videoStream,
|
||||
);
|
||||
} catch (e) {
|
||||
await electron.deleteFile(`${downloadPath}/${imageExportName}`);
|
||||
await electron.fs.rm(`${downloadPath}/${imageExportName}`);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
const fileExportName = await safeFileName(
|
||||
downloadPath,
|
||||
file.metadata.title,
|
||||
electron.fs.exists,
|
||||
);
|
||||
await electron.saveStreamToDisk(
|
||||
`${downloadPath}/${fileExportName}`,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
/**
|
||||
* @file Native filesystem access using custom Node.js functionality provided by
|
||||
* our desktop app.
|
||||
* @file Utilities for native filesystem access.
|
||||
*
|
||||
* Precondition: Unless mentioned otherwise, the functions in these file only
|
||||
* work when we are running in our desktop app.
|
||||
* While they don't have any direct dependencies to our desktop app, they were
|
||||
* written for use by the code that runs in our desktop app.
|
||||
*/
|
||||
|
||||
import { ensureElectron } from "@/next/electron";
|
||||
import { nameAndExtension } from "@/next/file";
|
||||
import sanitize from "sanitize-filename";
|
||||
import {
|
||||
|
@ -31,15 +29,15 @@ export const sanitizeFilename = (s: string) =>
|
|||
* We also ensure we don't return names which might collide with our own special
|
||||
* directories.
|
||||
*
|
||||
* This function only works when we are running inside an electron app (since it
|
||||
* requires permissionless access to the native filesystem to find a new
|
||||
* filename that doesn't conflict with any existing items).
|
||||
* @param exists A function to check if an item already exists at the given
|
||||
* path. Usually, you'd pass `fs.exists` from {@link Electron}.
|
||||
*
|
||||
* See also: {@link safeDirectoryName}
|
||||
* See also: {@link safeFileame}
|
||||
*/
|
||||
export const safeDirectoryName = async (
|
||||
directoryPath: string,
|
||||
name: string,
|
||||
exists: (path: string) => Promise<boolean>,
|
||||
): Promise<string> => {
|
||||
const specialDirectoryNames = [
|
||||
exportTrashDirectoryName,
|
||||
|
@ -62,10 +60,13 @@ export const safeDirectoryName = async (
|
|||
* Return a new sanitized and unique file name based on {@link name} that is not
|
||||
* the same as any existing item in the given {@link directoryPath}.
|
||||
*
|
||||
* This function only works when we are running inside an electron app.
|
||||
* @see {@link safeDirectoryName}.
|
||||
* This is a sibling of {@link safeDirectoryName} for use with file names.
|
||||
*/
|
||||
export const safeFileName = async (directoryPath: string, name: string) => {
|
||||
export const safeFileName = async (
|
||||
directoryPath: string,
|
||||
name: string,
|
||||
exists: (path: string) => Promise<boolean>,
|
||||
) => {
|
||||
let result = sanitizeFilename(name);
|
||||
let count = 1;
|
||||
while (await exists(`${directoryPath}/${result}`)) {
|
||||
|
@ -76,9 +77,3 @@ export const safeFileName = async (directoryPath: string, name: string) => {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if an item exists an the given {@link path} on the user's local
|
||||
* filesystem.
|
||||
*/
|
||||
export const exists = (path: string) => ensureElectron().fs.exists(path);
|
||||
|
|
|
@ -155,15 +155,39 @@ export interface Electron {
|
|||
* or watching some folders for changes and syncing them automatically.
|
||||
*
|
||||
* Towards this end, this fs object provides some generic file system access
|
||||
* functions that are needed for such features. In addition, there are other
|
||||
* feature specific methods too in the top level electron object.
|
||||
* functions that are needed for such features (in some cases, there are
|
||||
* other feature specific methods too in the top level electron object).
|
||||
*/
|
||||
fs: {
|
||||
/**
|
||||
* Return true if there is a file or directory at the given
|
||||
* {@link path}.
|
||||
*/
|
||||
/** Return true if there is an item at the given {@link path}. */
|
||||
exists: (path: string) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Equivalent of `mkdir -p`.
|
||||
*
|
||||
* Create a directory at the given path if it does not already exist.
|
||||
* Any parent directories in the path that don't already exist will also
|
||||
* be created recursively, i.e. this command is analogous to an running
|
||||
* `mkdir -p`.
|
||||
*/
|
||||
mkdirIfNeeded: (dirPath: string) => Promise<void>;
|
||||
|
||||
/** Rename {@link oldPath} to {@link newPath} */
|
||||
rename: (oldPath: string, newPath: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Equivalent of `rmdir`.
|
||||
*
|
||||
* Delete the directory at the {@link path} if it is empty.
|
||||
*/
|
||||
rmdir: (path: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Equivalent of `rm`.
|
||||
*
|
||||
* Delete the file at {@link path}.
|
||||
*/
|
||||
rm: (path: string) => Promise<void>;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -276,7 +300,6 @@ export interface Electron {
|
|||
) => Promise<void>;
|
||||
|
||||
// - FS legacy
|
||||
checkExistsAndCreateDir: (dirPath: string) => Promise<void>;
|
||||
saveStreamToDisk: (
|
||||
path: string,
|
||||
fileStream: ReadableStream,
|
||||
|
@ -285,9 +308,6 @@ export interface Electron {
|
|||
readTextFile: (path: string) => Promise<string>;
|
||||
isFolder: (dirPath: string) => Promise<boolean>;
|
||||
moveFile: (oldPath: string, newPath: string) => Promise<void>;
|
||||
deleteFolder: (path: string) => Promise<void>;
|
||||
deleteFile: (path: string) => Promise<void>;
|
||||
rename: (oldPath: string, newPath: string) => Promise<void>;
|
||||
|
||||
// - Upload
|
||||
|
||||
|
|
Loading…
Reference in a new issue