fix indexer lag (reimplement debounced progress event for jobs)

This commit is contained in:
Jamie Pine committed 2022-09-06 17:44:48 -07:00
1 parent cedbe86381
commit 4196a9823f
10 files changed
+265 -424

No files matched your search

+106 -106
View File
@@ -2,136 +2,136 @@
export type Operations = {
queries:
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
{ key: ["volumes.get"], result: Array<Volume> } |
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
{ key: ["tags.getForFile", LibraryArgs<number>], result: Array<Tag> } |
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData } |
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
{ key: ["locations.indexer_rulesget", LibraryArgs<number>], result: IndexerRule } |
{ key: ["files.readMetadata", LibraryArgs<number>], result: null } |
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
{ key: ["getNode"], result: NodeState } |
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
{ key: ["tags.getAll", LibraryArgs<null>], result: Array<Tag> } |
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
{ key: ["getNode"], result: NodeState } |
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
{ key: ["version"], result: string } |
{ key: ["jobs.getRunning", LibraryArgs<null>], result: Array<JobReport> } |
{ key: ["version"], result: string },
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
{ key: ["volumes.get"], result: Array<Volume> } |
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData },
mutations:
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
{ key: ["library.create", string], result: null } |
{ key: ["files.delete", LibraryArgs<number>], result: null } |
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
{ key: ["library.edit", EditLibraryArgs], result: null } |
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
{ key: ["library.delete", string], result: null } |
{ key: ["locations.indexer_rulesdelete", LibraryArgs<number>], result: null } |
{ key: ["library.create", string], result: null } |
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
{ key: ["files.delete", LibraryArgs<number>], result: null } |
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
{ key: ["library.edit", EditLibraryArgs], result: null } |
{ key: ["library.delete", string], result: null } |
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
{ key: ["locations.delete", LibraryArgs<number>], result: null },
subscriptions:
{ key: ["invalidateQuery"], result: InvalidateOperationEvent } |
{ key: ["jobs.newThumbnail", LibraryArgs<null>], result: string }
};
export interface InvalidateOperationEvent { key: string, arg: any }
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null }
export interface LibraryConfig { version: string | null, name: string, description: string }
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
export interface ConfigMetadata { version: string | null }
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
export interface LibraryArgs<T> { library_id: string, arg: T }
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
export interface TagCreateArgs { name: string, color: string }
export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
export interface Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
export interface Node { id: number, pub_id: Array<number>, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array<SyncEvent> | null, jobs: Array<Job> | null, Location: Array<Location> | null }
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null }
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
export interface Job { id: Array<number>, name: string, node_id: number, action: number, status: number, data: Array<number> | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null }
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
export interface LibraryConfig { version: string | null, name: string, description: string }
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export interface TagCreateArgs { name: string, color: string }
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
export interface GenerateThumbsForLocationArgs { id: number, path: string }
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
export interface LibraryArgs<T> { library_id: string, arg: T }
export interface InvalidateOperationEvent { key: string, arg: any }
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
export interface Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
export interface IdentifyUniqueFilesArgs { id: number, path: string }
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
export interface SetFavoriteArgs { id: number, favorite: boolean }
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
export interface SetNoteArgs { id: number, note: string | null }
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null }
export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
export interface IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null }
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null }
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
export interface ConfigMetadata { version: string | null }
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
export interface GenerateThumbsForLocationArgs { id: number, path: string }
export interface IdentifyUniqueFilesArgs { id: number, path: string }
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
export interface SetNoteArgs { id: number, note: string | null }
export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
+106 -106
View File
@@ -2,136 +2,136 @@
export type Operations = {
queries:
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
{ key: ["volumes.get"], result: Array<Volume> } |
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
{ key: ["tags.getForFile", LibraryArgs<number>], result: Array<Tag> } |
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData } |
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
{ key: ["locations.indexer_rulesget", LibraryArgs<number>], result: IndexerRule } |
{ key: ["files.readMetadata", LibraryArgs<number>], result: null } |
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
{ key: ["getNode"], result: NodeState } |
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
{ key: ["tags.getAll", LibraryArgs<null>], result: Array<Tag> } |
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
{ key: ["getNode"], result: NodeState } |
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
{ key: ["version"], result: string } |
{ key: ["jobs.getRunning", LibraryArgs<null>], result: Array<JobReport> } |
{ key: ["version"], result: string },
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
{ key: ["volumes.get"], result: Array<Volume> } |
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData },
mutations:
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
{ key: ["library.create", string], result: null } |
{ key: ["files.delete", LibraryArgs<number>], result: null } |
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
{ key: ["library.edit", EditLibraryArgs], result: null } |
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
{ key: ["library.delete", string], result: null } |
{ key: ["locations.indexer_rulesdelete", LibraryArgs<number>], result: null } |
{ key: ["library.create", string], result: null } |
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
{ key: ["files.delete", LibraryArgs<number>], result: null } |
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
{ key: ["library.edit", EditLibraryArgs], result: null } |
{ key: ["library.delete", string], result: null } |
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
{ key: ["locations.delete", LibraryArgs<number>], result: null },
subscriptions:
{ key: ["invalidateQuery"], result: InvalidateOperationEvent } |
{ key: ["jobs.newThumbnail", LibraryArgs<null>], result: string }
};
export interface InvalidateOperationEvent { key: string, arg: any }
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null }
export interface LibraryConfig { version: string | null, name: string, description: string }
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
export interface ConfigMetadata { version: string | null }
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
export interface LibraryArgs<T> { library_id: string, arg: T }
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
export interface TagCreateArgs { name: string, color: string }
export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
export interface Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
export interface Node { id: number, pub_id: Array<number>, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array<SyncEvent> | null, jobs: Array<Job> | null, Location: Array<Location> | null }
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null }
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
export interface Job { id: Array<number>, name: string, node_id: number, action: number, status: number, data: Array<number> | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null }
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
export interface LibraryConfig { version: string | null, name: string, description: string }
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export interface TagCreateArgs { name: string, color: string }
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
export interface GenerateThumbsForLocationArgs { id: number, path: string }
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
export interface LibraryArgs<T> { library_id: string, arg: T }
export interface InvalidateOperationEvent { key: string, arg: any }
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
export interface Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
export interface IdentifyUniqueFilesArgs { id: number, path: string }
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
export interface SetFavoriteArgs { id: number, favorite: boolean }
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
export interface SetNoteArgs { id: number, note: string | null }
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null }
export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
export interface IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null }
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null | null, location: Location | null | null, key: Key | null | null }
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
export interface ConfigMetadata { version: string | null }
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
export interface GenerateThumbsForLocationArgs { id: number, path: string }
export interface IdentifyUniqueFilesArgs { id: number, path: string }
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
export interface SetNoteArgs { id: number, note: string | null }
export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
+16 -7
View File
@@ -143,7 +143,7 @@ impl StatefulJob for FileIdentifierJob {
// analyze each file_path
for file_path in &file_paths {
// get the cas_id and extract metadata
match prepare_file(&data.location_path, file_path).await {
match assemble_object_metadata(&data.location_path, file_path).await {
Ok(file) => {
let cas_id = file.cas_id.clone();
// create entry into chunks for created file data
@@ -151,7 +151,7 @@ impl StatefulJob for FileIdentifierJob {
cas_lookup.insert(cas_id, file_path.id);
}
Err(e) => {
info!("Error processing file: {:#?}", e);
info!("Error assembling Object metadata: {:#?}", e);
continue;
}
};
@@ -167,12 +167,14 @@ impl StatefulJob for FileIdentifierJob {
.exec()
.await?;
info!("Found {} existing files", existing_files.len());
info!("Found {} existing Objects", existing_files.len());
// link those existing files to their file paths
// Had to put the file_path in a variable outside of the closure, to satisfy the borrow checker
let library_ctx = ctx.library_ctx();
// if existing_files.len() > 0 {
// connect Paths that match existing Objects in the database
for existing_file in &existing_files {
if let Err(e) = library_ctx
.db
@@ -203,6 +205,11 @@ impl StatefulJob for FileIdentifierJob {
.filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id))
.collect::<Vec<_>>();
if new_files.is_empty() {
error!("This shouldn't happen?");
return Ok(());
}
// assemble prisma values for new unique files
let mut values = Vec::with_capacity(new_files.len() * 3);
for file in &new_files {
@@ -213,6 +220,8 @@ impl StatefulJob for FileIdentifierJob {
]);
}
info!("{:?}", values);
// create new file records with assembled values
let created_files: Vec<FileCreated> = ctx
.library_ctx()
@@ -220,7 +229,7 @@ impl StatefulJob for FileIdentifierJob {
._query_raw(Raw::new(
&format!(
"INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {}
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
vec!["({}, {}, {})"; new_files.len()].join(",")
),
values,
@@ -231,7 +240,6 @@ impl StatefulJob for FileIdentifierJob {
error!("Error inserting files: {:#?}", e);
Vec::new()
});
for created_file in created_files {
// associate newly created files with their respective file_paths
// TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk
@@ -254,12 +262,13 @@ impl StatefulJob for FileIdentifierJob {
}
}
// handle last step
// set the step data cursor to the last row of this chunk
if let Some(last_row) = file_paths.last() {
data.cursor.file_path_id = last_row.id;
} else {
return Ok(());
}
// }
ctx.progress(vec![
JobReportUpdate::CompletedTaskCount(state.step_number),
@@ -348,7 +357,7 @@ struct FileCreated {
pub cas_id: String,
}
async fn prepare_file(
async fn assemble_object_metadata(
location_path: impl AsRef<Path>,
file_path: &file_path::Data,
) -> Result<CreateFile, io::Error> {
+33 -8
View File
@@ -18,7 +18,10 @@ use super::JobReport;
// used to update the worker state from inside the worker thread
#[derive(Debug)]
pub enum WorkerEvent {
Progressed(Vec<JobReportUpdate>),
Progressed {
updates: Vec<JobReportUpdate>,
debounce: bool,
},
Completed(oneshot::Sender<()>),
Failed(oneshot::Sender<()>),
Paused(Vec<u8>, oneshot::Sender<()>),
@@ -34,7 +37,18 @@ pub struct WorkerContext {
impl WorkerContext {
pub fn progress(&self, updates: Vec<JobReportUpdate>) {
self.events_tx
.send(WorkerEvent::Progressed(updates))
.send(WorkerEvent::Progressed {
updates,
debounce: false,
})
.expect("critical error: failed to send worker worker progress event updates");
}
pub fn progress_debounced(&self, updates: Vec<JobReportUpdate>) {
self.events_tx
.send(WorkerEvent::Progressed {
updates,
debounce: true,
})
.expect("critical error: failed to send worker worker progress event updates");
}
@@ -124,9 +138,10 @@ impl Worker {
loop {
interval.tick().await;
if events_tx
.send(WorkerEvent::Progressed(vec![
JobReportUpdate::SecondsElapsed(1),
]))
.send(WorkerEvent::Progressed {
updates: vec![JobReportUpdate::SecondsElapsed(1)],
debounce: false,
})
.is_err() && events_tx.is_closed()
{
break;
@@ -171,17 +186,27 @@ impl Worker {
mut worker_events_rx: UnboundedReceiver<WorkerEvent>,
library: LibraryContext,
) {
let mut last = Instant::now();
while let Some(command) = worker_events_rx.recv().await {
let mut worker = worker.lock().await;
match command {
WorkerEvent::Progressed(changes) => {
WorkerEvent::Progressed { updates, debounce } => {
if debounce {
let current = Instant::now();
if current.duration_since(last) > Duration::from_millis(1000 / 60) {
last = current
} else {
continue;
}
}
// protect against updates if job is not running
if worker.report.status != JobStatus::Running {
continue;
};
for change in changes {
match change {
for update in updates {
match update {
JobReportUpdate::TaskCount(task_count) => {
worker.report.task_count = task_count as i32;
}
+1 -1
View File
@@ -66,7 +66,7 @@ pub struct IndexerJobStepEntry {
impl IndexerJobData {
fn on_scan_progress(ctx: WorkerContext, progress: Vec<ScanProgress>) {
ctx.progress(
ctx.progress_debounced(
progress
.iter()
.map(|p| match p.clone() {
@@ -1,7 +1,7 @@
import { AppPropsContext, explorerStore } from '@sd/client';
import { ExplorerItem } from '@sd/core';
import clsx from 'clsx';
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { useSnapshot } from 'valtio';
import icons from '../../assets/icons';
@@ -39,6 +39,7 @@ export default function FileThumb({ data, ...props }: Props) {
if (file_thumb_url)
return (
<img
// onLoad={}
style={props.style}
className={clsx('pointer-events-none z-90', props.className)}
src={file_thumb_url}
@@ -1,17 +0,0 @@
import { ContextMenu, ContextMenuProps, Root, Trigger } from '@sd/ui';
import React, { ComponentProps } from 'react';
export const WithContextMenu: React.FC<{
menu: ContextMenuProps['items'];
children: ComponentProps<typeof Trigger>['children'];
}> = (props) => {
const { menu: sections = [], children } = props;
return (
<Root>
<Trigger>{children}</Trigger>
<ContextMenu items={sections} />
</Root>
);
};
+1 -9
View File
@@ -1,15 +1,7 @@
import { Button } from '@sd/ui';
import React from 'react';
import { WithContextMenu } from '../components/layout/MenuOverlay';
export const ContentScreen: React.FC<unknown> = (props) => {
// const [address, setAddress] = React.useState('');
return (
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
<WithContextMenu menu={[[{ label: 'jeff', children: [[{ label: 'jeff' }]] }]]}>
<Button variant="gray">Test</Button>
</WithContextMenu>
</div>
);
return <div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll"></div>;
};
-41
View File
@@ -1,41 +0,0 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { FileText, Plus, Trash } from 'phosphor-react';
import React from 'react';
import { ContextMenu } from './ContextMenu';
export default {
title: 'UI/Context Menu',
component: ContextMenu,
argTypes: {},
parameters: {},
args: {}
} as ComponentMeta<typeof ContextMenu>;
const Template: ComponentStory<typeof ContextMenu> = (args) => <ContextMenu {...args} />;
export const Default = Template.bind({});
Default.args = {
items: [
[
{
label: 'New Item',
icon: Plus,
onClick: () => {}
}
],
[
{
label: 'View Info',
icon: FileText,
onClick: () => {}
},
{
label: 'Delete',
icon: Trash,
danger: true,
onClick: () => {}
}
]
]
};
-128
View File
@@ -1,128 +0,0 @@
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import { Root, Trigger } from '@radix-ui/react-context-menu';
import clsx from 'clsx';
import { CaretRight, Icon } from 'phosphor-react';
import { Question } from 'phosphor-react';
import React from 'react';
export interface ContextMenuItem {
label: string;
icon?: Icon;
danger?: boolean;
active?: boolean;
leftItem?: React.ReactNode;
rightItem?: React.ReactNode;
onClick?: React.MouseEventHandler<HTMLDivElement>;
children?: ContextMenuSection[];
}
export type ContextMenuSection = (ContextMenuItem | string)[];
export interface ContextMenuProps {
items?: ContextMenuSection[];
className?: string;
isChild?: boolean;
}
export const ContextMenu: React.FC<ContextMenuProps> = (props) => {
const { items: sections = [], className, isChild, ...rest } = props;
const ContentPrimitive = isChild ? ContextMenuPrimitive.SubContent : ContextMenuPrimitive.Content;
return (
<ContentPrimitive
sideOffset={7}
// onInteractOutside={(e) => {
// e.preventDefault();
// }}
alignOffset={7}
className={clsx(
'shadow-md min-w-[11rem] py-0.5 shadow-gray-300 dark:shadow-gray-750 flex flex-col select-none cursor-default bg-gray-50 text-gray-800 border-gray-200 dark:bg-gray-950 dark:text-gray-100 text-left text-sm rounded-lg ',
className
)}
{...rest}
>
{sections.map((sec, i) => (
<React.Fragment key={i}>
{i !== 0 && (
<ContextMenuPrimitive.Separator className="mx-2 border-0 border-b pointer-events-none border-b-gray-300 dark:border-b-gray-600" />
)}
<ContextMenuPrimitive.Group className="flex flex-col items-stretch">
{sec.map((item) => {
if (typeof item === 'string')
return (
<ContextMenuPrimitive.Label
key={item}
className="mt-1 ml-2 text-xs text-gray-400 uppercase"
>
{item}
</ContextMenuPrimitive.Label>
);
const { icon: ItemIcon } = item;
let ItemComponent:
| typeof ContextMenuPrimitive.Item
| typeof ContextMenuPrimitive.Trigger = ContextMenuPrimitive.Item;
if ((item.children?.length ?? 0) > 0)
ItemComponent = (({ children, ref, ...props }) => (
<ContextMenuPrimitive.ContextMenuSub>
<ContextMenuPrimitive.SubTrigger {...props}>
{children}
</ContextMenuPrimitive.SubTrigger>
<ContextMenu
isChild
items={item.children}
className="relative -left-1 -top-2"
/>
</ContextMenuPrimitive.ContextMenuSub>
)) as typeof ContextMenuPrimitive.Trigger;
return (
<ItemComponent
style={{
font: 'inherit',
textAlign: 'inherit'
}}
className={clsx(
'focus:outline-none group cursor-default flex-1 px-1.5 py-1 group-first:pt-1.5 [&[data-state="open"]_div]:bg-primary',
item.danger && 'text-red-600 dark:text-red-400',
item.active && 'bg-gray-100 dark:bg-gray-950'
)}
onClick={item.onClick}
key={item.label}
>
<div
className={clsx(
'flex py-[0.3em] flex-row items-center px-1 rounded group-focus:bg-primary group-hover:bg-primary',
item.danger &&
'group-focus:bg-red-500 group-hover:bg-red-500 group-focus:text-white group-hover:text-white'
)}
>
{ItemIcon && <ItemIcon size={18} />}
{item.leftItem}
<ContextMenuPrimitive.Label className="ml-1.5 leading-snug flex-grow text-sm font-normal">
{item.label}
</ContextMenuPrimitive.Label>
{item.rightItem}
{(item.children?.length ?? 0) > 0 && (
<CaretRight weight="fill" size={12} alt="" />
)}
</div>
</ItemComponent>
);
})}
</ContextMenuPrimitive.Group>
</React.Fragment>
))}
</ContentPrimitive>
);
};
export { Trigger, Root };