Rename Files to Objects (#396)

* backend rename complete

* finalize Object refactor frontend
+ remove some exhaustive dep suppression
+ FIX INVALIDATE QUERY BUG
This commit is contained in:
Jamie Pine 2022-10-04 09:16:49 -07:00 committed by GitHub
parent ed06e3051e
commit cbd58ee438
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 313 additions and 375 deletions

1
.gitignore vendored
View file

@ -66,3 +66,4 @@ examples/*/*.lock
/sdserver_data
.spacedrive
dev.db-journal

View file

@ -14,7 +14,7 @@ const placeholderFileItems: FilePath[] = [
date_indexed: '2020-01-01T00:00:00.000Z',
date_modified: '2020-01-01T00:00:00.000Z',
extension: '',
file_id: 1,
object_id: 1,
id: 1,
location_id: 1,
materialized_path: '',
@ -28,7 +28,7 @@ const placeholderFileItems: FilePath[] = [
date_indexed: '2020-01-01T00:00:00.000Z',
date_modified: '2020-01-01T00:00:00.000Z',
extension: '',
file_id: 2,
object_id: 2,
id: 2,
location_id: 2,
materialized_path: '',
@ -42,7 +42,7 @@ const placeholderFileItems: FilePath[] = [
date_indexed: '2020-01-01T00:00:00.000Z',
date_modified: '2020-01-01T00:00:00.000Z',
extension: 'tsx',
file_id: 3,
object_id: 3,
id: 3,
location_id: 3,
materialized_path: '',
@ -56,7 +56,7 @@ const placeholderFileItems: FilePath[] = [
date_indexed: '2020-01-01T00:00:00.000Z',
date_modified: '2020-01-01T00:00:00.000Z',
extension: 'vite',
file_id: 4,
object_id: 4,
id: 4,
location_id: 4,
materialized_path: '',
@ -70,7 +70,7 @@ const placeholderFileItems: FilePath[] = [
date_indexed: '2020-01-01T00:00:00.000Z',
date_modified: '2020-01-01T00:00:00.000Z',
extension: 'docker',
file_id: 5,
object_id: 5,
id: 5,
location_id: 5,
materialized_path: '',
@ -90,7 +90,7 @@ export interface DeviceProps {
const Device = ({ name, locations, size, type }: DeviceProps) => {
return (
<View style={tw`bg-gray-600 border rounded-md border-gray-550 my-2`}>
<View style={tw`my-2 bg-gray-600 border rounded-md border-gray-550`}>
<View style={tw`flex flex-row items-center px-3.5 pt-3 pb-2`}>
<View style={tw`flex flex-row items-center`}>
{type === 'phone' && (
@ -107,7 +107,7 @@ const Device = ({ name, locations, size, type }: DeviceProps) => {
</View>
</View>
{/* Size */}
<Text style={tw`font-semibold text-sm ml-2 text-gray-400`}>{size}</Text>
<Text style={tw`ml-2 text-sm font-semibold text-gray-400`}>{size}</Text>
</View>
{/* Locations/Files TODO: Maybe use FlashList? */}
<FlatList

View file

