From 10e4c5cf9763aa5ada3ecac1bb041c0fdf819908 Mon Sep 17 00:00:00 2001 From: Valeri Ochinski Date: Sun, 29 Oct 2023 12:08:07 +0300 Subject: [PATCH] Confirm exit if file transfer is in progress Closes #3. --- po/nxdumpclient.pot | 80 ++++++++++++++++++++++++++-------------- src/Application.vala | 53 +++++++++++++++++++++++++- src/UsbDeviceClient.vala | 7 +++- src/Window.blp | 1 + src/Window.vala | 41 +++++++++++++++++--- 5 files changed, 147 insertions(+), 35 deletions(-) diff --git a/po/nxdumpclient.pot b/po/nxdumpclient.pot index 5de72b4..7681fb6 100644 --- a/po/nxdumpclient.pot +++ b/po/nxdumpclient.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: nxdumpclient\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-10-28 13:37+0300\n" +"POT-Creation-Date: 2023-10-29 12:06+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -39,7 +39,7 @@ msgstr "" msgid "Client for dumping over USB with nxdumptool" msgstr "" -#: data/org.v1993.NXDumpClient.appdata.xml.in:15 src/Application.vala:212 +#: data/org.v1993.NXDumpClient.appdata.xml.in:15 src/Application.vala:247 msgid "Client for dumping over USB with nxdumptool." msgstr "" @@ -203,32 +203,32 @@ msgctxt "version string" msgid "N/A" msgstr "" -#: src/widgets/FileRow.blp:13 +#: src/widgets/FileRow.blp:15 msgctxt "FileRow button" msgid "Reset to default" msgstr "" -#: src/Window.blp:26 +#: src/Window.blp:27 msgid "No Devices Connected" msgstr "" -#: src/Window.blp:27 +#: src/Window.blp:28 msgid "Connect your Switch and start nxdumptool to proceed" msgstr "" -#: src/Window.blp:52 +#: src/Window.blp:53 msgid "_Preferences" msgstr "" -#: src/Window.blp:53 +#: src/Window.blp:54 msgid "_Keyboard Shortcuts" msgstr "" -#: src/Window.blp:54 +#: src/Window.blp:55 msgid "_About NX Dump Client" msgstr "" -#: src/Window.blp:57 +#: src/Window.blp:58 msgid "_Quit" msgstr "" @@ -255,85 +255,109 @@ msgstr "" msgid "Error handling a new device" msgstr "" -#: src/UsbDeviceClient.vala:156 +#: src/UsbDeviceClient.vala:157 #, c-format msgid "Not enough space for dump (%s required)" msgstr "" -#: src/UsbDeviceClient.vala:552 +#: src/UsbDeviceClient.vala:557 msgid "NCA checksum verification failed (non-standard dump options?)" msgstr "" -#: src/UsbDeviceClient.vala:580 +#: src/UsbDeviceClient.vala:585 msgid "NSP header" msgstr "" -#: src/Application.vala:105 +#: src/Application.vala:36 +msgid "File transfer is in progres" +msgstr "" + +#: src/Application.vala:103 +msgid "Confirm exit" +msgstr "" + +#: src/Application.vala:103 +msgid "" +"A file transfer is currently in progress.\n" +"Are you sure you want to quit?" +msgstr "" + +#: src/Application.vala:108 +msgctxt "deny app exit" +msgid "Cancel" +msgstr "" + +#: src/Application.vala:109 +msgctxt "confirm app exit" +msgid "Confirm exit" +msgstr "" + +#: src/Application.vala:140 msgid "nxdumptool device connected" msgstr "" -#: src/Application.vala:113 +#: src/Application.vala:148 msgid "nxdumptool device disconnected" msgstr "" -#: src/Application.vala:130 +#: src/Application.vala:165 msgid "File transfer started" msgstr "" -#: src/Application.vala:155 +#: src/Application.vala:190 msgid "File transfer complete" msgstr "" -#: src/Application.vala:158 src/Application.vala:164 +#: src/Application.vala:193 src/Application.vala:199 msgid "Show in folder" msgstr "" -#: src/Application.vala:163 +#: src/Application.vala:198 #, c-format msgid "Transfer of file “%s” complete" msgstr "" -#: src/Application.vala:185 +#: src/Application.vala:220 msgid "File transfer canceled" msgstr "" -#: src/Application.vala:185 +#: src/Application.vala:220 msgid "File transfer failed" msgstr "" -#: src/Application.vala:192 +#: src/Application.vala:227 #, c-format msgid "Transfer of file “%s” canceled" msgstr "" -#: src/Application.vala:192 +#: src/Application.vala:227 #, c-format msgid "Transfer of file “%s” failed" msgstr "" -#: src/Application.vala:205 +#: src/Application.vala:240 msgid "" "Launch without a visible window if allowed to in settings, exit otherwise" msgstr "" -#: src/Application.vala:206 +#: src/Application.vala:241 msgid "Print udev rules required for USB access and exit" msgstr "" -#: src/Application.vala:207 +#: src/Application.vala:242 msgid "Print application version and exit" msgstr "" -#: src/Application.vala:331 +#: src/Application.vala:366 #, c-format msgid "Failed to initialize USB context: %s\n" msgstr "" -#: src/Application.vala:391 +#: src/Application.vala:426 msgid "translator-credits" msgstr "" -#: src/Application.vala:398 +#: src/Application.vala:433 msgctxt "credits section header" msgid "nxdumptool team" msgstr "" diff --git a/src/Application.vala b/src/Application.vala index 230541e..a01ffd2 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -27,6 +27,28 @@ namespace NXDumpClient { new Application().show_error(desc, message); } + internal class FileTransferInhibitor: Object { + public static uint current_count { get; private set; } + + private uint cookie = 0; + + construct { + cookie = new Application().inhibit(null, LOGOUT | SUSPEND, _("File transfer is in progres")); + ++current_count; + debug("Inhibited, cookie: %u, active inhibitors: %u", cookie, current_count); + } + + ~FileTransferInhibitor() { + var app = new Application(); + // May be false during app shutdown + if (app.is_registered) { + app.uninhibit(cookie); + } + --current_count; + debug("Uninhibited, cookie: %u, active inhibitors: %u", cookie, current_count); + } + } + [SingleInstance] public class Application: Adw.Application { // Settings object and corresponding fields @@ -77,6 +99,19 @@ namespace NXDumpClient { return !cancellable.is_cancelled() && main_window != null; } + public async bool query_app_exit() { + var dialog = new Adw.MessageDialog(main_window, _("Confirm exit"), _("A file transfer is currently in progress.\nAre you sure you want to quit?")) { + close_response = "cancel", + default_response = "confirm", + + }; + dialog.add_response("cancel", C_("deny app exit", "Cancel")); + dialog.add_response("confirm", C_("confirm app exit", "Confirm exit")); + dialog.set_response_appearance("confirm", DESTRUCTIVE); + var res = yield dialog.choose(cancellable); + return res == "confirm"; + } + public void show_error(string desc, string message) { if (should_show_toast()) { var toast = new Adw.Toast.format("%s: %s", desc, message) { @@ -215,7 +250,7 @@ namespace NXDumpClient { // In-app only { "about", this.on_about_action }, { "preferences", this.on_preferences_action }, - { "quit", this.quit }, + { "quit", this.on_quit_with_confirmation_action }, // May be invoked externally (e.g. from a notification) { "show-file", this.on_show_file_action, "s" } @@ -420,6 +455,22 @@ namespace NXDumpClient { show_file.begin(File.new_for_uri(param.get_string())); } + private void on_quit_with_confirmation_action() { + // Do not test if background is enabled - app exit bypasses that + if (FileTransferInhibitor.current_count == 0) { + quit(); + } else { + quit_with_confirmation.begin(); + } + } + + private async void quit_with_confirmation() { + var res = yield query_app_exit(); + if (res) { + quit(); + } + } + private async void show_file(File file) { hold(); // Ensure that we won't exit if invoked without activation try { diff --git a/src/UsbDeviceClient.vala b/src/UsbDeviceClient.vala index 4c2209f..b2a36c3 100644 --- a/src/UsbDeviceClient.vala +++ b/src/UsbDeviceClient.vala @@ -53,6 +53,7 @@ namespace NXDumpClient { int64 header_size; File file; FileOutputStream ostream; + FileTransferInhibitor inhibitor; } private const string COMMAND_MAGIC = "NXDT"; @@ -376,14 +377,17 @@ namespace NXDumpClient { var filename = (string)istream.read_bytes(0x301, cancellable).get_data(); File file; FileOutputStream ostream; + FileTransferInhibitor inhibitor; try { if (nsp_dump_status == null) { file = yield get_dump_target(filename, file_size, cancellable); ostream = yield file.replace_async(null, false, REPLACE_DESTINATION, Priority.DEFAULT, cancellable); + inhibitor = new FileTransferInhibitor(); } else { file = nsp_dump_status.file; ostream = nsp_dump_status.ostream; + inhibitor = nsp_dump_status.inhibitor; } } catch(Error e) { throw error_to_recoverable(e); @@ -417,7 +421,8 @@ namespace NXDumpClient { transferred_size = 0, header_size = nsp_header_size, file = file, - ostream = ostream + ostream = ostream, + inhibitor = inhibitor, }; yield send_status_success(); diff --git a/src/Window.blp b/src/Window.blp index 324d012..0a39e19 100644 --- a/src/Window.blp +++ b/src/Window.blp @@ -5,6 +5,7 @@ template $NXDumpClientWindow: Adw.ApplicationWindow { default-width: 600; default-height: 500; title: _("NX Dump Client"); + close-request => $on_close_request(); Box { orientation: vertical; diff --git a/src/Window.vala b/src/Window.vala index 5a86e76..dd585f1 100644 --- a/src/Window.vala +++ b/src/Window.vala @@ -26,7 +26,9 @@ namespace NXDumpClient { [GtkChild] private unowned Gtk.Stack content_selector; - protected Gtk.SelectionModel devices_model { get; set; } + public Gtk.SelectionModel devices_model { get; construct; } + + private bool close_confirmed = false; public Window(Gtk.Application app) { Object( @@ -56,24 +58,53 @@ namespace NXDumpClient { } [GtkCallback] - void setup_element(Gtk.SignalListItemFactory factory, Object o) { + private bool on_close_request() { + // Return true to NOT close, false to CLOSE + if (close_confirmed) { + return false; // Close, queried the user and was allowed to + } + + if (FileTransferInhibitor.current_count == 0) { + return false; // Close, nothing is running in background + } + + var app = new Application(); + if (app.hold_background_reference) { + return false; // Close, application will keep running + } + + query_for_close.begin(); + return true; // Do not close; present a query + } + + private async void query_for_close() { + var result = yield new Application().query_app_exit(); + if (result) { + close_confirmed = true; + close(); + close_confirmed = false; + } + } + + [GtkCallback] + private void setup_element(Gtk.SignalListItemFactory factory, Object o) { var litem = (Gtk.ListItem)o; litem.child = new DeviceStatusRow(); } [GtkCallback] - void teardown_element(Gtk.SignalListItemFactory factory, Object o) { + private void teardown_element(Gtk.SignalListItemFactory factory, Object o) { } [GtkCallback] - void bind_element(Gtk.SignalListItemFactory factory, Object o) { + private void bind_element(Gtk.SignalListItemFactory factory, Object o) { var litem = (Gtk.ListItem)o; var child = (DeviceStatusRow)litem.child; child.bind((UsbDeviceClient)litem.item); } [GtkCallback] - void unbind_element(Gtk.SignalListItemFactory factory, Object o) { + private void unbind_element(Gtk.SignalListItemFactory factory, Object o) { var litem = (Gtk.ListItem)o; var child = (DeviceStatusRow)litem.child; child.unbind();