Include files from trash
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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[];
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
|
||||
30
web/packages/new/photos/utils/file.ts
Normal file
30
web/packages/new/photos/utils/file.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user