diff options
Diffstat (limited to 'libdino/src/service/file_manager.vala')
-rw-r--r-- | libdino/src/service/file_manager.vala | 298 |
1 files changed, 253 insertions, 45 deletions
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<FileSender> file_senders = new ArrayList<FileSender>(); - public Gee.List<IncomingFileProcessor> incoming_processors = new ArrayList<IncomingFileProcessor>(); - private Gee.List<OutgoingFileProcessor> outgoing_processors = new ArrayList<OutgoingFileProcessor>(); + private Gee.List<FileEncryptor> file_encryptors = new ArrayList<FileEncryptor>(); + private Gee.List<FileDecryptor> file_decryptors = new ArrayList<FileDecryptor>(); + private Gee.List<FileProvider> file_providers = new ArrayList<FileProvider>(); 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; } } |