[ENG-325] Local paths for the context menu (#507)

* use `path_id` instead of `object_id` for finding local file paths

* fix bug

* make automounting better, UX-wise

* find file_paths correctly

* use `obj_id` for fetching metadata/preview media

* stop using `expect()` during jobs
This commit is contained in:
jake 2023-01-06 16:26:25 +00:00 committed by GitHub
parent de3a979127
commit 746ac76803
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 84 deletions

View file

@ -221,6 +221,8 @@ pub(crate) fn mount() -> RouterBuilder {
Protected::new(args.secret_key),
)?;
invalidate_query!(library, "keys.hasMasterPassword");
let automount = library
.db
.key()
@ -237,9 +239,9 @@ pub(crate) fn mount() -> RouterBuilder {
"Error deserializing UUID from string".into(),
)
})?)?;
}
invalidate_query!(library, "keys.hasMasterPassword");
invalidate_query!(library, "keys.listMounted");
}
Ok(())
})

View file

@ -16,7 +16,7 @@ pub struct FileDecryptorJobState {}
#[derive(Serialize, Deserialize, Debug, Type, Hash)]
pub struct FileDecryptorJobInit {
pub location_id: i32,
pub object_id: i32,
pub path_id: i32,
pub output_path: Option<PathBuf>,
pub password: Option<String>, // if this is set, we can assume the user chose password decryption
pub save_to_library: Option<bool>,
@ -50,24 +50,33 @@ impl StatefulJob for FileDecryptorJob {
.find_unique(location::id::equals(state.init.location_id))
.exec()
.await?
.expect("critical error: can't find location");
let root_path = location
.local_path
.as_ref()
.map(PathBuf::from)
.expect("critical error: issue getting local path as pathbuf");
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "can't find location".to_string(),
})?;
let root_path =
location
.local_path
.as_ref()
.map(PathBuf::from)
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "can't get path as pathbuf".to_string(),
})?;
let item = ctx
.library_ctx
.db
.file_path()
.find_first(vec![file_path::object_id::equals(Some(
state.init.object_id,
))])
.find_unique(file_path::location_id_id(
state.init.location_id,
state.init.path_id,
))
.exec()
.await?
.expect("critical error: can't find object");
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "can't find file_path with location id and path id".to_string(),
})?;
let obj_name = item.materialized_path;

View file

