Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive file identification #2396

Merged
merged 6 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion core/src/api/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,6 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
R.with2(library())
.subscription(|(node, _), _: ()| async move {
// TODO: Only return event for the library that was subscribed to

let mut event_bus_rx = node.event_bus.0.subscribe();
async_stream::stream! {
while let Ok(event) = event_bus_rx.recv().await {
Expand All @@ -355,4 +354,19 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
}
})
})
.procedure("newFilePathIdentified", {
R.with2(library())
.subscription(|(node, _), _: ()| async move {
// TODO: Only return event for the library that was subscribed to
let mut event_bus_rx = node.event_bus.0.subscribe();
async_stream::stream! {
while let Ok(event) = event_bus_rx.recv().await {
match event {
CoreEvent::NewIdentifiedObjects { file_path_ids } => yield file_path_ids,
_ => {}
}
}
}
})
})
}
5 changes: 4 additions & 1 deletion core/src/api/locations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ pub type ThumbnailKey = Vec<String>;
#[serde(tag = "type")]
pub enum ExplorerItem {
Path {
// provide the frontend with the thumbnail key explicitly
thumbnail: Option<ThumbnailKey>,
has_created_thumbnail: bool, // this is important
// this tells the frontend if a thumbnail actually exists or not
has_created_thumbnail: bool,
// we can't actually modify data from PCR types, thats why computed properties are used on ExplorerItem
item: file_path_with_object::Data,
},
Object {
Expand Down
9 changes: 8 additions & 1 deletion core/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::{

use sd_cache::patch_typedef;
use sd_p2p::RemoteIdentity;
use sd_prisma::prisma::file_path;

use std::sync::{atomic::Ordering, Arc};

use itertools::Itertools;
Expand Down Expand Up @@ -52,7 +54,12 @@ pub type Router = rspc::Router<Ctx>;
/// Represents an internal core event, these are exposed to client via a rspc subscription.
#[derive(Debug, Clone, Serialize, Type)]
pub enum CoreEvent {
NewThumbnail { thumb_key: Vec<String> },
NewThumbnail {
thumb_key: Vec<String>,
},
NewIdentifiedObjects {
file_path_ids: Vec<file_path::id::Type>,
},
JobProgress(JobProgressEvent),
InvalidateOperation(InvalidateOperationEvent),
}
Expand Down
7 changes: 6 additions & 1 deletion core/src/object/media/old_thumbnail/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,12 @@ pub(super) async fn generate_thumbnail(
}
}
}

// This if is REALLY needed, due to the sheer performance of the thumbnailer,
// I restricted to only send events notifying for thumbnails in the current
// opened directory, sending events for the entire location turns into a
// humongous bottleneck in the frontend lol, since it doesn't even knows
// what to do with thumbnails for inner directories lol
// - fogodev
if !in_background {
trace!("Emitting new thumbnail event");
if reporter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
api::CoreEvent,
library::Library,
location::ScanState,
old_job::{
Expand Down Expand Up @@ -226,6 +227,11 @@ impl StatefulJob for OldFileIdentifierJobInit {
new_metadata.total_objects_linked = total_objects_linked;
new_metadata.cursor = new_cursor;

// send an array of ids to let clients know new objects were identified
ctx.node.emit(CoreEvent::NewIdentifiedObjects {
file_path_ids: file_paths.iter().map(|fp| fp.id).collect(),
});

ctx.progress(vec![
JobReportUpdate::CompletedTaskCount(step_number * CHUNK_SIZE + file_paths.len()),
JobReportUpdate::Message(format!(
Expand Down
2 changes: 1 addition & 1 deletion interface/app/$libraryId/Explorer/FilePath/Original.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { usePlatform } from '~/util/Platform';

import { useExplorerContext } from '../Context';
import { explorerStore } from '../store';
import { ExplorerItemData } from '../util';
import { ExplorerItemData } from '../useExplorerItemData';
import { Image } from './Image';
import { useBlackBars, useSize } from './utils';

Expand Down
2 changes: 1 addition & 1 deletion interface/app/$libraryId/Explorer/FilePath/Thumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useIsDark } from '~/hooks';
import { pdfViewerEnabled } from '~/util/pdfViewer';
import { usePlatform } from '~/util/Platform';

import { useExplorerItemData } from '../util';
import { useExplorerItemData } from '../useExplorerItemData';
import { Image, ImageProps } from './Image';
import LayeredFileIcon from './LayeredFileIcon';
import { Original } from './Original';
Expand Down
3 changes: 2 additions & 1 deletion interface/app/$libraryId/Explorer/Inspector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ import AssignTagMenuItems from '../ContextMenu/AssignTagMenuItems';
import { FileThumb } from '../FilePath/Thumb';
import { useQuickPreviewStore } from '../QuickPreview/store';
import { explorerStore } from '../store';
import { uniqueId, useExplorerItemData } from '../util';
import { useExplorerItemData } from '../useExplorerItemData';
import { uniqueId } from '../util';
import { RenamableItemText } from '../View/RenamableItemText';
import FavoriteButton from './FavoriteButton';
import MediaData from './MediaData';
Expand Down
19 changes: 12 additions & 7 deletions interface/app/$libraryId/Explorer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
explorerLayout,
useExplorerLayoutStore,
useLibrarySubscription,
useRspcLibraryContext,
useSelector
} from '@sd/client';
import { useShortcut } from '~/hooks';
Expand Down Expand Up @@ -40,19 +41,23 @@ export default function Explorer(props: PropsWithChildren<Props>) {
const showInspector = useSelector(explorerStore, (s) => s.showInspector);

const showPathBar = explorer.showPathBar && layoutStore.showPathBar;

const rspc = useRspcLibraryContext();
// Can we put this somewhere else -_-
useLibrarySubscription(['jobs.newThumbnail'], {
onStarted: () => {
console.log('Started RSPC subscription new thumbnail');
},
onError: (err) => {
console.error('Error in RSPC subscription new thumbnail', err);
},
onData: (thumbKey) => {
explorerStore.addNewThumbnail(thumbKey);
}
});
useLibrarySubscription(['jobs.newFilePathIdentified'], {
onData: (ids) => {
if (ids?.length > 0) {
// I had planned to somehow fetch the Object, but its a lot more work than its worth given
// id have to fetch the file_path explicitly and patch the query
// for now, it seems to work a treat just invalidating the whole query
rspc.queryClient.invalidateQueries(['search.paths']);
}
}
});

useShortcut('showPathBar', (e) => {
e.stopPropagation();
Expand Down
5 changes: 2 additions & 3 deletions interface/app/$libraryId/Explorer/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,9 @@ export const explorerStore = proxy({
addNewThumbnail: (thumbKey: string[]) => {
explorerStore.newThumbnails.add(flattenThumbnailKey(thumbKey));
},
// this should be done when the explorer query is refreshed
// prevents memory leak
resetNewThumbnails: () => {
resetCache: () => {
explorerStore.newThumbnails.clear();
// explorerStore.newFilePathsIdentified.clear();
}
});

Expand Down
34 changes: 34 additions & 0 deletions interface/app/$libraryId/Explorer/useExplorerItemData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useMemo } from 'react';
import { getExplorerItemData, useSelector, type ExplorerItem } from '@sd/client';

import { explorerStore, flattenThumbnailKey } from './store';

// This is where we intercept the state of the explorer item to determine if we should rerender
// This hook is used inside every thumbnail in the explorer
export function useExplorerItemData(explorerItem: ExplorerItem) {
const newThumbnail = useSelector(explorerStore, (s) => {
const thumbnailKey =
explorerItem.type === 'Label'
? // labels have .thumbnails, plural
explorerItem.thumbnails?.[0]
: // all other explorer items have .thumbnail singular
'thumbnail' in explorerItem && explorerItem.thumbnail;

return !!(thumbnailKey && s.newThumbnails.has(flattenThumbnailKey(thumbnailKey)));
});

return useMemo(() => {
const itemData = getExplorerItemData(explorerItem);

if (!itemData.hasLocalThumbnail) {
itemData.hasLocalThumbnail = newThumbnail;
}

return itemData;
// whatever goes here, is what can cause an atomic re-render of an explorer item
// this is used for when new thumbnails are generated, and files identified
}, [explorerItem, newThumbnail]);
}

export type ExplorerItemData = ReturnType<typeof useExplorerItemData>;
30 changes: 1 addition & 29 deletions interface/app/$libraryId/Explorer/util.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,11 @@
import { useMemo } from 'react';
import { getExplorerItemData, useSelector, type ExplorerItem } from '@sd/client';
import { type ExplorerItem } from '@sd/client';
import { ExplorerParamsSchema } from '~/app/route-schemas';
import { useZodSearchParams } from '~/hooks';

import { explorerStore, flattenThumbnailKey } from './store';

export function useExplorerSearchParams() {
return useZodSearchParams(ExplorerParamsSchema);
}

export function useExplorerItemData(explorerItem: ExplorerItem) {
const newThumbnail = useSelector(explorerStore, (s) => {
const thumbnailKey =
explorerItem.type === 'Label'
? // labels have .thumbnails, plural
explorerItem.thumbnails?.[0]
: // all other explorer items have .thumbnail singular
'thumbnail' in explorerItem && explorerItem.thumbnail;

return !!(thumbnailKey && s.newThumbnails.has(flattenThumbnailKey(thumbnailKey)));
});

return useMemo(() => {
const itemData = getExplorerItemData(explorerItem);

if (!itemData.hasLocalThumbnail) {
itemData.hasLocalThumbnail = newThumbnail;
}

return itemData;
}, [explorerItem, newThumbnail]);
}

export type ExplorerItemData = ReturnType<typeof useExplorerItemData>;

export const pubIdToString = (pub_id: number[]) =>
pub_id.map((b) => b.toString(16).padStart(2, '0')).join('');

Expand Down
2 changes: 1 addition & 1 deletion interface/app/$libraryId/ephemeral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ const EphemeralExplorer = memo((props: { args: PathParams }) => {
{
enabled: path != null,
suspense: true,
onSuccess: () => explorerStore.resetNewThumbnails(),
onSuccess: () => explorerStore.resetCache(),
onBatch: (item) => {
cache.withNodes(item.nodes);
}
Expand Down
2 changes: 1 addition & 1 deletion interface/app/$libraryId/location/$id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const LocationExplorer = ({ location }: { location: Location; path?: string }) =
],
take,
paths: { order: explorerSettings.useSettingsSnapshot().order },
onSuccess: () => explorerStore.resetNewThumbnails()
onSuccess: () => explorerStore.resetCache()
});

const explorer = useExplorer({
Expand Down
2 changes: 1 addition & 1 deletion interface/app/$libraryId/saved-search/$id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function Inner({ id }: { id: number }) {
filters: search.allFilters,
take: 50,
paths: { order: explorerSettings.useSettingsSnapshot().order },
onSuccess: () => explorerStore.resetNewThumbnails()
onSuccess: () => explorerStore.resetCache()
});

const explorer = useExplorer({
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export type Procedures = {
subscriptions:
{ key: "auth.loginSession", input: never, result: Response } |
{ key: "invalidation.listen", input: never, result: InvalidateOperationEvent[] } |
{ key: "jobs.newFilePathIdentified", input: LibraryArgs<null>, result: number[] } |
{ key: "jobs.newThumbnail", input: LibraryArgs<null>, result: string[] } |
{ key: "jobs.progress", input: LibraryArgs<null>, result: JobProgressEvent } |
{ key: "library.actors", input: LibraryArgs<null>, result: { [key in string]: boolean } } |
Expand Down
Loading