From f0abb8aaf9d06106235ca5e0e6b3ca2e425c4422 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 18 Jul 2019 02:03:42 +0200 Subject: Refactor file receive/send interfaces and UI --- libdino/CMakeLists.txt | 2 +- libdino/src/application.vala | 1 - libdino/src/entity/file_transfer.vala | 22 +- libdino/src/service/content_item_store.vala | 16 +- libdino/src/service/file_manager.vala | 298 +++++++++++++++++++++---- libdino/src/service/jingle_file_manager.vala | 122 ---------- libdino/src/service/jingle_file_transfers.vala | 127 +++++++++++ 7 files changed, 408 insertions(+), 180 deletions(-) delete mode 100644 libdino/src/service/jingle_file_manager.vala create mode 100644 libdino/src/service/jingle_file_transfers.vala (limited to 'libdino') diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index 73386d0a..31ba69d3 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -35,7 +35,7 @@ SOURCES src/service/database.vala src/service/entity_capabilities_storage.vala src/service/file_manager.vala - src/service/jingle_file_manager.vala + src/service/jingle_file_transfers.vala src/service/message_processor.vala src/service/message_storage.vala src/service/module_manager.vala diff --git a/libdino/src/application.vala b/libdino/src/application.vala index 396aa91f..da098fb4 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -37,7 +37,6 @@ public interface Dino.Application : GLib.Application { RosterManager.start(stream_interactor, db); ChatInteraction.start(stream_interactor); FileManager.start(stream_interactor, db); - JingleFileManager.start(stream_interactor); ContentItemStore.start(stream_interactor, db); NotificationEvents.start(stream_interactor); SearchProcessor.start(stream_interactor, db); diff --git a/libdino/src/entity/file_transfer.vala b/libdino/src/entity/file_transfer.vala index 68234a48..4e4103b9 100644 --- a/libdino/src/entity/file_transfer.vala +++ b/libdino/src/entity/file_transfer.vala @@ -9,7 +9,7 @@ public class FileTransfer : Object { public enum State { COMPLETE, - IN_PROCESS, + IN_PROGRESS, NOT_STARTED, FAILED } @@ -27,7 +27,7 @@ public class FileTransfer : Object { public bool direction { get; set; } public DateTime time { get; set; } public DateTime? local_time { get; set; } - public Encryption encryption { get; set; } + public Encryption encryption { get; set; default=Encryption.NONE; } private InputStream? input_stream_ = null; public InputStream input_stream { @@ -54,9 +54,9 @@ public class FileTransfer : Object { public string path { get; set; } public string? mime_type { get; set; } // TODO(hrxi): expand to 64 bit - public int size { get; set; } + public int size { get; set; default=-1; } - public State state { get; set; } + public State state { get; set; default=State.NOT_STARTED; } public int provider { get; set; } public string info { get; set; } @@ -110,12 +110,15 @@ public class FileTransfer : Object { .value(db.file_transfer.local_time, (long) local_time.to_unix()) .value(db.file_transfer.encryption, encryption) .value(db.file_transfer.file_name, file_name) - .value(db.file_transfer.path, path) - .value(db.file_transfer.mime_type, mime_type) .value(db.file_transfer.size, size) .value(db.file_transfer.state, state) .value(db.file_transfer.provider, provider) .value(db.file_transfer.info, info); + + if (file_name != null) builder.value(db.file_transfer.file_name, file_name); + if (path != null) builder.value(db.file_transfer.path, path); + if (mime_type != null) builder.value(db.file_transfer.mime_type, mime_type); + id = (int) builder.perform(); notify.connect(on_update); } @@ -142,7 +145,14 @@ public class FileTransfer : Object { update_builder.set(db.file_transfer.encryption, encryption); break; case "file-name": update_builder.set(db.file_transfer.file_name, file_name); break; + case "path": + update_builder.set(db.file_transfer.path, path); break; + case "mime-type": + update_builder.set(db.file_transfer.mime_type, mime_type); break; + case "size": + update_builder.set(db.file_transfer.size, size); break; case "state": + if (state == State.IN_PROGRESS) return; update_builder.set(db.file_transfer.state, state); break; case "provider": update_builder.set(db.file_transfer.provider, provider); break; diff --git a/libdino/src/service/content_item_store.vala b/libdino/src/service/content_item_store.vala index 9f39ce59..3800f35d 100644 --- a/libdino/src/service/content_item_store.vala +++ b/libdino/src/service/content_item_store.vala @@ -257,18 +257,24 @@ public class FileItem : ContentItem { public FileItem(FileTransfer file_transfer, int id) { Jid jid = file_transfer.direction == FileTransfer.DIRECTION_SENT ? file_transfer.account.bare_jid.with_resource(file_transfer.account.resourcepart) : file_transfer.counterpart; - base(id, TYPE, jid, file_transfer.local_time, file_transfer.time, file_transfer.encryption, file_to_message_state(file_transfer.state)); + Entities.Message.Marked mark = Entities.Message.Marked.NONE; + if (file_transfer.direction == FileTransfer.DIRECTION_SENT) { + mark = file_to_message_state(file_transfer.state); + } + base(id, TYPE, jid, file_transfer.local_time, file_transfer.time, file_transfer.encryption, mark); this.file_transfer = file_transfer; - file_transfer.notify["state"].connect_after(() => { - this.mark = file_to_message_state(file_transfer.state); - }); + if (file_transfer.direction == FileTransfer.DIRECTION_SENT) { + file_transfer.notify["state"].connect_after(() => { + this.mark = file_to_message_state(file_transfer.state); + }); + } } private static Entities.Message.Marked file_to_message_state(FileTransfer.State state) { switch (state) { - case FileTransfer.State.IN_PROCESS: + case FileTransfer.State.IN_PROGRESS: return Entities.Message.Marked.UNSENT; case FileTransfer.State.COMPLETE: return Entities.Message.Marked.NONE; diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index 7665936c..50b38f01 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -16,8 +16,9 @@ public class FileManager : StreamInteractionModule, Object { private StreamInteractor stream_interactor; private Database db; private Gee.List file_senders = new ArrayList(); - public Gee.List incoming_processors = new ArrayList(); - private Gee.List outgoing_processors = new ArrayList(); + private Gee.List file_encryptors = new ArrayList(); + private Gee.List file_decryptors = new ArrayList(); + private Gee.List file_providers = new ArrayList(); public static void start(StreamInteractor stream_interactor, Database db) { FileManager m = new FileManager(stream_interactor, db); @@ -32,6 +33,9 @@ public class FileManager : StreamInteractionModule, Object { this.stream_interactor = stream_interactor; this.db = db; DirUtils.create_with_parents(get_storage_dir(), 0700); + + this.add_provider(new JingleFileProvider(stream_interactor)); + this.add_sender(new JingleFileSender(stream_interactor)); } public async void send_file(string uri, Conversation conversation) { @@ -43,6 +47,7 @@ public class FileManager : StreamInteractionModule, Object { file_transfer.time = new DateTime.now_utc(); file_transfer.local_time = new DateTime.now_utc(); file_transfer.encryption = conversation.encryption; + try { File file = File.new_for_path(uri); FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); @@ -50,26 +55,79 @@ public class FileManager : StreamInteractionModule, Object { file_transfer.mime_type = file_info.get_content_type(); file_transfer.size = (int)file_info.get_size(); file_transfer.input_stream = yield file.read_async(); + + yield save_file(file_transfer); + + file_transfer.persist(db); + received_file(file_transfer, conversation); } catch (Error e) { file_transfer.state = FileTransfer.State.FAILED; + warning("Error saving outgoing file: %s", e.message); + return; } - yield save_file(file_transfer); - file_transfer.persist(db); + try { + var file_meta = new FileMeta(); + file_meta.size = file_transfer.size; + file_meta.mime_type = file_transfer.mime_type; + + bool encrypted = false; + foreach (FileEncryptor file_encryptor in file_encryptors) { + if (file_encryptor.can_encrypt_file(conversation, file_transfer)) { + file_meta = file_encryptor.encrypt_file(conversation, file_transfer); + encrypted = true; + break; + } + } + if (conversation.encryption != Encryption.NONE && !encrypted) { + throw new FileSendError.ENCRYPTION_FAILED("File was not encrypted"); + } + + FileSendData file_send_data = null; + foreach (FileSender file_sender in file_senders) { + if (file_sender.can_send(conversation, file_transfer)) { + file_send_data = yield file_sender.prepare_send_file(conversation, file_transfer); + break; + } + } + + foreach (FileEncryptor file_encryptor in file_encryptors) { + if (file_encryptor.can_encrypt_file(conversation, file_transfer)) { + file_send_data = file_encryptor.preprocess_send_file(conversation, file_transfer, file_send_data, file_meta); + break; + } + } - foreach (OutgoingFileProcessor processor in outgoing_processors) { - if (processor.can_process(conversation, file_transfer)) { - processor.process(conversation, file_transfer); + bool sent = false; + foreach (FileSender file_sender in file_senders) { + if (file_sender.can_send(conversation, file_transfer)) { + yield file_sender.send_file(conversation, file_transfer, file_send_data); + sent = true; + break; + } } + if (!sent) { + throw new FileSendError.UPLOAD_FAILED("File was not sent"); + } + + conversation.last_active = file_transfer.time; + } catch (Error e) { + warning("Send file error: %s", e.message); + file_transfer.state = FileTransfer.State.FAILED; } + } - foreach (FileSender file_sender in file_senders) { - if (file_sender.can_send(conversation, file_transfer)) { - file_sender.send_file(conversation, file_transfer); - break; + public async void download_file(FileTransfer file_transfer) { + Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); + + FileProvider? file_provider = null; + foreach (FileProvider fp in file_providers) { + if (file_transfer.provider == fp.get_id()) { + file_provider = fp; } } - received_file(file_transfer, conversation); + + yield download_file_internal(file_provider, file_transfer, conversation); } public bool is_upload_available(Conversation conversation) { @@ -117,24 +175,28 @@ public class FileManager : StreamInteractionModule, Object { } public void add_provider(FileProvider file_provider) { - file_provider.file_incoming.connect((file_transfer, conversation) => { handle_incoming_file.begin(file_provider, file_transfer, conversation); }); + file_providers.add(file_provider); + file_provider.file_incoming.connect((info, from, time, local_time, conversation, receive_data, file_meta) => { + handle_incoming_file.begin(file_provider, info, from, time, local_time, conversation, receive_data, file_meta); + }); } public void add_sender(FileSender file_sender) { - // Order file_senders in reverse order of adding them -- HTTP is added - // later than Jingle. - file_senders.insert(0, file_sender); + file_senders.add(file_sender); file_sender.upload_available.connect((account) => { upload_available(account); }); + file_senders.sort((a, b) => { + return (int) (b.get_priority() - a.get_priority()); + }); } - public void add_incoming_processor(IncomingFileProcessor processor) { - incoming_processors.add(processor); + public void add_file_encryptor(FileEncryptor encryptor) { + file_encryptors.add(encryptor); } - public void add_outgoing_processor(OutgoingFileProcessor processor) { - outgoing_processors.add(processor); + public void add_file_decryptor(FileDecryptor decryptor) { + file_decryptors.add(decryptor); } public bool is_sender_trustworthy(FileTransfer file_transfer, Conversation conversation) { @@ -143,29 +205,124 @@ public class FileManager : StreamInteractionModule, Object { return file_transfer.direction == FileTransfer.DIRECTION_SENT || in_roster; } - private async void handle_incoming_file(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation) { - if (!is_sender_trustworthy(file_transfer, conversation)) return; + private async FileMeta get_file_meta(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation, FileReceiveData receive_data_) throws FileReceiveError { + FileReceiveData receive_data = receive_data_; + FileMeta file_meta = file_provider.get_file_meta(file_transfer); + + if (file_meta.size == -1) { + foreach (FileDecryptor file_decryptor in file_decryptors) { + if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { + receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data); + break; + } + } + + file_meta = yield file_provider.get_meta_info(file_transfer, receive_data, file_meta); - if (file_transfer.size == -1) { - yield file_provider.get_meta_info(file_transfer); + file_transfer.size = (int)file_meta.size; + file_transfer.file_name = file_meta.file_name; + file_transfer.mime_type = file_meta.mime_type; } + return file_meta; + } - if (file_transfer.size >= 0 && file_transfer.size < 5000000) { - string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; + private async void download_file_internal(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation) { + try { + // Get meta info + FileReceiveData receive_data = file_provider.get_file_receive_data(file_transfer); + foreach (FileDecryptor file_decryptor in file_decryptors) { + if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { + receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data); + break; + } + } + FileMeta file_meta = yield get_file_meta(file_provider, file_transfer, conversation, receive_data); + + + InputStream? input_stream = null; + + // Download and decrypt file + file_transfer.state = FileTransfer.State.IN_PROGRESS; + + foreach (FileDecryptor file_decryptor in file_decryptors) { + if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { + file_meta = file_decryptor.prepare_download_file(conversation, file_transfer, receive_data, file_meta); + break; + } + } + + input_stream = yield file_provider.download(file_transfer, receive_data, file_meta); + + foreach (FileDecryptor file_decryptor in file_decryptors) { + if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { + input_stream = yield file_decryptor.decrypt_file(input_stream, conversation, file_transfer, receive_data); + break; + } + } + + // Save file + string filename = Random.next_int().to_string("%x") + "_" + file_meta.file_name; File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); - yield file_provider.download(file_transfer, file); - try { - FileInfo file_info = file_transfer.get_file().query_info("*", FileQueryInfoFlags.NONE); - file_transfer.mime_type = file_info.get_content_type(); - } catch (Error e) { } + if (file_transfer.encryption == Encryption.PGP || file.get_path().has_suffix(".pgp")) { + file = File.new_for_path(file.get_path().substring(0, file.get_path().length - 4)); + } - file_transfer.persist(db); - received_file(file_transfer, conversation); + OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); + yield os.splice_async(input_stream, 0); + os.close(); + file_transfer.size = (int)file_meta.size; + file_transfer.file_name = file_meta.file_name; + file_transfer.path = file.get_basename(); + file_transfer.input_stream = yield file.read_async(); + + FileInfo file_info = file_transfer.get_file().query_info("*", FileQueryInfoFlags.NONE); + file_transfer.mime_type = file_info.get_content_type(); + + file_transfer.state = FileTransfer.State.COMPLETE; + } catch (Error e) { + warning("Error downloading file: %s", e.message); + file_transfer.state = FileTransfer.State.FAILED; } } - private async void save_file(FileTransfer file_transfer) { + private async void handle_incoming_file(FileProvider file_provider, string info, Jid from, DateTime time, DateTime local_time, Conversation conversation, FileReceiveData receive_data, FileMeta file_meta) { + FileTransfer file_transfer = new FileTransfer(); + file_transfer.account = conversation.account; + file_transfer.direction = from.bare_jid.equals(conversation.account.bare_jid) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED; + file_transfer.counterpart = file_transfer.direction == FileTransfer.DIRECTION_RECEIVED ? from : conversation.counterpart; + if (conversation.type_ in new Conversation.Type[]{Conversation.Type.GROUPCHAT, Conversation.Type.GROUPCHAT_PM}) { + file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; + } else { + file_transfer.ourpart = conversation.account.bare_jid.with_resource(conversation.account.resourcepart); + } + file_transfer.time = time; + file_transfer.local_time = local_time; + file_transfer.provider = file_provider.get_id(); + file_transfer.file_name = file_meta.file_name; + file_transfer.size = (int)file_meta.size; + file_transfer.info = info; + + file_transfer.persist(db); + + if (is_sender_trustworthy(file_transfer, conversation)) { + try { + yield get_file_meta(file_provider, file_transfer, conversation, receive_data); + + if (file_transfer.size >= 0 && file_transfer.size < 5000000) { + yield download_file_internal(file_provider, file_transfer, conversation); + } + } catch (Error e) { + warning("Error downloading file: %s", e.message); + file_transfer.state = FileTransfer.State.FAILED; + } + } + + conversation.last_active = file_transfer.time; + received_file(file_transfer, conversation); + } + + private async void save_file(FileTransfer file_transfer) throws FileSendError { try { string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); @@ -176,33 +333,84 @@ public class FileManager : StreamInteractionModule, Object { file_transfer.path = filename; file_transfer.input_stream = yield file.read_async(); } catch (Error e) { - file_transfer.state = FileTransfer.State.FAILED; + throw new FileSendError.SAVE_FAILED("Saving file error: %s".printf(e.message)); } } +} +public errordomain FileSendError { + ENCRYPTION_FAILED, + UPLOAD_FAILED, + SAVE_FAILED +} + +public errordomain FileReceiveError { + GET_METADATA_FAILED, + DECRYPTION_FAILED, + DOWNLOAD_FAILED +} + +public class FileMeta { + public int64 size = -1; + public string? mime_type = null; + public string? file_name = null; + public Encryption encryption = Encryption.NONE; +} + +public class HttpFileMeta : FileMeta { + public Message message; +} + +public class FileSendData { } + +public class HttpFileSendData : FileSendData { + public string url_down { get; set; } + public string url_up { get; set; } + + public bool encrypt_message { get; set; default=true; } +} + +public class FileReceiveData { } + +public class HttpFileReceiveData : FileReceiveData { + public string url { get; set; } } public interface FileProvider : Object { - public signal void file_incoming(FileTransfer file_transfer, Conversation conversation); - public abstract async void get_meta_info(FileTransfer file_transfer); - public abstract async void download(FileTransfer file_transfer, File file); + public signal void file_incoming(string info, Jid from, DateTime time, DateTime local_time, Conversation conversation, FileReceiveData receive_data, FileMeta file_meta); + + public abstract FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError; + public abstract FileReceiveData? get_file_receive_data(FileTransfer file_transfer); + + public abstract async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError; + public abstract async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError; + + public abstract int get_id(); } public interface FileSender : Object { public signal void upload_available(Account account); + public abstract bool is_upload_available(Conversation conversation); public abstract bool can_send(Conversation conversation, FileTransfer file_transfer); - public abstract void send_file(Conversation conversation, FileTransfer file_transfer); + public abstract async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError; + public abstract async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data) throws FileSendError; + + public abstract int get_id(); + public abstract float get_priority(); } -public interface IncomingFileProcessor : Object { - public abstract bool can_process(FileTransfer file_transfer); - public abstract void process(FileTransfer file_transfer); +public interface FileEncryptor : Object { + public abstract bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer); + public abstract FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError; + public abstract FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError; } -public interface OutgoingFileProcessor : Object { - public abstract bool can_process(Conversation conversation, FileTransfer file_transfer); - public abstract void process(Conversation conversation, FileTransfer file_transfer); +public interface FileDecryptor : Object { + public abstract FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data); + public abstract FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta); + public abstract bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data); + public abstract async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError; } } diff --git a/libdino/src/service/jingle_file_manager.vala b/libdino/src/service/jingle_file_manager.vala deleted file mode 100644 index 595afae0..00000000 --- a/libdino/src/service/jingle_file_manager.vala +++ /dev/null @@ -1,122 +0,0 @@ -using Gdk; -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { - -public class JingleFileManager : StreamInteractionModule, FileProvider, FileSender, Object { - public static ModuleIdentity IDENTITY = new ModuleIdentity("jingle_files"); - public string id { get { return IDENTITY.id; } } - - private StreamInteractor stream_interactor; - private HashMap file_transfers - = new HashMap(); - - public static void start(StreamInteractor stream_interactor) { - JingleFileManager m = new JingleFileManager(stream_interactor); - stream_interactor.add_module(m); - } - - private JingleFileManager(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - - stream_interactor.get_module(FileManager.IDENTITY).add_sender(this); - stream_interactor.get_module(FileManager.IDENTITY).add_provider(this); - stream_interactor.stream_negotiated.connect(on_stream_negotiated); - } - - private void on_stream_negotiated(Account account, XmppStream stream) { - stream_interactor.module_manager.get_module(account, Xmpp.Xep.JingleFileTransfer.Module.IDENTITY).file_incoming.connect((stream, jingle_file_transfer) => { - Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jingle_file_transfer.peer.bare_jid, account); - if (conversation == null) { - // TODO(hrxi): What to do? - return; - } - string id = random_uuid(); - - FileTransfer file_transfer = new FileTransfer(); - file_transfer.account = account; - file_transfer.counterpart = jingle_file_transfer.peer.bare_jid; - file_transfer.ourpart = account.bare_jid; - file_transfer.encryption = Encryption.NONE; - file_transfer.time = new DateTime.now_utc(); - file_transfer.local_time = new DateTime.now_utc(); - file_transfer.direction = FileTransfer.DIRECTION_RECEIVED; - file_transfer.file_name = jingle_file_transfer.file_name; - file_transfer.size = (int)jingle_file_transfer.size; - file_transfer.state = FileTransfer.State.NOT_STARTED; - file_transfer.provider = 1; - file_transfer.info = id; - file_transfers[id] = jingle_file_transfer; - - file_incoming(file_transfer, conversation); - }); - } - - async void get_meta_info(FileTransfer file_transfer) { - // In Jingle, all the metadata is provided up-front, so there's no more - // metadata to get. - } - async void download(FileTransfer file_transfer, File file_) { - // TODO(hrxi) What should happen if `stream == null`? - XmppStream? stream = stream_interactor.get_stream(file_transfer.account); - Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_file_transfer = file_transfers[file_transfer.info]; - jingle_file_transfer.accept(stream); - file_transfer.input_stream = jingle_file_transfer.stream; - - // TODO(hrxi): BEGIN: Copied from plugins/http-files/src/file_provider.vala - foreach (IncomingFileProcessor processor in stream_interactor.get_module(FileManager.IDENTITY).incoming_processors) { - if (processor.can_process(file_transfer)) { - processor.process(file_transfer); - } - } - - // TODO(hrxi): should this be an &&? - File file = file_; - if (file_transfer.encryption == Encryption.PGP || file.get_path().has_suffix(".pgp")) { - file = File.new_for_path(file.get_path().substring(0, file.get_path().length - 4)); - } - // TODO(hrxi): END: Copied from plugins/http-files/src/file_provider.vala - - try { - OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); - yield os.splice_async(file_transfer.input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); - file_transfer.path = file.get_basename(); - file_transfer.input_stream = yield file.read_async(); - - file_transfer.state = FileTransfer.State.COMPLETE; - } catch (Error e) { - file_transfer.state = FileTransfer.State.FAILED; - return; - } - } - - public bool is_upload_available(Conversation conversation) { - // TODO(hrxi) Here and in `send_file`: What should happen if `stream == null`? - XmppStream? stream = stream_interactor.get_stream(conversation.account); - foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { - if (stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { - return true; - } - } - return false; - } - public bool can_send(Conversation conversation, FileTransfer file_transfer) { - return file_transfer.encryption != Encryption.OMEMO; - } - public void send_file(Conversation conversation, FileTransfer file_transfer) { - XmppStream? stream = stream_interactor.get_stream(file_transfer.account); - foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { - // TODO(hrxi): Prioritization of transports (and resources?). - if (!stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { - continue; - } - stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream.begin(stream, full_jid, file_transfer.input_stream, file_transfer.file_name, file_transfer.size); - return; - } - } -} - -} diff --git a/libdino/src/service/jingle_file_transfers.vala b/libdino/src/service/jingle_file_transfers.vala new file mode 100644 index 00000000..c90986d2 --- /dev/null +++ b/libdino/src/service/jingle_file_transfers.vala @@ -0,0 +1,127 @@ +using Gdk; +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + +public class JingleFileProvider : FileProvider, Object { + + private StreamInteractor stream_interactor; + private HashMap file_transfers = new HashMap(); + + public JingleFileProvider(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + stream_interactor.stream_negotiated.connect(on_stream_negotiated); + } + + public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError { + var file_meta = new FileMeta(); + file_meta.file_name = file_transfer.file_name; + file_meta.size = file_transfer.size; + return file_meta; + } + + public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) { + return new FileReceiveData(); + } + + public async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { + return file_meta; + } + + public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { + // TODO(hrxi) What should happen if `stream == null`? + XmppStream? stream = stream_interactor.get_stream(file_transfer.account); + Xmpp.Xep.JingleFileTransfer.FileTransfer? jingle_file_transfer = file_transfers[file_transfer.info]; + if (jingle_file_transfer == null) { + throw new FileReceiveError.DOWNLOAD_FAILED("Transfer data not available anymore"); + } + jingle_file_transfer.accept(stream); + return jingle_file_transfer.stream; + } + + public int get_id() { + return 1; + } + + private void on_stream_negotiated(Account account, XmppStream stream) { + stream_interactor.module_manager.get_module(account, Xmpp.Xep.JingleFileTransfer.Module.IDENTITY).file_incoming.connect((stream, jingle_file_transfer) => { + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jingle_file_transfer.peer.bare_jid, account); + if (conversation == null) { + // TODO(hrxi): What to do? + return; + } + string id = random_uuid(); + + file_transfers[id] = jingle_file_transfer; + + FileMeta file_meta = new FileMeta(); + file_meta.size = jingle_file_transfer.size; + file_meta.file_name = jingle_file_transfer.file_name; + + var time = new DateTime.now_utc(); + var from = jingle_file_transfer.peer.bare_jid; + + file_incoming(id, from, time, time, conversation, new FileReceiveData(), file_meta); + }); + } +} + +public class JingleFileSender : FileSender, Object { + + private StreamInteractor stream_interactor; + + public JingleFileSender(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public bool is_upload_available(Conversation conversation) { + XmppStream? stream = stream_interactor.get_stream(conversation.account); + if (stream == null) return false; + + foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { + if (stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { + return true; + } + } + return false; + } + + public bool can_send(Conversation conversation, FileTransfer file_transfer) { + XmppStream? stream = stream_interactor.get_stream(file_transfer.account); + if (stream == null) return false; + + foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { + if (stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { + return true; + } + } + return false; + } + + public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError { + return new FileSendData(); + } + + public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data) throws FileSendError { + // TODO(hrxi) What should happen if `stream == null`? + XmppStream? stream = stream_interactor.get_stream(file_transfer.account); + foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { + // TODO(hrxi): Prioritization of transports (and resources?). + if (!stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { + continue; + } + stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream.begin(stream, full_jid, file_transfer.input_stream, file_transfer.file_name, file_transfer.size); + return; + } + } + + public int get_id() { return 1; } + + public float get_priority() { return 50; } +} + +} -- cgit v1.2.3-54-g00ecf