diff --git a/core/crates/prisma-helpers/src/lib.rs b/core/crates/prisma-helpers/src/lib.rs index d562ab534..5b649ab2a 100644 --- a/core/crates/prisma-helpers/src/lib.rs +++ b/core/crates/prisma-helpers/src/lib.rs @@ -87,6 +87,20 @@ file_path::select!(file_path_to_isolate_with_id { name extension }); +file_path::select!(file_path_sisters { + id + materialized_path + is_dir + name + extension + cas_id + integrity_checksum + location: select { + id + path + name + } +}); file_path::select!(file_path_walker { pub_id location_id diff --git a/core/src/api/files.rs b/core/src/api/files.rs index d83d1e2bd..25f834c19 100644 --- a/core/src/api/files.rs +++ b/core/src/api/files.rs @@ -16,7 +16,7 @@ use crate::{ use sd_core_file_path_helper::{FilePathError, IsolatedFilePathData}; use sd_core_prisma_helpers::{ - file_path_to_isolate, file_path_to_isolate_with_id, object_with_file_paths, + file_path_sisters, file_path_to_isolate, file_path_to_isolate_with_id, object_with_file_paths, object_with_media_data, }; @@ -31,6 +31,7 @@ use sd_sync::OperationFactory; use sd_utils::{db::maybe_missing, error::FileIOError, msgpack}; use std::{ + collections::HashSet, ffi::OsString, path::{Path, PathBuf}, sync::Arc, @@ -170,6 +171,32 @@ pub(crate) fn mount() -> AlphaRouter { .map(|str| str.to_string())) }) }) + .procedure("getDuplicates", { + R.with2(library()) + .query(|(_, library), object_id: i32| async move { + let file_paths = library + .db + .file_path() + .find_many(vec![file_path::object_id::equals(Some(object_id))]) + .select(file_path_sisters::select()) + .exec() + .await?; + + let mut unique_location_ids = HashSet::new(); + let locations: Vec<_> = + file_paths + .clone() + .into_iter() + .filter_map(|file_path| { + file_path.location.as_ref().cloned().filter(|location| { + unique_location_ids.insert(location.id.clone()) + }) + }) + .collect(); + + Ok((locations, file_paths)) + }) + }) .procedure("setNote", { #[derive(Type, Deserialize)] pub struct SetNoteArgs { diff --git a/interface/app/$libraryId/Explorer/Inspector/index.tsx b/interface/app/$libraryId/Explorer/Inspector/index.tsx index ea94f8c8f..f69db0289 100644 --- a/interface/app/$libraryId/Explorer/Inspector/index.tsx +++ b/interface/app/$libraryId/Explorer/Inspector/index.tsx @@ -12,6 +12,20 @@ import { Icon as PhosphorIcon, Snowflake } from '@phosphor-icons/react'; +import clsx from 'clsx'; +import dayjs from 'dayjs'; +import { + forwardRef, + useCallback, + useEffect, + useMemo, + useState, + type HTMLAttributes, + type ReactNode +} from 'react'; +import { useLocation } from 'react-router'; +import { Link as NavLink } from 'react-router-dom'; +import Sticky from 'react-sticky-el'; import { FilePath, FilePathForFrontend, @@ -28,20 +42,6 @@ import { type ExplorerItem } from '@sd/client'; import { Button, Divider, DropdownMenu, toast, Tooltip, tw } from '@sd/ui'; -import clsx from 'clsx'; -import dayjs from 'dayjs'; -import { - forwardRef, - useCallback, - useEffect, - useMemo, - useState, - type HTMLAttributes, - type ReactNode -} from 'react'; -import { useLocation } from 'react-router'; -import { Link as NavLink } from 'react-router-dom'; -import Sticky from 'react-sticky-el'; import { LibraryIdParamsSchema } from '~/app/route-schemas'; import { Folder, Icon } from '~/components'; import { useLocale, useZodRouteParams } from '~/hooks'; @@ -233,6 +233,10 @@ export const SingleItemMetadata = ({ item }: { item: ExplorerItem }) => { enabled: filePathData != null && readyToFetch }); + const duplicateFilePaths = useLibraryQuery(['files.getDuplicates', objectData?.id ?? -1], { + enabled: objectData != null && readyToFetch + }); + const filesMediaData = useLibraryQuery(['files.getMediaData', objectData?.id ?? -1], { enabled: objectData != null && readyToFetch }); @@ -526,7 +530,6 @@ const MultiItemMetadata = ({ items }: { items: ExplorerItem[] }) => { const onlyNonIndexed = metadata.types.has('NonIndexedPath') && metadata.types.size === 1; const filesSize = humanizeSize(metadata.size); - return ( <> diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 5c9c249a7..6bb856c34 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -13,6 +13,7 @@ export type Procedures = { { key: "ephemeralFiles.getMediaData", input: string, result: MediaData | null } | { key: "files.get", input: LibraryArgs, result: ObjectWithFilePaths2 | null } | { key: "files.getConvertibleImageExtensions", input: never, result: string[] } | + { key: "files.getDuplicates", input: LibraryArgs, result: [({ id: number; name: string | null; path: string | null })[], FilePathSisters[]] } | { key: "files.getMediaData", input: LibraryArgs, result: MediaData } | { key: "files.getPath", input: LibraryArgs, result: string | null } | { key: "invalidation.test-invalidate", input: never, result: number } | @@ -299,6 +300,8 @@ export type FilePathOrder = { field: "name"; value: SortOrder } | { field: "size export type FilePathSearchArgs = { take?: number | null; orderAndPagination?: OrderAndPagination | null; filters?: SearchFilterArgs[]; groupDirectories?: boolean } +export type FilePathSisters = { id: number; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location: { id: number; name: string | null; path: string | null } | null; materialized_path: string | null; name: string | null; extension: string | null } + export type Flash = { /** * Specifies how flash was used (on, auto, off, forced, onvalid)