mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-18 13:09:15 +00:00
basic single file duplication
This commit is contained in:
parent
24de617b92
commit
f81c1b35a8
|
@ -4,6 +4,7 @@ use crate::{
|
||||||
object::fs::{
|
object::fs::{
|
||||||
decrypt::{FileDecryptorJob, FileDecryptorJobInit},
|
decrypt::{FileDecryptorJob, FileDecryptorJobInit},
|
||||||
delete::{FileDeleterJob, FileDeleterJobInit},
|
delete::{FileDeleterJob, FileDeleterJobInit},
|
||||||
|
duplicate::{FileDuplicatorJob, FileDuplicatorJobInit},
|
||||||
encrypt::{FileEncryptorJob, FileEncryptorJobInit},
|
encrypt::{FileEncryptorJob, FileEncryptorJobInit},
|
||||||
erase::{FileEraserJob, FileEraserJobInit},
|
erase::{FileEraserJob, FileEraserJobInit},
|
||||||
},
|
},
|
||||||
|
@ -122,6 +123,16 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
library.spawn_job(Job::new(args, FileEraserJob {})).await;
|
library.spawn_job(Job::new(args, FileEraserJob {})).await;
|
||||||
invalidate_query!(library, "locations.getExplorerData");
|
invalidate_query!(library, "locations.getExplorerData");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.library_mutation("duplicateFiles", |t| {
|
||||||
|
t(|_, args: FileDuplicatorJobInit, library| async move {
|
||||||
|
library
|
||||||
|
.spawn_job(Job::new(args, FileDuplicatorJob {}))
|
||||||
|
.await;
|
||||||
|
invalidate_query!(library, "locations.getExplorerData");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
89
core/src/object/fs/duplicate.rs
Normal file
89
core/src/object/fs/duplicate.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use super::{context_menu_fs_info, FsInfo, ObjectType};
|
||||||
|
use crate::job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
|
use std::{collections::VecDeque, hash::Hash};
|
||||||
|
|
||||||
|
pub struct FileDuplicatorJob {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct FileDuplicatorJobState {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Hash, Type)]
|
||||||
|
pub struct FileDuplicatorJobInit {
|
||||||
|
pub location_id: i32,
|
||||||
|
pub path_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct FileDuplicatorJobStep {
|
||||||
|
pub fs_info: FsInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
const JOB_NAME: &str = "file_duplicator";
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl StatefulJob for FileDuplicatorJob {
|
||||||
|
type Data = FileDuplicatorJobState;
|
||||||
|
type Init = FileDuplicatorJobInit;
|
||||||
|
type Step = FileDuplicatorJobStep;
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
JOB_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init(&self, ctx: WorkerContext, state: &mut JobState<Self>) -> Result<(), JobError> {
|
||||||
|
let fs_info = context_menu_fs_info(
|
||||||
|
&ctx.library_ctx.db,
|
||||||
|
state.init.location_id,
|
||||||
|
state.init.path_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
state.steps = VecDeque::new();
|
||||||
|
state.steps.push_back(FileDuplicatorJobStep { fs_info });
|
||||||
|
|
||||||
|
ctx.progress(vec![JobReportUpdate::TaskCount(state.steps.len())]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_step(
|
||||||
|
&self,
|
||||||
|
ctx: WorkerContext,
|
||||||
|
state: &mut JobState<Self>,
|
||||||
|
) -> Result<(), JobError> {
|
||||||
|
let step = &state.steps[0];
|
||||||
|
let info = &step.fs_info;
|
||||||
|
|
||||||
|
match info.obj_type {
|
||||||
|
ObjectType::File => {
|
||||||
|
let mut output_path = info.obj_path.clone();
|
||||||
|
output_path.set_file_name(
|
||||||
|
info.obj_path
|
||||||
|
.clone()
|
||||||
|
.file_stem()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string() + "-Copy" + "."
|
||||||
|
+ info
|
||||||
|
.obj_path
|
||||||
|
.extension()
|
||||||
|
.map_or_else(|| "", |x| x.to_str().unwrap()),
|
||||||
|
);
|
||||||
|
std::fs::copy(info.obj_path.clone(), output_path)
|
||||||
|
}
|
||||||
|
ObjectType::Directory => todo!(),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
ctx.progress(vec![JobReportUpdate::CompletedTaskCount(
|
||||||
|
state.step_number + 1,
|
||||||
|
)]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finalize(&self, _ctx: WorkerContext, state: &mut JobState<Self>) -> JobResult {
|
||||||
|
Ok(Some(serde_json::to_value(&state.init)?))
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
|
|
||||||
pub mod decrypt;
|
pub mod decrypt;
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
|
pub mod duplicate;
|
||||||
pub mod encrypt;
|
pub mod encrypt;
|
||||||
pub mod erase;
|
pub mod erase;
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ export type Procedures = {
|
||||||
| { key: 'files.decryptFiles'; input: LibraryArgs<FileDecryptorJobInit>; result: null }
|
| { key: 'files.decryptFiles'; input: LibraryArgs<FileDecryptorJobInit>; result: null }
|
||||||
| { key: 'files.delete'; input: LibraryArgs<number>; result: null }
|
| { key: 'files.delete'; input: LibraryArgs<number>; result: null }
|
||||||
| { key: 'files.deleteFiles'; input: LibraryArgs<FileDeleterJobInit>; result: null }
|
| { key: 'files.deleteFiles'; input: LibraryArgs<FileDeleterJobInit>; result: null }
|
||||||
|
| { key: 'files.duplicateFiles'; input: LibraryArgs<FileDuplicatorJobInit>; result: null }
|
||||||
| { key: 'files.encryptFiles'; input: LibraryArgs<FileEncryptorJobInit>; result: null }
|
| { key: 'files.encryptFiles'; input: LibraryArgs<FileEncryptorJobInit>; result: null }
|
||||||
| { key: 'files.eraseFiles'; input: LibraryArgs<FileEraserJobInit>; result: null }
|
| { key: 'files.eraseFiles'; input: LibraryArgs<FileEraserJobInit>; result: null }
|
||||||
| { key: 'files.setFavorite'; input: LibraryArgs<SetFavoriteArgs>; result: null }
|
| { key: 'files.setFavorite'; input: LibraryArgs<SetFavoriteArgs>; result: null }
|
||||||
|
@ -193,6 +194,11 @@ export interface FileDeleterJobInit {
|
||||||
path_id: number;
|
path_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileDuplicatorJobInit {
|
||||||
|
location_id: number;
|
||||||
|
path_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FileEncryptorJobInit {
|
export interface FileEncryptorJobInit {
|
||||||
location_id: number;
|
location_id: number;
|
||||||
path_id: number;
|
path_id: number;
|
||||||
|
|
|
@ -172,6 +172,8 @@ export function FileItemContextMenu({ ...props }: FileItemContextMenuProps) {
|
||||||
const hasMountedKeys =
|
const hasMountedKeys =
|
||||||
mountedUuids.data !== undefined && mountedUuids.data.length > 0 ? true : false;
|
mountedUuids.data !== undefined && mountedUuids.data.length > 0 ? true : false;
|
||||||
|
|
||||||
|
const duplicateFiles = useLibraryMutation('files.duplicateFiles');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<CM.ContextMenu trigger={props.children}>
|
<CM.ContextMenu trigger={props.children}>
|
||||||
|
@ -186,7 +188,15 @@ export function FileItemContextMenu({ ...props }: FileItemContextMenuProps) {
|
||||||
<CM.Separator />
|
<CM.Separator />
|
||||||
|
|
||||||
<CM.Item label="Rename" />
|
<CM.Item label="Rename" />
|
||||||
<CM.Item label="Duplicate" keybind="⌘D" />
|
<CM.Item
|
||||||
|
label="Duplicate"
|
||||||
|
keybind="⌘D"
|
||||||
|
onClick={(e) => {
|
||||||
|
expStore.locationId &&
|
||||||
|
props.item.id &&
|
||||||
|
duplicateFiles.mutate({ location_id: expStore.locationId, path_id: props.item.id });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<CM.Separator />
|
<CM.Separator />
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue