From ea46e7736a26157757e8a6e34a1a2d6a8ee9e167 Mon Sep 17 00:00:00 2001 From: Jamie Pine <32987599+jamiepine@users.noreply.github.com> Date: Tue, 23 May 2023 15:21:52 -0700 Subject: [PATCH] [ENG-635] Job time estimation (#842) * fix extension case sensitivity * job time estimation wip * update schema * use `DateTime` to handle estimated completion time * use a date with `dayjs` * remove old migrations * update migrations * remove dead code * Quick tweaks * unused import --------- Co-authored-by: brxken128 <77554505+brxken128@users.noreply.github.com> Co-authored-by: ameer2468 <33054370+ameer2468@users.noreply.github.com> --- .../migration.sql | 44 ++++++++++ core/prisma/schema.prisma | 12 +-- core/src/job/job_manager.rs | 9 +- core/src/job/worker.rs | 22 ++++- .../Layout/Sidebar/JobManager/Job.tsx | 86 +++++++++++-------- .../Layout/Sidebar/JobManager/JobGroup.tsx | 14 +-- .../Sidebar/JobManager/useJobTimeText.tsx | 29 ------- interface/app/$libraryId/overview/index.tsx | 11 +-- packages/client/src/core.ts | 36 ++++---- 9 files changed, 157 insertions(+), 106 deletions(-) create mode 100644 core/prisma/migrations/20230523212622_job_estimated_completion/migration.sql delete mode 100644 interface/app/$libraryId/Layout/Sidebar/JobManager/useJobTimeText.tsx diff --git a/core/prisma/migrations/20230523212622_job_estimated_completion/migration.sql b/core/prisma/migrations/20230523212622_job_estimated_completion/migration.sql new file mode 100644 index 000000000..7d6a37dee --- /dev/null +++ b/core/prisma/migrations/20230523212622_job_estimated_completion/migration.sql @@ -0,0 +1,44 @@ +/* + Warnings: + + - You are about to drop the column `parent_id` on the `file_path` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "job" ADD COLUMN "date_estimated_completion" DATETIME; + +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_file_path" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "is_dir" BOOLEAN NOT NULL DEFAULT false, + "cas_id" TEXT, + "integrity_checksum" TEXT, + "location_id" INTEGER NOT NULL, + "materialized_path" TEXT NOT NULL, + "name" TEXT NOT NULL, + "extension" TEXT NOT NULL, + "size_in_bytes" TEXT NOT NULL DEFAULT '0', + "inode" BLOB NOT NULL, + "device" BLOB NOT NULL, + "object_id" INTEGER, + "key_id" INTEGER, + "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "file_path_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "file_path_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "file_path_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_file_path" ("cas_id", "date_created", "date_indexed", "date_modified", "device", "extension", "id", "inode", "integrity_checksum", "is_dir", "key_id", "location_id", "materialized_path", "name", "object_id", "pub_id", "size_in_bytes") SELECT "cas_id", "date_created", "date_indexed", "date_modified", "device", "extension", "id", "inode", "integrity_checksum", "is_dir", "key_id", "location_id", "materialized_path", "name", "object_id", "pub_id", "size_in_bytes" FROM "file_path"; +DROP TABLE "file_path"; +ALTER TABLE "new_file_path" RENAME TO "file_path"; +CREATE UNIQUE INDEX "file_path_pub_id_key" ON "file_path"("pub_id"); +CREATE UNIQUE INDEX "file_path_integrity_checksum_key" ON "file_path"("integrity_checksum"); +CREATE INDEX "file_path_location_id_idx" ON "file_path"("location_id"); +CREATE INDEX "file_path_location_id_materialized_path_idx" ON "file_path"("location_id", "materialized_path"); +CREATE UNIQUE INDEX "file_path_location_id_materialized_path_name_extension_key" ON "file_path"("location_id", "materialized_path", "name", "extension"); +CREATE UNIQUE INDEX "file_path_location_id_inode_device_key" ON "file_path"("location_id", "inode", "device"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index 99bc7047f..3fd4cdd6b 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -385,11 +385,13 @@ model Job { parent_id Bytes? - task_count Int @default(1) - completed_task_count Int @default(0) - date_created DateTime @default(now()) - date_started DateTime? @default(now()) // Started execution - date_completed DateTime? @default(now()) // Finished execution + task_count Int @default(1) + completed_task_count Int @default(0) + date_estimated_completion DateTime? // Estimated timestamp that the job will be complete at + + date_created DateTime @default(now()) + date_started DateTime? @default(now()) // Started execution + date_completed DateTime? @default(now()) // Finished execution nodes Node @relation(fields: [node_id], references: [id], onDelete: Cascade, onUpdate: Cascade) diff --git a/core/src/job/job_manager.rs b/core/src/job/job_manager.rs index ef9b6d319..7c2db88ad 100644 --- a/core/src/job/job_manager.rs +++ b/core/src/job/job_manager.rs @@ -331,6 +331,7 @@ pub struct JobReport { pub completed_task_count: i32, pub message: String, + pub estimated_completion: DateTime, // pub percentage_complete: f64, } @@ -364,8 +365,8 @@ impl From for JobReport { .map(|errors_str| errors_str.split("\n\n").map(str::to_string).collect()) .unwrap_or_default(), created_at: Some(data.date_created.into()), - started_at: data.date_started.map(|d| d.into()), - completed_at: data.date_completed.map(|d| d.into()), + started_at: data.date_started.map(DateTime::into), + completed_at: data.date_completed.map(DateTime::into), parent_id: data .parent_id .map(|id| Uuid::from_slice(&id).expect("corrupted database")), @@ -373,6 +374,9 @@ impl From for JobReport { task_count: data.task_count, completed_task_count: data.completed_task_count, message: String::new(), + estimated_completion: data + .date_estimated_completion + .map_or(Utc::now(), DateTime::into), } } } @@ -395,6 +399,7 @@ impl JobReport { parent_id: None, completed_task_count: 0, message: String::new(), + estimated_completion: Utc::now(), } } diff --git a/core/src/job/worker.rs b/core/src/job/worker.rs index 469e77b04..06fe622f0 100644 --- a/core/src/job/worker.rs +++ b/core/src/job/worker.rs @@ -1,7 +1,7 @@ use crate::invalidate_query; use crate::job::{DynJob, JobError, JobManager, JobReportUpdate, JobStatus}; use crate::library::Library; -use chrono::Utc; +use chrono::{DateTime, Utc}; use std::{sync::Arc, time::Duration}; use tokio::sync::oneshot; use tokio::{ @@ -67,6 +67,7 @@ pub struct Worker { report: JobReport, worker_events_tx: UnboundedSender, worker_events_rx: Option>, + start_time: Option>, } impl Worker { @@ -78,6 +79,7 @@ impl Worker { report, worker_events_tx, worker_events_rx: Some(worker_events_rx), + start_time: None, } } @@ -111,6 +113,8 @@ impl Worker { worker.report.started_at = Some(Utc::now()); } + worker.start_time = Some(Utc::now()); + // If the report doesn't have a created_at date, it's a new report if worker.report.created_at.is_none() { worker.report.create(&library).await?; @@ -217,6 +221,22 @@ impl Worker { } } } + // Calculate elapsed time + if let Some(start_time) = worker.start_time { + let elapsed = Utc::now() - start_time; + + // Calculate remaining time + let task_count = worker.report.task_count as usize; + let completed_task_count = worker.report.completed_task_count as usize; + let remaining_task_count = task_count.saturating_sub(completed_task_count); + let remaining_time_per_task = elapsed / (completed_task_count + 1) as i32; // Adding 1 to avoid division by zero + let remaining_time = remaining_time_per_task * remaining_task_count as i32; + + // Update the report with estimated remaining time + worker.report.estimated_completion = Utc::now() + .checked_add_signed(remaining_time) + .unwrap_or(Utc::now()); + } invalidate_query!(library, "jobs.getRunning"); } diff --git a/interface/app/$libraryId/Layout/Sidebar/JobManager/Job.tsx b/interface/app/$libraryId/Layout/Sidebar/JobManager/Job.tsx index 5243f59ff..b40129883 100644 --- a/interface/app/$libraryId/Layout/Sidebar/JobManager/Job.tsx +++ b/interface/app/$libraryId/Layout/Sidebar/JobManager/Job.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import dayjs from 'dayjs'; import { Camera, Copy, @@ -16,12 +17,11 @@ import { memo } from 'react'; import { JobReport } from '@sd/client'; import { ProgressBar } from '@sd/ui'; import './Job.scss'; -import { useJobTimeText } from './useJobTimeText'; interface JobNiceData { name: string; icon: React.ForwardRefExoticComponent; - filesDiscovered: string; + subtext: string; } const getNiceData = ( @@ -35,9 +35,7 @@ const getNiceData = ( ? `Indexed paths at ${job.metadata?.location_path} ` : `Processing added location...`, icon: Folder, - filesDiscovered: `${numberWithCommas( - job.metadata?.total_paths || 0 - )} ${JobCountTextCondition(job, 'path')}` + subtext: `${numberWithCommas(job.metadata?.total_paths || 0)} ${appendPlural(job, 'path')}` }, thumbnailer: { name: `${ @@ -46,12 +44,14 @@ const getNiceData = ( : 'Generated thumbnails' }`, icon: Camera, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'item')}` + subtext: `${numberWithCommas(job.completed_task_count)} of ${numberWithCommas( + job.task_count + )} ${appendPlural(job, 'thumbnail')}` }, shallow_thumbnailer: { name: `Generating thumbnails for current directory`, icon: Camera, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'item')}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'item')}` }, file_identifier: { name: `${ @@ -60,60 +60,51 @@ const getNiceData = ( : 'Extracted metadata' }`, icon: Eye, - filesDiscovered: + subtext: job.message || - `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'item')}` + `${numberWithCommas(job.metadata.total_orphan_paths)} ${appendPlural( + job, + 'file', + 'file_identifier' + )}` }, object_validator: { name: `Generated full object hashes`, icon: Fingerprint, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition( - job, - 'object' - )}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'object')}` }, file_encryptor: { name: `Encrypted`, icon: LockSimple, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'file')}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'file')}` }, file_decryptor: { name: `Decrypted`, icon: LockSimpleOpen, - filesDiscovered: `${numberWithCommas(job.task_count)}${JobCountTextCondition(job, 'file')}` + subtext: `${numberWithCommas(job.task_count)}${appendPlural(job, 'file')}` }, file_eraser: { name: `Securely erased`, icon: TrashSimple, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'file')}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'file')}` }, file_deleter: { name: `Deleted`, icon: Trash, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'file')}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'file')}` }, file_copier: { name: `Copied`, icon: Copy, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'file')}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'file')}` }, file_cutter: { name: `Moved`, icon: Scissors, - filesDiscovered: `${numberWithCommas(job.task_count)} ${JobCountTextCondition(job, 'file')}` + subtext: `${numberWithCommas(job.task_count)} ${appendPlural(job, 'file')}` } }); -const StatusColors: Record = { - Running: 'text-blue-500', - Failed: 'text-red-500', - Completed: 'text-green-500', - CompletedWithErrors: 'text-orange-500', - Queued: 'text-yellow-500', - Canceled: 'text-gray-500', - Paused: 'text-gray-500' -}; - interface JobProps { job: JobReport; clearJob?: (arg: string) => void; @@ -121,15 +112,28 @@ interface JobProps { isGroup?: boolean; } +function formatEstimatedRemainingTime(end_date: string) { + const duration = dayjs.duration(new Date(end_date).getTime() - Date.now()); + + if (duration.hours() > 0) { + return `${duration.hours()} hour${duration.hours() > 1 ? 's' : ''} remaining`; + } else if (duration.minutes() > 0) { + return `${duration.minutes()} minute${duration.minutes() > 1 ? 's' : ''} remaining`; + } else { + return `${duration.seconds()} second${duration.seconds() > 1 ? 's' : ''} remaining`; + } +} + function Job({ job, clearJob, className, isGroup }: JobProps) { const niceData = getNiceData(job, isGroup)[job.name] || { name: job.name, icon: Question, - filesDiscovered: job.name + subtext: job.name }; const isRunning = job.status === 'Running'; - const time = useJobTimeText(job); + // dayjs from seconds to time + const time = isRunning ? formatEstimatedRemainingTime(job.estimated_completion) : ''; return (
  • -
    +
    - {niceData.name} + {niceData.name}

    {job.status === 'Queued' &&

    {job.status}:

    } - {niceData.filesDiscovered} + {niceData.subtext} {time && ' • '} {time}

    -
    +
    {/* {job.status === 'Running' && ( )} @@ -67,17 +67,19 @@ function JobGroup({ data, clearJob }: JobGroupProps) { src={Folder} className={clsx('relative left-[-2px] top-2 z-10 mr-3 h-6 w-6')} /> -
    +
    -

    +

    {allJobsCompleted - ? `Added location "${data.metadata.init.location.name || ''}"` + ? `Added location "${ + data.metadata.init.location.name || '' + }"` : `Indexing "${data.metadata.init.location.name || ''}"`}

    {tasks.total} - {tasks.total <= 1 ? 'item' : 'items'} + {tasks.total <= 1 ? 'task' : 'tasks'} {' • '} {date_started} {!allJobsCompleted && totalGroupTime && ' • '} diff --git a/interface/app/$libraryId/Layout/Sidebar/JobManager/useJobTimeText.tsx b/interface/app/$libraryId/Layout/Sidebar/JobManager/useJobTimeText.tsx deleted file mode 100644 index 837a3e28b..000000000 --- a/interface/app/$libraryId/Layout/Sidebar/JobManager/useJobTimeText.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import dayjs from 'dayjs'; -import { useEffect, useMemo } from 'react'; -import { JobReport } from '@sd/client'; -import { useForceUpdate } from '~/util'; - -export function useJobTimeText(job: JobReport): string | null { - const forceUpdate = useForceUpdate(); - - const elapsedTimeText = useMemo(() => { - let newText: string; - if (job.status === 'Running') { - newText = `Elapsed ${dayjs(job.started_at).fromNow(true)}`; - } else if (job.completed_at) { - newText = `Took ${dayjs(job.started_at).from(job.completed_at, true)}`; - } else { - newText = `Took ${dayjs(job.started_at).fromNow(true)}`; - } - return newText; - }, [job]); - - useEffect(() => { - if (job.status === 'Running') { - const interval = setInterval(forceUpdate, 1000); - return () => clearInterval(interval); - } - }, [job.status, forceUpdate]); - - return elapsedTimeText === 'Took NaN years' ? null : elapsedTimeText; -} diff --git a/interface/app/$libraryId/overview/index.tsx b/interface/app/$libraryId/overview/index.tsx index 1daef6fcb..a7b2ae2ff 100644 --- a/interface/app/$libraryId/overview/index.tsx +++ b/interface/app/$libraryId/overview/index.tsx @@ -1,8 +1,7 @@ -import { getIcon, iconNames } from '@sd/assets/util'; -import { useInfiniteQuery } from '@tanstack/react-query'; -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import 'react-loading-skeleton/dist/skeleton.css'; import { z } from '@sd/ui/src/forms'; +import { Category } from '~/../packages/client/src'; import { useExplorerTopBarOptions } from '~/hooks'; import Explorer from '../Explorer'; import { SEARCH_PARAMS } from '../Explorer/util'; @@ -11,8 +10,7 @@ import { TopBarPortal } from '../TopBar/Portal'; import TopBarOptions from '../TopBar/TopBarOptions'; import Statistics from '../overview/Statistics'; import { Categories } from './Categories'; -import { useItems } from "./data" -import { Category } from '~/../packages/client/src'; +import { useItems } from './data'; export type SearchArgs = z.infer; @@ -46,8 +44,7 @@ export const Component = () => { isFetchingNextPage={query.isFetchingNextPage} scrollRef={page?.ref} > - - + ); diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 5e32fb2df..9e31b9303 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -103,6 +103,8 @@ export type MasterPasswordChangeArgs = { password: Protected; algorithm: */ export type NodeConfig = { id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null } +export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string } + /** * This denotes the `StoredKey` version. */ @@ -117,12 +119,12 @@ export type EncryptedKey = number[] export type PeerId = string -export type 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 } - export type GenerateThumbsForLocationArgs = { id: number; path: string } export type LibraryConfigWrapped = { uuid: string; config: LibraryConfig } +export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string } + /** * These parameters define the password-hashing level. * @@ -204,19 +206,17 @@ export type Salt = number[] */ export type Category = "Recents" | "Favorites" | "Photos" | "Videos" | "Movies" | "Music" | "Documents" | "Downloads" | "Encrypted" | "Projects" | "Applications" | "Archives" | "Databases" | "Games" | "Books" | "Contacts" | "Trash" -export type Statistics = { id: number; date_captured: string; total_object_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 type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string } - export type FileCopierJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: string; target_file_name_suffix: string | null } export type SetFavoriteArgs = { id: number; favorite: boolean } -export type Location = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string } - export type FilePathFilterArgs = { locationId?: number | null; search?: string; extension?: string | null; createdAt?: OptionalRange; path?: string | null; object?: ObjectFilterArgs | null } -export type Object = { id: number; pub_id: number[]; kind: number; 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_accessed: string | null } +export type Statistics = { id: number; date_captured: string; total_object_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 type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string } + +export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string } export type FilePathSearchOrdering = { name: SortOrder } | { sizeInBytes: SortOrder } | { dateCreated: SortOrder } | { dateModified: SortOrder } | { dateIndexed: SortOrder } | { object: ObjectSearchOrdering } @@ -229,10 +229,10 @@ export type IdentifyUniqueFilesArgs = { id: number; path: string } */ export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm" -export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string } - export type OwnedOperationItem = { id: any; data: OwnedOperationData } +export type 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 } + export type ObjectSearchOrdering = { dateAccessed: SortOrder } export type CRDTOperationType = SharedOperation | RelationOperation | OwnedOperation @@ -248,7 +248,7 @@ export type MaybeNot = T | { not: T } export type SpacedropArgs = { peer_id: PeerId; file_path: string[] } -export type JobReport = { id: string; name: string; action: string | null; data: number[] | null; metadata: any | null; is_background: boolean; errors_text: string[]; created_at: string | null; started_at: string | null; completed_at: string | null; parent_id: string | null; status: JobStatus; task_count: number; completed_task_count: number; message: string } +export type JobReport = { id: string; name: string; action: string | null; data: number[] | null; metadata: any | null; is_background: boolean; errors_text: string[]; created_at: string | null; started_at: string | null; completed_at: string | null; parent_id: string | null; status: JobStatus; task_count: number; completed_task_count: number; message: string; estimated_completion: string } export type ObjectFilterArgs = { favorite?: boolean | null; hidden?: boolean | null; dateAccessed?: MaybeNot | null; kind?: number[]; tags?: number[] } @@ -302,6 +302,8 @@ export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMa export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null +export type Tag = { id: number; pub_id: number[]; name: string | null; color: string | null; total_objects: number | null; redundancy_goal: number | null; date_created: string; date_modified: string } + export type TagUpdateArgs = { id: number; name: string | null; color: string | null } export type ObjectValidatorArgs = { id: number; path: string } @@ -310,11 +312,15 @@ export type TagAssignArgs = { object_id: number; tag_id: number; unassign: boole export type ChangeNodeNameArgs = { name: string } +export type Object = { id: number; pub_id: number[]; kind: number; 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_accessed: string | null } + /** * This defines all available password hashing algorithms. */ export type HashingAlgorithm = { name: "Argon2id"; params: Params } | { name: "BalloonBlake3"; params: Params } +export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" | "CompletedWithErrors" + export type FilePathWithObject = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string; object: Object | null } export type LocationWithIndexerRules = { id: number; pub_id: number[]; node_id: number; name: string; path: string; total_capacity: number | null; available_capacity: number | null; is_archived: boolean; generate_preview_media: boolean; sync_preview_media: boolean; hidden: boolean; date_created: string; indexer_rules: { indexer_rule: IndexerRule }[] } @@ -332,14 +338,8 @@ export type AutomountUpdateArgs = { uuid: string; status: boolean } export type Protected = T -export type FilePath = { id: number; pub_id: number[]; is_dir: boolean; cas_id: string | null; integrity_checksum: string | null; location_id: number; materialized_path: string; name: string; extension: string; size_in_bytes: string; inode: number[]; device: number[]; object_id: number | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string } - -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused" | "CompletedWithErrors" - export type RestoreBackupArgs = { password: Protected; secret_key: Protected; path: string } -export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string } - export type RelationOperation = { relation_item: string; relation_group: string; relation: string; data: RelationOperationData } /**