Reuse viewer windows and organize favorites
This commit is contained in:
parent
ef2a0a7915
commit
bb73c1abea
3 changed files with 587 additions and 163 deletions
291
src/window.rs
291
src/window.rs
|
|
@ -14,14 +14,15 @@ use gtk::prelude::*;
|
|||
use crate::browser::{
|
||||
BrowserEntry, EntryKind, SortDirection, SortField, classify_file, sort_entries,
|
||||
};
|
||||
use crate::config::{AppConfig, FavoriteEntry, LocationKind, LocationProfile};
|
||||
use crate::config::{AppConfig, FavoriteEntry, LocationKind, LocationProfile, ViewerMode};
|
||||
use crate::database::Database;
|
||||
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, FileDownloadControl, MediaViewerWindow, SiblingNavigator, ViewerWindow,
|
||||
FavoriteControl, FileDownloadControl, FileViewerControls, MediaViewerWindow, SiblingNavigator,
|
||||
ViewModeControl, ViewerWindow,
|
||||
};
|
||||
use crate::zip_cache::ZipCache;
|
||||
|
||||
|
|
@ -70,12 +71,6 @@ impl ComicWindow {
|
|||
locations.append(&edit_location_button);
|
||||
locations.append(&delete_location_button);
|
||||
|
||||
let favorites = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
let favorite_dropdown = gtk::DropDown::from_strings(&["즐겨찾기 선택"]);
|
||||
favorite_dropdown.set_hexpand(true);
|
||||
favorites.append(>k::Label::new(Some("즐겨찾기")));
|
||||
favorites.append(&favorite_dropdown);
|
||||
|
||||
let navigation = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
let up_button = gtk::Button::builder()
|
||||
.icon_name("go-up-symbolic")
|
||||
|
|
@ -119,7 +114,6 @@ impl ComicWindow {
|
|||
let status_label = gtk::Label::builder().xalign(0.0).build();
|
||||
|
||||
root.append(&locations);
|
||||
root.append(&favorites);
|
||||
root.append(&navigation);
|
||||
root.append(&controls);
|
||||
root.append(&scrolled);
|
||||
|
|
@ -129,7 +123,6 @@ impl ComicWindow {
|
|||
let controller = Rc::new(BrowserController {
|
||||
window,
|
||||
location_dropdown,
|
||||
favorite_dropdown,
|
||||
edit_location_button,
|
||||
delete_location_button,
|
||||
path_entry,
|
||||
|
|
@ -141,6 +134,7 @@ impl ComicWindow {
|
|||
state: RefCell::new(BrowserState {
|
||||
profile_index: 0,
|
||||
favorite_profile: None,
|
||||
browsing_favorites: false,
|
||||
root_uri: root_uri.clone(),
|
||||
current_uri: root_uri,
|
||||
entries: Vec::new(),
|
||||
|
|
@ -158,7 +152,6 @@ impl ComicWindow {
|
|||
});
|
||||
|
||||
controller.rebuild_profiles();
|
||||
controller.rebuild_favorites();
|
||||
controller.connect_signals(
|
||||
open_button,
|
||||
refresh_button,
|
||||
|
|
@ -178,6 +171,7 @@ impl ComicWindow {
|
|||
struct BrowserState {
|
||||
profile_index: usize,
|
||||
favorite_profile: Option<LocationProfile>,
|
||||
browsing_favorites: bool,
|
||||
root_uri: String,
|
||||
current_uri: String,
|
||||
entries: Vec<BrowserEntry>,
|
||||
|
|
@ -192,7 +186,6 @@ struct BrowserState {
|
|||
struct BrowserController {
|
||||
window: gtk::ApplicationWindow,
|
||||
location_dropdown: gtk::DropDown,
|
||||
favorite_dropdown: gtk::DropDown,
|
||||
edit_location_button: gtk::Button,
|
||||
delete_location_button: gtk::Button,
|
||||
path_entry: gtk::Entry,
|
||||
|
|
@ -244,9 +237,6 @@ impl BrowserController {
|
|||
self.location_dropdown
|
||||
.connect_selected_notify(move |_| controller.select_profile());
|
||||
let controller = Rc::clone(self);
|
||||
self.favorite_dropdown
|
||||
.connect_selected_notify(move |_| controller.select_favorite());
|
||||
let controller = Rc::clone(self);
|
||||
self.search_entry
|
||||
.connect_search_changed(move |_| controller.render());
|
||||
let controller = Rc::clone(self);
|
||||
|
|
@ -261,8 +251,8 @@ impl BrowserController {
|
|||
|
||||
let context_click = gtk::GestureClick::builder().button(3).build();
|
||||
let controller = Rc::clone(self);
|
||||
context_click.connect_pressed(move |_, _, _, y| {
|
||||
controller.show_context_menu(y as i32);
|
||||
context_click.connect_pressed(move |_, _, x, y| {
|
||||
controller.show_context_menu(x, y);
|
||||
});
|
||||
self.list_box.add_controller(context_click);
|
||||
|
||||
|
|
@ -296,38 +286,26 @@ impl BrowserController {
|
|||
|
||||
fn rebuild_profiles(&self) {
|
||||
let profiles = self.profiles();
|
||||
let names: Vec<_> = profiles
|
||||
let mut names: Vec<_> = profiles
|
||||
.iter()
|
||||
.map(|profile| profile.name.as_str())
|
||||
.map(|profile| profile.name.clone())
|
||||
.collect();
|
||||
names.push("즐겨찾기".into());
|
||||
let names: Vec<_> = names.iter().map(String::as_str).collect();
|
||||
self.location_dropdown
|
||||
.set_model(Some(>k::StringList::new(&names)));
|
||||
}
|
||||
|
||||
fn rebuild_favorites(&self) {
|
||||
let config = self.config.borrow();
|
||||
let mut names = Vec::with_capacity(config.favorites.len() + 1);
|
||||
names.push("즐겨찾기 선택".to_owned());
|
||||
names.extend(config.favorites.iter().map(|favorite| {
|
||||
let marker = if favorite.is_directory {
|
||||
"폴더"
|
||||
} else {
|
||||
"파일"
|
||||
};
|
||||
format!("[{marker}] {}", favorite.name)
|
||||
}));
|
||||
let names: Vec<_> = names.iter().map(String::as_str).collect();
|
||||
self.favorite_dropdown
|
||||
.set_model(Some(>k::StringList::new(&names)));
|
||||
self.favorite_dropdown.set_selected(0);
|
||||
}
|
||||
|
||||
fn select_profile(self: &Rc<Self>) {
|
||||
let profile_index = self.location_dropdown.selected() as usize;
|
||||
let editable = profile_index > 0 && profile_index <= self.config.borrow().locations.len();
|
||||
let profiles = self.profiles();
|
||||
if profile_index == profiles.len() {
|
||||
self.show_favorites();
|
||||
return;
|
||||
}
|
||||
let editable = profile_index > 0 && profile_index < profiles.len();
|
||||
self.edit_location_button.set_sensitive(editable);
|
||||
self.delete_location_button.set_sensitive(editable);
|
||||
let profiles = self.profiles();
|
||||
let Some(profile) = profiles.get(profile_index) else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -337,9 +315,11 @@ impl BrowserController {
|
|||
let mut state = self.state.borrow_mut();
|
||||
state.profile_index = profile_index;
|
||||
state.favorite_profile = None;
|
||||
state.browsing_favorites = false;
|
||||
state.root_uri = root_uri.clone();
|
||||
state.current_uri = root_uri;
|
||||
drop(state);
|
||||
self.path_entry.set_editable(true);
|
||||
self.search_entry.set_text("");
|
||||
self.refresh();
|
||||
}
|
||||
|
|
@ -347,15 +327,28 @@ impl BrowserController {
|
|||
}
|
||||
}
|
||||
|
||||
fn select_favorite(self: &Rc<Self>) {
|
||||
let Some(index) = (self.favorite_dropdown.selected() as usize).checked_sub(1) else {
|
||||
return;
|
||||
};
|
||||
let Some(favorite) = self.config.borrow().favorites.get(index).cloned() else {
|
||||
self.favorite_dropdown.set_selected(0);
|
||||
return;
|
||||
};
|
||||
self.favorite_dropdown.set_selected(0);
|
||||
fn show_favorites(&self) {
|
||||
self.edit_location_button.set_sensitive(false);
|
||||
self.delete_location_button.set_sensitive(false);
|
||||
self.path_entry.set_text("즐겨찾기");
|
||||
self.path_entry.set_editable(false);
|
||||
self.search_entry.set_text("");
|
||||
let entries = self
|
||||
.config
|
||||
.borrow()
|
||||
.favorites
|
||||
.iter()
|
||||
.map(favorite_browser_entry)
|
||||
.collect();
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.browsing_favorites = true;
|
||||
state.favorite_profile = None;
|
||||
state.entries = entries;
|
||||
drop(state);
|
||||
self.render();
|
||||
}
|
||||
|
||||
fn open_favorite(self: &Rc<Self>, favorite: FavoriteEntry) {
|
||||
let Ok(root) = root_file(&favorite.profile) else {
|
||||
self.status_label
|
||||
.set_text("즐겨찾기의 저장된 위치가 올바르지 않습니다.");
|
||||
|
|
@ -374,6 +367,7 @@ impl BrowserController {
|
|||
self.delete_location_button.set_sensitive(false);
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.favorite_profile = Some(favorite.profile.clone());
|
||||
state.browsing_favorites = false;
|
||||
state.root_uri = root.uri().into();
|
||||
state.current_uri = favorite.uri;
|
||||
drop(state);
|
||||
|
|
@ -412,11 +406,15 @@ impl BrowserController {
|
|||
etag: None,
|
||||
};
|
||||
let siblings = Rc::new(vec![entry.clone()]);
|
||||
controller.open_viewable(entry, siblings, 0, favorite.profile);
|
||||
controller.open_viewable(entry, siblings, 0, favorite.profile, None);
|
||||
});
|
||||
}
|
||||
|
||||
fn open_entered_path(self: &Rc<Self>) {
|
||||
let browsing_favorites = self.state.borrow().browsing_favorites;
|
||||
if browsing_favorites {
|
||||
return;
|
||||
}
|
||||
let profile = self.current_profile();
|
||||
let entered = self.path_entry.text();
|
||||
let file = file_from_user_input(profile.kind, entered.as_str());
|
||||
|
|
@ -431,6 +429,10 @@ impl BrowserController {
|
|||
}
|
||||
|
||||
fn go_up(self: &Rc<Self>) {
|
||||
let browsing_favorites = self.state.borrow().browsing_favorites;
|
||||
if browsing_favorites {
|
||||
return;
|
||||
}
|
||||
let state = self.state.borrow();
|
||||
let current = gio::File::for_uri(&state.current_uri);
|
||||
let root = gio::File::for_uri(&state.root_uri);
|
||||
|
|
@ -442,6 +444,11 @@ impl BrowserController {
|
|||
}
|
||||
|
||||
fn refresh(self: &Rc<Self>) {
|
||||
let browsing_favorites = self.state.borrow().browsing_favorites;
|
||||
if browsing_favorites {
|
||||
self.show_favorites();
|
||||
return;
|
||||
}
|
||||
let (profile, uri, generation) = {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.generation = state.generation.wrapping_add(1);
|
||||
|
|
@ -602,6 +609,20 @@ impl BrowserController {
|
|||
else {
|
||||
return;
|
||||
};
|
||||
let browsing_favorites = self.state.borrow().browsing_favorites;
|
||||
if browsing_favorites {
|
||||
let favorite = self
|
||||
.config
|
||||
.borrow()
|
||||
.favorites
|
||||
.iter()
|
||||
.find(|favorite| favorite.uri == entry.uri)
|
||||
.cloned();
|
||||
if let Some(favorite) = favorite {
|
||||
self.open_favorite(favorite);
|
||||
}
|
||||
return;
|
||||
}
|
||||
match entry.kind {
|
||||
EntryKind::Directory => {
|
||||
self.state.borrow_mut().current_uri = entry.uri;
|
||||
|
|
@ -623,7 +644,7 @@ impl BrowserController {
|
|||
};
|
||||
let siblings = Rc::new(siblings);
|
||||
if let Some(current) = siblings.iter().position(|item| item.uri == entry.uri) {
|
||||
self.open_viewable(entry, siblings, current, self.current_profile());
|
||||
self.open_viewable(entry, siblings, current, self.current_profile(), None);
|
||||
}
|
||||
}
|
||||
EntryKind::OtherFile => {}
|
||||
|
|
@ -636,9 +657,12 @@ impl BrowserController {
|
|||
siblings: Rc<Vec<BrowserEntry>>,
|
||||
current: usize,
|
||||
profile: LocationProfile,
|
||||
reuse_window: Option<gtk::ApplicationWindow>,
|
||||
) {
|
||||
match entry.kind {
|
||||
EntryKind::ZipArchive => self.open_archive(entry, siblings, current, profile),
|
||||
EntryKind::ZipArchive => {
|
||||
self.open_archive(entry, siblings, current, profile, reuse_window)
|
||||
}
|
||||
EntryKind::Image | EntryKind::Video => {
|
||||
let media_siblings = Rc::new(
|
||||
siblings
|
||||
|
|
@ -651,7 +675,7 @@ impl BrowserController {
|
|||
.iter()
|
||||
.position(|sibling| sibling.uri == entry.uri)
|
||||
{
|
||||
self.open_media(entry, media_siblings, current, profile);
|
||||
self.open_media(entry, media_siblings, current, profile, reuse_window);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -666,7 +690,7 @@ impl BrowserController {
|
|||
) -> SiblingNavigator {
|
||||
let controller = Rc::downgrade(self);
|
||||
let count = siblings.len();
|
||||
let open = Rc::new(move |next: usize| {
|
||||
let open = Rc::new(move |next: usize, reuse_window: gtk::ApplicationWindow| {
|
||||
let Some(controller) = controller.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -681,6 +705,7 @@ impl BrowserController {
|
|||
Rc::clone(&siblings),
|
||||
next,
|
||||
profile.clone(),
|
||||
Some(reuse_window),
|
||||
);
|
||||
true
|
||||
});
|
||||
|
|
@ -693,13 +718,19 @@ impl BrowserController {
|
|||
siblings: Rc<Vec<BrowserEntry>>,
|
||||
current: usize,
|
||||
profile: LocationProfile,
|
||||
reuse_window: Option<gtk::ApplicationWindow>,
|
||||
) {
|
||||
self.status_label.set_text("ZIP 파일 다운로드 준비 중...");
|
||||
let progress_label = self.status_label.clone();
|
||||
let progress_label = reuse_window.as_ref().map_or_else(
|
||||
|| self.status_label.clone(),
|
||||
|window| show_viewer_loading(window, &entry.name, "ZIP 파일을 준비하는 중..."),
|
||||
);
|
||||
let result_label = progress_label.clone();
|
||||
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);
|
||||
let view_mode_control = self.view_mode_control();
|
||||
self.zip_cache.prepare(
|
||||
entry,
|
||||
move |current, total| {
|
||||
|
|
@ -709,6 +740,7 @@ impl BrowserController {
|
|||
move |result| match result {
|
||||
Ok(source) => {
|
||||
controller.status_label.set_text("다운로드 완료");
|
||||
result_label.set_text("ZIP 파일을 여는 중...");
|
||||
let Some(application) = controller.window.application() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -717,12 +749,20 @@ impl BrowserController {
|
|||
source,
|
||||
Rc::clone(&controller.database),
|
||||
controller.config.borrow().fast_image_loading,
|
||||
Some(navigator),
|
||||
favorite_control,
|
||||
download_control,
|
||||
view_mode_control,
|
||||
FileViewerControls {
|
||||
sibling_navigator: Some(navigator),
|
||||
favorite_control,
|
||||
download_control,
|
||||
reuse_window,
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(error) => controller.status_label.set_text(&format!("{error:#}")),
|
||||
Err(error) => {
|
||||
let message = format!("{error:#}");
|
||||
controller.status_label.set_text(&message);
|
||||
result_label.set_text(&message);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -733,11 +773,16 @@ impl BrowserController {
|
|||
siblings: Rc<Vec<BrowserEntry>>,
|
||||
current: usize,
|
||||
profile: LocationProfile,
|
||||
reuse_window: Option<gtk::ApplicationWindow>,
|
||||
) {
|
||||
let kind = entry.kind;
|
||||
self.status_label
|
||||
.set_text("미디어 파일 다운로드 준비 중...");
|
||||
let progress_label = self.status_label.clone();
|
||||
let progress_label = reuse_window.as_ref().map_or_else(
|
||||
|| self.status_label.clone(),
|
||||
|window| show_viewer_loading(window, &entry.name, "미디어 파일을 준비하는 중..."),
|
||||
);
|
||||
let result_label = progress_label.clone();
|
||||
let controller = Rc::clone(self);
|
||||
let navigator = self.sibling_navigator(siblings, current, profile.clone());
|
||||
let favorite_control = self.favorite_control(&entry, &profile);
|
||||
|
|
@ -751,6 +796,7 @@ impl BrowserController {
|
|||
move |result| match result {
|
||||
Ok(source) => {
|
||||
controller.status_label.set_text("다운로드 완료");
|
||||
result_label.set_text("미디어 파일을 여는 중...");
|
||||
let Some(application) = controller.window.application() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -758,18 +804,37 @@ impl BrowserController {
|
|||
&application,
|
||||
source,
|
||||
kind,
|
||||
Some(navigator),
|
||||
favorite_control,
|
||||
download_control,
|
||||
FileViewerControls {
|
||||
sibling_navigator: Some(navigator),
|
||||
favorite_control,
|
||||
download_control,
|
||||
reuse_window,
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(error) => controller.status_label.set_text(&format!("{error:#}")),
|
||||
Err(error) => {
|
||||
let message = format!("{error:#}");
|
||||
controller.status_label.set_text(&message);
|
||||
result_label.set_text(&message);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn show_context_menu(self: &Rc<Self>, y: i32) {
|
||||
let Some(row) = self.list_box.row_at_y(y) else {
|
||||
fn show_context_menu(self: &Rc<Self>, x: f64, y: f64) {
|
||||
let Some(widget) = self.list_box.pick(x, y, gtk::PickFlags::DEFAULT) else {
|
||||
return;
|
||||
};
|
||||
let Some(row) = widget
|
||||
.clone()
|
||||
.downcast::<gtk::ListBoxRow>()
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
widget
|
||||
.ancestor(gtk::ListBoxRow::static_type())
|
||||
.and_then(|widget| widget.downcast::<gtk::ListBoxRow>().ok())
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(entry) = self
|
||||
|
|
@ -869,6 +934,32 @@ impl BrowserController {
|
|||
}
|
||||
}
|
||||
|
||||
fn view_mode_control(self: &Rc<Self>) -> ViewModeControl {
|
||||
let selected = self.config.borrow().viewer_mode.dropdown_index();
|
||||
let controller = Rc::downgrade(self);
|
||||
ViewModeControl {
|
||||
selected,
|
||||
change: Rc::new(move |index| {
|
||||
let Some(controller) = controller.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let viewer_mode = ViewerMode::from_dropdown_index(index);
|
||||
if controller.config.borrow().viewer_mode == viewer_mode {
|
||||
return;
|
||||
}
|
||||
let mut updated = controller.config.borrow().clone();
|
||||
updated.viewer_mode = viewer_mode;
|
||||
if let Err(error) = updated.save(&controller.config_path) {
|
||||
controller
|
||||
.status_label
|
||||
.set_text(&format!("보기 모드를 저장할 수 없습니다: {error:#}"));
|
||||
return;
|
||||
}
|
||||
*controller.config.borrow_mut() = updated;
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_favorite(&self, entry: &BrowserEntry, profile: &LocationProfile) -> bool {
|
||||
let mut updated = self.config.borrow().clone();
|
||||
let was_active = self.is_favorite(&entry.uri);
|
||||
|
|
@ -879,7 +970,10 @@ impl BrowserController {
|
|||
return was_active;
|
||||
}
|
||||
*self.config.borrow_mut() = updated;
|
||||
self.rebuild_favorites();
|
||||
let browsing_favorites = self.state.borrow().browsing_favorites;
|
||||
if browsing_favorites {
|
||||
self.show_favorites();
|
||||
}
|
||||
self.status_label.set_text(if active {
|
||||
"즐겨찾기에 추가했습니다."
|
||||
} else {
|
||||
|
|
@ -1245,6 +1339,51 @@ fn navigable_sibling(current: EntryKind, candidate: EntryKind) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn favorite_browser_entry(favorite: &FavoriteEntry) -> BrowserEntry {
|
||||
BrowserEntry {
|
||||
uri: favorite.uri.clone(),
|
||||
name: favorite.name.clone(),
|
||||
kind: if favorite.is_directory {
|
||||
EntryKind::Directory
|
||||
} else {
|
||||
classify_file(Path::new(&favorite.name))
|
||||
},
|
||||
size: None,
|
||||
modified: None,
|
||||
created: None,
|
||||
etag: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn show_viewer_loading(
|
||||
window: >k::ApplicationWindow,
|
||||
file_name: &str,
|
||||
status: &str,
|
||||
) -> gtk::Label {
|
||||
window.set_title(Some(file_name));
|
||||
let header = gtk::HeaderBar::new();
|
||||
header.set_title_widget(Some(>k::Label::new(Some(file_name))));
|
||||
window.set_titlebar(Some(&header));
|
||||
|
||||
let content = gtk::Box::new(gtk::Orientation::Vertical, 12);
|
||||
content.set_halign(gtk::Align::Center);
|
||||
content.set_valign(gtk::Align::Center);
|
||||
content.set_hexpand(true);
|
||||
content.set_vexpand(true);
|
||||
let spinner = gtk::Spinner::new();
|
||||
spinner.set_spinning(true);
|
||||
let name_label = gtk::Label::new(Some(file_name));
|
||||
name_label.add_css_class("title-2");
|
||||
name_label.set_ellipsize(gtk::pango::EllipsizeMode::Middle);
|
||||
name_label.set_max_width_chars(60);
|
||||
let status_label = gtk::Label::new(Some(status));
|
||||
content.append(&spinner);
|
||||
content.append(&name_label);
|
||||
content.append(&status_label);
|
||||
window.set_child(Some(&content));
|
||||
status_label
|
||||
}
|
||||
|
||||
fn download_part_path(final_path: &std::path::Path) -> PathBuf {
|
||||
final_path.with_file_name(format!(
|
||||
"{}.part",
|
||||
|
|
@ -1315,6 +1454,26 @@ mod tests {
|
|||
assert!(!navigable_sibling(EntryKind::Video, EntryKind::OtherFile));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn favorites_are_rendered_as_browser_paths_and_files() {
|
||||
let profile = home_profile();
|
||||
let folder = favorite_browser_entry(&FavoriteEntry {
|
||||
name: "Comics".into(),
|
||||
uri: "file:///tmp/Comics".into(),
|
||||
is_directory: true,
|
||||
profile: profile.clone(),
|
||||
});
|
||||
let archive = favorite_browser_entry(&FavoriteEntry {
|
||||
name: "book.zip".into(),
|
||||
uri: "file:///tmp/book.zip".into(),
|
||||
is_directory: false,
|
||||
profile,
|
||||
});
|
||||
|
||||
assert_eq!(folder.kind, EntryKind::Directory);
|
||||
assert_eq!(archive.kind, EntryKind::ZipArchive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_download_uses_distinct_suffix() {
|
||||
assert_eq!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue