Add viewer navigation and file downloads
This commit is contained in:
parent
490c925f62
commit
ef2a0a7915
3 changed files with 221 additions and 43 deletions
108
src/window.rs
108
src/window.rs
|
|
@ -20,7 +20,9 @@ use crate::source::{
|
|||
display_remote_uri, ensure_mounted, enumerate_directory, file_from_user_input, is_within_root,
|
||||
mount_operation, parent_within_root, root_file, validate_profile,
|
||||
};
|
||||
use crate::viewer::{FavoriteControl, MediaViewerWindow, SiblingNavigator, ViewerWindow};
|
||||
use crate::viewer::{
|
||||
FavoriteControl, FileDownloadControl, MediaViewerWindow, SiblingNavigator, ViewerWindow,
|
||||
};
|
||||
use crate::zip_cache::ZipCache;
|
||||
|
||||
pub struct ComicWindow {
|
||||
|
|
@ -607,12 +609,13 @@ impl BrowserController {
|
|||
self.refresh();
|
||||
}
|
||||
EntryKind::ZipArchive | EntryKind::Image | EntryKind::Video => {
|
||||
let current_kind = entry.kind;
|
||||
let siblings = {
|
||||
let state = self.state.borrow();
|
||||
let mut entries = state
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.kind.is_viewable())
|
||||
.filter(|entry| navigable_sibling(current_kind, entry.kind))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
sort_entries(&mut entries, state.sort_field, state.sort_direction);
|
||||
|
|
@ -637,7 +640,19 @@ impl BrowserController {
|
|||
match entry.kind {
|
||||
EntryKind::ZipArchive => self.open_archive(entry, siblings, current, profile),
|
||||
EntryKind::Image | EntryKind::Video => {
|
||||
self.open_media(entry, siblings, current, profile)
|
||||
let media_siblings = Rc::new(
|
||||
siblings
|
||||
.iter()
|
||||
.filter(|sibling| sibling.kind.is_media())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
if let Some(current) = media_siblings
|
||||
.iter()
|
||||
.position(|sibling| sibling.uri == entry.uri)
|
||||
{
|
||||
self.open_media(entry, media_siblings, current, profile);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -650,16 +665,17 @@ impl BrowserController {
|
|||
profile: LocationProfile,
|
||||
) -> SiblingNavigator {
|
||||
let controller = Rc::downgrade(self);
|
||||
Rc::new(move |forward| {
|
||||
let count = siblings.len();
|
||||
let open = Rc::new(move |next: usize| {
|
||||
let Some(controller) = controller.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
let Some(next) = sibling_index(current, siblings.len(), forward) else {
|
||||
if next >= siblings.len() {
|
||||
controller
|
||||
.status_label
|
||||
.set_text("더 이상 이동할 파일이 없습니다.");
|
||||
return false;
|
||||
};
|
||||
}
|
||||
controller.open_viewable(
|
||||
siblings[next].clone(),
|
||||
Rc::clone(&siblings),
|
||||
|
|
@ -667,7 +683,8 @@ impl BrowserController {
|
|||
profile.clone(),
|
||||
);
|
||||
true
|
||||
})
|
||||
});
|
||||
SiblingNavigator::new(current, count, open)
|
||||
}
|
||||
|
||||
fn open_archive(
|
||||
|
|
@ -682,6 +699,7 @@ impl BrowserController {
|
|||
let controller = Rc::clone(self);
|
||||
let navigator = self.sibling_navigator(siblings, current, profile.clone());
|
||||
let favorite_control = self.favorite_control(&entry, &profile);
|
||||
let download_control = self.download_control(&entry);
|
||||
self.zip_cache.prepare(
|
||||
entry,
|
||||
move |current, total| {
|
||||
|
|
@ -701,6 +719,7 @@ impl BrowserController {
|
|||
controller.config.borrow().fast_image_loading,
|
||||
Some(navigator),
|
||||
favorite_control,
|
||||
download_control,
|
||||
);
|
||||
}
|
||||
Err(error) => controller.status_label.set_text(&format!("{error:#}")),
|
||||
|
|
@ -722,6 +741,7 @@ impl BrowserController {
|
|||
let controller = Rc::clone(self);
|
||||
let navigator = self.sibling_navigator(siblings, current, profile.clone());
|
||||
let favorite_control = self.favorite_control(&entry, &profile);
|
||||
let download_control = self.download_control(&entry);
|
||||
self.zip_cache.prepare_media(
|
||||
entry,
|
||||
move |current, total| {
|
||||
|
|
@ -740,6 +760,7 @@ impl BrowserController {
|
|||
kind,
|
||||
Some(navigator),
|
||||
favorite_control,
|
||||
download_control,
|
||||
);
|
||||
}
|
||||
Err(error) => controller.status_label.set_text(&format!("{error:#}")),
|
||||
|
|
@ -761,8 +782,7 @@ impl BrowserController {
|
|||
return;
|
||||
};
|
||||
let can_favorite = entry.kind == EntryKind::Directory || entry.kind.is_viewable();
|
||||
let can_download =
|
||||
entry.kind != EntryKind::Directory && !gio::File::for_uri(&entry.uri).is_native();
|
||||
let can_download = entry.kind != EntryKind::Directory;
|
||||
if !can_favorite && !can_download {
|
||||
return;
|
||||
}
|
||||
|
|
@ -792,13 +812,17 @@ impl BrowserController {
|
|||
});
|
||||
}
|
||||
if can_download {
|
||||
let download_button = gtk::Button::with_label("다운로드...");
|
||||
let download_button = gtk::Button::with_label("파일 다운로드...");
|
||||
actions.append(&download_button);
|
||||
let controller = Rc::clone(self);
|
||||
let popover_to_close = popover.clone();
|
||||
download_button.connect_clicked(move |_| {
|
||||
popover_to_close.popdown();
|
||||
controller.choose_download_destination(entry.clone());
|
||||
controller.choose_download_destination(
|
||||
entry.clone(),
|
||||
controller.window.clone().upcast(),
|
||||
controller.status_label.clone(),
|
||||
);
|
||||
});
|
||||
}
|
||||
popover.set_child(Some(&actions));
|
||||
|
|
@ -832,6 +856,19 @@ impl BrowserController {
|
|||
}
|
||||
}
|
||||
|
||||
fn download_control(self: &Rc<Self>, entry: &BrowserEntry) -> FileDownloadControl {
|
||||
let entry = entry.clone();
|
||||
let controller = Rc::downgrade(self);
|
||||
FileDownloadControl {
|
||||
download: Rc::new(move |parent, status_label| {
|
||||
let Some(controller) = controller.upgrade() else {
|
||||
return;
|
||||
};
|
||||
controller.choose_download_destination(entry.clone(), parent, status_label);
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_favorite(&self, entry: &BrowserEntry, profile: &LocationProfile) -> bool {
|
||||
let mut updated = self.config.borrow().clone();
|
||||
let was_active = self.is_favorite(&entry.uri);
|
||||
|
|
@ -851,13 +888,17 @@ impl BrowserController {
|
|||
active
|
||||
}
|
||||
|
||||
fn choose_download_destination(self: &Rc<Self>, entry: BrowserEntry) {
|
||||
fn choose_download_destination(
|
||||
self: &Rc<Self>,
|
||||
entry: BrowserEntry,
|
||||
parent: gtk::Window,
|
||||
status_label: gtk::Label,
|
||||
) {
|
||||
let dialog = gtk::FileDialog::builder()
|
||||
.title("다운로드 위치 선택")
|
||||
.initial_name(&entry.name)
|
||||
.build();
|
||||
let controller = Rc::downgrade(self);
|
||||
let parent = self.window.clone();
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let result = dialog.save_future(Some(&parent)).await;
|
||||
let Some(controller) = controller.upgrade() else {
|
||||
|
|
@ -865,30 +906,27 @@ impl BrowserController {
|
|||
};
|
||||
match result {
|
||||
Ok(destination) if destination.is_native() => {
|
||||
controller.download_remote_file(entry, destination);
|
||||
controller.download_file(entry, destination, status_label);
|
||||
}
|
||||
Ok(_) => controller
|
||||
.status_label
|
||||
.set_text("로컬 파일 시스템 위치를 선택해 주세요."),
|
||||
Ok(_) => status_label.set_text("로컬 파일 시스템 위치를 선택해 주세요."),
|
||||
Err(error) if error.matches(gio::IOErrorEnum::Cancelled) => {}
|
||||
Err(error) => controller
|
||||
.status_label
|
||||
.set_text(&format!("저장 위치를 선택할 수 없습니다: {error}")),
|
||||
Err(error) => {
|
||||
status_label.set_text(&format!("저장 위치를 선택할 수 없습니다: {error}"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn download_remote_file(&self, entry: BrowserEntry, destination: gio::File) {
|
||||
fn download_file(&self, entry: BrowserEntry, destination: gio::File, status_label: gtk::Label) {
|
||||
let Some(final_path) = destination.path() else {
|
||||
self.status_label
|
||||
.set_text("로컬 파일 시스템 위치를 선택해 주세요.");
|
||||
status_label.set_text("로컬 파일 시스템 위치를 선택해 주세요.");
|
||||
return;
|
||||
};
|
||||
let part_path = download_part_path(&final_path);
|
||||
let source = gio::File::for_uri(&entry.uri);
|
||||
let part_file = gio::File::for_path(&part_path);
|
||||
let progress_label = self.status_label.clone();
|
||||
let result_label = self.status_label.clone();
|
||||
let progress_label = status_label.clone();
|
||||
let result_label = status_label;
|
||||
let expected_size = entry.size;
|
||||
source.copy_async(
|
||||
&part_file,
|
||||
|
|
@ -1199,11 +1237,11 @@ fn find_prefix_index(entries: &[BrowserEntry], query: &str, start: usize) -> Opt
|
|||
.find(|index| entries[*index].name.to_lowercase().starts_with(&query))
|
||||
}
|
||||
|
||||
fn sibling_index(current: usize, count: usize, forward: bool) -> Option<usize> {
|
||||
if forward {
|
||||
(current + 1 < count).then_some(current + 1)
|
||||
} else {
|
||||
current.checked_sub(1)
|
||||
fn navigable_sibling(current: EntryKind, candidate: EntryKind) -> bool {
|
||||
match current {
|
||||
EntryKind::ZipArchive => candidate.is_viewable(),
|
||||
EntryKind::Image | EntryKind::Video => candidate.is_media(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1270,11 +1308,11 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn sibling_navigation_stops_at_boundaries() {
|
||||
assert_eq!(sibling_index(1, 3, false), Some(0));
|
||||
assert_eq!(sibling_index(1, 3, true), Some(2));
|
||||
assert_eq!(sibling_index(0, 3, false), None);
|
||||
assert_eq!(sibling_index(2, 3, true), None);
|
||||
fn media_navigation_excludes_zip_archives() {
|
||||
assert!(navigable_sibling(EntryKind::Image, EntryKind::Video));
|
||||
assert!(navigable_sibling(EntryKind::Video, EntryKind::Image));
|
||||
assert!(!navigable_sibling(EntryKind::Image, EntryKind::ZipArchive));
|
||||
assert!(!navigable_sibling(EntryKind::Video, EntryKind::OtherFile));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue