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.
This commit is contained in:
Xavier Del Campo Romero 2023-10-04 01:23:11 +02:00
parent f3c50f0739
commit 6d3aef2718
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2
8 changed files with 71 additions and 10 deletions

View File

@ -70,6 +70,7 @@ public class FileTransfer : Object {
public State state { get; set; default=State.NOT_STARTED; }
public int provider { get; set; }
public string info { get; set; }
public uint64 transferred_bytes {get; set; }
public Cancellable cancellable { get; default=new Cancellable(); }
private Database? db;

View File

@ -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);
@ -253,6 +254,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;
}
@ -288,6 +290,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;
@ -324,7 +327,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) {

View File

@ -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)");
}

View File

@ -31,7 +31,8 @@ public class FileDefaultWidget : EventBox {
cancel_button = new ModelButton() { text=_("Cancel"), visible=true };
}
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.active = false; // A hidden spinning spinner still uses CPU. Deactivate asap
@ -58,7 +59,17 @@ public class FileDefaultWidget : EventBox {
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.active = true;
image_stack.set_visible_child_name("spinner");

View File

@ -65,7 +65,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;
@ -120,6 +121,7 @@ public class FileDefaultWidgetController : Object {
public string file_transfer_path { get; set; }
public string file_transfer_state { get; set; }
public string file_transfer_mime_type { get; set; }
public uint64 file_transfer_transferred_bytes { get; set; }
private StreamInteractor? stream_interactor;
private string file_uri;
@ -143,10 +145,12 @@ public class FileDefaultWidgetController : Object {
file_transfer.bind_property("path", this, "file-transfer-path");
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");
this.notify["file-transfer-path"].connect(update_file_info);
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);
update_file_info();
}
@ -155,13 +159,15 @@ public class FileDefaultWidgetController : Object {
file_uri = file.get_uri();
state = FileTransfer.State.COMPLETE;
widget.name_label.label = this.file_name = file_name;
widget.update_file_info(mime_type, state, -1);
widget.update_file_info(mime_type, 0, false, state, -1);
}
private void update_file_info() {
file_uri = file_transfer.get_file().get_uri();
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 open_file() {

View File

@ -74,7 +74,8 @@ public class FileSendOverlay : Gtk.EventBox {
if (widget == null) {
FileDefaultWidget default_widget = new FileDefaultWidget() { visible=true };
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;
}

View File

@ -106,6 +106,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);
}

View File

@ -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<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(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) {