From 595902355efc1d0f3a50e5e0d6b21886810a0f0e Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Sat, 7 Oct 2023 01:02:11 +0200 Subject: [PATCH] Show file upload/download progress Fixes upstream issue #1350. Notes: Image uploads were incorrectly handled by Dino, as they were always reported as completed even if they were not, maybe so as to show the image preview from the start. Now, Dino shows the upload progress for all file types, and the image is only shown when completed. (cherry picked from commit 700708b7e10ce1110528a2b83b854fae3f6be95e) --- libdino/src/entity/file_transfer.vala | 1 + libdino/src/service/file_manager.vala | 5 ++++- .../src/service/jingle_file_transfers.vala | 14 +++++++++++++- .../file_default_widget.vala | 15 +++++++++++++-- .../file_widget.vala | 11 ++++++++--- main/src/ui/file_send_overlay.vala | 3 ++- plugins/http-files/src/file_sender.vala | 12 ++++++++++++ .../module/xep/0234_jingle_file_transfer.vala | 19 +++++++++++++++++-- 8 files changed, 70 insertions(+), 10 deletions(-) diff --git a/libdino/src/entity/file_transfer.vala b/libdino/src/entity/file_transfer.vala index 20bc1a7a..c5e188c2 100644 --- a/libdino/src/entity/file_transfer.vala +++ b/libdino/src/entity/file_transfer.vala @@ -71,6 +71,7 @@ public class FileTransfer : Object { public int provider { get; set; } public string info { get; set; } public Cancellable cancellable { get; default=new Cancellable(); } + public uint64 transferred_bytes { get; set; } private Database? db; private string storage_dir; diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index 4073fe40..ed9524cb 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -120,6 +120,7 @@ public class FileManager : StreamInteractionModule, Object { } yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta); + file_transfer.state = FileTransfer.State.COMPLETE; } catch (Error e) { warning("Send file error: %s", e.message); @@ -250,6 +251,7 @@ public class FileManager : StreamInteractionModule, Object { ssize_t read; while ((read = yield input_stream.read_async(buffer, Priority.LOW, file_transfer.cancellable)) > 0) { buffer.length = (int) read; + file_transfer.transferred_bytes += (uint64)read; yield os.write_async(buffer, Priority.LOW, file_transfer.cancellable); buffer.length = 1024; } @@ -297,6 +299,7 @@ public class FileManager : StreamInteractionModule, Object { file_transfer.file_name = file_meta.file_name; file_transfer.size = (int)file_meta.size; file_transfer.info = info; + file_transfer.transferred_bytes = 0; var encryption = file_provider.get_encryption(file_transfer, receive_data, file_meta); if (encryption != Encryption.NONE) file_transfer.encryption = encryption; @@ -333,7 +336,7 @@ public class FileManager : StreamInteractionModule, Object { File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); yield os.splice_async(file_transfer.input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); - file_transfer.state = FileTransfer.State.COMPLETE; + file_transfer.state = FileTransfer.State.IN_PROGRESS; file_transfer.path = filename; file_transfer.input_stream = yield file.read_async(); } catch (Error e) { diff --git a/libdino/src/service/jingle_file_transfers.vala b/libdino/src/service/jingle_file_transfers.vala index 624be607..733376ba 100644 --- a/libdino/src/service/jingle_file_transfers.vala +++ b/libdino/src/service/jingle_file_transfers.vala @@ -220,7 +220,19 @@ public class JingleFileSender : FileSender, Object { } } try { - yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream(stream, full_jid, file_transfer.input_stream, file_transfer.server_file_name, file_meta.size, precondition_name, precondition_options); + var? module = stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY); + + if (module == null) + throw new FileSendError.UPLOAD_FAILED("unexpected null module"); + + module.transferred_bytes.connect((bytes) => { + file_transfer.transferred_bytes += bytes; + }); + + yield module.offer_file_stream(stream, full_jid, + file_transfer.cancellable, file_transfer.input_stream, + file_transfer.server_file_name, file_meta.size, + precondition_name, precondition_options); } catch (Error e) { throw new FileSendError.UPLOAD_FAILED(@"offer_file_stream failed: $(e.message)"); } diff --git a/main/src/ui/conversation_content_view/file_default_widget.vala b/main/src/ui/conversation_content_view/file_default_widget.vala index 352c8d7a..22142e7e 100644 --- a/main/src/ui/conversation_content_view/file_default_widget.vala +++ b/main/src/ui/conversation_content_view/file_default_widget.vala @@ -39,7 +39,8 @@ public class FileDefaultWidget : Box { }); } - public void update_file_info(string? mime_type, FileTransfer.State state, long size) { + public void update_file_info(string? mime_type, uint64 transferred_bytes, + bool direction, FileTransfer.State state, long size) { this.state = state; spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap @@ -61,7 +62,17 @@ public class FileDefaultWidget : Box { popover_menu.closed.connect(on_pointer_left); break; case FileTransfer.State.IN_PROGRESS: - mime_label.label = _("Downloading %s…").printf(get_size_string(size)); + uint progress = 0; + + if (size > 0) + progress = (uint)((transferred_bytes * (uint64)100) / (uint64)size); + + if (direction == FileTransfer.DIRECTION_SENT) { + mime_label.label = _("Uploading %s (%u%%)…").printf(get_size_string(size), progress); + } + else { + mime_label.label = _("Downloading %s (%u%%)…").printf(get_size_string(size), progress); + } spinner.start(); image_stack.set_visible_child_name("spinner"); diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index f522356b..7695ae36 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -89,7 +89,8 @@ public class FileWidget : SizeRequestBox { } private async void update_widget() { - if (show_image() && state != State.IMAGE) { + if (show_image() && state != State.IMAGE + && file_transfer.state == FileTransfer.State.COMPLETE) { var content_bak = content; FileImageWidget file_image_widget = null; @@ -108,7 +109,7 @@ public class FileWidget : SizeRequestBox { } catch (Error e) { } } - if (!show_image() && state != State.DEFAULT) { + if (state != State.DEFAULT) { if (content != null) this.remove(content); FileDefaultWidget default_file_widget = new FileDefaultWidget(); default_widget_controller = new FileDefaultWidgetController(default_file_widget); @@ -215,6 +216,7 @@ public class FileDefaultWidgetController : Object { private FileTransfer? file_transfer; public string file_transfer_state { get; set; } public string file_transfer_mime_type { get; set; } + public uint64 file_transfer_transferred_bytes { get; set; } private FileTransfer.State state; @@ -225,6 +227,7 @@ public class FileDefaultWidgetController : Object { this.notify["file-transfer-state"].connect(update_file_info); this.notify["file-transfer-mime-type"].connect(update_file_info); + this.notify["file-transfer-transferred-bytes"].connect(update_file_info); } public void set_file_transfer(FileTransfer file_transfer) { @@ -234,13 +237,15 @@ public class FileDefaultWidgetController : Object { file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("mime-type", this, "file-transfer-mime-type"); + file_transfer.bind_property("transferred-bytes", this, "file-transfer-transferred-bytes"); update_file_info(); } private void update_file_info() { state = file_transfer.state; - widget.update_file_info(file_transfer.mime_type, file_transfer.state, file_transfer.size); + widget.update_file_info(file_transfer.mime_type, file_transfer.transferred_bytes, + file_transfer.direction, file_transfer.state, file_transfer.size); } private void on_clicked() { diff --git a/main/src/ui/file_send_overlay.vala b/main/src/ui/file_send_overlay.vala index da05d63d..7744bfc9 100644 --- a/main/src/ui/file_send_overlay.vala +++ b/main/src/ui/file_send_overlay.vala @@ -81,7 +81,8 @@ public class FileSendOverlay { if (widget == null) { FileDefaultWidget default_widget = new FileDefaultWidget(); default_widget.name_label.label = file_name; - default_widget.update_file_info(mime_type, FileTransfer.State.COMPLETE, (long)file_info.get_size()); + default_widget.update_file_info(mime_type, 0, FileTransfer.DIRECTION_SENT, + FileTransfer.State.COMPLETE, (long)file_info.get_size()); widget = default_widget; } diff --git a/plugins/http-files/src/file_sender.vala b/plugins/http-files/src/file_sender.vala index 957611d0..a9b76f04 100644 --- a/plugins/http-files/src/file_sender.vala +++ b/plugins/http-files/src/file_sender.vala @@ -103,6 +103,18 @@ public class HttpFileSender : FileSender, Object { put_message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body)); put_message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body)); #endif + + file_transfer.transferred_bytes = 0; + put_message.wrote_body_data.connect((chunk) => { + if (file_transfer.size != 0) { +#if SOUP_3_0 + file_transfer.transferred_bytes += chunk; +#else + file_transfer.transferred_bytes += chunk.length; +#endif + } + }); + foreach (var entry in file_send_data.headers.entries) { put_message.request_headers.append(entry.key, entry.value); } diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala index f85049ac..d9c3b658 100644 --- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala +++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala @@ -8,6 +8,7 @@ private const string NS_URI = "urn:xmpp:jingle:apps:file-transfer:5"; public class Module : Jingle.ContentType, XmppStreamModule { + public signal void transferred_bytes(size_t bytes); public signal void file_incoming(XmppStream stream, FileTransfer file_transfer); public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0234_jingle_file_transfer"); @@ -42,7 +43,10 @@ public class Module : Jingle.ContentType, XmppStreamModule { return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, required_transport_type, required_components, full_jid); } - public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error { + public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, + Cancellable cancellable, InputStream input_stream, string basename, + int64 size, string? precondition_name = null, + Object? precondition_options = null) throws Jingle.Error { StanzaNode file_node; StanzaNode description = new StanzaNode.build("description", NS_URI) .add_self_xmlns() @@ -107,7 +111,18 @@ public class Module : Jingle.ContentType, XmppStreamModule { } IOStream io_stream = yield connection.stream.wait_async(); yield io_stream.input_stream.close_async(); - yield io_stream.output_stream.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); + + ssize_t read; + var buffer = new uint8[1024]; + while ((read = yield input_stream.read_async(buffer, Priority.LOW, cancellable)) > 0) { + buffer.length = (int) read; + transferred_bytes((size_t)read); + yield io_stream.output_stream.write_async(buffer, Priority.LOW, cancellable); + buffer.length = 1024; + } + + yield input_stream.close_async(); + yield io_stream.output_stream.close_async(); yield connection.terminate(true); } catch (Error e) { if (session != null) {