Compare commits

...

10 Commits

Author SHA1 Message Date
Xavier Del Campo Romero 795f2db716
BUILD.md: Add dependencies for Alpine Linux 2023-10-09 13:16:47 +02:00
Xavier Del Campo Romero f277f9a33e
Inhibit suspend on file upload/download 2023-10-09 13:16:46 +02:00
Xavier Del Campo Romero 3623c70f8e
WIP call notifications 2023-10-09 13:16:46 +02:00
Xavier Del Campo Romero 5ddeba5e1d
Remove USE_SOUP3
CMake had already replaced it with SOUP_VERSION, so it made no sense for
configure to insist on USE_SOUP3.
2023-10-09 13:16:46 +02:00
Xavier Del Campo Romero 778132642f
SoupVersion.cmake: Force version required by Nice
Otherwise, Dino would crash due to conflicting Soup 2 and 3 symbols.
2023-10-09 13:16:46 +02:00
Xavier Del Campo Romero 1f5c0fea3f
connection_manager.vala: Use yield on disconnect() 2023-10-09 13:16:46 +02:00
Xavier Del Campo Romero 4e963868a6
file_sender.vala: Split very long line 2023-10-09 13:16:45 +02:00
Xavier Del Campo Romero eef34926fd
Check XmppStream against null
Most of the calls to stream_interactor.get_stream(account) were already
doing null checks, but there were still some missing.
2023-10-09 13:16:45 +02:00
Xavier Del Campo Romero eaa0f2fd3b
file_manager.vala: Disable automatic download 2023-10-09 13:16:45 +02:00
Xavier Del Campo Romero 800958be65
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.
2023-10-09 13:16:15 +02:00
20 changed files with 170 additions and 39 deletions

View File

@ -1,5 +1,5 @@
find_package(Nice QUIET)
if (Nice_FOUND AND NOT SOUP_VERSION AND NOT USE_SOUP3)
if (Nice_FOUND)
file(GET_RUNTIME_DEPENDENCIES
RESOLVED_DEPENDENCIES_VAR Nice_DEPENDENCIES
UNRESOLVED_DEPENDENCIES_VAR Nice_UNRESOLVED_DEPENDENCIES
@ -9,11 +9,21 @@ if (Nice_FOUND AND NOT SOUP_VERSION AND NOT USE_SOUP3)
)
foreach (lib ${Nice_DEPENDENCIES})
if (lib MATCHES ".*/libsoup-3.*")
if(SOUP_VERSION AND NOT SOUP_VERSION EQUAL 3)
message(FATAL_ERROR "libnice-${Nice_VERSION} depends on "
"libsoup-3, but SOUP_VERSION=${SOUP_VERSION} was given.")
endif()
set(SOUP_VERSION 3)
endif ()
endforeach ()
foreach (lib ${Nice_DEPENDENCIES})
if (lib MATCHES ".*/libsoup-2.*")
if(SOUP_VERSION AND NOT SOUP_VERSION EQUAL 2)
message(FATAL_ERROR "libnice-${Nice_VERSION} depends on "
"libsoup-2, but SOUP_VERSION=${SOUP_VERSION} was given.")
endif()
set(SOUP_VERSION 2)
endif ()
endforeach ()
@ -24,7 +34,7 @@ elseif (NOT SOUP_VERSION)
find_package(Soup2 QUIET)
find_package(Soup3 QUIET)
# Only use libsoup 3 if specifically requested or when libsoup 2 is not available
if (Soup3_FOUND AND NOT Soup2_FOUND OR USE_SOUP3)
if (Soup3_FOUND AND NOT Soup2_FOUND)
set(SOUP_VERSION 3)
else ()
set(SOUP_VERSION 2)

6
configure vendored
View File

@ -22,7 +22,7 @@ DISABLE_FAST_VAPI=
LIB_SUFFIX=
NO_DEBUG=
FETCH_ONLY=
USE_SOUP3=
SOUP_VERSION=2
EXEC_PREFIX=
BINDIR=
@ -113,7 +113,7 @@ while true; do
--valac-flags ) VALAC_FLAGS="$2"; shift; shift ;;
--lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;;
--with-libsignal-in-tree ) BUILD_LIBSIGNAL_IN_TREE=yes; shift ;;
--with-libsoup3 ) USE_SOUP3=yes; shift ;;
--with-libsoup3 ) SOUP_VERSION=3; shift ;;
--disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;;
--no-debug ) NO_DEBUG=yes; shift ;;
--fetch-only ) FETCH_ONLY=yes; shift ;;
@ -259,7 +259,7 @@ cmake -G "$cmake_type" \
-DDISABLED_PLUGINS="$DISABLED_PLUGINS" \
-DBUILD_TESTS="$BUILD_TESTS" \
-DBUILD_LIBSIGNAL_IN_TREE="$BUILD_LIBSIGNAL_IN_TREE" \
-DUSE_SOUP3="$USE_SOUP3" \
-DSOUP_VERSION="$SOUP_VERSION" \
-DVALA_EXECUTABLE="$VALAC" \
-DCMAKE_VALA_FLAGS="$VALACFLAGS" \
-DDISABLE_FAST_VAPI="$DISABLE_FAST_VAPI" \

View File

@ -20,6 +20,12 @@ sudo zypper install cmake gcc-c++ gpgme-devel libnotify-devel libgcrypt-devel pk
sudo pacman -S cmake vala ninja glib2 glib-networking gtk3 gpgme libgee>=0.10 libgcrypt libsoup sqlite qrencode gspell gstreamer gst-plugins-base gst-plugins-good gst-plugin-gtk webrtc-audio-processing libnice libsrtp libsignal-protocol-c
```
### Alpine Linux
```
sudo apk add git make cmake gcc g++ vala libgee-dev sqlite-dev gst-plugins-good gtk4.0-dev libadwaita-dev libgcrypt-dev libsrtp-dev libsoup-dev libnice-dev musl-utils libqrencode-dev libsignal-protocol-c-dev gpgme-dev gstreamer-dev gst-plugins-base-dev
```
# libsignal-protocol-c
If build complains about missing or incompatible libsignal-protocol-c and it is not provided by your distribution, you can fetch and build it in tree by using `./configure --with-libsignal-in-tree`.

View File

@ -15,6 +15,8 @@ public interface Application : GLib.Application {
public abstract StreamInteractor stream_interactor { get; set; }
public abstract Plugins.Registry plugin_registry { get; set; }
public abstract SearchPathGenerator? search_path_generator { get; set; }
public abstract uint inhibit_app(string reason);
public abstract void uninhibit_app(uint cookie);
internal static string print_xmpp;

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

@ -144,7 +144,7 @@ public class AvatarManager : StreamInteractionModule, Object {
}
uint8[] buffer;
pixbuf.save_to_buffer(out buffer, "png");
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) {
Xmpp.Xep.UserAvatars.publish_png(stream, buffer, pixbuf.width, pixbuf.height);
}

View File

@ -21,22 +21,26 @@ public class BlockingManager : StreamInteractionModule, Object {
}
public bool is_blocked(Account account, Jid jid) {
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_blocked(stream, jid.to_string());
}
public void block(Account account, Jid jid) {
XmppStream stream = stream_interactor.get_stream(account);
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).block(stream, { jid.to_string() });
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) {
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).block(stream, { jid.to_string() });
}
}
public void unblock(Account account, Jid jid) {
XmppStream stream = stream_interactor.get_stream(account);
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).unblock(stream, { jid.to_string() });
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) {
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).unblock(stream, { jid.to_string() });
}
}
public bool is_supported(Account account) {
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_supported(stream);
}
}

View File

@ -126,7 +126,7 @@ public class Dino.CallState : Object {
call.state = Call.State.DECLINED;
if (use_cim) {
XmppStream stream = stream_interactor.get_stream(call.account);
XmppStream? stream = stream_interactor.get_stream(call.account);
if (stream == null) return;
stream.get_module(Xep.CallInvites.Module.IDENTITY).send_reject(stream, cim_counterpart, cim_call_id, cim_message_type);
}
@ -143,7 +143,7 @@ public class Dino.CallState : Object {
peers_cpy.add_all(peers.values);
if (group_call != null) {
XmppStream stream = stream_interactor.get_stream(call.account);
XmppStream? stream = stream_interactor.get_stream(call.account);
if (stream != null) {
stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, group_call.muc_jid);
}

View File

@ -56,15 +56,15 @@ public class ConnectionManager : Object {
public bool acked;
public Connection() {
reset();
reset.begin();
}
public void reset() {
public async void reset() {
acked = false;
if (stream != null) {
stream.detach_modules();
stream.disconnect.begin();
yield stream.disconnect();
}
stream = null;
uuid = Xmpp.random_uuid();

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);
@ -249,14 +250,19 @@ public class FileManager : StreamInteractionModule, Object {
OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION);
file_transfer.cancellable.reset();
uint inhibit_cookie = Application.get_default()
.inhibit_app("Ongoing file download");
uint8[] buffer = new uint8[1024];
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;
}
yield input_stream.close_async(Priority.LOW, file_transfer.cancellable);
Application.get_default().uninhibit_app(inhibit_cookie);
yield os.close_async(Priority.LOW, file_transfer.cancellable);
file_transfer.path = file.get_basename();
file_transfer.input_stream = yield file.read_async();
@ -288,6 +294,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;
@ -307,11 +314,6 @@ public class FileManager : StreamInteractionModule, Object {
warning("Error downloading file: %s", e.message);
file_transfer.state = FileTransfer.State.FAILED;
}
if (file_transfer.size >= 0 && file_transfer.size < 5000000) {
download_file_internal.begin(file_provider, file_transfer, conversation, (_, res) => {
download_file_internal.end(res);
});
}
}
conversation.last_active = file_transfer.time;
@ -324,7 +326,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

@ -366,7 +366,10 @@ public class Dino.HistorySync {
* prev_page_result: null if this is the first page request
**/
private async PageRequestResult get_mam_page(Account account, Xmpp.MessageArchiveManagement.V2.MamQueryParams query_params, PageRequestResult? prev_page_result, Cancellable? cancellable = null) {
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null) {
return new PageRequestResult.with_result(PageResult.Error);
}
Xmpp.MessageArchiveManagement.QueryResult query_result = null;
if (prev_page_result == null) {
query_result = yield Xmpp.MessageArchiveManagement.V2.query_archive(stream, query_params, cancellable);
@ -592,5 +595,9 @@ public class Dino.HistorySync {
this.query_result = query_result;
this.stanzas = stanzas;
}
public PageRequestResult.with_result(PageResult page_result) {
this.page_result = page_result;
}
}
}

View File

@ -96,7 +96,8 @@ public class NotificationEvents : StreamInteractionModule, Object {
}
break;
case CallItem.TYPE:
// handled in `on_call_incoming`
// Also handled in `on_call_incoming`
notify_content_item(item, conversation);
break;
}
}

View File

@ -342,5 +342,20 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
});
dialog.present();
}
}
private uint inhibit_app(string reason) {
uint inhibit_cookie = inhibit(window, SUSPEND, reason);
if (inhibit_cookie == 0) {
warning("suspend inhibit request failed or unsupported");
}
return inhibit_cookie;
}
private void uninhibit_app(uint inhibit_cookie) {
if (inhibit_cookie != 0) {
uninhibit(inhibit_cookie);
}
}
}

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;
@ -143,10 +144,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 +158,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

@ -25,7 +25,11 @@ public class HttpFileSender : FileSender, Object {
if (stream == null) return null;
try {
var slot_result = yield stream_interactor.module_manager.get_module(file_transfer.account, Xmpp.Xep.HttpFileUpload.Module.IDENTITY).request_slot(stream, file_transfer.server_file_name, file_meta.size, file_meta.mime_type);
var slot_result = yield stream_interactor.module_manager
.get_module(file_transfer.account,
Xmpp.Xep.HttpFileUpload.Module.IDENTITY)
.request_slot(stream, file_transfer.server_file_name,
file_meta.size, file_meta.mime_type);
send_data.url_down = slot_result.url_get;
send_data.url_up = slot_result.url_put;
send_data.headers = slot_result.headers;
@ -106,19 +110,38 @@ 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);
}
uint inhibit_cookie = 0;
try {
inhibit_cookie = Application.get_default()
.inhibit_app("Ongoing file upload");
#if SOUP_3_0
yield session.send_async(put_message, GLib.Priority.LOW, file_transfer.cancellable);
#else
yield session.send_async(put_message, file_transfer.cancellable);
#endif
Application.get_default().uninhibit_app(inhibit_cookie);
if (put_message.status_code < 200 || put_message.status_code >= 300) {
throw new FileSendError.UPLOAD_FAILED("HTTP status code %s".printf(put_message.status_code.to_string()));
}
} catch (Error e) {
Application.get_default().uninhibit_app(inhibit_cookie);
throw new FileSendError.UPLOAD_FAILED("HTTP upload error: %s".printf(e.message));
}
}

View File

@ -8,9 +8,35 @@ public class Plugin : RootInterface, Object {
public void registered(Dino.Application app) {
this.app = app;
Canberra.Context.create(out sound_context);
string sound_id = "",
description = "Unknown notification";
app.stream_interactor.get_module(NotificationEvents.IDENTITY).notify_content_item.connect((item, conversation) => {
sound_context.play(0, Canberra.PROP_EVENT_ID, "message-new-instant", Canberra.PROP_EVENT_DESCRIPTION, "New Dino message");
var? module = app.stream_interactor.get_module(NotificationEvents.IDENTITY);
if (module == null)
return;
module.notify_content_item.connect((item, conversation) => {
switch (item.type_) {
case MessageItem.TYPE:
description = "New Dino message";
sound_id = "message-new-instant";
break;
case CallItem.TYPE:
description = "Incoming call";
sound_id = "phone-incoming-call";
break;
case FileItem.TYPE:
description = "Incoming file";
sound_id = "message-new-instant";
break;
}
sound_context.play(0,
Canberra.PROP_EVENT_ID,
sound_id,
Canberra.PROP_EVENT_DESCRIPTION,
description);
});
}

View File

@ -39,7 +39,11 @@ namespace Xmpp.MessageArchiveManagement.V2 {
}
}
private StanzaNode create_base_query(XmppStream stream, MamQueryParams mam_params) {
private StanzaNode create_base_query(XmppStream? stream, MamQueryParams mam_params) {
if (stream == null) {
return new StanzaNode();
}
var fields = new ArrayList<DataForms.DataForm.Field>();
if (mam_params.with != null) {
@ -61,7 +65,12 @@ namespace Xmpp.MessageArchiveManagement.V2 {
return MessageArchiveManagement.create_base_query(stream, MessageArchiveManagement.NS_URI_2, mam_params.query_id, fields);
}
public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) {
public async QueryResult query_archive(XmppStream? stream, MamQueryParams mam_params, Cancellable? cancellable = null) {
if (stream == null) {
var result = new QueryResult();
result.error = true;
return result;
}
var query_node = create_base_query(stream, mam_params);
if (!mam_params.use_ns2_extended) {
query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id));

View File

@ -53,7 +53,11 @@ public class Module : XmppStreamModule {
}
}
internal StanzaNode create_base_query(XmppStream stream, string ns, string? queryid, Gee.List<DataForms.DataForm.Field> fields) {
internal StanzaNode create_base_query(XmppStream? stream, string ns, string? queryid, Gee.List<DataForms.DataForm.Field> fields) {
if (stream == null) {
return new StanzaNode();
}
DataForms.DataForm data_form = new DataForms.DataForm();
DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" };
@ -156,7 +160,11 @@ public class MessageFlag : Xmpp.MessageFlag {
}
private static string NS_VER(XmppStream stream) {
return stream.get_flag(Flag.IDENTITY).ns_ver;
var? flag = stream.get_flag(Flag.IDENTITY);
if (flag == null) {
return "";
}
return flag.ns_ver;
}
}