@ -29,7 +29,7 @@ pub struct FileEncryptorJobState {}
#[derive(Serialize, Deserialize, Type, Hash)]
pub struct FileEncryptorJobInit {
pub location_id: i32,
pub object_id: i32,
pub path_id: i32,
pub key_uuid: uuid::Uuid,
pub algorithm: Algorithm,
pub metadata: bool,
@ -42,11 +42,12 @@ pub struct FileEncryptorJobStep {
obj_name: String,
obj_path: PathBuf,
obj_type: ObjectType,
obj_id: Option<i32>,
}
#[derive(Serialize, Deserialize)]
pub struct Metadata {
pub object_id: i32,
pub path_id: i32,
pub name: String,
pub hidden: bool,
pub favourite: bool,
@ -78,24 +79,35 @@ impl StatefulJob for FileEncryptorJob {
.find_unique(location::id::equals(state.init.location_id))
.exec()
.await?
.expect("critical error: can't find location");
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "can't find location".to_string(),
})?;
let root_path = location
.local_path
.as_ref()
.map(PathBuf::from)
.expect("critical error: issue getting local path as pathbuf");
let root_path =
location
.local_path
.as_ref()
.map(PathBuf::from)
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "can't get path as pathbuf".to_string(),
})?;
let item = ctx
.library_ctx
.db
.file_path()
.find_first(vec![file_path::object_id::equals(Some(
state.init.object_id,
))])
.find_unique(file_path::location_id_id(
state.init.location_id,
state.init.path_id,
))
.exec()
.await?
.expect("critical error: can't find object");
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "can't find file_path with location id and path id".to_string(),
})?;
let obj_name = item.materialized_path;
@ -114,6 +126,7 @@ impl StatefulJob for FileEncryptorJob {
obj_name,
obj_path,
obj_type,
obj_id: item.object_id,
});
ctx.progress(vec![JobReportUpdate::TaskCount(state.steps.len())]);
@ -149,7 +162,10 @@ impl StatefulJob for FileEncryptorJob {
let mut path = step.obj_path.clone();
let extension = if let Some(ext) = path.extension() {
ext.to_str()
.expect("critical error: path is not valid utf-8")
.ok_or(JobError::EarlyFinish {
name: self.name().to_string(),
reason: "path isn't valid UTF-8".to_string(),
})?
.to_string() + ".sdenc"
} else {
"sdenc".to_string()
@ -179,41 +195,49 @@ impl StatefulJob for FileEncryptorJob {
if state.init.metadata || state.init.preview_media {
// if any are requested, we can make the query as it'll be used at least once
let object = ctx
.library_ctx
.db
.object()
.find_unique(object::id::equals(state.init.object_id))
.exec()
.await?
.expect("critical error: can't get object info");
if let Some(obj_id) = step.obj_id {
let object = ctx
.library_ctx
.db
.object()
.find_unique(object::id::equals(obj_id))
.exec()
.await?
.ok_or(JobError::JobDataNotFound(String::from(
"can't find information about the object",
)))?;
if state.init.metadata {
let metadata = Metadata {
object_id: state.init.object_id,
name: step.obj_name.clone(),
hidden: object.hidden,
favourite: object.favorite,
important: object.important,
note: object.note,
date_created: object.date_created,
date_modified: object.date_modified,
};
if state.init.metadata {
let metadata = Metadata {
path_id: state.init.path_id,
name: step.obj_name.clone(),
hidden: object.hidden,
favourite: object.favorite,
important: object.important,
note: object.note,
date_created: object.date_created,
date_modified: object.date_modified,
};
header.add_metadata(
LATEST_METADATA,
state.init.algorithm,
master_key.clone(),
&metadata,
)?;
}
header.add_metadata(
LATEST_METADATA,
state.init.algorithm,
master_key.clone(),
&metadata,
)?;
}
if state.init.preview_media
&& (object.has_thumbnail
|| object.has_video_preview || object.has_thumbstrip)
{
// need to find the preview media, read it and return it as Some()
// not currently able to do this as thumnails don't generate
if state.init.preview_media
&& (object.has_thumbnail
|| object.has_video_preview || object.has_thumbstrip)
{
// need to find the preview media, read it and return it as Some()
// not currently able to do this as thumnails don't generate
}
} else {
warn!(
"skipping metadata/preview media inclusion, no associated object found"
)
}
}

View file

@ -95,9 +95,9 @@ export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerI
export type ExplorerItem = { type: "Path" } & FilePathWithObject | { type: "Object" } & ObjectWithFilePaths
export interface FileDecryptorJobInit { location_id: number, object_id: number, output_path: string | null, password: string | null, save_to_library: boolean | null }
export interface FileDecryptorJobInit { location_id: number, path_id: number, output_path: string | null, password: string | null, save_to_library: boolean | null }
export interface FileEncryptorJobInit { location_id: number, object_id: number, key_uuid: string, algorithm: Algorithm, metadata: boolean, preview_media: boolean, output_path: string | null }
export interface FileEncryptorJobInit { location_id: number, path_id: number, key_uuid: string, algorithm: Algorithm, metadata: boolean, preview_media: boolean, output_path: string | null }
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 }

View file

@ -12,13 +12,13 @@ interface DecryptDialogProps {
open: boolean;
setOpen: (isShowing: boolean) => void;
location_id: number | null;
object_id: number | null;
path_id: number | undefined;
setAlertDialogData: (data: GenericAlertDialogProps) => void;
}
export const DecryptFileDialog = (props: DecryptDialogProps) => {
const platform = usePlatform();
const { location_id, object_id } = props;
const { location_id, path_id } = props;
const decryptFile = useLibraryMutation('files.decryptFiles');
const [outputPath, setOutputpath] = useState('');
const [password, setPassword] = useState('');
@ -59,11 +59,11 @@ export const DecryptFileDialog = (props: DecryptDialogProps) => {
props.setOpen(false);
location_id &&
object_id &&
path_id &&
decryptFile.mutate(
{
location_id,
object_id,
path_id,
output_path: output,
password: pw,
save_to_library: save

View file

@ -15,13 +15,13 @@ interface EncryptDialogProps {
open: boolean;
setOpen: (isShowing: boolean) => void;
location_id: number | null;
object_id: number | null;
path_id: number | undefined;
setAlertDialogData: (data: GenericAlertDialogProps) => void;
}
export const EncryptFileDialog = (props: EncryptDialogProps) => {
const platform = usePlatform();
const { location_id, object_id } = props;
const { location_id, path_id } = props;
const keys = useLibraryQuery(['keys.list']);
const mountedUuids = useLibraryQuery(['keys.listMounted'], {
onSuccess: (data) => {
@ -64,13 +64,13 @@ export const EncryptFileDialog = (props: EncryptDialogProps) => {
props.setOpen(false);
location_id &&
object_id &&
path_id &&
encryptFile.mutate(
{
algorithm,
key_uuid: key,
location_id,
object_id,
path_id,
metadata,
preview_media: previewMedia,
output_path: output

View file

@ -98,20 +98,24 @@ export default function Explorer(props: Props) {
value={alertDialogData.value}
inputBox={alertDialogData.inputBox}
/>
<EncryptFileDialog
location_id={expStore.locationId}
object_id={expStore.contextMenuObjectId}
open={showEncryptDialog}
setOpen={setShowEncryptDialog}
setAlertDialogData={setAlertDialogData}
/>
<DecryptFileDialog
location_id={expStore.locationId}
object_id={expStore.contextMenuObjectId}
open={showDecryptDialog}
setOpen={setShowDecryptDialog}
setAlertDialogData={setAlertDialogData}
/>
{props.data && props.data.items[expStore.selectedRowIndex] && (
<EncryptFileDialog
location_id={expStore.locationId}
path_id={props.data?.items[expStore.selectedRowIndex].id}
open={showEncryptDialog}
setOpen={setShowEncryptDialog}
setAlertDialogData={setAlertDialogData}
/>
)}
{props.data && props.data.items[expStore.selectedRowIndex] && (
<DecryptFileDialog
location_id={expStore.locationId}
path_id={props.data?.items[expStore.selectedRowIndex].id}
open={showDecryptDialog}
setOpen={setShowDecryptDialog}
setAlertDialogData={setAlertDialogData}
/>
)}
</>
);
}