Include files from trash

This commit is contained in:
Manav Rathi
2024-06-26 20:59:20 +05:30
parent 61d35159fa
commit 67d9e650ba
14 changed files with 120 additions and 111 deletions

View File

@@ -1,8 +1,12 @@
import { WhatsNew } from "@/new/photos/components/WhatsNew";
import { shouldShowWhatsNew } from "@/new/photos/services/changelog";
import { fetchAndSaveFeatureFlagsIfNeeded } from "@/new/photos/services/feature-flags";
import { getLocalFiles } from "@/new/photos/services/files";
import {
getLocalFiles,
getLocalTrashedFiles,
} from "@/new/photos/services/files";
import { EnteFile } from "@/new/photos/types/file";
import { mergeMetadata } from "@/new/photos/utils/file";
import log from "@/next/log";
import { CenteredFlex } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
@@ -94,7 +98,7 @@ import { syncCLIPEmbeddings } from "services/embeddingService";
import { syncEntities } from "services/entityService";
import { syncFiles } from "services/fileService";
import locationSearchService from "services/locationSearchService";
import { getLocalTrashedFiles, syncTrash } from "services/trashService";
import { syncTrash } from "services/trashService";
import uploadManager from "services/upload/uploadManager";
import { isTokenValid, syncMapEnabled } from "services/userService";
import { Collection, CollectionSummaries } from "types/collection";
@@ -125,7 +129,6 @@ import {
getSelectedFiles,
getUniqueFiles,
handleFileOps,
mergeMetadata,
sortFiles,
} from "utils/file";
import { isArchivedFile } from "utils/magicMetadata";

View File

@@ -1,4 +1,5 @@
import { EnteFile } from "@/new/photos/types/file";
import { mergeMetadata } from "@/new/photos/utils/file";
import log from "@/next/log";
import {
CenteredFlex,
@@ -65,12 +66,7 @@ import {
SetFilesDownloadProgressAttributesCreator,
} from "types/gallery";
import { downloadCollectionFiles, isHiddenCollection } from "utils/collection";
import {
downloadSelectedFiles,
getSelectedFiles,
mergeMetadata,
sortFiles,
} from "utils/file";
import { downloadSelectedFiles, getSelectedFiles, sortFiles } from "utils/file";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
export default function PublicCollectionGallery() {

View File

@@ -1,5 +1,8 @@
import type { EmbeddingModel } from "@/new/photos/services/embedding";
import { getAllLocalFiles } from "@/new/photos/services/files";
import {
getAllLocalFiles,
getLocalTrashedFiles,
} from "@/new/photos/services/files";
import { EnteFile } from "@/new/photos/types/file";
import { inWorker } from "@/next/env";
import log from "@/next/log";
@@ -18,7 +21,6 @@ import type {
} from "types/embedding";
import { getLocalCollections } from "./collectionService";
import type { FaceIndex } from "./face/types";
import { getLocalTrashedFiles } from "./trashService";
type FileML = FaceIndex & {
updatedAt: number;

View File

@@ -3,6 +3,7 @@ import { decodeLivePhoto } from "@/media/live-photo";
import type { Metadata } from "@/media/types/file";
import { getAllLocalFiles } from "@/new/photos/services/files";
import { EnteFile } from "@/new/photos/types/file";
import { mergeMetadata } from "@/new/photos/utils/file";
import { ensureElectron } from "@/next/electron";
import log from "@/next/log";
import { wait } from "@/utils/promise";
@@ -29,11 +30,7 @@ import {
getCollectionUserFacingName,
getNonEmptyPersonalCollections,
} from "utils/collection";
import {
getPersonalFiles,
getUpdatedEXIFFileForDownload,
mergeMetadata,
} from "utils/file";
import { getPersonalFiles, getUpdatedEXIFFileForDownload } from "utils/file";
import { safeDirectoryName, safeFileName } from "utils/native-fs";
import { writeStream } from "utils/native-stream";
import { getAllLocalCollections } from "../collectionService";

View File

@@ -2,6 +2,7 @@ import { FILE_TYPE } from "@/media/file-type";
import { decodeLivePhoto } from "@/media/live-photo";
import { getAllLocalFiles } from "@/new/photos/services/files";
import { EnteFile } from "@/new/photos/types/file";
import { mergeMetadata } from "@/new/photos/utils/file";
import { ensureElectron } from "@/next/electron";
import { nameAndExtension } from "@/next/file";
import log from "@/next/log";
@@ -22,11 +23,7 @@ import {
FileExportNames,
} from "types/export";
import { getNonEmptyPersonalCollections } from "utils/collection";
import {
getIDBasedSortedFiles,
getPersonalFiles,
mergeMetadata,
} from "utils/file";
import { getIDBasedSortedFiles, getPersonalFiles } from "utils/file";
import {
safeDirectoryName,
safeFileName,

View File

@@ -7,6 +7,7 @@ import {
TrashRequest,
} from "@/new/photos/types/file";
import { BulkUpdateMagicMetadataRequest } from "@/new/photos/types/magicMetadata";
import { mergeMetadata } from "@/new/photos/utils/file";
import log from "@/next/log";
import { apiURL } from "@/next/origins";
import ComlinkCryptoWorker from "@ente/shared/crypto";
@@ -16,12 +17,7 @@ import { REQUEST_BATCH_SIZE } from "constants/api";
import { Collection } from "types/collection";
import { SetFiles } from "types/gallery";
import { batch } from "utils/common";
import {
decryptFile,
getLatestVersionFiles,
mergeMetadata,
sortFiles,
} from "utils/file";
import { decryptFile, getLatestVersionFiles, sortFiles } from "utils/file";
import {
getCollectionLastSyncTime,
setCollectionLastSyncTime,

View File

@@ -1,4 +1,5 @@
import { EncryptedEnteFile, EnteFile } from "@/new/photos/types/file";
import { mergeMetadata } from "@/new/photos/utils/file";
import log from "@/next/log";
import { apiURL } from "@/next/origins";
import ComlinkCryptoWorker from "@ente/shared/crypto";
@@ -7,7 +8,7 @@ import HTTPService from "@ente/shared/network/HTTPService";
import localForage from "@ente/shared/storage/localForage";
import { Collection, CollectionPublicMagicMetadata } from "types/collection";
import { LocalSavedPublicCollectionFiles } from "types/publicCollection";
import { decryptFile, mergeMetadata, sortFiles } from "utils/file";
import { decryptFile, sortFiles } from "utils/file";
const PUBLIC_COLLECTION_FILES_TABLE = "public-collection-files";
const PUBLIC_COLLECTIONS_TABLE = "public-collections";

View File

@@ -1,4 +1,9 @@
import { EnteFile } from "@/new/photos/types/file";
import {
getLocalTrash,
getTrashedFiles,
TRASH,
} from "@/new/photos/services/files";
import { EncryptedTrashItem, Trash } from "@/new/photos/types/file";
import log from "@/next/log";
import { apiURL } from "@/next/origins";
import HTTPService from "@ente/shared/network/HTTPService";
@@ -6,23 +11,12 @@ import localForage from "@ente/shared/storage/localForage";
import { getToken } from "@ente/shared/storage/localStorage/helpers";
import { Collection } from "types/collection";
import { SetFiles } from "types/gallery";
import { EncryptedTrashItem, Trash } from "types/trash";
import { decryptFile, mergeMetadata, sortTrashFiles } from "utils/file";
import { decryptFile } from "utils/file";
import { getCollection } from "./collectionService";
const TRASH = "file-trash";
const TRASH_TIME = "trash-time";
const DELETED_COLLECTION = "deleted-collection";
async function getLocalTrash() {
const trash = (await localForage.getItem<Trash>(TRASH)) || [];
return trash;
}
export async function getLocalTrashedFiles() {
return getTrashedFiles(await getLocalTrash());
}
export async function getLocalDeletedCollections() {
const trashedCollections: Array<Collection> =
(await localForage.getItem<Collection[]>(DELETED_COLLECTION)) || [];
@@ -136,19 +130,6 @@ export const updateTrash = async (
return currentTrash;
};
export function getTrashedFiles(trash: Trash): EnteFile[] {
return sortTrashFiles(
mergeMetadata(
trash.map((trashedFile) => ({
...trashedFile.file,
updationTime: trashedFile.updatedAt,
deleteBy: trashedFile.deleteBy,
isTrashed: true,
})),
),
);
}
export const emptyTrash = async () => {
try {
const token = getToken();

View File

@@ -1,16 +0,0 @@
import { EncryptedEnteFile, EnteFile } from "@/new/photos/types/file";
export interface TrashItem extends Omit<EncryptedTrashItem, "file"> {
file: EnteFile;
}
export interface EncryptedTrashItem {
file: EncryptedEnteFile;
isDeleted: boolean;
isRestored: boolean;
deleteBy: number;
createdAt: number;
updatedAt: number;
}
export type Trash = TrashItem[];

View File

@@ -12,6 +12,7 @@ import {
FileWithUpdatedMagicMetadata,
} from "@/new/photos/types/file";
import { VISIBILITY_STATE } from "@/new/photos/types/magicMetadata";
import { mergeMetadata } from "@/new/photos/utils/file";
import { lowercaseExtension } from "@/next/file";
import log from "@/next/log";
import { CustomErrorMessage, type Electron } from "@/next/types/ipc";
@@ -197,20 +198,6 @@ export function sortFiles(files: EnteFile[], sortAsc = false) {
});
}
export function sortTrashFiles(files: EnteFile[]) {
return files.sort((a, b) => {
if (a.deleteBy === b.deleteBy) {
if (a.metadata.creationTime === b.metadata.creationTime) {
return (
b.metadata.modificationTime - a.metadata.modificationTime
);
}
return b.metadata.creationTime - a.metadata.creationTime;
}
return a.deleteBy - b.deleteBy;
});
}
export async function decryptFile(
file: EncryptedEnteFile,
collectionKey: string,
@@ -432,31 +419,6 @@ export function isSharedFile(user: User, file: EnteFile) {
return file.ownerID !== user.id;
}
/**
* [Note: File name for local EnteFile objects]
*
* The title property in a file's metadata is the original file's name. The
* metadata of a file cannot be edited. So if later on the file's name is
* changed, then the edit is stored in the `editedName` property of the public
* metadata of the file.
*
* This function merges these edits onto the file object that we use locally.
* Effectively, post this step, the file's metadata.title can be used in lieu of
* its filename.
*/
export function mergeMetadata(files: EnteFile[]): EnteFile[] {
return files.map((file) => {
if (file.pubMagicMetadata?.data.editedTime) {
file.metadata.creationTime = file.pubMagicMetadata.data.editedTime;
}
if (file.pubMagicMetadata?.data.editedName) {
file.metadata.title = file.pubMagicMetadata.data.editedName;
}
return file;
});
}
export function updateExistingFilePubMetadata(
existingFile: EnteFile,
updatedFile: EnteFile,

View File

@@ -1,3 +1,4 @@
import { getLocalTrashedFiles } from "@/new/photos/services/files";
import { authenticatedRequestHeaders } from "@/next/http";
import { apiURL } from "@/next/origins";
import log from "@/next/log";
@@ -84,7 +85,11 @@ type RemoteEmbedding = z.infer<typeof RemoteEmbedding>;
*/
export const syncRemoteFaceEmbeddings = async () => {
let sinceTime = faceEmbeddingSyncTime();
const localFiles = await getAllLocalFiles();
// Include files from trash, otherwise they'll get unnecessarily reindexed
// if the user restores them from trash before permanent deletion.
const localFiles = (await getAllLocalFiles()).concat(
await getLocalTrashedFiles(),
);
const localFilesByID = new Map(localFiles.map((f) => [f.id, f]));
const decryptEmbedding = async (remoteEmbedding: RemoteEmbedding) => {
@@ -199,6 +204,7 @@ const saveFaceEmbeddingSyncTime = (t: number) =>
* out of sync in the shape of the types they represent, the TypeScript compiler
* will flag it for us.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const FaceIndex = z
.object({
fileID: z.number(),

View File

@@ -1,7 +1,8 @@
import { type EnteFile } from "@/new/photos/types/file";
import log from "@/next/log";
import { Events, eventBus } from "@ente/shared/events";
import localForage from "@ente/shared/storage/localForage";
import { type EnteFile, type Trash } from "../types/file";
import { mergeMetadata } from "../utils/file";
const FILES_TABLE = "files";
const HIDDEN_FILES_TABLE = "hidden-files";
@@ -36,3 +37,41 @@ export const setLocalFiles = async (
log.error("Failed to save files", e);
}
};
export const TRASH = "file-trash";
export async function getLocalTrash() {
const trash = (await localForage.getItem<Trash>(TRASH)) ?? [];
return trash;
}
export async function getLocalTrashedFiles() {
return getTrashedFiles(await getLocalTrash());
}
export function getTrashedFiles(trash: Trash): EnteFile[] {
return sortTrashFiles(
mergeMetadata(
trash.map((trashedFile) => ({
...trashedFile.file,
updationTime: trashedFile.updatedAt,
deleteBy: trashedFile.deleteBy,
isTrashed: true,
})),
),
);
}
const sortTrashFiles = (files: EnteFile[]) => {
return files.sort((a, b) => {
if (a.deleteBy === b.deleteBy) {
if (a.metadata.creationTime === b.metadata.creationTime) {
return (
b.metadata.modificationTime - a.metadata.modificationTime
);
}
return b.metadata.creationTime - a.metadata.creationTime;
}
return (a.deleteBy ?? 0) - (b.deleteBy ?? 0);
});
};

View File

@@ -126,3 +126,18 @@ export interface FilePublicMagicMetadataProps {
export type FilePublicMagicMetadata =
MagicMetadataCore<FilePublicMagicMetadataProps>;
export interface TrashItem extends Omit<EncryptedTrashItem, "file"> {
file: EnteFile;
}
export interface EncryptedTrashItem {
file: EncryptedEnteFile;
isDeleted: boolean;
isRestored: boolean;
deleteBy: number;
createdAt: number;
updatedAt: number;
}
export type Trash = TrashItem[];

View File

@@ -0,0 +1,30 @@
import type { EnteFile } from "../types/file";
/**
* [Note: File name for local EnteFile objects]
*
* The title property in a file's metadata is the original file's name. The
* metadata of a file cannot be edited. So if later on the file's name is
* changed, then the edit is stored in the `editedName` property of the public
* metadata of the file.
*
* This function merges these edits onto the file object that we use locally.
* Effectively, post this step, the file's metadata.title can be used in lieu of
* its filename.
*/
export function mergeMetadata(files: EnteFile[]): EnteFile[] {
return files.map((file) => {
// TODO: Until the types reflect reality
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (file.pubMagicMetadata?.data.editedTime) {
file.metadata.creationTime = file.pubMagicMetadata.data.editedTime;
}
// TODO: Until the types reflect reality
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (file.pubMagicMetadata?.data.editedName) {
file.metadata.title = file.pubMagicMetadata.data.editedName;
}
return file;
});
}