mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-18 21:36:56 -04:00
wip
This commit is contained in:
@@ -52,7 +52,43 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Socket {
|
||||
Ok(ws::Message::Text(text)) => {
|
||||
let msg: SocketMessage = serde_json::from_str(&text).unwrap();
|
||||
|
||||
ctx.notify(msg);
|
||||
let core = self.core.clone();
|
||||
|
||||
let recipient = ctx.address().recipient();
|
||||
|
||||
let fut = async move {
|
||||
match msg.payload {
|
||||
SocketMessagePayload::Query(query) => {
|
||||
match core.query(query).await {
|
||||
Ok(response) => recipient.do_send(SocketResponse {
|
||||
id: msg.id.clone(),
|
||||
payload: SocketResponsePayload::Query(response),
|
||||
}),
|
||||
Err(err) => {
|
||||
// println!("query error: {:?}", err);
|
||||
// Err(err.to_string())
|
||||
},
|
||||
};
|
||||
},
|
||||
SocketMessagePayload::Command(command) => {
|
||||
match core.command(command).await {
|
||||
Ok(response) => recipient.do_send(SocketResponse {
|
||||
id: msg.id.clone(),
|
||||
payload: SocketResponsePayload::Query(response),
|
||||
}),
|
||||
Err(err) => {
|
||||
// println!("command error: {:?}", err);
|
||||
// Err(err.to_string())
|
||||
},
|
||||
};
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
};
|
||||
|
||||
fut.into_actor(self).spawn(ctx);
|
||||
|
||||
()
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
@@ -82,50 +118,6 @@ impl Handler<SocketResponse> for Socket {
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<SocketMessage> for Socket {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: SocketMessage, ctx: &mut Self::Context) -> Self::Result {
|
||||
let core = self.core.clone();
|
||||
|
||||
let recipient = ctx.address().recipient();
|
||||
|
||||
let fut = async move {
|
||||
match msg.payload {
|
||||
SocketMessagePayload::Query(query) => {
|
||||
match core.query(query).await {
|
||||
Ok(response) => recipient.do_send(SocketResponse {
|
||||
id: msg.id.clone(),
|
||||
payload: SocketResponsePayload::Query(response),
|
||||
}),
|
||||
Err(err) => {
|
||||
// println!("query error: {:?}", err);
|
||||
// Err(err.to_string())
|
||||
},
|
||||
};
|
||||
},
|
||||
SocketMessagePayload::Command(command) => {
|
||||
match core.command(command).await {
|
||||
Ok(response) => recipient.do_send(SocketResponse {
|
||||
id: msg.id.clone(),
|
||||
payload: SocketResponsePayload::Query(response),
|
||||
}),
|
||||
Err(err) => {
|
||||
// println!("command error: {:?}", err);
|
||||
// Err(err.to_string())
|
||||
},
|
||||
};
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
};
|
||||
|
||||
fut.into_actor(self).spawn(ctx);
|
||||
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> impl Responder {
|
||||
format!("Spacedrive Server!")
|
||||
|
||||
37
core/prisma/migrations/20220424140258_/migration.sql
Normal file
37
core/prisma/migrations/20220424140258_/migration.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `total_byte_capacity` on the `library_statistics` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- CreateTable
|
||||
CREATE TABLE "FileConflict" (
|
||||
"original_file_id" INTEGER NOT NULL,
|
||||
"detactched_file_id" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_library_statistics" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"library_id" INTEGER NOT NULL,
|
||||
"total_file_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"total_bytes_used" TEXT NOT NULL DEFAULT '0',
|
||||
"total_bytes_capacity" TEXT NOT NULL DEFAULT '0',
|
||||
"total_unique_bytes" TEXT NOT NULL DEFAULT '0',
|
||||
"total_bytes_free" TEXT NOT NULL DEFAULT '0',
|
||||
"preview_media_bytes" TEXT NOT NULL DEFAULT '0'
|
||||
);
|
||||
INSERT INTO "new_library_statistics" ("date_captured", "id", "library_id", "total_bytes_used", "total_file_count", "total_unique_bytes") SELECT "date_captured", "id", "library_id", "total_bytes_used", "total_file_count", "total_unique_bytes" FROM "library_statistics";
|
||||
DROP TABLE "library_statistics";
|
||||
ALTER TABLE "new_library_statistics" RENAME TO "library_statistics";
|
||||
CREATE UNIQUE INDEX "library_statistics_library_id_key" ON "library_statistics"("library_id");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FileConflict_original_file_id_key" ON "FileConflict"("original_file_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FileConflict_detactched_file_id_key" ON "FileConflict"("detactched_file_id");
|
||||
@@ -43,13 +43,15 @@ model Library {
|
||||
}
|
||||
|
||||
model LibraryStatistics {
|
||||
id Int @id @default(autoincrement())
|
||||
date_captured DateTime @default(now())
|
||||
library_id Int @unique
|
||||
total_file_count Int @default(0)
|
||||
total_bytes_used String @default("0")
|
||||
total_byte_capacity String @default("0")
|
||||
total_unique_bytes String @default("0")
|
||||
id Int @id @default(autoincrement())
|
||||
date_captured DateTime @default(now())
|
||||
library_id Int @unique
|
||||
total_file_count Int @default(0)
|
||||
total_bytes_used String @default("0")
|
||||
total_bytes_capacity String @default("0")
|
||||
total_unique_bytes String @default("0")
|
||||
total_bytes_free String @default("0")
|
||||
preview_media_bytes String @default("0")
|
||||
|
||||
@@map("library_statistics")
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
use anyhow::Result;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::state::client::LibraryState;
|
||||
use crate::{db::migrate, prisma::library, state};
|
||||
use crate::{prisma, Core};
|
||||
|
||||
use super::LibraryError;
|
||||
|
||||
pub static LIBRARY_DB_NAME: &str = "library.db";
|
||||
pub static DEFAULT_NAME: &str = "My Library";
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LibraryError {
|
||||
#[error("Database error")]
|
||||
DatabaseError(#[from] prisma::QueryError),
|
||||
}
|
||||
|
||||
pub async fn get(core: &Core) -> Result<library::Data, LibraryError> {
|
||||
let config = state::client::get();
|
||||
let db = &core.database;
|
||||
|
||||
@@ -1 +1,14 @@
|
||||
pub mod loader;
|
||||
pub mod statistics;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{prisma, sys::SysError};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LibraryError {
|
||||
#[error("Database error")]
|
||||
DatabaseError(#[from] prisma::QueryError),
|
||||
#[error("System error")]
|
||||
SysError(#[from] SysError),
|
||||
}
|
||||
|
||||
67
core/src/library/statistics.rs
Normal file
67
core/src/library/statistics.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use crate::{prisma::library_statistics, state::client, CoreContext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::LibraryError;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS, Clone)]
|
||||
#[ts(export)]
|
||||
pub struct Statistics {
|
||||
total_file_count: i32,
|
||||
total_bytes_used: String,
|
||||
total_bytes_capacity: String,
|
||||
total_bytes_free: String,
|
||||
total_unique_bytes: String,
|
||||
preview_media_bytes: String,
|
||||
library_db_size: String,
|
||||
}
|
||||
|
||||
impl Into<Statistics> for library_statistics::Data {
|
||||
fn into(self) -> Statistics {
|
||||
Statistics {
|
||||
total_file_count: self.total_file_count,
|
||||
total_bytes_used: self.total_bytes_used,
|
||||
total_bytes_capacity: self.total_bytes_capacity,
|
||||
total_bytes_free: self.total_bytes_free,
|
||||
total_unique_bytes: self.total_unique_bytes,
|
||||
preview_media_bytes: self.preview_media_bytes,
|
||||
library_db_size: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Statistics {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
total_file_count: 0,
|
||||
total_bytes_used: String::new(),
|
||||
total_bytes_capacity: String::new(),
|
||||
total_bytes_free: String::new(),
|
||||
total_unique_bytes: String::new(),
|
||||
preview_media_bytes: String::new(),
|
||||
library_db_size: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Statistics {
|
||||
pub async fn recalculate(ctx: &CoreContext) -> Result<(), LibraryError> {
|
||||
let config = client::get();
|
||||
let db = &ctx.database;
|
||||
|
||||
let library_data = config.get_current_library();
|
||||
|
||||
let library_statistics_db = match db
|
||||
.library_statistics()
|
||||
.find_unique(library_statistics::id::equals(library_data.library_id))
|
||||
.exec()
|
||||
.await?
|
||||
{
|
||||
Some(library_statistics_db) => library_statistics_db.into(),
|
||||
// create the default values if database has no entry
|
||||
None => Statistics::default(),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ pub static CLIENT_STATE_CONFIG_NAME: &str = "client_state.json";
|
||||
#[ts(export)]
|
||||
pub struct LibraryState {
|
||||
pub library_uuid: String,
|
||||
pub library_id: i32,
|
||||
pub library_path: String,
|
||||
pub offline: bool,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Todo
|
||||
|
||||
- Landing sections
|
||||
- Client pool
|
||||
- Custom scrollbars
|
||||
- Tag files
|
||||
- Right click menu
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ExplorerScreen } from './screens/Explorer';
|
||||
import { useCoreEvents } from './hooks/useCoreEvents';
|
||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||
import { OverviewScreen } from './screens/Overview';
|
||||
import { SpacesScreen } from './screens/Spaces';
|
||||
import { DebugScreen } from './screens/Debug';
|
||||
import { Modal } from './components/layout/Modal';
|
||||
import GeneralSettings from './screens/settings/GeneralSettings';
|
||||
import SlideUp from './components/transitions/SlideUp';
|
||||
@@ -27,6 +27,7 @@ import { Button } from '@sd/ui';
|
||||
import { CoreEvent } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
import './style.scss';
|
||||
import { ContentScreen } from './screens/Content';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -114,7 +115,8 @@ function Router() {
|
||||
<Route path="/" element={<AppLayout />}>
|
||||
<Route index element={<RedirectPage to="/overview" />} />
|
||||
<Route path="overview" element={<OverviewScreen />} />
|
||||
<Route path="spaces" element={<SpacesScreen />} />
|
||||
<Route path="content" element={<ContentScreen />} />
|
||||
<Route path="debug" element={<DebugScreen />} />
|
||||
<Route path="settings/*" element={<SettingsRoutes />} />
|
||||
<Route path="explorer/:id" element={<ExplorerScreen />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LockClosedIcon } from '@heroicons/react/outline';
|
||||
import { CogIcon, EyeOffIcon, PlusIcon, ServerIcon } from '@heroicons/react/solid';
|
||||
import clsx from 'clsx';
|
||||
import { CirclesFour, EjectSimple, MonitorPlay, Planet } from 'phosphor-react';
|
||||
import { CirclesFour, Code, EjectSimple, MonitorPlay, Planet } from 'phosphor-react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom';
|
||||
import { TrafficLights } from '../os/TrafficLights';
|
||||
@@ -111,10 +111,14 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||
<Icon component={Planet} />
|
||||
Overview
|
||||
</SidebarLink>
|
||||
<SidebarLink to="spaces">
|
||||
<SidebarLink to="content">
|
||||
<Icon component={CirclesFour} />
|
||||
Content
|
||||
</SidebarLink>
|
||||
<SidebarLink to="debug">
|
||||
<Icon component={Code} />
|
||||
Debug
|
||||
</SidebarLink>
|
||||
{/* <SidebarLink to="explorer">
|
||||
<Icon component={MonitorPlay} />
|
||||
Explorer
|
||||
@@ -141,7 +145,8 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||
)}
|
||||
>
|
||||
<div className="w-[18px] mr-2 -mt-0.5">
|
||||
{isActive ? <FolderWhite /> : <Folder />}
|
||||
<FolderWhite className={clsx(!isActive && 'hidden')} />
|
||||
<Folder className={clsx(isActive && 'hidden')} />
|
||||
</div>
|
||||
{location.name}
|
||||
<div className="flex-grow" />
|
||||
|
||||
13
packages/interface/src/screens/Content.tsx
Normal file
13
packages/interface/src/screens/Content.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const ContentScreen: React.FC<{}> = (props) => {
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen p-5 overflow-x-scroll">
|
||||
<div className="flex flex-col space-y-5 pb-7">
|
||||
<h1 className="text-lg font-bold ">Content</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +1,41 @@
|
||||
import { useBridgeQuery } from '@sd/client';
|
||||
import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
import { Button } from '@sd/ui';
|
||||
import React from 'react';
|
||||
import ReactJson from 'react-json-view';
|
||||
import FileItem from '../components/file/FileItem';
|
||||
import CodeBlock from '../components/primitive/Codeblock';
|
||||
import { Tag } from '../components/primitive/Tag';
|
||||
|
||||
export const SpacesScreen: React.FC<{}> = (props) => {
|
||||
export const DebugScreen: React.FC<{}> = (props) => {
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
const { data: jobs } = useBridgeQuery('JobGetRunning');
|
||||
const { data: jobHistory } = useBridgeQuery('JobGetHistory');
|
||||
const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
|
||||
onMutate: () => {
|
||||
alert('Database purged');
|
||||
}
|
||||
});
|
||||
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen p-5 overflow-x-scroll">
|
||||
<div className="flex flex-col space-y-5 pb-7">
|
||||
<h1 className="text-lg font-bold ">Developer Debugger</h1>
|
||||
<div className="flex flex-row pb-4 space-x-2">
|
||||
<Button className="w-40" variant="gray" size="sm" onClick={() => {}}>
|
||||
Open data folder
|
||||
</Button>
|
||||
<Button className="w-40" variant="gray" size="sm" onClick={() => purgeDB(undefined)}>
|
||||
Purge database
|
||||
</Button>
|
||||
<Button
|
||||
className="w-40"
|
||||
variant="gray"
|
||||
size="sm"
|
||||
onClick={() => identifyFiles(undefined)}
|
||||
>
|
||||
Identify unique files
|
||||
</Button>
|
||||
</div>
|
||||
<h1 className="text-sm font-bold ">Running Jobs</h1>
|
||||
<CodeBlock src={{ ...jobs }} />
|
||||
<h1 className="text-sm font-bold ">Job History</h1>
|
||||
@@ -11,15 +11,6 @@ import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
||||
export default function GeneralSettings() {
|
||||
const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
||||
|
||||
const [fakeSliderVal, setFakeSliderVal] = useState([30, 0]);
|
||||
|
||||
const { mutate: purgeDB } = useBridgeCommand('PurgeDatabase', {
|
||||
onMutate: () => {
|
||||
alert('Database purged');
|
||||
}
|
||||
});
|
||||
const { mutate: identifyFiles } = useBridgeCommand('IdentifyUniqueFiles');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow max-w-4xl space-y-4">
|
||||
<div className="mt-3 mb-3">
|
||||
@@ -31,17 +22,7 @@ export default function GeneralSettings() {
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</p>
|
||||
<div className="flex flex-row pb-4 space-x-2">
|
||||
<Button className="w-40" variant="gray" size="sm" onClick={() => {}}>
|
||||
Open data folder
|
||||
</Button>
|
||||
<Button className="w-40" variant="gray" size="sm" onClick={() => purgeDB(undefined)}>
|
||||
Purge database
|
||||
</Button>
|
||||
<Button className="w-40" variant="gray" size="sm" onClick={() => identifyFiles(undefined)}>
|
||||
Identify unique files
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* <InputContainer
|
||||
title="Test scan directory"
|
||||
description="This will create a job to scan the directory you specify to the database."
|
||||
@@ -83,37 +64,6 @@ export default function GeneralSettings() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button className="mb-3" variant="primary">
|
||||
Add Location
|
||||
</Button>
|
||||
</div>
|
||||
</InputContainer>
|
||||
<InputContainer
|
||||
title="Volumes"
|
||||
description="A list of mounted volumes on this machine, for no reason."
|
||||
>
|
||||
<Slider
|
||||
step={5}
|
||||
value={fakeSliderVal}
|
||||
onValueChange={setFakeSliderVal}
|
||||
defaultValue={[25, 75]}
|
||||
/>
|
||||
</InputContainer>
|
||||
<InputContainer
|
||||
title="A long input"
|
||||
description="Local cache storage for media previews and thumbnails."
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Input
|
||||
className="flex-grow"
|
||||
placeholder="/users/jamie/Library/Application Support/spacedrive/cache"
|
||||
/>
|
||||
</div>
|
||||
</InputContainer>
|
||||
<InputContainer title="Vault" description="Enable vault storage with VeraCrypt.">
|
||||
<div className="flex flex-row">
|
||||
<Button variant="primary">Enable Vault</Button>
|
||||
{/*<Input className="flex-grow" value="jeff" placeholder="/users/jamie/Desktop" />*/}
|
||||
</div>
|
||||
</InputContainer>
|
||||
|
||||
|
||||
BIN
pnpm-lock.yaml
generated
BIN
pnpm-lock.yaml
generated
Binary file not shown.
Reference in New Issue
Block a user