Preview decode cancellation, cleanup UI routing, null-safe drag-drop
Stop previous streaming decode thread before spawning a new one via
decode_cancel flag. Route ImportMode::Cleaning to cleanup progress
screen. Guard drag-drop import against empty VFS list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4 files changed,
+38 insertions,
-17 deletions
| 454 |
454 |
|
} else {
|
| 455 |
455 |
|
self.screen = AppScreen::VaultSetup;
|
| 456 |
456 |
|
}
|
| 457 |
|
- |
return;
|
| 458 |
457 |
|
}
|
| 459 |
458 |
|
});
|
| 460 |
459 |
|
|
| 563 |
562 |
|
self.vault_setup_path = Some(path);
|
| 564 |
563 |
|
}
|
| 565 |
564 |
|
}
|
| 566 |
|
- |
if self.vault_setup_path.is_some() {
|
| 567 |
|
- |
if ui.small_button("Reset to default").clicked() {
|
| 568 |
|
- |
self.vault_setup_path = None;
|
| 569 |
|
- |
}
|
|
565 |
+ |
if self.vault_setup_path.is_some()
|
|
566 |
+ |
&& ui.small_button("Reset to default").clicked()
|
|
567 |
+ |
{
|
|
568 |
+ |
self.vault_setup_path = None;
|
| 570 |
569 |
|
}
|
| 571 |
570 |
|
});
|
| 572 |
571 |
|
|
| 1020 |
1019 |
|
}
|
| 1021 |
1020 |
|
|
| 1022 |
1021 |
|
if let Some(ref mut browser) = self.browser {
|
| 1023 |
|
- |
for path in dropped {
|
| 1024 |
|
- |
if path.is_dir() {
|
| 1025 |
|
- |
let strategy = audiofiles_browser::import::ImportStrategy::MergeIntoVfs {
|
| 1026 |
|
- |
vfs_id: browser.current_vfs_id(),
|
| 1027 |
|
- |
parent_id: browser.current_dir,
|
| 1028 |
|
- |
};
|
| 1029 |
|
- |
browser.start_folder_import(path, strategy);
|
| 1030 |
|
- |
} else {
|
| 1031 |
|
- |
browser.import_path(&path);
|
|
1022 |
+ |
if let Some(vfs_id) = browser.current_vfs_id() {
|
|
1023 |
+ |
for path in dropped {
|
|
1024 |
+ |
if path.is_dir() {
|
|
1025 |
+ |
let strategy = audiofiles_browser::import::ImportStrategy::MergeIntoVfs {
|
|
1026 |
+ |
vfs_id,
|
|
1027 |
+ |
parent_id: browser.current_dir,
|
|
1028 |
+ |
};
|
|
1029 |
+ |
browser.start_folder_import(path, strategy);
|
|
1030 |
+ |
} else {
|
|
1031 |
+ |
browser.import_path(&path);
|
|
1032 |
+ |
}
|
| 1032 |
1033 |
|
}
|
| 1033 |
1034 |
|
}
|
| 1034 |
1035 |
|
audiofiles_browser::editor::draw_browser(ctx, browser, self.sync_manager.as_ref());
|
| 30 |
30 |
|
}
|
| 31 |
31 |
|
ImportMode::Importing { .. }
|
| 32 |
32 |
|
| ImportMode::Analyzing { .. }
|
| 33 |
|
- |
| ImportMode::Exporting { .. } => {
|
|
33 |
+ |
| ImportMode::Exporting { .. }
|
|
34 |
+ |
| ImportMode::Cleaning { .. } => {
|
| 34 |
35 |
|
if state.poll_workers() {
|
| 35 |
36 |
|
ctx.request_repaint();
|
| 36 |
37 |
|
}
|
| 44 |
45 |
|
ImportMode::Exporting { .. } => {
|
| 45 |
46 |
|
export_screens::draw_export_progress(ctx, state);
|
| 46 |
47 |
|
}
|
|
48 |
+ |
ImportMode::Cleaning { .. } => {
|
|
49 |
+ |
import_screens::draw_cleanup_progress(ctx, state);
|
|
50 |
+ |
}
|
| 47 |
51 |
|
// poll_workers may have transitioned the mode
|
| 48 |
52 |
|
ImportMode::TagFolders { .. } => {
|
| 49 |
53 |
|
import_screens::draw_tag_folders(ctx, state);
|
| 4 |
4 |
|
//! to the cpal audio output thread through [`PreviewPlayback`] behind a `parking_lot::Mutex`.
|
| 5 |
5 |
|
|
| 6 |
6 |
|
use std::path::Path;
|
|
7 |
+ |
use std::sync::atomic::Ordering;
|
| 7 |
8 |
|
|
| 8 |
9 |
|
use tracing::{instrument, warn};
|
| 9 |
10 |
|
|
| 276 |
277 |
|
.make(&codec_params, &DecoderOptions::default())
|
| 277 |
278 |
|
.map_err(|e| PreviewError::Decoder(e.to_string()))?;
|
| 278 |
279 |
|
|
|
280 |
+ |
// Cancel any previous streaming decode thread
|
|
281 |
+ |
shared.decode_cancel.store(true, Ordering::Release);
|
|
282 |
+ |
|
| 279 |
283 |
|
// Set up the initial buffer and playback state (not yet playing)
|
| 280 |
284 |
|
{
|
| 281 |
285 |
|
let capacity = n_frames_estimate.unwrap_or(source_sample_rate as usize * 60) * 2;
|
| 292 |
296 |
|
guard.total_frames_estimate = n_frames_estimate;
|
| 293 |
297 |
|
}
|
| 294 |
298 |
|
|
|
299 |
+ |
// Clear cancel flag for the new decode thread
|
|
300 |
+ |
shared.decode_cancel.store(false, Ordering::Release);
|
|
301 |
+ |
|
| 295 |
302 |
|
let shared = std::sync::Arc::clone(shared);
|
| 296 |
303 |
|
std::thread::spawn(move || {
|
| 297 |
304 |
|
let mut total_frames = 0usize;
|
| 298 |
305 |
|
let mut started = false;
|
| 299 |
306 |
|
|
| 300 |
307 |
|
loop {
|
|
308 |
+ |
// Check cancellation before each packet
|
|
309 |
+ |
if shared.decode_cancel.load(Ordering::Acquire) {
|
|
310 |
+ |
let mut guard = shared.preview.lock();
|
|
311 |
+ |
guard.streaming = false;
|
|
312 |
+ |
return;
|
|
313 |
+ |
}
|
|
314 |
+ |
|
| 301 |
315 |
|
let packet = match format.next_packet() {
|
| 302 |
316 |
|
Ok(p) => p,
|
| 303 |
317 |
|
Err(symphonia::core::errors::Error::IoError(ref e))
|
| 44 |
44 |
|
let current_vfs_id = state.current_vfs_id();
|
| 45 |
45 |
|
let current_dir = state.current_dir;
|
| 46 |
46 |
|
if ui.radio(is_flat, "Flat (all files in current directory)").clicked() && !is_flat {
|
| 47 |
|
- |
if let ImportMode::ConfigureImport { ref mut strategy, .. } = state.import_mode {
|
|
47 |
+ |
if let (ImportMode::ConfigureImport { ref mut strategy, .. }, Some(vfs_id)) =
|
|
48 |
+ |
(&mut state.import_mode, current_vfs_id)
|
|
49 |
+ |
{
|
| 48 |
50 |
|
*strategy = ImportStrategy::Flat {
|
| 49 |
|
- |
vfs_id: current_vfs_id,
|
|
51 |
+ |
vfs_id,
|
| 50 |
52 |
|
parent_id: current_dir,
|
| 51 |
53 |
|
};
|
| 52 |
54 |
|
}
|