mimick/library/
upload_picker.rs1use std::path::PathBuf;
9use std::sync::Arc;
10
11use gtk::gio;
12use gtk::prelude::*;
13
14use crate::app_context::AppContext;
15use crate::monitor::compute_sha1_chunked;
16use crate::queue_manager::FileTask;
17
18pub fn pick_and_upload(
23 parent: &libadwaita::ApplicationWindow,
24 ctx: Arc<AppContext>,
25 album: Option<(String, String)>,
26) {
27 let dialog = gtk::FileDialog::builder()
28 .title(if album.is_some() {
29 "Upload to album"
30 } else {
31 "Upload to library"
32 })
33 .modal(true)
34 .build();
35
36 let parent = parent.clone();
37 dialog.open_multiple(Some(&parent), gio::Cancellable::NONE, move |result| {
38 let files = match result {
39 Ok(files) => files,
40 Err(err) => {
41 if !err.matches(gtk::DialogError::Dismissed) {
42 log::warn!("Upload picker failed: {}", err);
43 }
44 return;
45 }
46 };
47 let paths: Vec<PathBuf> = (0..files.n_items())
48 .filter_map(|i| files.item(i))
49 .filter_map(|obj| obj.downcast::<gio::File>().ok())
50 .filter_map(|file| file.path())
51 .collect();
52 if paths.is_empty() {
53 return;
54 }
55 spawn_enqueue(ctx.clone(), album.clone(), paths);
56 });
57}
58
59pub(crate) fn spawn_enqueue(
60 ctx: Arc<AppContext>,
61 album: Option<(String, String)>,
62 paths: Vec<PathBuf>,
63) {
64 spawn_enqueue_with_callback(ctx, album, paths, |_, _| {});
65}
66
67pub(crate) fn spawn_enqueue_with_callback<F>(
70 ctx: Arc<AppContext>,
71 album: Option<(String, String)>,
72 paths: Vec<PathBuf>,
73 on_complete: F,
74) where
75 F: FnOnce(usize, usize) + 'static,
76{
77 glib::MainContext::default().spawn_local(async move {
78 let total = paths.len();
79 let mut queued = 0usize;
80 for path in paths {
81 if let Some(task) = build_file_task(&ctx, &album, &path).await
82 && ctx.queue_manager.add_to_queue(task).await
83 {
84 queued += 1;
85 }
86 }
87 let skipped = total - queued;
88 log::info!(
89 "Manual upload: queued {} file(s), skipped {}",
90 queued,
91 skipped
92 );
93 on_complete(queued, skipped);
94 });
95}
96
97async fn build_file_task(
99 ctx: &AppContext,
100 album: &Option<(String, String)>,
101 path: &std::path::Path,
102) -> Option<FileTask> {
103 let path_str = path.to_str().map(str::to_owned)?;
104 let watch_path = path
105 .parent()
106 .and_then(|p| p.to_str())
107 .map(str::to_owned)
108 .unwrap_or_default();
109 let hash_target = path_str.clone();
110 let checksum =
111 match tokio::task::spawn_blocking(move || compute_sha1_chunked(&hash_target)).await {
112 Ok(Ok(c)) => c,
113 Ok(Err(err)) => {
114 log::warn!("Could not checksum upload '{}': {}", path_str, err);
115 return None;
116 }
117 Err(err) => {
118 log::warn!("Checksum task failed for '{}': {}", path_str, err);
119 return None;
120 }
121 };
122 let sidecar_path = if ctx.config.read().data.upload_xmp_sidecars {
123 crate::sidecar::find_sidecar(path).map(|p| p.to_string_lossy().into_owned())
124 } else {
125 None
126 };
127 Some(FileTask {
128 path: path_str,
129 watch_path,
130 checksum,
131 album_id: album.as_ref().map(|(id, _)| id.clone()),
132 album_name: album.as_ref().map(|(_, name)| name.clone()),
133 reassociate_only: false,
134 skip_album: album.is_none(),
135 sidecar_path,
136 })
137}