Skip to content

Commit

Permalink
Reactive file identification (#2396)
Browse files Browse the repository at this point in the history
* yes

* Explain mysterious if

* Use id alias just for consistency reasons

* yes

* Rust fmt

* fix ts

---------

Co-authored-by: Ericson "Fogo" Soares <[email protected]>
Co-authored-by: Utku Bakir <[email protected]>
  • Loading branch information
3 people committed Apr 25, 2024
1 parent 64bbce3 commit ab46cff
Show file tree
Hide file tree
Showing 16 changed files with 96 additions and 49 deletions.
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

0 comments on commit ab46cff

Please sign in to comment.