mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-06 22:33:27 +00:00
File Opening (#762)
* file opening * eslint disable in gererated commands * bruh? * Fixing materialized_path joining and some warnings * no unwraps * formatting --------- Co-authored-by: Ericson Soares <ericson.ds999@gmail.com>
This commit is contained in:
parent
c7d182e12b
commit
3570c5f3d8
93
Cargo.lock
generated
93
Cargo.lock
generated
|
@ -724,6 +724,18 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "builtin-psl-connectors"
|
||||
version = "0.1.0"
|
||||
|
@ -732,7 +744,7 @@ dependencies = [
|
|||
"connection-string",
|
||||
"either",
|
||||
"enumflags2 0.7.5",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"lsp-types",
|
||||
"once_cell",
|
||||
"psl-core",
|
||||
|
@ -1053,7 +1065,7 @@ version = "4.0.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1076,7 +1088,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"clap",
|
||||
"hex",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"sd-crypto",
|
||||
"tokio",
|
||||
]
|
||||
|
@ -1807,7 +1819,7 @@ version = "0.1.0"
|
|||
source = "git+https://github.com/Brendonovich/prisma-engines?tag=pcr-0.6.5#7cc20cc54a7ab0b5e38c81901826939a91a17ba0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"pest",
|
||||
]
|
||||
|
||||
|
@ -1898,7 +1910,7 @@ dependencies = [
|
|||
"cuid",
|
||||
"either",
|
||||
"enumflags2 0.7.5",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"prisma-value",
|
||||
"psl-core",
|
||||
"schema-ast",
|
||||
|
@ -2054,7 +2066,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
|
@ -2674,7 +2686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-crate 1.2.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
|
@ -2705,7 +2717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"bstr 0.2.17",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
|
@ -2890,9 +2902,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
|
@ -3264,6 +3276,12 @@ version = "1.0.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.7.0"
|
||||
|
@ -4908,6 +4926,17 @@ dependencies = [
|
|||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opener"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788"
|
||||
dependencies = [
|
||||
"bstr 1.4.0",
|
||||
"normpath",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.42"
|
||||
|
@ -5757,7 +5786,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2c828f93f5ca4826f97fedcbd3f9a536c16b12cff3dbbb4a007f932bbad95b12"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -5826,7 +5855,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"diagnostics",
|
||||
"enumflags2 0.7.5",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"itertools",
|
||||
"lsp-types",
|
||||
"once_cell",
|
||||
|
@ -7395,16 +7424,20 @@ dependencies = [
|
|||
"axum",
|
||||
"http",
|
||||
"httpz 0.0.3",
|
||||
"opener",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rspc",
|
||||
"sd-core",
|
||||
"sd-desktop-macos",
|
||||
"serde",
|
||||
"specta",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-specta",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid 1.2.1",
|
||||
"window-shadows",
|
||||
]
|
||||
|
||||
|
@ -7417,12 +7450,13 @@ dependencies = [
|
|||
"chrono",
|
||||
"document-features",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta-macros",
|
||||
"tauri",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"uhlc",
|
||||
|
@ -7506,7 +7540,7 @@ dependencies = [
|
|||
"connection-string",
|
||||
"either",
|
||||
"enumflags2 0.7.5",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"migration-connector",
|
||||
"once_cell",
|
||||
"psl",
|
||||
|
@ -7564,7 +7598,7 @@ dependencies = [
|
|||
"bigdecimal",
|
||||
"enumflags2 0.7.5",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"once_cell",
|
||||
"psl",
|
||||
"quaint",
|
||||
|
@ -7675,7 +7709,7 @@ version = "0.24.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
|
@ -7823,7 +7857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709"
|
||||
dependencies = [
|
||||
"cfg-expr 0.10.3",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"version-compare 0.1.0",
|
||||
|
@ -7910,7 +7944,7 @@ dependencies = [
|
|||
"glib",
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"http",
|
||||
"ignore",
|
||||
"minisign-verify",
|
||||
|
@ -7957,7 +7991,7 @@ checksum = "0991fb306849897439dbd4a72e4cbed2413e2eb26cb4b3ba220b94edba8b4b88"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"json-patch",
|
||||
"semver 1.0.14",
|
||||
"serde_json",
|
||||
|
@ -7997,7 +8031,7 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
|
@ -8045,6 +8079,21 @@ dependencies = [
|
|||
"wry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23419889204476d5a70a04077d4628061a5bb667b18804a4572df2fe6ccb8ba5"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"indoc 2.0.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"tauri",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.2.1"
|
||||
|
@ -8054,7 +8103,7 @@ dependencies = [
|
|||
"brotli",
|
||||
"ctor",
|
||||
"glob",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"html5ever",
|
||||
"infer",
|
||||
"json-patch",
|
||||
|
@ -8739,7 +8788,7 @@ version = "0.1.0"
|
|||
source = "git+https://github.com/Brendonovich/prisma-engines?tag=pcr-0.6.5#7cc20cc54a7ab0b5e38c81901826939a91a17ba0"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"indoc",
|
||||
"indoc 1.0.8",
|
||||
"quaint",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -19,6 +19,10 @@ tracing = "0.1.36"
|
|||
serde = "1.0.145"
|
||||
percent-encoding = "2.2.0"
|
||||
http = "0.2.8"
|
||||
opener = "0.6.1"
|
||||
specta.workspace = true
|
||||
tauri-specta = { version = "1.0.0", features = ["typescript"] }
|
||||
uuid = { version = "1.1.2", features = ["serde"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
axum = { version = "0.6.4", features = ["headers", "query"] }
|
||||
|
|
39
apps/desktop/src-tauri/src/file.rs
Normal file
39
apps/desktop/src-tauri/src/file.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use sd_core::Node;
|
||||
use serde::Serialize;
|
||||
use specta::Type;
|
||||
|
||||
#[derive(Serialize, Type)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum OpenFilePathResult {
|
||||
NoLibrary,
|
||||
NoFile,
|
||||
OpenError(String),
|
||||
AllGood,
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[specta::specta]
|
||||
pub async fn open_file_path(
|
||||
library: uuid::Uuid,
|
||||
id: i32,
|
||||
node: tauri::State<'_, Arc<Node>>,
|
||||
) -> Result<OpenFilePathResult, ()> {
|
||||
let res = if let Some(library) = node.library_manager.get_library(library).await {
|
||||
let Ok(Some(path)) = library
|
||||
.get_file_path(id)
|
||||
.await
|
||||
else {
|
||||
return Ok(OpenFilePathResult::NoFile)
|
||||
};
|
||||
|
||||
opener::open(path)
|
||||
.map(|_| OpenFilePathResult::AllGood)
|
||||
.unwrap_or_else(|e| OpenFilePathResult::OpenError(e.to_string()))
|
||||
} else {
|
||||
OpenFilePathResult::NoLibrary
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
|
@ -14,9 +14,11 @@ use tracing::{debug, error};
|
|||
#[cfg(target_os = "linux")]
|
||||
mod app_linux;
|
||||
|
||||
mod file;
|
||||
mod menu;
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[specta::specta]
|
||||
async fn app_ready(app_handle: tauri::AppHandle) {
|
||||
let window = app_handle.get_window("main").unwrap();
|
||||
|
||||
|
@ -29,6 +31,15 @@ pub fn tauri_error_plugin<R: Runtime>(err: NodeError) -> TauriPlugin<R> {
|
|||
.build()
|
||||
}
|
||||
|
||||
macro_rules! tauri_handlers {
|
||||
($($name:path),+) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
tauri_specta::ts::export(specta::collect_types![$($name),+], "../src/commands.ts").unwrap();
|
||||
|
||||
tauri::generate_handler![$($name),+]
|
||||
}};
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> tauri::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -58,10 +69,12 @@ async fn main() -> tauri::Result<()> {
|
|||
endpoint.tauri_uri_scheme("spacedrive"),
|
||||
);
|
||||
|
||||
let app = app.plugin(rspc::integrations::tauri::plugin(router, {
|
||||
let node = node.clone();
|
||||
move || node.clone()
|
||||
}));
|
||||
let app = app
|
||||
.plugin(rspc::integrations::tauri::plugin(router, {
|
||||
let node = node.clone();
|
||||
move || node.clone()
|
||||
}))
|
||||
.manage(node.clone());
|
||||
|
||||
(Some(node), app)
|
||||
}
|
||||
|
@ -107,8 +120,8 @@ async fn main() -> tauri::Result<()> {
|
|||
Ok(())
|
||||
})
|
||||
.on_menu_event(menu::handle_menu_event)
|
||||
.invoke_handler(tauri::generate_handler![app_ready])
|
||||
.menu(menu::get_menu())
|
||||
.invoke_handler(tauri_handlers![app_ready, file::open_file_path])
|
||||
.build(tauri::generate_context!())?;
|
||||
|
||||
app.run(move |app_handler, event| {
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '@sd/interface';
|
||||
import { getSpacedropState } from '@sd/interface/hooks/useSpacedropState';
|
||||
import '@sd/ui/style';
|
||||
import { appReady, openFilePath } from './commands';
|
||||
|
||||
const client = hooks.createClient({
|
||||
links: [
|
||||
|
@ -72,7 +73,8 @@ const platform: Platform = {
|
|||
openFilePickerDialog: () => dialog.open(),
|
||||
saveFilePickerDialog: () => dialog.save(),
|
||||
showDevtools: () => invoke('show_devtools'),
|
||||
openPath: (path) => shell.open(path)
|
||||
openPath: (path) => shell.open(path),
|
||||
openFilePath
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
@ -82,7 +84,7 @@ const router = createMemoryRouter(routes);
|
|||
export default function App() {
|
||||
useEffect(() => {
|
||||
// This tells Tauri to show the current window because it's finished loading
|
||||
invoke('app_ready');
|
||||
appReady();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
23
apps/desktop/src/commands.ts
Normal file
23
apps/desktop/src/commands.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__TAURI_INVOKE__<T>(cmd: string, args?: Record<string, unknown>): Promise<T>;
|
||||
}
|
||||
}
|
||||
|
||||
const invoke = window.__TAURI_INVOKE__;
|
||||
|
||||
export function appReady() {
|
||||
return invoke<null>('app_ready');
|
||||
}
|
||||
|
||||
export function openFilePath(library: string, id: number) {
|
||||
return invoke<OpenFilePathResult>('open_file_path', { library, id });
|
||||
}
|
||||
|
||||
export type OpenFilePathResult =
|
||||
| { t: 'NoLibrary' }
|
||||
| { t: 'NoFile' }
|
||||
| { t: 'OpenError'; c: string }
|
||||
| { t: 'AllGood' };
|
|
@ -10,12 +10,8 @@ rust-version = "1.68.1"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
mobile = [
|
||||
] # This feature allows features to be disabled when the Core is running on mobile.
|
||||
ffmpeg = [
|
||||
"dep:ffmpeg-next",
|
||||
"dep:sd-ffmpeg",
|
||||
] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg.
|
||||
mobile = [] # This feature allows features to be disabled when the Core is running on mobile.
|
||||
ffmpeg = ["dep:ffmpeg-next", "dep:sd-ffmpeg"] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg.
|
||||
location-watcher = ["dep:notify"]
|
||||
sync-messages = []
|
||||
|
||||
|
|
|
@ -105,7 +105,10 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
|
|||
|
||||
invalidate_query!(
|
||||
// SAFETY: This unwrap is alright as we just created the library
|
||||
ctx.library_manager.get_ctx(new_library.uuid).await.unwrap(),
|
||||
ctx.library_manager
|
||||
.get_library(new_library.uuid)
|
||||
.await
|
||||
.unwrap(),
|
||||
"library.getStatistics"
|
||||
);
|
||||
|
||||
|
|
|
@ -249,9 +249,9 @@ pub(crate) fn mount_invalidate() -> AlphaRouter<Ctx> {
|
|||
},
|
||||
// Given human reaction time of ~250 milli this should be a good ballance.
|
||||
_ = tokio::time::sleep(Duration::from_millis(200)) => {
|
||||
let x = buf.drain().map(|(_k, v)| v).collect::<Vec<_>>();
|
||||
if !x.is_empty() {
|
||||
match tx.send(x) {
|
||||
let events = buf.drain().map(|(_k, v)| v).collect::<Vec<_>>();
|
||||
if !events.is_empty() {
|
||||
match tx.send(events) {
|
||||
Ok(_) => {},
|
||||
// All receivers are shutdown means that all clients are disconnected.
|
||||
Err(_) => {
|
||||
|
|
|
@ -34,7 +34,7 @@ pub(crate) fn library() -> impl MwV3<Ctx, NewCtx = (Ctx, Library)> {
|
|||
MwArgMapperMiddleware::<LibraryArgsLike>::new().mount(|mw, ctx: Ctx, library_id| async move {
|
||||
let library = ctx
|
||||
.library_manager
|
||||
.get_ctx(library_id)
|
||||
.get_library(library_id)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
rspc::Error::new(
|
||||
|
|
|
@ -167,7 +167,7 @@ async fn handle_file(
|
|||
} else {
|
||||
let library = node
|
||||
.library_manager
|
||||
.get_ctx(library_id)
|
||||
.get_library(library_id)
|
||||
.await
|
||||
.ok_or_else(|| HandleCustomUriError::NotFound("library"))?;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use tracing_subscriber::{prelude::*, EnvFilter};
|
|||
pub mod api;
|
||||
pub mod custom_uri;
|
||||
pub(crate) mod job;
|
||||
pub(crate) mod library;
|
||||
pub mod library;
|
||||
pub(crate) mod location;
|
||||
pub(crate) mod migrations;
|
||||
pub(crate) mod node;
|
||||
|
@ -27,7 +27,7 @@ pub(crate) mod util;
|
|||
pub(crate) mod volume;
|
||||
|
||||
#[allow(warnings, unused)]
|
||||
pub(crate) mod prisma;
|
||||
mod prisma;
|
||||
pub(crate) mod prisma_sync;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -41,7 +41,7 @@ pub struct NodeContext {
|
|||
|
||||
pub struct Node {
|
||||
config: Arc<NodeConfigManager>,
|
||||
library_manager: Arc<LibraryManager>,
|
||||
pub library_manager: Arc<LibraryManager>,
|
||||
location_manager: Arc<LocationManager>,
|
||||
jobs: Arc<JobManager>,
|
||||
p2p: Arc<P2PManager>,
|
||||
|
@ -109,11 +109,12 @@ impl Node {
|
|||
"desktop=debug"
|
||||
.parse()
|
||||
.expect("Error invalid tracing directive!"),
|
||||
), // .add_directive(
|
||||
// "rspc=debug"
|
||||
// .parse()
|
||||
// .expect("Error invalid tracing directive!"),
|
||||
// ),
|
||||
),
|
||||
// .add_directive(
|
||||
// "rspc=debug"
|
||||
// .parse()
|
||||
// .expect("Error invalid tracing directive!"),
|
||||
// ),
|
||||
);
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let subscriber = subscriber.with(tracing_subscriber::fmt::layer().with_filter(CONSOLE_LOG_FILTER));
|
||||
|
@ -161,7 +162,7 @@ impl Node {
|
|||
while let Ok((library_id, operations)) = p2p_rx.recv().await {
|
||||
debug!("going to ingest {} operations", operations.len());
|
||||
|
||||
let Some(library) = library_manager.get_ctx(library_id).await else {
|
||||
let Some(library) = library_manager.get_library(library_id).await else {
|
||||
warn!("no library found!");
|
||||
continue;
|
||||
};
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use crate::{
|
||||
api::CoreEvent,
|
||||
job::{IntoJob, JobInitData, JobManagerError, StatefulJob},
|
||||
location::LocationManager,
|
||||
location::{file_path_helper::MaterializedPath, LocationManager},
|
||||
node::NodeConfigManager,
|
||||
object::preview::THUMBNAIL_CACHE_DIR_NAME,
|
||||
prisma::PrismaClient,
|
||||
prisma::{file_path, location, PrismaClient},
|
||||
sync::SyncManager,
|
||||
NodeContext,
|
||||
};
|
||||
|
||||
use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
@ -18,7 +19,7 @@ use sd_crypto::keys::keymanager::KeyManager;
|
|||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::LibraryConfig;
|
||||
use super::{LibraryConfig, LibraryManagerError};
|
||||
|
||||
/// LibraryContext holds context for a library which can be passed around the application.
|
||||
#[derive(Clone)]
|
||||
|
@ -97,4 +98,30 @@ impl Library {
|
|||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the full path of a file
|
||||
pub async fn get_file_path(&self, id: i32) -> Result<Option<PathBuf>, LibraryManagerError> {
|
||||
Ok(self
|
||||
.db
|
||||
.file_path()
|
||||
.find_first(vec![
|
||||
file_path::location::is(vec![location::node_id::equals(self.node_local_id)]),
|
||||
file_path::id::equals(id),
|
||||
])
|
||||
.select(file_path::select!({
|
||||
materialized_path
|
||||
location: select {
|
||||
id
|
||||
path
|
||||
}
|
||||
}))
|
||||
.exec()
|
||||
.await?
|
||||
.map(|record| {
|
||||
Path::new(&record.location.path).join(&MaterializedPath::from((
|
||||
record.location.id,
|
||||
&record.materialized_path,
|
||||
)))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -304,7 +304,7 @@ impl LibraryManager {
|
|||
}
|
||||
|
||||
// get_ctx will return the library context for the given library id.
|
||||
pub(crate) async fn get_ctx(&self, library_id: Uuid) -> Option<Library> {
|
||||
pub async fn get_library(&self, library_id: Uuid) -> Option<Library> {
|
||||
self.libraries
|
||||
.read()
|
||||
.await
|
||||
|
|
|
@ -81,7 +81,7 @@ impl InitConfig {
|
|||
}
|
||||
});
|
||||
|
||||
let library = match library_manager.get_ctx(lib.id).await {
|
||||
let library = match library_manager.get_library(lib.id).await {
|
||||
Some(lib) => lib,
|
||||
None => {
|
||||
let library = library_manager
|
||||
|
@ -95,7 +95,7 @@ impl InitConfig {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
library_manager.get_ctx(library.uuid).await.unwrap()
|
||||
library_manager.get_library(library.uuid).await.unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -14,13 +14,22 @@ import {
|
|||
TrashSimple
|
||||
} from 'phosphor-react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { ExplorerItem, isObject, useLibraryMutation, useLibraryQuery } from '@sd/client';
|
||||
import {
|
||||
ExplorerItem,
|
||||
isObject,
|
||||
useLibraryContext,
|
||||
useLibraryMutation,
|
||||
useLibraryQuery
|
||||
} from '@sd/client';
|
||||
import { ContextMenu, dialogManager } from '@sd/ui';
|
||||
import { useExplorerParams } from '~/app/$libraryId/location/$id';
|
||||
import { showAlertDialog } from '~/components/AlertDialog';
|
||||
import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
||||
import { useOperatingSystem } from '~/hooks/useOperatingSystem';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
import AssignTagMenuItems from '../AssignTagMenuItems';
|
||||
import { OpenInNativeExplorer } from '../ContextMenu';
|
||||
import { getItemFilePath } from '../util';
|
||||
import DecryptDialog from './DecryptDialog';
|
||||
import DeleteDialog from './DeleteDialog';
|
||||
import EncryptDialog from './EncryptDialog';
|
||||
|
@ -45,13 +54,7 @@ export default ({ data, className, ...props }: Props) => {
|
|||
return (
|
||||
<div onClick={(e) => e.stopPropagation()} className={clsx('flex', className)}>
|
||||
<ContextMenu.Root trigger={props.children}>
|
||||
<ContextMenu.Item label="Open" keybind="⌘O" />
|
||||
<ContextMenu.Item
|
||||
label="Quick view"
|
||||
keybind="␣"
|
||||
onClick={() => (getExplorerStore().quickViewObject = data)}
|
||||
/>
|
||||
<ContextMenu.Item label="Open with..." keybind="⌘^O" />
|
||||
<OpenOrDownloadOptions data={data} />
|
||||
|
||||
<ContextMenu.Separator />
|
||||
|
||||
|
@ -248,3 +251,33 @@ export default ({ data, className, ...props }: Props) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenOrDownloadOptions = (props: { data: ExplorerItem }) => {
|
||||
const os = useOperatingSystem();
|
||||
const platform = usePlatform();
|
||||
|
||||
const filePath = getItemFilePath(props.data);
|
||||
const openFilePath = platform.openFilePath;
|
||||
|
||||
const { library } = useLibraryContext();
|
||||
|
||||
if (os === 'browser') return <ContextMenu.Item label="Download" />;
|
||||
else
|
||||
return (
|
||||
<>
|
||||
{filePath && openFilePath && (
|
||||
<ContextMenu.Item
|
||||
label="Open"
|
||||
keybind="⌘O"
|
||||
onClick={() => openFilePath(library.uuid, filePath.id)}
|
||||
/>
|
||||
)}
|
||||
<ContextMenu.Item
|
||||
label="Quick view"
|
||||
keybind="␣"
|
||||
onClick={() => (getExplorerStore().quickViewObject = props.data)}
|
||||
/>
|
||||
<ContextMenu.Item label="Open with..." keybind="⌘^O" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,6 +17,8 @@ export type Platform = {
|
|||
saveFilePickerDialog?(): Promise<string | null>;
|
||||
showDevtools?(): void;
|
||||
openPath?(path: string): void;
|
||||
// Opens a file path with a given ID
|
||||
openFilePath?(library: string, id: number): any;
|
||||
};
|
||||
|
||||
// Keep this private and use through helpers below
|
||||
|
|
|
@ -107,8 +107,6 @@ export type Ordering = { name: boolean }
|
|||
*/
|
||||
export type StoredKeyVersion = "V1"
|
||||
|
||||
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 }
|
||||
|
||||
/**
|
||||
* This should be used for passing an encrypted key around.
|
||||
*
|
||||
|
@ -135,12 +133,16 @@ export type ExplorerData = { context: ExplorerContext; items: ExplorerItem[] }
|
|||
|
||||
export type RenameFileArgs = { location_id: number; file_name: string; new_file_name: string }
|
||||
|
||||
export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string }
|
||||
|
||||
/**
|
||||
* Represents the operating system which the remote peer is running.
|
||||
* This is not used internally and predominantly is designed to be used for display purposes by the embedding application.
|
||||
*/
|
||||
export type OperatingSystem = "Windows" | "Linux" | "MacOS" | "Ios" | "Android" | { Other: 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 }
|
||||
|
||||
/**
|
||||
* This is a stored key, and can be freely written to the database.
|
||||
*
|
||||
|
@ -196,13 +198,17 @@ export type LocationUpdateArgs = { id: number; name: string | null; generate_pre
|
|||
|
||||
export type FileCutterJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: 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; parent_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"
|
||||
|
||||
export type ObjectValidatorArgs = { id: number; path: string }
|
||||
|
||||
export type FileEraserJobInit = { location_id: number; path_id: number; passes: 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 Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
||||
|
||||
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 Volume = { name: string; mount_point: string; total_capacity: string; available_capacity: string; is_removable: boolean; disk_type: string | null; file_system: string | null; is_root_filesystem: boolean }
|
||||
|
||||
|
@ -215,6 +221,8 @@ export type Algorithm = "XChaCha20Poly1305" | "Aes256Gcm"
|
|||
|
||||
export type ExplorerItem = { type: "Path"; has_thumbnail: boolean; item: FilePathWithObject } | { type: "Object"; has_thumbnail: boolean; item: ObjectWithFilePaths }
|
||||
|
||||
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 JobReport = { id: string; name: string; action: string | null; data: number[] | null; metadata: any | null; is_background: boolean; 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 OwnedOperationItem = { id: any; data: OwnedOperationData }
|
||||
|
@ -223,8 +231,6 @@ export type SetFavoriteArgs = { id: number; favorite: boolean }
|
|||
|
||||
export type CRDTOperationType = SharedOperation | RelationOperation | OwnedOperation
|
||||
|
||||
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 }
|
||||
|
||||
/**
|
||||
* TODO: P2P event for the frontend
|
||||
*/
|
||||
|
@ -232,8 +238,6 @@ export type P2PEvent = { type: "DiscoveredPeer"; peer_id: PeerId; metadata: Peer
|
|||
|
||||
export type SpacedropArgs = { peer_id: PeerId; file_path: 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 }
|
||||
|
||||
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 })[] }
|
||||
|
||||
export type NodeState = ({ id: string; name: string; p2p_port: number | null; p2p_email: string | null; p2p_img_url: string | null }) & { data_path: string }
|
||||
|
@ -250,15 +254,11 @@ export type SharedOperationCreateData = { u: { [key: string]: any } } | "a"
|
|||
|
||||
export type KeyAddArgs = { algorithm: Algorithm; hashing_algorithm: HashingAlgorithm; key: Protected<string>; library_sync: boolean; automount: 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 }
|
||||
|
||||
/**
|
||||
* Can wrap a query argument to require it to contain a `library_id` and provide helpers for working with libraries.
|
||||
*/
|
||||
export type LibraryArgs<T> = { library_id: string; arg: T }
|
||||
|
||||
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
||||
|
||||
/**
|
||||
* `LocationCreateArgs` is the argument received from the client using `rspc` to create a new location.
|
||||
* It has the actual path and a vector of indexer rules ids, to create many-to-many relationships
|
||||
|
@ -272,6 +272,8 @@ export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMa
|
|||
|
||||
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | 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 }
|
||||
|
||||
export type TagAssignArgs = { object_id: number; tag_id: number; unassign: boolean }
|
||||
|
||||
export type FileCopierJobInit = { source_location_id: number; source_path_id: number; target_location_id: number; target_path: string; target_file_name_suffix: string | null }
|
||||
|
@ -287,8 +289,6 @@ export type ExplorerContext = ({ type: "Location" } & Location) | ({ type: "Tag"
|
|||
|
||||
export type SetNoteArgs = { id: number; note: string | null }
|
||||
|
||||
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; parent_id: number[] | null; key_id: number | null; date_created: string; date_modified: string; date_indexed: string }
|
||||
|
||||
/**
|
||||
* LibraryConfig holds the configuration for a specific library. This is stored as a '{uuid}.sdlibrary' file.
|
||||
*/
|
||||
|
@ -300,6 +300,8 @@ export type FileDecryptorJobInit = { location_id: number; path_id: number; mount
|
|||
|
||||
export type AutomountUpdateArgs = { uuid: string; status: 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 Protected<T> = T
|
||||
|
||||
/**
|
||||
|
@ -314,8 +316,6 @@ export type Protected<T> = T
|
|||
*/
|
||||
export type IndexerRuleCreateArgs = { kind: RuleKind; name: string; parameters: string[] }
|
||||
|
||||
export type IndexerRule = { id: number; kind: number; name: string; default: boolean; parameters: number[]; date_created: string; date_modified: string }
|
||||
|
||||
export type LightScanArgs = { location_id: number; sub_path: string }
|
||||
|
||||
export type RestoreBackupArgs = { password: Protected<string>; secret_key: Protected<string>; path: string }
|
||||
|
|
Loading…
Reference in a new issue