From 7dbc04ac737b9a8a1e398277cbd57a0fd91b8280 Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Sat, 12 Oct 2024 10:41:37 +0200 Subject: [PATCH 1/9] style(containers-panel): Avoid no-result-page on switching connection --- src/view/containers_panel.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/view/containers_panel.rs b/src/view/containers_panel.rs index 04ec4cb3..8f859796 100644 --- a/src/view/containers_panel.rs +++ b/src/view/containers_panel.rs @@ -417,17 +417,28 @@ mod imp { .upcast() }); - self.filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - model.connect_items_changed(clone!(@weak obj => move |model, _, removed, _| { - obj.imp() - .filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - - if removed > 0 { - obj.deselect_hidden_containers(model.upcast_ref()); + model.connect_items_changed(clone!( + #[weak] + obj, + move |model, _, removed, _| { + obj.imp().filter_stack.set_visible_child_name( + if model.n_items() > 0 + || !obj + .container_list() + .as_ref() + .is_some_and(model::ContainerList::initialized) + { + "list" + } else { + "empty" + }, + ); + + if removed > 0 { + obj.deselect_hidden_containers(model.upcast_ref()); + } } - })); + )); } self.container_list.set(value); From d596850540c0a3225e7501c02cb429a882feecdb Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Sat, 12 Oct 2024 10:41:46 +0200 Subject: [PATCH 2/9] style(pods-panel): Avoid no-result-page on switching connection --- src/view/pods_panel.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/view/pods_panel.rs b/src/view/pods_panel.rs index b1b2d6ca..d92ff061 100644 --- a/src/view/pods_panel.rs +++ b/src/view/pods_panel.rs @@ -353,17 +353,28 @@ mod imp { view::PodRow::from(item.downcast_ref().unwrap()).upcast() }); - self.filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - model.connect_items_changed(clone!(@weak obj => move |model, _, removed, _| { - obj.imp() - .filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - - if removed > 0 { - obj.deselect_hidden_pods(model.upcast_ref()); + model.connect_items_changed(clone!( + #[weak] + obj, + move |model, _, removed, _| { + obj.imp().filter_stack.set_visible_child_name( + if model.n_items() > 0 + || !obj + .pod_list() + .as_ref() + .is_some_and(model::PodList::initialized) + { + "list" + } else { + "empty" + }, + ); + + if removed > 0 { + obj.deselect_hidden_pods(model.upcast_ref()); + } } - })); + )); ACTIONS_SELECTION .iter() From 25b702cf8ca5866350f9b075704e1b3967bff92d Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Sat, 12 Oct 2024 10:41:56 +0200 Subject: [PATCH 3/9] style(images-panel): Avoid no-result-page on switching connection --- src/view/images_panel.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/view/images_panel.rs b/src/view/images_panel.rs index 0ba764b6..7b00bd96 100644 --- a/src/view/images_panel.rs +++ b/src/view/images_panel.rs @@ -345,17 +345,28 @@ mod imp { view::ImageRow::from(item.downcast_ref().unwrap()).upcast() }); - self.filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - model.connect_items_changed(clone!(@weak obj => move |model, _, removed, _| { - obj.imp() - .filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - - if removed > 0 { - obj.deselect_hidden_images(model.upcast_ref()); + model.connect_items_changed(clone!( + #[weak] + obj, + move |model, _, removed, _| { + obj.imp().filter_stack.set_visible_child_name( + if model.n_items() > 0 + || !obj + .image_list() + .as_ref() + .is_some_and(model::ImageList::initialized) + { + "list" + } else { + "empty" + }, + ); + + if removed > 0 { + obj.deselect_hidden_images(model.upcast_ref()); + } } - })); + )); obj.action_set_enabled(ACTION_DELETE_SELECTION, false); value.connect_notify_local( From 1b5b167ed9c91735f2e2635b490714f3ca7ec2c4 Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Sat, 12 Oct 2024 10:42:07 +0200 Subject: [PATCH 4/9] style(volumes-panel): Avoid no-result-page on switching connection --- src/view/volumes_panel.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/view/volumes_panel.rs b/src/view/volumes_panel.rs index 3a8b28b3..f99ab8bd 100644 --- a/src/view/volumes_panel.rs +++ b/src/view/volumes_panel.rs @@ -334,17 +334,27 @@ mod imp { view::VolumeRow::from(item.downcast_ref().unwrap()).upcast() }); - self.filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - model.connect_items_changed(clone!(@weak obj => move |model, _, removed, _| { - obj.imp() - .filter_stack - .set_visible_child_name(if model.n_items() > 0 { "list" } else { "empty" }); - - if removed > 0 { - obj.deselect_hidden_volumes(model.upcast_ref()); + model.connect_items_changed(clone!( + #[weak] + obj, + move |model, _, removed, _| { + obj.imp().filter_stack.set_visible_child_name( + if model.n_items() > 0 || { + !obj.volume_list() + .as_ref() + .is_some_and(model::VolumeList::initialized) + } { + "list" + } else { + "empty" + }, + ); + + if removed > 0 { + obj.deselect_hidden_volumes(model.upcast_ref()); + } } - })); + )); } self.volume_list.set(value); From 66c2194ccc62c9e07f442166ecfc279032ac9ddb Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Thu, 10 Oct 2024 11:44:26 +0200 Subject: [PATCH 5/9] fix(containers-panels): Correctly reset search --- src/view/containers_panel.rs | 148 +++++++++++++++++++++-------------- src/view/containers_panel.ui | 2 +- 2 files changed, 90 insertions(+), 60 deletions(-) diff --git a/src/view/containers_panel.rs b/src/view/containers_panel.rs index 8f859796..bfa04206 100644 --- a/src/view/containers_panel.rs +++ b/src/view/containers_panel.rs @@ -144,7 +144,7 @@ mod imp { ); klass.install_action(ACTION_SHOW_ALL_CONTAINERS, None, |widget, _, _| { - widget.set_show_only_running_containers(false); + widget.show_all_containers(); }); } @@ -364,9 +364,9 @@ mod imp { self.update_filter(filter_change); } - pub(super) fn set_container_list(&self, value: Option<&model::ContainerList>) { + pub(super) fn set_container_list(&self, value: &model::ContainerList) { let obj = &*self.obj(); - if obj.container_list().as_ref() == value { + if obj.container_list().as_ref() == Some(value) { return; } @@ -374,74 +374,99 @@ mod imp { .iter() .for_each(|action_name| obj.action_set_enabled(action_name, false)); - if let Some(container_list) = value { - container_list.connect_notify_local( - Some("num-selected"), - clone!(@weak obj => move |list, _| { - ACTIONS_SELECTION - .iter() - .for_each(|action_name| { - obj.action_set_enabled(action_name, list.num_selected() > 0); + value.connect_notify_local( + Some("num-selected"), + clone!( + #[weak] + obj, + move |list, _| { + ACTIONS_SELECTION.iter().for_each(|action_name| { + obj.action_set_enabled(action_name, list.num_selected() > 0); }); - }), - ); + } + ), + ); - container_list.connect_notify_local( - Some("running"), - clone!(@weak obj => move |_, _| { - obj.imp().update_filter(gtk::FilterChange::Different) - }), - ); + value.connect_notify_local( + Some("running"), + clone!( + #[weak] + obj, + move |_, _| obj.imp().update_filter(gtk::FilterChange::Different) + ), + ); - container_list.connect_container_name_changed(clone!(@weak obj => move |_, _| { + value.connect_container_name_changed(clone!( + #[weak] + obj, + move |_, _| { obj.imp().update_filter(gtk::FilterChange::Different); glib::timeout_add_seconds_local_once( 1, - clone!(@weak obj => move || obj.imp().update_sorter()), + clone!( + #[weak] + obj, + move || obj.imp().update_sorter() + ), ); - })); + } + )); - let model = gtk::SortListModel::new( - Some(gtk::FilterListModel::new( - Some(container_list.to_owned()), - self.filter.get().cloned(), - )), - self.sorter.get().cloned(), - ); - - self.flow_box.bind_model(Some(&model), |item| { - gtk::FlowBoxChild::builder() - .focusable(false) - .child(&view::ContainerCard::from(item.downcast_ref().unwrap())) - .build() - .upcast() - }); + let model = gtk::SortListModel::new( + Some(gtk::FilterListModel::new( + Some(value.to_owned()), + self.filter.get().cloned(), + )), + self.sorter.get().cloned(), + ); - model.connect_items_changed(clone!( - #[weak] - obj, - move |model, _, removed, _| { - obj.imp().filter_stack.set_visible_child_name( - if model.n_items() > 0 - || !obj - .container_list() - .as_ref() - .is_some_and(model::ContainerList::initialized) - { - "list" - } else { - "empty" - }, - ); + self.flow_box.bind_model(Some(&model), |item| { + gtk::FlowBoxChild::builder() + .focusable(false) + .child(&view::ContainerCard::from(item.downcast_ref().unwrap())) + .build() + .upcast() + }); - if removed > 0 { - obj.deselect_hidden_containers(model.upcast_ref()); - } + self.set_filter_stack_visible_child(value, &model); + model.connect_items_changed(clone!( + #[weak] + obj, + #[weak] + value, + move |model, _, removed, _| { + obj.imp().set_filter_stack_visible_child(&value, model); + + if removed > 0 { + obj.deselect_hidden_containers(model.upcast_ref()); } - )); - } + } + )); + value.connect_initialized_notify(clone!( + #[weak] + obj, + #[weak] + model, + move |container_list| obj + .imp() + .set_filter_stack_visible_child(container_list, &model) + )); - self.container_list.set(value); + self.container_list.set(Some(value)); + } + + fn set_filter_stack_visible_child( + &self, + container_list: &model::ContainerList, + model: &impl IsA, + ) { + self.filter_stack.set_visible_child_name( + if model.n_items() > 0 || !container_list.initialized() { + "list" + } else { + "empty" + }, + ); } fn update_filter(&self, filter_change: gtk::FilterChange) { @@ -478,6 +503,11 @@ impl ContainersPanel { .and_then(model::ContainerList::client) } + pub(crate) fn show_all_containers(&self) { + self.set_show_only_running_containers(false); + self.set_search_mode(false); + } + pub(crate) fn set_search_mode(&self, value: bool) { self.imp().search_bar.set_search_mode(value); } diff --git a/src/view/containers_panel.ui b/src/view/containers_panel.ui index 96fc1b69..d6ba3c9e 100644 --- a/src/view/containers_panel.ui +++ b/src/view/containers_panel.ui @@ -257,7 +257,7 @@ package-x-generic-symbolic - No Running Containers + No Matching Containers From bf3cf246fb0e50d7a1d2bc4c191f8454b2eacac9 Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Fri, 18 Oct 2024 11:40:49 +0200 Subject: [PATCH 6/9] fix(pods-panel): Correctly reset search --- src/view/pods_panel.rs | 44 +++++++++++++++++++++++++++++------------- src/view/pods_panel.ui | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/view/pods_panel.rs b/src/view/pods_panel.rs index d92ff061..aaa9c038 100644 --- a/src/view/pods_panel.rs +++ b/src/view/pods_panel.rs @@ -143,7 +143,7 @@ mod imp { ); klass.install_action(ACTION_SHOW_ALL_PODS, None, |widget, _, _| { - widget.set_show_only_running_pods(false); + widget.show_all_pods(); }); } @@ -353,28 +353,27 @@ mod imp { view::PodRow::from(item.downcast_ref().unwrap()).upcast() }); + self.set_filter_stack_visible_child(value, &model); model.connect_items_changed(clone!( #[weak] obj, + #[weak] + value, move |model, _, removed, _| { - obj.imp().filter_stack.set_visible_child_name( - if model.n_items() > 0 - || !obj - .pod_list() - .as_ref() - .is_some_and(model::PodList::initialized) - { - "list" - } else { - "empty" - }, - ); + obj.imp().set_filter_stack_visible_child(&value, model); if removed > 0 { obj.deselect_hidden_pods(model.upcast_ref()); } } )); + value.connect_initialized_notify(clone!( + #[weak] + obj, + #[weak] + model, + move |value| obj.imp().set_filter_stack_visible_child(value, &model) + )); ACTIONS_SELECTION .iter() @@ -393,6 +392,20 @@ mod imp { self.pod_list.set(Some(value)); } + fn set_filter_stack_visible_child( + &self, + pod_list: &model::PodList, + model: &impl IsA, + ) { + self.filter_stack.set_visible_child_name( + if model.n_items() > 0 || !pod_list.initialized() { + "list" + } else { + "empty" + }, + ); + } + fn update_filter(&self, filter_change: gtk::FilterChange) { if let Some(filter) = self.filter.get() { filter.changed(filter_change); @@ -414,6 +427,11 @@ impl Default for PodsPanel { } impl PodsPanel { + pub(crate) fn show_all_pods(&self) { + self.set_show_only_running_pods(false); + self.set_search_mode(false); + } + pub(crate) fn set_search_mode(&self, value: bool) { self.imp().search_bar.set_search_mode(value); } diff --git a/src/view/pods_panel.ui b/src/view/pods_panel.ui index bb239583..5736ad64 100644 --- a/src/view/pods_panel.ui +++ b/src/view/pods_panel.ui @@ -255,7 +255,7 @@ pods-symbolic - No Running Pods + No Matching Pods From 93b6928ba6a14f8ece1a4d3cdd925d8fccfb8308 Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Fri, 18 Oct 2024 11:40:57 +0200 Subject: [PATCH 7/9] fix(images-panel): Correctly reset search --- src/view/images_panel.rs | 44 ++++++++++++++++++++++++++++------------ src/view/images_panel.ui | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/view/images_panel.rs b/src/view/images_panel.rs index 7b00bd96..a6b4c41d 100644 --- a/src/view/images_panel.rs +++ b/src/view/images_panel.rs @@ -115,7 +115,7 @@ mod imp { ); klass.install_action(ACTION_SHOW_ALL_IMAGES, None, |widget, _, _| { - widget.set_hide_intermediate_images(false); + widget.show_all_images(); }); } @@ -345,28 +345,27 @@ mod imp { view::ImageRow::from(item.downcast_ref().unwrap()).upcast() }); + self.set_filter_stack_visible_child(value, &model); model.connect_items_changed(clone!( #[weak] obj, + #[weak] + value, move |model, _, removed, _| { - obj.imp().filter_stack.set_visible_child_name( - if model.n_items() > 0 - || !obj - .image_list() - .as_ref() - .is_some_and(model::ImageList::initialized) - { - "list" - } else { - "empty" - }, - ); + obj.imp().set_filter_stack_visible_child(&value, model); if removed > 0 { obj.deselect_hidden_images(model.upcast_ref()); } } )); + value.connect_initialized_notify(clone!( + #[weak] + obj, + #[weak] + model, + move |value| obj.imp().set_filter_stack_visible_child(value, &model) + )); obj.action_set_enabled(ACTION_DELETE_SELECTION, false); value.connect_notify_local( @@ -379,6 +378,20 @@ mod imp { self.image_list.set(Some(value)); } + fn set_filter_stack_visible_child( + &self, + image_list: &model::ImageList, + model: &impl IsA, + ) { + self.filter_stack.set_visible_child_name( + if model.n_items() > 0 || !image_list.initialized() { + "list" + } else { + "empty" + }, + ); + } + fn update_filter(&self, filter_change: gtk::FilterChange) { if let Some(filter) = self.filter.get() { filter.changed(filter_change); @@ -407,6 +420,11 @@ impl Default for ImagesPanel { } impl ImagesPanel { + pub(crate) fn show_all_images(&self) { + self.set_hide_intermediate_images(false); + self.set_search_mode(false); + } + pub(crate) fn set_search_mode(&self, value: bool) { self.imp().search_bar.set_search_mode(value); } diff --git a/src/view/images_panel.ui b/src/view/images_panel.ui index f2c8ce30..5ce511b1 100644 --- a/src/view/images_panel.ui +++ b/src/view/images_panel.ui @@ -280,7 +280,7 @@ image-x-generic-symbolic - No Tagged Images + No Matching Images From a72e49f603e6357246e1117eac6a52cf56a0276b Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Fri, 18 Oct 2024 11:41:09 +0200 Subject: [PATCH 8/9] fix(volumes-panel): Correctly reset search --- src/view/volumes_panel.rs | 124 +++++++++++++++++++++++--------------- src/view/volumes_panel.ui | 2 +- 2 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/view/volumes_panel.rs b/src/view/volumes_panel.rs index f99ab8bd..146f553d 100644 --- a/src/view/volumes_panel.rs +++ b/src/view/volumes_panel.rs @@ -40,7 +40,7 @@ mod imp { pub(super) filter: OnceCell, pub(super) sorter: OnceCell, pub(super) search_term: RefCell, - #[property(get, set = Self::set_volume_list, explicit_notify, nullable)] + #[property(get, set = Self::set_volume_list)] pub(super) volume_list: glib::WeakRef, #[property(get, set)] pub(super) show_only_used_volumes: Cell, @@ -109,7 +109,7 @@ mod imp { ); klass.install_action(ACTION_SHOW_ALL_VOLUMES, None, |widget, _, _| { - widget.set_show_only_used_volumes(false); + widget.show_all_volumes(); }); } @@ -299,66 +299,87 @@ mod imp { self.update_filter(filter_change); } - pub(super) fn set_volume_list(&self, value: Option<&model::VolumeList>) { + pub(super) fn set_volume_list(&self, value: &model::VolumeList) { let obj = &*self.obj(); - if obj.volume_list().as_ref() == value { + if obj.volume_list().as_ref() == Some(value) { return; } obj.action_set_enabled(ACTION_DELETE_SELECTION, false); - if let Some(volume_list) = value { - volume_list.connect_notify_local( - Some("num-selected"), - clone!(@weak obj => move |list, _| { + value.connect_notify_local( + Some("num-selected"), + clone!( + #[weak] + obj, + move |list, _| { obj.action_set_enabled(ACTION_DELETE_SELECTION, list.num_selected() > 0); - }), - ); + } + ), + ); - volume_list.connect_notify_local( - Some("used"), - clone!(@weak obj => move |_, _| { - obj.imp().update_filter(gtk::FilterChange::Different); - }), - ); - - let model = gtk::SortListModel::new( - Some(gtk::FilterListModel::new( - Some(volume_list.to_owned()), - self.filter.get().cloned(), - )), - self.sorter.get().cloned(), - ); - - self.list_box.bind_model(Some(&model), |item| { - view::VolumeRow::from(item.downcast_ref().unwrap()).upcast() - }); - - model.connect_items_changed(clone!( + value.connect_notify_local( + Some("used"), + clone!( #[weak] obj, - move |model, _, removed, _| { - obj.imp().filter_stack.set_visible_child_name( - if model.n_items() > 0 || { - !obj.volume_list() - .as_ref() - .is_some_and(model::VolumeList::initialized) - } { - "list" - } else { - "empty" - }, - ); + move |_, _| { + obj.imp().update_filter(gtk::FilterChange::Different); + } + ), + ); - if removed > 0 { - obj.deselect_hidden_volumes(model.upcast_ref()); - } + let model = gtk::SortListModel::new( + Some(gtk::FilterListModel::new( + Some(value.to_owned()), + self.filter.get().cloned(), + )), + self.sorter.get().cloned(), + ); + + self.list_box.bind_model(Some(&model), |item| { + view::VolumeRow::from(item.downcast_ref().unwrap()).upcast() + }); + + self.set_filter_stack_visible_child(value, &model); + model.connect_items_changed(clone!( + #[weak] + obj, + #[weak] + value, + move |model, _, removed, _| { + obj.imp().set_filter_stack_visible_child(&value, model); + + if removed > 0 { + obj.deselect_hidden_volumes(model.upcast_ref()); } - )); - } + } + )); + value.connect_initialized_notify(clone!( + #[weak] + obj, + #[weak] + model, + move |volume_list| obj + .imp() + .set_filter_stack_visible_child(volume_list, &model) + )); + + self.volume_list.set(Some(value)); + } - self.volume_list.set(value); - obj.notify("volume-list"); + fn set_filter_stack_visible_child( + &self, + volume_list: &model::VolumeList, + model: &impl IsA, + ) { + self.filter_stack.set_visible_child_name( + if model.n_items() > 0 || !volume_list.initialized() { + "list" + } else { + "empty" + }, + ); } fn update_filter(&self, filter_change: gtk::FilterChange) { @@ -382,6 +403,11 @@ impl Default for VolumesPanel { } impl VolumesPanel { + pub(crate) fn show_all_volumes(&self) { + self.set_show_only_used_volumes(false); + self.set_search_mode(false); + } + pub(crate) fn set_search_mode(&self, value: bool) { self.imp().search_bar.set_search_mode(value); } diff --git a/src/view/volumes_panel.ui b/src/view/volumes_panel.ui index 13bc4501..511ac8ca 100644 --- a/src/view/volumes_panel.ui +++ b/src/view/volumes_panel.ui @@ -255,7 +255,7 @@ drive-harddisk-symbolic - No Volumes in Use + No Matching Volumes From 49bb20f833e9ed338db7e33f1a8b030be261135e Mon Sep 17 00:00:00 2001 From: Marcus Behrendt Date: Mon, 21 Oct 2024 15:43:25 +0200 Subject: [PATCH 9/9] feat(action): Use `BufWriter` for downloading container files Fixes #841 --- src/model/action.rs | 168 ++++++++++++++++++++++++++++++++------------ 1 file changed, 123 insertions(+), 45 deletions(-) diff --git a/src/model/action.rs b/src/model/action.rs index e8d7916c..31aeb8c0 100644 --- a/src/model/action.rs +++ b/src/model/action.rs @@ -3,14 +3,16 @@ use std::cell::Cell; use std::cell::OnceCell; use std::cell::RefCell; use std::ffi::OsStr; -use std::mem; use std::path::PathBuf; use std::sync::Arc; -use std::sync::Mutex; use adw::prelude::*; +use futures::lock::Mutex; use futures::stream; +use futures::FutureExt; use futures::StreamExt; +use futures::TryFutureExt; +use futures::TryStreamExt; use gettextrs::gettext; use gio::subclass::prelude::*; use glib::clone; @@ -18,6 +20,8 @@ use glib::Properties; use gtk::gio; use gtk::glib; use serde::Deserialize; +use tokio::io::AsyncWriteExt; +use tokio::io::BufWriter; use crate::model; use crate::model::AbstractContainerListExt; @@ -568,56 +572,130 @@ impl Action { let abort_registration = obj.setup_abort_handle(); - obj.insert_line(&gettext("Copying bytes into memory…")); + obj.insert_line(&gettext("Writing to file…")); - let buf = Arc::new(Mutex::new(Vec::new())); - obj.insert_line(&gettext!("Size: {}", glib::format_size(0))); - - utils::run_stream_with_finish_handler( - container.api().unwrap(), - |container| { - stream::Abortable::new(container.copy_from(container_path), abort_registration) - .boxed() + utils::do_async( + async move { + tokio::fs::File::options() + .write(true) + .create(true) + .truncate(true) + .open(&host_path) + .await }, clone!( - @weak obj, - @strong buf - => @default-return glib::ControlFlow::Break, move |result: podman::Result>| - { - match result { - Ok(chunk) => { - let mut buf = buf.lock().unwrap(); - buf.extend(chunk); - obj.replace_last_line(&gettext!("Size: {}", glib::format_size(buf.len() as u64))); - glib::ControlFlow::Continue - } - Err(e) => { - obj.insert_line(&e.to_string()); - obj.set_state(State::Failed); - glib::ControlFlow::Break - } - } - }), - clone!(@weak obj => move || { - let mut buf_ = Vec::::new(); - mem::swap(&mut *buf.lock().unwrap(), &mut buf_); - - utils::do_async({ - let path = host_path.clone(); - async move { tokio::fs::write(path, buf_).await } - }, - clone!(@weak obj => move |result| match result { - Ok(_) => { - obj.insert_line(&gettext("Finished")); - obj.set_state(State::Finished); - } + #[weak] + obj, + #[weak] + container, + move |file| { + match file { Err(e) => { obj.insert_line(&e.to_string()); obj.set_state(State::Failed); } - }) - ); - }), + Ok(file) => { + let writer = Arc::new(Mutex::new(BufWriter::new(file))); + obj.insert_line(&gettext!("Written: {}", glib::format_size(0))); + + utils::run_stream_with_finish_handler( + container.api().unwrap(), + { + let writer = writer.clone(); + move |container| { + stream::Abortable::new( + container.copy_from(container_path), + abort_registration, + ) + .map_err(anyhow::Error::from) + .scan( + Ok((writer, 0)), + |state: &mut anyhow::Result<_>, chunk| match state { + Err(_) => futures::future::ready(None).boxed(), + Ok((writer, written)) => match chunk { + Err(e) => { + futures::future::ready(Some(Err(e))).boxed() + } + Ok(chunk) => { + *written += chunk.len(); + + let writer = writer.clone(); + let written = *written; + async move { + Some({ + let mut writer = + writer.lock().await; + writer + .write_all(&chunk) + .map_err(anyhow::Error::from) + .map_ok(|_| written) + .await + }) + } + .boxed() + } + }, + }, + ) + .boxed() + } + }, + clone!( + #[weak] + obj, + #[upgrade_or] + glib::ControlFlow::Break, + move |result: anyhow::Result| { + match result { + Ok(written) => { + obj.replace_last_line(&gettext!( + "Written: {}", + glib::format_size(written as u64) + )); + glib::ControlFlow::Continue + } + Err(e) => { + obj.insert_line(&e.to_string()); + obj.set_state(State::Failed); + glib::ControlFlow::Break + } + } + } + ), + clone!( + #[weak] + obj, + move || { + obj.insert_line(&gettext("Flushing…")); + utils::do_async( + { + let writer = writer.clone(); + async move { writer.lock().await.flush().await } + }, + clone!( + #[weak] + obj, + move |result| { + match result { + Ok(_) => { + obj.insert_line(&gettext("Finished")); + obj.set_state(State::Finished); + } + Err(e) => { + obj.insert_line(&e.to_string()); + obj.set_state(State::Failed); + } + } + } + ), + ); + } + ), + ); + } + } + } + ), ); obj