[desktop] Fix export related IPC - Part 3/x (#1439)

This commit is contained in:
Manav Rathi 2024-04-14 19:04:38 +05:30 committed by GitHub
commit 28574e516d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 175 additions and 200 deletions

View file

@ -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);
};

View file

@ -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());

View file

@ -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

View file

@ -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,
),
);
}
};

View file

@ -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,

View file

@ -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;
}

View file

@ -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}`,

View file

@ -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);

View file

@ -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