[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>
This commit is contained in:
Jamie Pine 2023-05-23 15:21:52 -07:00 committed by GitHub
parent da2e78dc61
commit ea46e7736a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 157 additions and 106 deletions

View file

@ -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;

View file

@ -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)

View file

@ -331,6 +331,7 @@ pub struct JobReport {
pub completed_task_count: i32,
pub message: String,
pub estimated_completion: DateTime<Utc>,
// pub percentage_complete: f64,
}
@ -364,8 +365,8 @@ impl From<job::Data> 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<job::Data> 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(),
}
}

View file

@ -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<WorkerEvent>,
worker_events_rx: Option<UnboundedReceiver<WorkerEvent>>,
start_time: Option<DateTime<Utc>>,
}
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");
}

View file

@ -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<any>;
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<JobReport['status'], string> = {
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 (
<li
@ -148,20 +152,20 @@ function Job({ job, clearJob, className, isGroup }: JobProps) {
)}
/>
</div>
<div className="flex w-full flex-col">
<div className="flex flex-col w-full">
<div className="flex items-center">
<div className="truncate">
<span className="truncate font-semibold">{niceData.name}</span>
<span className="font-semibold truncate">{niceData.name}</span>
<p className="mb-[5px] mt-[2px] flex gap-1 truncate text-ink-faint">
{job.status === 'Queued' && <p>{job.status}:</p>}
{niceData.filesDiscovered}
{niceData.subtext}
{time && ' • '}
<span className="truncate">{time}</span>
</p>
<div className="flex gap-1 truncate text-ink-faint"></div>
</div>
<div className="grow" />
<div className="ml-7 flex flex-row space-x-2">
<div className="flex flex-row space-x-2 ml-7">
{/* {job.status === 'Running' && (
<Button size="icon">
<Tooltip label="Coming Soon">
@ -189,12 +193,18 @@ function Job({ job, clearJob, className, isGroup }: JobProps) {
);
}
function JobCountTextCondition(job: JobReport, word: string) {
const addStoEnd = job.task_count > 1 || job?.task_count === 0 ? `${word}s` : `${word}`;
return addStoEnd;
function appendPlural(job: JobReport, word: string, niceDataKey?: string) {
const condition = (condition: boolean) => (condition ? `${word}s` : `${word}`);
switch (niceDataKey) {
case 'file_identifier':
return condition(job.metadata?.total_orphan_paths > 1);
default:
return condition(job.task_count > 1);
}
}
function numberWithCommas(x: number) {
if (!x) return 0;
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

View file

@ -2,7 +2,7 @@ import { Folder } from '@sd/assets/icons';
import clsx from 'clsx';
import dayjs from 'dayjs';
import { X } from 'phosphor-react';
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { JobReport } from '@sd/client';
import { Button, ProgressBar, Tooltip } from '@sd/ui';
import Job from './Job';
@ -51,7 +51,7 @@ function JobGroup({ data, clearJob }: JobGroupProps) {
size="icon"
>
<Tooltip label="Remove">
<X className="h-4 w-4 cursor-pointer" />
<X className="w-4 h-4 cursor-pointer" />
</Tooltip>
</Button>
)}
@ -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')}
/>
<div className="flex w-full flex-col">
<div className="flex flex-col w-full">
<div className="flex items-center">
<div className="truncate">
<p className="truncate font-semibold">
<p className="font-semibold truncate">
{allJobsCompleted
? `Added location "${data.metadata.init.location.name || ''}"`
? `Added location "${
data.metadata.init.location.name || ''
}"`
: `Indexing "${data.metadata.init.location.name || ''}"`}
</p>
<p className="my-[2px] text-ink-faint">
<b>{tasks.total} </b>
{tasks.total <= 1 ? 'item' : 'items'}
{tasks.total <= 1 ? 'task' : 'tasks'}
{' • '}
{date_started}
{!allJobsCompleted && totalGroupTime && ' • '}

View file

@ -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;
}

View file

@ -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<typeof SEARCH_PARAMS>;
@ -46,8 +44,7 @@ export const Component = () => {
isFetchingNextPage={query.isFetchingNextPage}
scrollRef={page?.ref}
>
<Statistics />
<Categories selected={selectedCategory} onSelectedChanged={setSelectedCategory}/>
<Categories selected={selectedCategory} onSelectedChanged={setSelectedCategory} />
</Explorer>
</>
);

View file

@ -103,6 +103,8 @@ export type MasterPasswordChangeArgs = { password: Protected<string>; 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<string>; 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> = 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<string | null> | 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> = 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<string>; secret_key: Protected<string>; 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 }
/**