Skip to content

Commit

Permalink
[desktop] Fix export when selecting root folder (#4437)
Browse files Browse the repository at this point in the history
This fixes the issue where files would be exported twice on pressing
resync if the user selected "C://" (or some other root drive) as the
export destination.
  • Loading branch information
mnvr authored Dec 18, 2024
2 parents 7559ab4 + 5920999 commit 4bfa398
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 55 deletions.
99 changes: 61 additions & 38 deletions web/apps/photos/src/services/export/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ensureElectron } from "@/base/electron";
import { joinPath } from "@/base/file-name";
import log from "@/base/log";
import { downloadManager } from "@/gallery/services/download";
import { writeStream } from "@/gallery/utils/native-stream";
Expand Down Expand Up @@ -477,7 +478,10 @@ class ExportService {
await this.verifyExportFolderExists(exportFolder);
const oldCollectionExportName =
collectionIDExportNameMap.get(collection.id);
const oldCollectionExportPath = `${exportFolder}/${oldCollectionExportName}`;
const oldCollectionExportPath = joinPath(
exportFolder,
oldCollectionExportName,
);
const newCollectionExportName = await safeDirectoryName(
exportFolder,
getCollectionUserFacingName(collection),
Expand All @@ -486,7 +490,10 @@ class ExportService {
log.info(
`renaming collection with id ${collection.id} from ${oldCollectionExportName} to ${newCollectionExportName}`,
);
const newCollectionExportPath = `${exportFolder}/${newCollectionExportName}`;
const newCollectionExportPath = joinPath(
exportFolder,
newCollectionExportName,
);
await this.addCollectionExportedRecord(
exportFolder,
collection.id,
Expand Down Expand Up @@ -576,7 +583,10 @@ class ExportService {
"collection is not empty, can't remove",
);
}
const collectionExportPath = `${exportFolder}/${collectionExportName}`;
const collectionExportPath = joinPath(
exportFolder,
collectionExportName,
);
await this.removeCollectionExportedRecord(
exportFolder,
collectionID,
Expand Down Expand Up @@ -667,7 +677,10 @@ class ExportService {
collectionExportName,
);
}
const collectionExportPath = `${exportDir}/${collectionExportName}`;
const collectionExportPath = joinPath(
exportDir,
collectionExportName,
);
await fs.mkdirIfNeeded(collectionExportPath);
await fs.mkdirIfNeeded(
getMetadataFolderExportPath(collectionExportPath),
Expand Down Expand Up @@ -880,7 +893,7 @@ class ExportService {
const exportRecord = await this.getExportRecord(folder);
const newRecord: ExportRecord = { ...exportRecord, ...newData };
await ensureElectron().fs.writeFile(
`${folder}/${exportRecordFileName}`,
joinPath(folder, exportRecordFileName),
JSON.stringify(newRecord, null, 2),
);
return newRecord;
Expand All @@ -898,7 +911,7 @@ class ExportService {
const fs = electron.fs;
try {
await this.verifyExportFolderExists(folder);
const exportRecordJSONPath = `${folder}/${exportRecordFileName}`;
const exportRecordJSONPath = joinPath(folder, exportRecordFileName);
if (!(await fs.exists(exportRecordJSONPath))) {
return await this.createEmptyExportRecord(exportRecordJSONPath);
}
Expand All @@ -925,7 +938,10 @@ class ExportService {
collectionName,
fs.exists,
);
const collectionExportPath = `${exportFolder}/${collectionExportName}`;
const collectionExportPath = joinPath(
exportFolder,
collectionExportName,
);
await fs.mkdirIfNeeded(collectionExportPath);
await fs.mkdirIfNeeded(
getMetadataFolderExportPath(collectionExportPath),
Expand Down Expand Up @@ -964,7 +980,7 @@ class ExportService {
);
await writeStream(
electron,
`${collectionExportPath}/${fileExportName}`,
joinPath(collectionExportPath, fileExportName),
originalFileStream,
);
await this.addFileExportedRecord(
Expand Down Expand Up @@ -1012,7 +1028,7 @@ class ExportService {
);
await writeStream(
electron,
`${collectionExportPath}/${imageExportName}`,
joinPath(collectionExportPath, imageExportName),
new Response(livePhoto.imageData).body,
);

Expand All @@ -1024,11 +1040,11 @@ class ExportService {
try {
await writeStream(
electron,
`${collectionExportPath}/${videoExportName}`,
joinPath(collectionExportPath, videoExportName),
new Response(livePhoto.videoData).body,
);
} catch (e) {
await fs.rm(`${collectionExportPath}/${imageExportName}`);
await fs.rm(joinPath(collectionExportPath, imageExportName));
throw e;
}

Expand Down Expand Up @@ -1145,7 +1161,7 @@ export const selectAndPrepareExportDirectory = async (): Promise<
const rootDir = await electron.selectDirectory();
if (!rootDir) return undefined;

const exportDir = `${rootDir}/${exportDirectoryName}`;
const exportDir = joinPath(rootDir, exportDirectoryName);
await electron.fs.mkdirIfNeeded(exportDir);
return exportDir;
};
Expand Down Expand Up @@ -1264,43 +1280,29 @@ const readOnDiskFileExportRecordIDs = async (

const fileExportNames = exportRecord.fileExportNames ?? {};

const ls2 = new Array([...ls].slice(0, 100));
log.info(JSON.stringify({ exportDir, ls: ls2, fileExportNames }));

for (const file of files) {
if (isCanceled.status) throw Error(CustomError.EXPORT_STOPPED);

const collectionExportName = collectionIDFolderNameMap.get(
file.collectionID,
);
log.info(
JSON.stringify({ cid: file.collectionID, collectionExportName }),
);
if (!collectionExportName) continue;

const collectionExportPath = `${exportDir}/${collectionExportName}`;
const collectionExportPath = joinPath(exportDir, collectionExportName);
const recordID = getExportRecordFileUID(file);
const exportName = fileExportNames[recordID];
log.info(
JSON.stringify({
exportName,
constructed: `${collectionExportPath}/${exportName}`,
has: ls.has(`${collectionExportPath}/${exportName}`),
}),
);

if (!exportName) continue;

if (ls.has(`${collectionExportPath}/${exportName}`)) {
if (ls.has(joinPath(collectionExportPath, exportName))) {
result.add(recordID);
} else {
// It might be a live photo - these store a JSON string instead of
// the file's name as the exportName.
try {
const { image, video } = parseLivePhotoExportName(exportName);
if (
ls.has(`${collectionExportPath}/${image}`) &&
ls.has(`${collectionExportPath}/${video}`)
ls.has(joinPath(collectionExportPath, image)) &&
ls.has(joinPath(collectionExportPath, video))
) {
result.add(recordID);
}
Expand Down Expand Up @@ -1415,15 +1417,18 @@ const getGoogleLikeMetadataFile = (
};

export const getMetadataFolderExportPath = (collectionExportPath: string) =>
`${collectionExportPath}/${exportMetadataDirectoryName}`;
joinPath(collectionExportPath, exportMetadataDirectoryName);

// if filepath is /home/user/Ente/Export/Collection1/1.jpg
// then metadata path is /home/user/Ente/Export/Collection1/ENTE_METADATA_FOLDER/1.jpg.json
const getFileMetadataExportPath = (
collectionExportPath: string,
fileExportName: string,
) =>
`${collectionExportPath}/${exportMetadataDirectoryName}/${fileExportName}.json`;
joinPath(
collectionExportPath,
joinPath(exportMetadataDirectoryName, `${fileExportName}.json`),
);

export const getLivePhotoExportName = (
imageExportName: string,
Expand Down Expand Up @@ -1468,18 +1473,33 @@ const moveToTrash = async (
) => {
const fs = ensureElectron().fs;

const filePath = `${exportDir}/${collectionName}/${fileName}`;
const trashDir = `${exportDir}/${exportTrashDirectoryName}/${collectionName}`;
const filePath = joinPath(exportDir, joinPath(collectionName, fileName));
const trashDir = joinPath(
exportDir,
joinPath(exportTrashDirectoryName, collectionName),
);
const metadataFileName = `${fileName}.json`;
const metadataFilePath = `${exportDir}/${collectionName}/${exportMetadataDirectoryName}/${metadataFileName}`;
const metadataTrashDir = `${exportDir}/${exportTrashDirectoryName}/${collectionName}/${exportMetadataDirectoryName}`;
const metadataFilePath = joinPath(
exportDir,
joinPath(
collectionName,
joinPath(exportMetadataDirectoryName, metadataFileName),
),
);
const metadataTrashDir = joinPath(
exportDir,
joinPath(
exportTrashDirectoryName,
joinPath(collectionName, exportMetadataDirectoryName),
),
);

log.info(`Moving file ${filePath} and its metadata to trash folder`);

if (await fs.exists(filePath)) {
await fs.mkdirIfNeeded(trashDir);
const trashFileName = await safeFileName(trashDir, fileName, fs.exists);
const trashFilePath = `${trashDir}/${trashFileName}`;
const trashFilePath = joinPath(trashDir, trashFileName);
await fs.rename(filePath, trashFilePath);
}

Expand All @@ -1490,7 +1510,10 @@ const moveToTrash = async (
metadataFileName,
fs.exists,
);
const metadataTrashFilePath = `${metadataTrashDir}/${metadataTrashFileName}`;
const metadataTrashFilePath = joinPath(
metadataTrashDir,
metadataTrashFileName,
);
await fs.rename(metadataFilePath, metadataTrashFilePath);
}
};
30 changes: 21 additions & 9 deletions web/apps/photos/src/services/export/migration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ensureElectron } from "@/base/electron";
import { nameAndExtension } from "@/base/file-name";
import { joinPath, nameAndExtension } from "@/base/file-name";
import log from "@/base/log";
import { downloadManager } from "@/gallery/services/download";
import type { Collection } from "@/media/collection";
Expand Down Expand Up @@ -225,7 +225,10 @@ const migrateCollectionFolders = async (
) => {
const fs = ensureElectron().fs;
for (const collection of collections) {
const oldPath = `${exportDir}/${collection.id}_${oldSanitizeName(collection.name)}`;
const oldPath = joinPath(
exportDir,
`${collection.id}_${oldSanitizeName(collection.name)}`,
);
const newPath = await safeDirectoryName(
exportDir,
collection.name,
Expand All @@ -249,19 +252,28 @@ async function migrateFiles(
const fs = ensureElectron().fs;
for (const file of files) {
const collectionPath = collectionIDPathMap.get(file.collectionID);
const metadataPath = `${collectionPath}/${exportMetadataDirectoryName}`;
const metadataPath = joinPath(
collectionPath,
exportMetadataDirectoryName,
);

const oldFileName = `${file.id}_${oldSanitizeName(file.metadata.title)}`;
const oldFilePath = `${collectionPath}/${oldFileName}`;
const oldFileMetadataPath = `${metadataPath}/${oldFileName}.json`;
const oldFilePath = joinPath(collectionPath, oldFileName);
const oldFileMetadataPath = joinPath(
metadataPath,
`${oldFileName}.json`,
);

const newFileName = await safeFileName(
collectionPath,
file.metadata.title,
fs.exists,
);
const newFilePath = `${collectionPath}/${newFileName}`;
const newFileMetadataPath = `${metadataPath}/${newFileName}.json`;
const newFilePath = joinPath(collectionPath, newFileName);
const newFileMetadataPath = joinPath(
metadataPath,
`${newFileName}.json`,
);

if (!(await fs.exists(oldFilePath))) continue;

Expand Down Expand Up @@ -444,7 +456,7 @@ async function removeCollectionExportMissingMetadataFolder(
if (
await fs.exists(
getMetadataFolderExportPath(
`${exportDir}/${collectionExportName}`,
joinPath(exportDir, collectionExportName),
),
)
) {
Expand Down Expand Up @@ -511,7 +523,7 @@ const oldSanitizeName = (name: string) =>
name.replaceAll("/", "_").replaceAll(" ", "_");

const getFileSavePath = (collectionFolderPath: string, fileSaveName: string) =>
`${collectionFolderPath}/${fileSaveName}`;
joinPath(collectionFolderPath, fileSaveName);

const getUniqueFileExportNameForMigration = (
collectionPath: string,
Expand Down
6 changes: 5 additions & 1 deletion web/apps/photos/src/utils/collection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ensureElectron } from "@/base/electron";
import { joinPath } from "@/base/file-name";
import log from "@/base/log";
import {
COLLECTION_ROLE,
Expand Down Expand Up @@ -168,7 +169,10 @@ async function createCollectionDownloadFolder(
collectionName,
fs.exists,
);
const collectionDownloadPath = `${downloadDirPath}/${collectionDownloadName}`;
const collectionDownloadPath = joinPath(
downloadDirPath,
collectionDownloadName,
);
await fs.mkdirIfNeeded(collectionDownloadPath);
return collectionDownloadPath;
}
Expand Down
13 changes: 9 additions & 4 deletions web/apps/photos/src/utils/file/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sharedCryptoWorker } from "@/base/crypto";
import { joinPath } from "@/base/file-name";
import log from "@/base/log";
import { type Electron } from "@/base/types/ipc";
import { downloadAndRevokeObjectURL } from "@/base/utils/web";
Expand Down Expand Up @@ -404,7 +405,7 @@ async function downloadFileDesktop(
const imageStream = new Response(imageData).body;
await writeStream(
electron,
`${downloadDir}/${imageExportName}`,
joinPath(downloadDir, imageExportName),
imageStream,
);
try {
Expand All @@ -416,11 +417,11 @@ async function downloadFileDesktop(
const videoStream = new Response(videoData).body;
await writeStream(
electron,
`${downloadDir}/${videoExportName}`,
joinPath(downloadDir, videoExportName),
videoStream,
);
} catch (e) {
await fs.rm(`${downloadDir}/${imageExportName}`);
await fs.rm(joinPath(downloadDir, imageExportName));
throw e;
}
} else {
Expand All @@ -429,7 +430,11 @@ async function downloadFileDesktop(
file.metadata.title,
fs.exists,
);
await writeStream(electron, `${downloadDir}/${fileExportName}`, stream);
await writeStream(
electron,
joinPath(downloadDir, fileExportName),
stream,
);
}
}

Expand Down
Loading

0 comments on commit 4bfa398

Please sign in to comment.