@ -53,11 +53,9 @@ export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" }
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
export type ExplorerItem = { type: "Path" } & { 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 } | { type: "Object" } & { 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, paths: Array<FilePath> }
export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null } | { type: "Object" } & { 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, file_paths: Array<FilePath> }
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 }
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 }
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string }
export interface GenerateThumbsForLocationArgs { id: number, path: string }
@ -93,17 +91,19 @@ export interface NodeConfig { version: string | null, id: string, name: string,
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
export interface Object { 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 }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export interface SetFavoriteArgs { id: number, favorite: boolean }
export interface SetNoteArgs { id: number, note: 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 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 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 }
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
export interface TagAssignArgs { object_id: number, tag_id: number, unassign: boolean }
export interface TagCreateArgs { name: string, color: string }

View file

@ -1,14 +1,5 @@
-- CreateTable
CREATE TABLE "_migrations" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"checksum" TEXT NOT NULL,
"steps_applied" INTEGER NOT NULL DEFAULT 0,
"applied_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "sync_events" (
CREATE TABLE "sync_event" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"node_id" INTEGER NOT NULL,
"timestamp" TEXT NOT NULL,
@ -16,14 +7,14 @@ CREATE TABLE "sync_events" (
"kind" INTEGER NOT NULL,
"column" TEXT,
"value" TEXT NOT NULL,
CONSTRAINT "sync_events_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
CONSTRAINT "sync_event_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "statistics" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"total_file_count" INTEGER NOT NULL DEFAULT 0,
"total_object_count" INTEGER NOT NULL DEFAULT 0,
"library_db_size" TEXT NOT NULL DEFAULT '0',
"total_bytes_used" TEXT NOT NULL DEFAULT '0',
"total_bytes_capacity" TEXT NOT NULL DEFAULT '0',
@ -33,7 +24,7 @@ CREATE TABLE "statistics" (
);
-- CreateTable
CREATE TABLE "nodes" (
CREATE TABLE "node" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"name" TEXT NOT NULL,
@ -45,7 +36,7 @@ CREATE TABLE "nodes" (
);
-- CreateTable
CREATE TABLE "volumes" (
CREATE TABLE "volume" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"node_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
@ -59,7 +50,7 @@ CREATE TABLE "volumes" (
);
-- CreateTable
CREATE TABLE "locations" (
CREATE TABLE "location" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"node_id" INTEGER NOT NULL,
@ -73,11 +64,11 @@ CREATE TABLE "locations" (
"is_online" BOOLEAN NOT NULL DEFAULT true,
"is_archived" BOOLEAN NOT NULL DEFAULT false,
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "locations_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
CONSTRAINT "location_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "files" (
CREATE TABLE "object" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"cas_id" TEXT NOT NULL,
"integrity_checksum" TEXT,
@ -97,18 +88,18 @@ CREATE TABLE "files" (
"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 "files_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "keys" ("id") ON DELETE SET NULL ON UPDATE CASCADE
CONSTRAINT "object_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "file_paths" (
CREATE TABLE "file_path" (
"id" INTEGER NOT NULL,
"is_dir" BOOLEAN NOT NULL DEFAULT false,
"location_id" INTEGER NOT NULL,
"materialized_path" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT,
"file_id" INTEGER,
"object_id" INTEGER,
"parent_id" INTEGER,
"key_id" INTEGER,
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -116,19 +107,19 @@ CREATE TABLE "file_paths" (
"date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("location_id", "id"),
CONSTRAINT "file_paths_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "file_paths_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "file_paths_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "keys" ("id") ON DELETE SET NULL ON UPDATE CASCADE
CONSTRAINT "file_path_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "file_path_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "file_path_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "file_conflicts" (
"original_file_id" INTEGER NOT NULL,
"detactched_file_id" INTEGER NOT NULL
CREATE TABLE "file_conflict" (
"original_object_id" INTEGER NOT NULL,
"detactched_object_id" INTEGER NOT NULL
);
-- CreateTable
CREATE TABLE "keys" (
CREATE TABLE "key" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"checksum" TEXT NOT NULL,
"name" TEXT,
@ -150,34 +141,34 @@ CREATE TABLE "media_data" (
"duration_seconds" INTEGER,
"codecs" TEXT,
"streams" INTEGER,
CONSTRAINT "media_data_id_fkey" FOREIGN KEY ("id") REFERENCES "files" ("id") ON DELETE CASCADE ON UPDATE CASCADE
CONSTRAINT "media_data_id_fkey" FOREIGN KEY ("id") REFERENCES "object" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "tags" (
CREATE TABLE "tag" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"name" TEXT,
"color" TEXT,
"total_files" INTEGER DEFAULT 0,
"total_objects" INTEGER DEFAULT 0,
"redundancy_goal" INTEGER DEFAULT 1,
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "tags_on_file" (
CREATE TABLE "tag_on_object" (
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"tag_id" INTEGER NOT NULL,
"file_id" INTEGER NOT NULL,
"object_id" INTEGER NOT NULL,
PRIMARY KEY ("tag_id", "file_id"),
CONSTRAINT "tags_on_file_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tags" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "tags_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
PRIMARY KEY ("tag_id", "object_id"),
CONSTRAINT "tag_on_object_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tag" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "tag_on_object_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
-- CreateTable
CREATE TABLE "labels" (
CREATE TABLE "label" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"name" TEXT,
@ -186,18 +177,18 @@ CREATE TABLE "labels" (
);
-- CreateTable
CREATE TABLE "label_on_file" (
CREATE TABLE "label_on_object" (
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"label_id" INTEGER NOT NULL,
"file_id" INTEGER NOT NULL,
"object_id" INTEGER NOT NULL,
PRIMARY KEY ("label_id", "file_id"),
CONSTRAINT "label_on_file_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "labels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "label_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
PRIMARY KEY ("label_id", "object_id"),
CONSTRAINT "label_on_object_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "label" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "label_on_object_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
-- CreateTable
CREATE TABLE "spaces" (
CREATE TABLE "space" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"name" TEXT,
@ -207,18 +198,18 @@ CREATE TABLE "spaces" (
);
-- CreateTable
CREATE TABLE "file_in_space" (
CREATE TABLE "object_in_space" (
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"space_id" INTEGER NOT NULL,
"file_id" INTEGER NOT NULL,
"object_id" INTEGER NOT NULL,
PRIMARY KEY ("space_id", "file_id"),
CONSTRAINT "file_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "file_in_space_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
PRIMARY KEY ("space_id", "object_id"),
CONSTRAINT "object_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "space" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "object_in_space_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
-- CreateTable
CREATE TABLE "jobs" (
CREATE TABLE "job" (
"id" BLOB NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"node_id" INTEGER NOT NULL,
@ -231,11 +222,11 @@ CREATE TABLE "jobs" (
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"seconds_elapsed" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "jobs_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE CASCADE ON UPDATE CASCADE
CONSTRAINT "job_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "albums" (
CREATE TABLE "album" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"name" TEXT NOT NULL,
@ -245,29 +236,29 @@ CREATE TABLE "albums" (
);
-- CreateTable
CREATE TABLE "files_in_albums" (
CREATE TABLE "object_in_album" (
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"album_id" INTEGER NOT NULL,
"file_id" INTEGER NOT NULL,
"object_id" INTEGER NOT NULL,
PRIMARY KEY ("album_id", "file_id"),
CONSTRAINT "files_in_albums_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "albums" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "files_in_albums_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
PRIMARY KEY ("album_id", "object_id"),
CONSTRAINT "object_in_album_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "album" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "object_in_album_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
-- CreateTable
CREATE TABLE "comments" (
CREATE TABLE "comment" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pub_id" BLOB NOT NULL,
"content" TEXT NOT NULL,
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"file_id" INTEGER,
CONSTRAINT "comments_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE SET NULL ON UPDATE CASCADE
"object_id" INTEGER,
CONSTRAINT "comment_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "indexer_rules" (
CREATE TABLE "indexer_rule" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"kind" INTEGER NOT NULL,
"name" TEXT NOT NULL,
@ -277,60 +268,57 @@ CREATE TABLE "indexer_rules" (
);
-- CreateTable
CREATE TABLE "indexer_rules_in_location" (
CREATE TABLE "indexer_rule_in_location" (
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"location_id" INTEGER NOT NULL,
"indexer_rule_id" INTEGER NOT NULL,
PRIMARY KEY ("location_id", "indexer_rule_id"),
CONSTRAINT "indexer_rules_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "indexer_rules_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rules" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
CONSTRAINT "indexer_rule_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT "indexer_rule_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rule" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
);
-- CreateIndex
CREATE UNIQUE INDEX "_migrations_checksum_key" ON "_migrations"("checksum");
CREATE UNIQUE INDEX "node_pub_id_key" ON "node"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "nodes_pub_id_key" ON "nodes"("pub_id");
CREATE UNIQUE INDEX "volume_node_id_mount_point_name_key" ON "volume"("node_id", "mount_point", "name");
-- CreateIndex
CREATE UNIQUE INDEX "volumes_node_id_mount_point_name_key" ON "volumes"("node_id", "mount_point", "name");
CREATE UNIQUE INDEX "location_pub_id_key" ON "location"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "locations_pub_id_key" ON "locations"("pub_id");
CREATE UNIQUE INDEX "object_cas_id_key" ON "object"("cas_id");
-- CreateIndex
CREATE UNIQUE INDEX "files_cas_id_key" ON "files"("cas_id");
CREATE UNIQUE INDEX "object_integrity_checksum_key" ON "object"("integrity_checksum");
-- CreateIndex
CREATE UNIQUE INDEX "files_integrity_checksum_key" ON "files"("integrity_checksum");
CREATE INDEX "file_path_location_id_idx" ON "file_path"("location_id");
-- CreateIndex
CREATE INDEX "file_paths_location_id_idx" ON "file_paths"("location_id");
CREATE UNIQUE INDEX "file_path_location_id_materialized_path_name_extension_key" ON "file_path"("location_id", "materialized_path", "name", "extension");
-- CreateIndex
CREATE UNIQUE INDEX "file_paths_location_id_materialized_path_name_extension_key" ON "file_paths"("location_id", "materialized_path", "name", "extension");
CREATE UNIQUE INDEX "file_conflict_original_object_id_key" ON "file_conflict"("original_object_id");
-- CreateIndex
CREATE UNIQUE INDEX "file_conflicts_original_file_id_key" ON "file_conflicts"("original_file_id");
CREATE UNIQUE INDEX "file_conflict_detactched_object_id_key" ON "file_conflict"("detactched_object_id");
-- CreateIndex
CREATE UNIQUE INDEX "file_conflicts_detactched_file_id_key" ON "file_conflicts"("detactched_file_id");
CREATE UNIQUE INDEX "key_checksum_key" ON "key"("checksum");
-- CreateIndex
CREATE UNIQUE INDEX "keys_checksum_key" ON "keys"("checksum");
CREATE UNIQUE INDEX "tag_pub_id_key" ON "tag"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "tags_pub_id_key" ON "tags"("pub_id");
CREATE UNIQUE INDEX "label_pub_id_key" ON "label"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "labels_pub_id_key" ON "labels"("pub_id");
CREATE UNIQUE INDEX "space_pub_id_key" ON "space"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "spaces_pub_id_key" ON "spaces"("pub_id");
CREATE UNIQUE INDEX "album_pub_id_key" ON "album"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "albums_pub_id_key" ON "albums"("pub_id");
-- CreateIndex
CREATE UNIQUE INDEX "comments_pub_id_key" ON "comments"("pub_id");
CREATE UNIQUE INDEX "comment_pub_id_key" ON "comment"("pub_id");

View file

@ -8,16 +8,6 @@ generator client {
output = "../src/prisma.rs"
}
model Migration {
id Int @id @default(autoincrement())
name String
checksum String @unique
steps_applied Int @default(0)
applied_at DateTime @default(now())
@@map("_migrations")
}
model SyncEvent {
id Int @id @default(autoincrement())
node_id Int
@ -33,13 +23,13 @@ model SyncEvent {
node Node @relation(fields: [node_id], references: [id])
@@map("sync_events")
@@map("sync_event")
}
model Statistics {
id Int @id @default(autoincrement())
date_captured DateTime @default(now())
total_file_count Int @default(0)
total_object_count Int @default(0)
library_db_size String @default("0")
total_bytes_used String @default("0")
total_bytes_capacity String @default("0")
@ -65,7 +55,7 @@ model Node {
Location Location[]
@@map("nodes")
@@map("node")
}
model Volume {
@ -81,7 +71,7 @@ model Volume {
date_modified DateTime @default(now())
@@unique([node_id, mount_point, name])
@@map("volumes")
@@map("volume")
}
model Location {
@ -103,10 +93,10 @@ model Location {
file_paths FilePath[]
indexer_rules IndexerRulesInLocation[]
@@map("locations")
@@map("location")
}
model File {
model Object {
id Int @id @default(autoincrement())
// content addressable storage id - sha256 sampled checksum
cas_id String @unique
@ -118,11 +108,11 @@ model File {
kind Int @default(0)
size_in_bytes String
key_id Int?
// handy ways to mark a file
// handy ways to mark an object
hidden Boolean @default(false)
favorite Boolean @default(false)
important Boolean @default(false)
// if we have generated preview media for this file
// if we have generated preview media for this object
has_thumbnail Boolean @default(false)
has_thumbstrip Boolean @default(false)
has_video_preview Boolean @default(false)
@ -130,24 +120,24 @@ model File {
ipfs_id String?
// plain text note
note String?
// the original known creation date of this file
// the original known creation date of this object
date_created DateTime @default(now())
// the last time this file was modified
// the last time this object was modified
date_modified DateTime @default(now())
// when this file was first indexed
// when this object was first indexed
date_indexed DateTime @default(now())
tags TagOnFile[]
labels LabelOnFile[]
albums FileInAlbum[]
spaces FileInSpace[]
paths FilePath[]
tags TagOnObject[]
labels LabelOnObject[]
albums ObjectInAlbum[]
spaces ObjectInSpace[]
file_paths FilePath[]
comments Comment[]
media_data MediaData?
key Key? @relation(fields: [key_id], references: [id])
@@map("files")
@@map("object")
}
model FilePath {
@ -160,8 +150,8 @@ model FilePath {
// the name and extension
name String
extension String?
// the unique File for this file path
file_id Int?
// the unique Object for this file path
object_id Int?
// the parent in the file tree
parent_id Int?
key_id Int? // replacement for encryption
@ -172,7 +162,7 @@ model FilePath {
date_modified DateTime @default(now())
date_indexed DateTime @default(now())
file File? @relation(fields: [file_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
object Object? @relation(fields: [object_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
location Location? @relation(fields: [location_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
// NOTE: this self relation for the file tree was causing SQLite to go to forever bed, disabling until workaround
@ -184,15 +174,15 @@ model FilePath {
@@id([location_id, id])
@@unique([location_id, materialized_path, name, extension])
@@index([location_id])
@@map("file_paths")
@@map("file_path")
}
// if there is a conflicting cas_id, the conficting file should be updated to have a larger cas_id as the field is unique, however this record is kept to tell the indexer (upon discovering this CAS) that there is alternate versions of the file and to check by a full integrity hash to define for which to associate with.
model FileConflict {
original_file_id Int @unique
detactched_file_id Int @unique
original_object_id Int @unique
detactched_object_id Int @unique
@@map("file_conflicts")
@@map("file_conflict")
}
// keys allow us to know exactly which files can be decrypted with a given key
@ -207,10 +197,10 @@ model Key {
// so we know which algorithm to use, can be null if user must select
algorithm Int? @default(0)
files File[]
objects Object[]
file_paths FilePath[]
@@map("keys")
@@map("key")
}
model MediaData {
@ -227,8 +217,8 @@ model MediaData {
codecs String? // eg: "h264,acc"
streams Int?
// change this relation to File after testing
files File? @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
// change this relation to Object after testing
objects Object? @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@map("media_data")
}
@ -238,27 +228,27 @@ model Tag {
pub_id Bytes @unique
name String?
color String?
total_files Int? @default(0)
total_objects Int? @default(0)
redundancy_goal Int? @default(1)
date_created DateTime @default(now())
date_modified DateTime @default(now())
tag_files TagOnFile[]
tag_objects TagOnObject[]
@@map("tags")
@@map("tag")
}
model TagOnFile {
model TagOnObject {
date_created DateTime @default(now())
tag_id Int
tag Tag @relation(fields: [tag_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
file_id Int
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
object_id Int
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@id([tag_id, file_id])
@@map("tags_on_file")
@@id([tag_id, object_id])
@@map("tag_on_object")
}
model Label {
@ -268,22 +258,22 @@ model Label {
date_created DateTime @default(now())
date_modified DateTime @default(now())
label_files LabelOnFile[]
label_objects LabelOnObject[]
@@map("labels")
@@map("label")
}
model LabelOnFile {
model LabelOnObject {
date_created DateTime @default(now())
label_id Int
label Label @relation(fields: [label_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
file_id Int
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
object_id Int
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@id([label_id, file_id])
@@map("label_on_file")
@@id([label_id, object_id])
@@map("label_on_object")
}
model Space {
@ -294,22 +284,22 @@ model Space {
date_created DateTime @default(now())
date_modified DateTime @default(now())
files FileInSpace[]
objects ObjectInSpace[]
@@map("spaces")
@@map("space")
}
model FileInSpace {
model ObjectInSpace {
date_created DateTime @default(now())
space_id Int
space Space @relation(fields: [space_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
file_id Int
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
object_id Int
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@id([space_id, file_id])
@@map("file_in_space")
@@id([space_id, object_id])
@@map("object_in_space")
}
model Job {
@ -329,7 +319,7 @@ model Job {
nodes Node @relation(fields: [node_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
@@map("jobs")
@@map("job")
}
model Album {
@ -341,22 +331,22 @@ model Album {
date_created DateTime @default(now())
date_modified DateTime @default(now())
files FileInAlbum[]
objects ObjectInAlbum[]
@@map("albums")
@@map("album")
}
model FileInAlbum {
model ObjectInAlbum {
date_created DateTime @default(now())
album_id Int
album Album @relation(fields: [album_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
file_id Int
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
object_id Int
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@id([album_id, file_id])
@@map("files_in_albums")
@@id([album_id, object_id])
@@map("object_in_album")
}
model Comment {
@ -365,10 +355,10 @@ model Comment {
content String
date_created DateTime @default(now())
date_modified DateTime @default(now())
file_id Int?
file File? @relation(fields: [file_id], references: [id])
object_id Int?
object Object? @relation(fields: [object_id], references: [id])
@@map("comments")
@@map("comment")
}
model IndexerRule {
@ -381,7 +371,7 @@ model IndexerRule {
locations IndexerRulesInLocation[]
@@map("indexer_rules")
@@map("indexer_rule")
}
model IndexerRulesInLocation {
@ -394,5 +384,5 @@ model IndexerRulesInLocation {
indexer_rule IndexerRule @relation(fields: [indexer_rule_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@id([location_id, indexer_rule_id])
@@map("indexer_rules_in_location")
@@map("indexer_rule_in_location")
}

View file

@ -1,4 +1,4 @@
use crate::{invalidate_query, prisma::file};
use crate::{invalidate_query, prisma::object};
use rspc::Type;
use serde::Deserialize;
@ -26,8 +26,11 @@ pub(crate) fn mount() -> RouterBuilder {
.library_mutation("setNote", |_, args: SetNoteArgs, library| async move {
library
.db
.file()
.update(file::id::equals(args.id), vec![file::note::set(args.note)])
.object()
.update(
object::id::equals(args.id),
vec![object::note::set(args.note)],
)
.exec()
.await?;
@ -40,10 +43,10 @@ pub(crate) fn mount() -> RouterBuilder {
|_, args: SetFavoriteArgs, library| async move {
library
.db
.file()
.object()
.update(
file::id::equals(args.id),
vec![file::favorite::set(args.favorite)],
object::id::equals(args.id),
vec![object::favorite::set(args.favorite)],
)
.exec()
.await?;
@ -56,8 +59,8 @@ pub(crate) fn mount() -> RouterBuilder {
.library_mutation("delete", |_, id: i32, library| async move {
library
.db
.file()
.delete(file::id::equals(id))
.object()
.delete(object::id::equals(id))
.exec()
.await?;

View file

@ -57,7 +57,7 @@ pub(crate) fn mount() -> RouterBuilder {
let params = vec![
id::set(1), // Each library is a database so only one of these ever exists
date_captured::set(Utc::now().into()),
total_file_count::set(0),
total_object_count::set(0),
library_db_size::set(library_db_size.to_string()),
total_bytes_used::set(0.to_string()),
total_bytes_capacity::set(total_capacity.to_string()),

View file

@ -6,7 +6,7 @@ use crate::{
scan_location, LocationCreateArgs, LocationError, LocationUpdateArgs,
},
object::preview::THUMBNAIL_CACHE_DIR_NAME,
prisma::{file, file_path, indexer_rule, indexer_rules_in_location, location, tag},
prisma::{file_path, indexer_rule, indexer_rules_in_location, location, object, tag},
};
use rspc::{self, ErrorCode, Type};
@ -29,14 +29,14 @@ pub enum ExplorerContext {
// Space(object_in_space::Data),
}
file_path::include!(file_path_with_file { file });
file::include!(file_with_paths { paths });
file_path::include!(file_path_with_object { object });
object::include!(object_with_file_paths { file_paths });
#[derive(Serialize, Deserialize, Type, Debug)]
#[serde(tag = "type")]
pub enum ExplorerItem {
Path(Box<file_path_with_file::Data>),
Object(Box<file_with_paths::Data>),
Path(Box<file_path_with_object::Data>),
Object(Box<object_with_file_paths::Data>),
}
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
@ -100,7 +100,7 @@ pub(crate) fn mount() -> RouterBuilder {
file_path::location_id::equals(location.id),
file_path::parent_id::equals(Some(directory.id)),
])
.include(file_path_with_file::include())
.include(file_path_with_object::include())
.exec()
.await?;
@ -109,16 +109,16 @@ pub(crate) fn mount() -> RouterBuilder {
items: file_paths
.into_iter()
.map(|mut file_path| {
if let Some(file) = &mut file_path.file.as_mut() {
if let Some(object) = &mut file_path.object.as_mut() {
// TODO: Use helper function to build this url as as the Rust file loading layer
let thumb_path = library
.config()
.data_directory()
.join(THUMBNAIL_CACHE_DIR_NAME)
.join(&file.cas_id)
.join(&object.cas_id)
.with_extension("webp");
file.has_thumbnail = thumb_path.exists();
object.has_thumbnail = thumb_path.exists();
}
ExplorerItem::Path(Box::new(file_path))
})

View file

@ -4,10 +4,10 @@ use tracing::info;
use uuid::Uuid;
use crate::{
api::locations::{file_with_paths, ExplorerContext, ExplorerData, ExplorerItem},
api::locations::{object_with_file_paths, ExplorerContext, ExplorerData, ExplorerItem},
invalidate_query,
object::preview::THUMBNAIL_CACHE_DIR_NAME,
prisma::{file, tag, tag_on_file},
prisma::{object, tag, tag_on_object},
};
use super::{utils::LibraryRequest, RouterBuilder};
@ -20,7 +20,7 @@ pub struct TagCreateArgs {
#[derive(Debug, Type, Deserialize)]
pub struct TagAssignArgs {
pub file_id: i32,
pub object_id: i32,
pub tag_id: i32,
pub unassign: bool,
}
@ -52,32 +52,32 @@ pub(crate) fn mount() -> RouterBuilder {
let files: Vec<ExplorerItem> = library
.db
.file()
.find_many(vec![file::tags::some(vec![tag_on_file::tag_id::equals(
tag_id,
)])])
.include(file_with_paths::include())
.object()
.find_many(vec![object::tags::some(vec![
tag_on_object::tag_id::equals(tag_id),
])])
.include(object_with_file_paths::include())
.exec()
.await?
.into_iter()
.map(|mut file| {
.map(|mut object| {
// sorry brendan
// grab the first path and tac on the name
let oldest_path = &file.paths[0];
file.name = Some(oldest_path.name.clone());
file.extension = oldest_path.extension.clone();
let oldest_path = &object.file_paths[0];
object.name = Some(oldest_path.name.clone());
object.extension = oldest_path.extension.clone();
// a long term fix for this would be to have the indexer give the Object a name and extension, sacrificing its own and only store newly found Path names that differ from the Object name
let thumb_path = library
.config()
.data_directory()
.join(THUMBNAIL_CACHE_DIR_NAME)
.join(&file.cas_id)
.join(&object.cas_id)
.with_extension("webp");
file.has_thumbnail = thumb_path.exists();
object.has_thumbnail = thumb_path.exists();
ExplorerItem::Object(Box::new(file))
ExplorerItem::Object(Box::new(object))
})
.collect();
@ -88,12 +88,12 @@ pub(crate) fn mount() -> RouterBuilder {
items: files,
})
})
.library_query("getForFile", |_, file_id: i32, library| async move {
.library_query("getForFile", |_, object_id: i32, library| async move {
Ok(library
.db
.tag()
.find_many(vec![tag::tag_files::some(vec![
tag_on_file::file_id::equals(file_id),
.find_many(vec![tag::tag_objects::some(vec![
tag_on_object::object_id::equals(object_id),
])])
.exec()
.await?)
@ -128,17 +128,17 @@ pub(crate) fn mount() -> RouterBuilder {
if args.unassign {
library
.db
.tag_on_file()
.delete(tag_on_file::tag_id_file_id(args.tag_id, args.file_id))
.tag_on_object()
.delete(tag_on_object::tag_id_object_id(args.tag_id, args.object_id))
.exec()
.await?;
} else {
library
.db
.tag_on_file()
.tag_on_object()
.create(
tag::id::equals(args.tag_id),
file::id::equals(args.file_id),
object::id::equals(args.object_id),
vec![],
)
.exec()

View file

@ -92,7 +92,7 @@ impl InvalidRequests {
#[allow(clippy::crate_in_macro_def)]
macro_rules! invalidate_query {
($ctx:expr, $key:literal) => {{
let _: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type
let ctx: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type
#[cfg(debug_assertions)]
{
@ -105,13 +105,15 @@ macro_rules! invalidate_query {
.push(crate::api::utils::InvalidationRequest {
key: $key,
arg_ty: None,
macro_src: concat!(file!(), ":", line!()),
macro_src: concat!(file!(), ":", line!()),
})
}
}
// The error are ignored here because they aren't mission critical. If they fail the UI might be outdated for a bit.
crate::api::utils::InvalidateOperationEvent::dangerously_create($key, serde_json::Value::Null)
ctx.emit(crate::api::CoreEvent::InvalidateOperation(
crate::api::utils::InvalidateOperationEvent::dangerously_create($key, serde_json::Value::Null)
))
}};
($ctx:expr, $key:literal: $arg_ty:ty, $arg:expr $(,)?) => {{
let _: $arg_ty = $arg; // Assert the type the user provided is correct

View file

@ -1,7 +1,7 @@
use crate::{
job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext},
library::LibraryContext,
prisma::{file, file_path, location},
prisma::{file_path, location, object},
};
use chrono::{DateTime, FixedOffset};
@ -162,10 +162,10 @@ impl StatefulJob for FileIdentifierJob {
for file_path in &file_paths {
// get the cas_id and extract metadata
match assemble_object_metadata(&data.location_path, file_path).await {
Ok(file) => {
let cas_id = file.cas_id.clone();
Ok(object) => {
let cas_id = object.cas_id.clone();
// create entry into chunks for created file data
chunk.insert(file_path.id, file);
chunk.insert(file_path.id, object);
cas_lookup.insert(cas_id, file_path.id);
}
Err(e) => {
@ -177,23 +177,23 @@ impl StatefulJob for FileIdentifierJob {
// find all existing files by cas id
let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect();
let existing_files = db
.file()
.find_many(vec![file::cas_id::in_vec(generated_cas_ids)])
let existing_objects = db
.object()
.find_many(vec![object::cas_id::in_vec(generated_cas_ids)])
.exec()
.await?;
info!("Found {} existing files", existing_files.len());
info!("Found {} existing files", existing_objects.len());
for existing_file in &existing_files {
for existing_object in &existing_objects {
if let Err(e) = db
.file_path()
.update(
file_path::location_id_id(
state.init.location_id,
*cas_lookup.get(&existing_file.cas_id).unwrap(),
*cas_lookup.get(&existing_object.cas_id).unwrap(),
),
vec![file_path::file_id::set(Some(existing_file.id))],
vec![file_path::object_id::set(Some(existing_object.id))],
)
.exec()
.await
@ -202,26 +202,26 @@ impl StatefulJob for FileIdentifierJob {
}
}
let existing_files_cas_ids = existing_files
let existing_object_cas_ids = existing_objects
.iter()
.map(|file| file.cas_id.clone())
.map(|object| object.cas_id.clone())
.collect::<HashSet<_>>();
// extract files that don't already exist in the database
let new_files = chunk
// extract objects that don't already exist in the database
let new_objects = chunk
.iter()
.map(|(_id, create_file)| create_file)
.filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id))
.filter(|create_file| !existing_object_cas_ids.contains(&create_file.cas_id))
.collect::<Vec<_>>();
if !new_files.is_empty() {
if !new_objects.is_empty() {
// assemble prisma values for new unique files
let mut values = Vec::with_capacity(new_files.len() * 3);
for file in &new_files {
let mut values = Vec::with_capacity(new_objects.len() * 3);
for object in &new_objects {
values.extend([
PrismaValue::String(file.cas_id.clone()),
PrismaValue::Int(file.size_in_bytes),
PrismaValue::DateTime(file.date_created),
PrismaValue::String(object.cas_id.clone()),
PrismaValue::Int(object.size_in_bytes),
PrismaValue::DateTime(object.date_created),
]);
}
@ -230,9 +230,9 @@ impl StatefulJob for FileIdentifierJob {
let created_files: Vec<FileCreated> = db
._query_raw(Raw::new(
&format!(
"INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {}
"INSERT INTO object (cas_id, size_in_bytes, date_created) VALUES {}
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
vec!["({}, {}, {})"; new_files.len()].join(",")
vec!["({}, {}, {})"; new_objects.len()].join(",")
),
values,
))
@ -256,7 +256,7 @@ impl StatefulJob for FileIdentifierJob {
state.init.location_id,
*cas_lookup.get(&created_file.cas_id).unwrap(),
),
vec![file_path::file_id::set(Some(created_file.id))],
vec![file_path::object_id::set(Some(created_file.id))],
)
.exec()
.await
@ -305,7 +305,7 @@ impl StatefulJob for FileIdentifierJob {
fn orphan_path_filters(location_id: i32, file_path_id: Option<i32>) -> Vec<file_path::WhereParam> {
let mut params = vec![
file_path::file_id::equals(None),
file_path::object_id::equals(None),
file_path::is_dir::equals(false),
file_path::location_id::equals(location_id),
];
@ -329,7 +329,7 @@ async fn count_orphan_file_paths(
.db
.file_path()
.count(vec![
file_path::file_id::equals(None),
file_path::object_id::equals(None),
file_path::is_dir::equals(false),
file_path::location_id::equals(location_id),
])

View file

@ -28,7 +28,7 @@ pub struct ObjectsForExplorer {
#[derive(Debug, Serialize, Deserialize, Type)]
pub enum ObjectData {
Object(Box<prisma::file::Data>),
Object(Box<prisma::object::Data>),
Path(Box<prisma::file_path::Data>),
}

View file

@ -38,7 +38,7 @@ pub struct ThumbnailJobState {
root_path: PathBuf,
}
file_path::include!(file_path_with_file { file });
file_path::include!(file_path_with_object { object });
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
enum ThumbnailJobStepKind {
@ -49,7 +49,7 @@ enum ThumbnailJobStepKind {
#[derive(Debug, Serialize, Deserialize)]
pub struct ThumbnailJobStep {
file: file_path_with_file::Data,
file_path: file_path_with_object::Data,
kind: ThumbnailJobStepKind,
}
@ -179,7 +179,7 @@ impl StatefulJob for ThumbnailJob {
let step = &state.steps[0];
ctx.progress(vec![JobReportUpdate::Message(format!(
"Processing {}",
step.file.materialized_path
step.file_path.materialized_path
))]);
let data = state
@ -188,16 +188,16 @@ impl StatefulJob for ThumbnailJob {
.expect("critical error: missing data on job state");
// assemble the file path
let path = data.root_path.join(&step.file.materialized_path);
let path = data.root_path.join(&step.file_path.materialized_path);
trace!("image_file {:?}", step);
// get cas_id, if none found skip
let cas_id = match &step.file.file {
let cas_id = match &step.file_path.object {
Some(f) => f.cas_id.clone(),
_ => {
warn!(
"skipping thumbnail generation for {}",
step.file.materialized_path
step.file_path.materialized_path
);
return Ok(());
}
@ -327,10 +327,10 @@ async fn get_files_by_extension(
.db
.file_path()
.find_many(params)
.include(file_path_with_file::include())
.include(file_path_with_object::include())
.exec()
.await?
.into_iter()
.map(|file| ThumbnailJobStep { file, kind })
.map(|file_path| ThumbnailJobStep { file_path, kind })
.collect())
}

View file

@ -8,7 +8,7 @@ pub enum MigrationError {
#[error("An error occurred while initialising a new database connection: {0}")]
NewClient(#[from] Box<NewClientError>),
#[cfg(debug_assertions)]
#[error("An error occurred during migartion: {0}")]
#[error("An error occurred during migration: {0}")]
MigrateFailed(#[from] DbPushError),
#[cfg(not(debug_assertions))]
#[error("An error occurred during migration: {0}")]

View file

@ -3,11 +3,11 @@
"version": "0.0.0",
"private": true,
"scripts": {
"prep": "pnpm db:gen && cargo test",
"prep": "pnpm gen:prisma && cargo test",
"build": "turbo run build",
"landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web",
"db:migrate": "cd crates && cargo prisma migrate dev",
"db:gen": "cd core && cargo prisma generate",
"gen:migrations": "cd core && cargo prisma migrate dev",
"gen:prisma": "cd core && cargo prisma generate",
"format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
"desktop": "pnpm --filter @sd/desktop --",
"web": "pnpm --filter @sd/web -- ",

View file

@ -53,11 +53,9 @@ export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" }
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
export type ExplorerItem = { type: "Path" } & { 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 } | { type: "Object" } & { 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, paths: Array<FilePath> }
export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null } | { type: "Object" } & { 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, file_paths: Array<FilePath> }
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 }
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 }
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string }
export interface GenerateThumbsForLocationArgs { id: number, path: string }
@ -93,17 +91,19 @@ export interface NodeConfig { version: string | null, id: string, name: string,
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
export interface Object { 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 }
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
export interface SetFavoriteArgs { id: number, favorite: boolean }
export interface SetNoteArgs { id: number, note: 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 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 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 }
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
export interface TagAssignArgs { object_id: number, tag_id: number, unassign: boolean }
export interface TagCreateArgs { name: string, color: string }

View file

@ -1,5 +1,5 @@
import { useCurrentLibrary, useInvalidateQuery } from '@sd/client';
import { Route, Routes } from 'react-router-dom';
import { Navigate, Route, Routes } from 'react-router-dom';
import { AppLayout } from './AppLayout';
import { NotFound } from './NotFound';
@ -10,7 +10,6 @@ import { DebugScreen } from './screens/Debug';
import { LocationExplorer } from './screens/LocationExplorer';
import { OverviewScreen } from './screens/Overview';
import { PhotosScreen } from './screens/Photos';
import { RedirectPage } from './screens/Redirect';
import { TagExplorer } from './screens/TagExplorer';
import { SettingsScreen } from './screens/settings/Settings';
import AppearanceSettings from './screens/settings/client/AppearanceSettings';
@ -49,12 +48,12 @@ export function AppRouter() {
<Route
path="*"
element={
<h1 className="text-white p-4">Please select or create a library in the sidebar.</h1>
<h1 className="p-4 text-white">Please select or create a library in the sidebar.</h1>
}
/>
) : (
<>
<Route index element={<RedirectPage to="/overview" />} />
<Route index element={<Navigate to="/overview" />} />
<Route path="overview" element={<OverviewScreen />} />
<Route path="content" element={<ContentScreen />} />
<Route path="photos" element={<PhotosScreen />} />

View file

@ -34,7 +34,7 @@ const AssignTagMenuItems = (props: { objectId: number }) => {
assignTag({
tag_id: tag.id,
file_id: props.objectId,
object_id: props.objectId,
unassign: active
});
}}

View file

@ -21,7 +21,7 @@ function FileItem({ data, selected, index, ...rest }: Props) {
return (
<div
onContextMenu={(e) => {
const objectId = isObject(data) ? data.id : data.file?.id;
const objectId = isObject(data) ? data.id : data.object?.id;
if (objectId != undefined) {
getExplorerStore().contextMenuObjectId = objectId;
if (index != undefined) {

View file

@ -23,7 +23,7 @@ export default function FileThumb({ data, ...props }: Props) {
if (isPath(data) && data.is_dir)
return <Folder className={props.iconClassNames} size={props.size * 0.7} />;
const cas_id = isObject(data) ? data.cas_id : data.file?.cas_id;
const cas_id = isObject(data) ? data.cas_id : data.object?.cas_id;
if (cas_id) {
// this won't work
@ -32,7 +32,7 @@ export default function FileThumb({ data, ...props }: Props) {
const has_thumbnail = isObject(data)
? data.has_thumbnail
: isPath(data)
? data.file?.has_thumbnail
? data.object?.has_thumbnail
: new_thumbnail;
const url = platform.getThumbnailUrlById(cas_id);

View file

@ -1,6 +1,6 @@
import { ShareIcon } from '@heroicons/react/24/solid';
import { useLibraryQuery } from '@sd/client';
import { ExplorerContext, ExplorerItem, File, FilePath, Location } from '@sd/client';
import { ExplorerContext, ExplorerItem, FilePath, Location, Object } from '@sd/client';
import { Button, TextArea } from '@sd/ui';
import clsx from 'clsx';
import moment from 'moment';
@ -24,7 +24,7 @@ interface Props {
export const Inspector = (props: Props) => {
const is_dir = props.data?.type === 'Path' ? props.data.is_dir : false;
const objectData = isObject(props.data) ? props.data : props.data.file;
const objectData = isObject(props.data) ? props.data : props.data.object;
// this prevents the inspector from fetching data when the user is navigating quickly
const [readyToFetch, setReadyToFetch] = useState(false);

View file

@ -198,6 +198,5 @@ const WrappedItem: React.FC<WrappedItemProps> = ({ item, index, isSelected, kind
// selected={isSelected}
// />
// );
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [item, index, isSelected]);
};

View file

@ -1,11 +1,11 @@
import { useLibraryMutation } from '@sd/client';
import { File } from '@sd/client';
import { Object as SDObject } from '@sd/client';
import { Button } from '@sd/ui';
import { Heart } from 'phosphor-react';
import { useEffect, useState } from 'react';
interface Props {
data: File;
data: SDObject;
}
export default function FavoriteButton(props: Props) {

View file

@ -1,6 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useLibraryMutation } from '@sd/client';
import { File } from '@sd/client';
import { Object as SDObject } from '@sd/client';
import { TextArea } from '@sd/ui';
import { debounce } from 'lodash';
import { useCallback, useState } from 'react';
@ -9,25 +8,28 @@ import { Divider } from './Divider';
import { MetaItem } from './MetaItem';
interface Props {
data: File;
data: SDObject;
}
export default function Note(props: Props) {
// notes are cached in a store by their file id
// this is so we can ensure every note has been sent to Rust even
// when quickly navigating files, which cancels update function
const [note, setNote] = useState((props.data as File)?.note || '');
const [note, setNote] = useState(props.data.note || '');
const { mutate: fileSetNote } = useLibraryMutation('files.setNote');
const debouncedNote = useCallback(
debounce((note: string) => {
fileSetNote({
id: props.data.id,
note
});
}, 2000),
[props.data.id]
(note: string) =>
debounce(
() =>
fileSetNote({
id: props.data.id,
note
}),
2000
),
[props.data.id, fileSetNote]
);
// when input is updated, cache note

View file

@ -1,4 +1,4 @@
import { ExplorerItem, File, FilePath } from '@sd/client';
import { ExplorerItem, FilePath } from '@sd/client';
export function isPath(item: ExplorerItem): item is Extract<ExplorerItem, { type: 'Path' }> {
return item.type === 'Path';

View file

@ -186,10 +186,10 @@ export function Sidebar() {
buttonText={isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '}
buttonTextClassName={library === null || isLoadingLibraries ? 'text-gray-300' : undefined}
items={[
libraries?.map((library) => ({
name: library.config.name,
selected: library.uuid === library?.uuid,
onPress: () => switchLibrary(library.uuid)
libraries?.map((lib) => ({
name: lib.config.name,
selected: lib.uuid === library?.uuid,
onPress: () => switchLibrary(lib.uuid)
})) || [],
[
{

View file

@ -53,13 +53,12 @@ const useCounter = ({ name, start = 0, end, duration = 2, saveState = true }: Us
duration,
easing: 'easeOutCubic'
});
useEffect(() => {
if (saveState && value == end) {
setLastValue(name, end);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [end, value]);
}, [end, name, saveState, setLastValue, value]);
if (start === end) return end;

View file

@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { getExplorerStore, useCurrentLibrary, useLibraryQuery } from '@sd/client';
import { useEffect } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';

View file

@ -100,42 +100,22 @@ export const OverviewScreen = () => {
// get app props from context
useEffect(() => {
if (platform.demoMode === true) {
if (!Object.entries(overviewStats).length)
setOverviewStats({
total_bytes_capacity: '8093333345230',
preview_media_bytes: '2304387532',
library_db_size: '83345230',
total_file_count: '20342345',
total_bytes_free: '89734502034',
total_bytes_used: '8093333345230',
total_unique_bytes: '9347397'
});
} else {
const newStatistics: OverviewStats = {
total_bytes_capacity: '0',
preview_media_bytes: '0',
library_db_size: '0',
total_file_count: '0',
total_bytes_free: '0',
total_bytes_used: '0',
total_unique_bytes: '0'
};
const newStatistics: OverviewStats = {
total_bytes_capacity: '0',
preview_media_bytes: '0',
library_db_size: '0',
total_object_count: '0',
total_bytes_free: '0',
total_bytes_used: '0',
total_unique_bytes: '0'
};
Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => {
newStatistics[key as keyof Statistics] = `${value}`;
});
Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => {
newStatistics[key as keyof Statistics] = `${value}`;
});
setOverviewStats(newStatistics);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [platform, libraryStatistics]);
// useEffect(() => {
// setTimeout(() => {
// setOverviewStat('total_bytes_capacity', '4093333345230');
// }, 2000);
// }, [overviewStats]);
setOverviewStats(newStatistics);
}, [platform, libraryStatistics, setOverviewStats]);
const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames;

View file

@ -1,19 +0,0 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router';
export interface RedirectPageProps {
to: string;
}
export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
const { to: destination } = props;
const navigate = useNavigate();
useEffect(() => {
navigate(destination);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};

View file

@ -35,16 +35,14 @@ export default function LibraryGeneralSettings() {
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nameDebounced, descriptionDebounced]);
}, [nameDebounced, descriptionDebounced, library, editLibrary]);
useEffect(() => {
if (library) {
setName(library.config.name);
setDescription(library.config.description);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [libraries]);
}, [libraries, library]);
useEffect(() => {
if (library) {
@ -52,7 +50,6 @@ export default function LibraryGeneralSettings() {
setName(library.config.name);
setDescription(library.config.description);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [library]);
useEffect(() => {

View file

@ -1,5 +1,5 @@
import { TrashIcon } from '@heroicons/react/24/outline';
import { useLibraryMutation, useLibraryQuery } from '@sd/client';
import { Tag, useLibraryMutation, useLibraryQuery } from '@sd/client';
import { TagUpdateArgs } from '@sd/client';
import { Button, Input } from '@sd/ui';
import clsx from 'clsx';
@ -23,11 +23,7 @@ export default function TagsSettings() {
const { data: tags } = useLibraryQuery(['tags.list']);
const [selectedTag, setSelectedTag] = useState<null | number>(null);
const currentTag = useMemo(() => {
return tags?.find((t) => t.id === selectedTag);
}, [tags, selectedTag]);
const [selectedTag, setSelectedTag] = useState<null | Tag>(tags?.[0] ?? null);
const { mutate: createTag, isLoading } = useLibraryMutation('tags.create', {
onError: (e) => {
@ -38,25 +34,27 @@ export default function TagsSettings() {
}
});
const { mutate: updateTag } = useLibraryMutation('tags.update');
const updateTag = useLibraryMutation('tags.update');
const { mutate: deleteTag, isLoading: tagDeleteLoading } = useLibraryMutation('tags.delete');
// set default selected tag
useEffect(() => {
if (!currentTag && tags?.length) setSelectedTag(tags[0].id);
}, [currentTag, tags]);
useEffect(() => {
reset(currentTag);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentTag]);
const { register, handleSubmit, watch, reset, control } = useForm({
defaultValues: currentTag as TagUpdateArgs
const deleteTag = useLibraryMutation('tags.delete', {
onSuccess: () => {
setSelectedTag(null);
}
});
const submitTagUpdate = handleSubmit((data) => updateTag(data));
const { register, handleSubmit, watch, reset, control } = useForm({
defaultValues: selectedTag as TagUpdateArgs
});
const setTag = useCallback(
(tag: Tag | null) => {
if (tag) reset(tag);
setSelectedTag(tag);
},
[setSelectedTag, reset]
);
const submitTagUpdate = handleSubmit((data) => updateTag.mutate(data));
// eslint-disable-next-line react-hooks/exhaustive-deps
const autoUpdateTag = useCallback(useDebounce(submitTagUpdate, 500), []);
@ -113,11 +111,11 @@ export default function TagsSettings() {
<div className="flex flex-wrap gap-2 m-1">
{tags?.map((tag) => (
<div
onClick={() => setSelectedTag(tag.id === selectedTag ? null : tag.id)}
onClick={() => setTag(tag.id === selectedTag?.id ? null : tag)}
key={tag.id}
className={clsx(
'flex items-center rounded px-1.5 py-0.5',
selectedTag === tag.id && 'ring'
selectedTag?.id === tag.id && 'ring'
)}
style={{ backgroundColor: tag.color + 'CC' }}
>
@ -126,7 +124,7 @@ export default function TagsSettings() {
))}
</div>
</Card>
{currentTag ? (
{selectedTag ? (
<form onSubmit={submitTagUpdate}>
<div className="flex flex-row mb-10 space-x-3">
<div className="flex flex-col">
@ -159,9 +157,9 @@ export default function TagsSettings() {
title="Delete Tag"
description="Are you sure you want to delete this tag? This cannot be undone and tagged files will be unlinked."
ctaAction={() => {
deleteTag(currentTag.id);
deleteTag.mutate(selectedTag.id);
}}
loading={tagDeleteLoading}
loading={deleteTag.isLoading}
ctaDanger
ctaLabel="Delete"
trigger={