From 8b43df8ec3f92477f857280668a9f29f0b9d6229 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sat, 14 Oct 2017 19:34:30 +0200 Subject: [PATCH] Handle file uploading in libdino & main, have http upload as provider --- libdino/src/plugin/interfaces.vala | 9 -- libdino/src/service/file_manager.vala | 109 ++++++++++++++---- main/CMakeLists.txt | 1 + main/data/manage_accounts/dialog.ui | 2 +- main/src/ui/chat_input/smiley_converter.vala | 4 +- .../ui/conversation_titlebar/file_entry.vala | 72 ++++++++++++ main/src/ui/conversation_titlebar/view.vala | 1 + plugins/http-files/CMakeLists.txt | 1 - .../src/contact_titlebar_entry.vala | 72 ------------ plugins/http-files/src/file_provider.vala | 36 +----- plugins/http-files/src/manager.vala | 64 ++++++---- plugins/http-files/src/plugin.vala | 4 +- 12 files changed, 207 insertions(+), 168 deletions(-) create mode 100644 main/src/ui/conversation_titlebar/file_entry.vala delete mode 100644 plugins/http-files/src/contact_titlebar_entry.vala diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 09877560..655ef13a 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -109,15 +109,6 @@ public interface MessageDisplayProvider : Object { public abstract MetaConversationItem? get_item(Entities.Message message, Entities.Conversation conversation); } -public interface FileProvider : Object { - public signal void file_incoming(FileTransfer file_transfer); -} - -public interface FileProcessor : Object { - public abstract bool can_process(FileTransfer file_transfer); - public abstract FileTransfer process(FileTransfer file_transfer); -} - public interface FileWidget : Object { public abstract Object get_widget(WidgetType type); } diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index b165039f..6517e9a5 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -10,11 +10,12 @@ public class FileManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("file"); public string id { get { return IDENTITY.id; } } + public signal void upload_available(Account account); public signal void received_file(FileTransfer file_transfer); private StreamInteractor stream_interactor; private Database db; - private Gee.List file_transfers = new ArrayList(); + private Gee.List file_senders = new ArrayList(); public static void start(StreamInteractor stream_interactor, Database db) { FileManager m = new FileManager(stream_interactor, db); @@ -31,24 +32,39 @@ public class FileManager : StreamInteractionModule, Object { DirUtils.create_with_parents(get_storage_dir(), 0700); } - public void add_provider(Plugins.FileProvider file_provider) { - file_provider.file_incoming.connect((file_transfer) => { - file_transfers.add(file_transfer); - string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; - file_transfer.file_name = filename; - File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); - try { - OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); - os.splice(file_transfer.input_stream, 0); - os.close(); - file_transfer.state = FileTransfer.State.COMPLETE; - } catch (Error e) { - file_transfer.state = FileTransfer.State.FAILED; + public void send_file(string uri, Conversation conversation) { + File file = File.new_for_path(uri); + FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); + + FileTransfer file_transfer = new FileTransfer(); + file_transfer.account = conversation.account; + file_transfer.counterpart = conversation.counterpart; + file_transfer.ourpart = conversation.account.bare_jid; + file_transfer.direction = FileTransfer.DIRECTION_SENT; + file_transfer.time = new DateTime.now_utc(); + file_transfer.local_time = new DateTime.now_utc(); + file_transfer.encryption = Encryption.NONE; + file_transfer.file_name = file_info.get_display_name(); + file_transfer.input_stream = file.read(); + file_transfer.mime_type = file_info.get_content_type(); + file_transfer.size = (int)file_info.get_size(); + save_file(file_transfer); + + file_transfer.persist(db); + + foreach (FileSender file_sender in file_senders) { + if (file_sender.can_send(conversation, file_transfer)) { + file_sender.send_file(conversation, file_transfer); } - file_transfer.persist(db); - file_transfer.input_stream = file.read(); - received_file(file_transfer); - }); + } + received_file(file_transfer); + } + + public bool is_upload_available(Conversation conversation) { + foreach (FileSender file_sender in file_senders) { + if (file_sender.is_upload_available(conversation)) return true; + } + return false; } public Gee.List get_file_transfers(Account account, Jid counterpart, DateTime after, DateTime before) { @@ -62,13 +78,66 @@ public class FileManager : StreamInteractionModule, Object { Gee.List ret = new ArrayList(); foreach (Qlite.Row row in select) { FileTransfer file_transfer = new FileTransfer.from_row(db, row); - File file = File.new_for_path(Path.build_filename(get_storage_dir(), file_transfer.file_name)); - file_transfer.input_stream = file.read(); + File file = File.new_for_path(Path.build_filename(get_storage_dir(), file_transfer.path ?? file_transfer.file_name)); + try { + file_transfer.input_stream = file.read(); + } catch (IOError e) { } ret.insert(0, file_transfer); } return ret; } + public void add_provider(FileProvider file_provider) { + file_provider.file_incoming.connect((file_transfer) => { + save_file(file_transfer); + file_transfer.persist(db); + received_file(file_transfer); + }); + } + + public void add_sender(FileSender file_sender) { + file_senders.add(file_sender); + file_sender.upload_available.connect((account) => { + upload_available(account); + }); + } + + private void save_file(FileTransfer file_transfer) { + 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)); + try { + OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); + os.splice(file_transfer.input_stream, 0); + os.close(); + file_transfer.state = FileTransfer.State.COMPLETE; + } catch (Error e) { + file_transfer.state = FileTransfer.State.FAILED; + } + file_transfer.path = filename; + file_transfer.input_stream = file.read(); + } + +} + +public interface FileProvider : Object { + public signal void file_incoming(FileTransfer file_transfer); +} + +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 interface IncommingFileProcessor : Object { + public abstract bool can_process(FileTransfer file_transfer); + public abstract FileTransfer process(FileTransfer file_transfer); +} + +public interface OutgoingFileProcessor : Object { + public abstract bool can_process(FileTransfer file_transfer); + public abstract FileTransfer process(FileTransfer file_transfer); } } diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b6f079c7..3451e91d 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -107,6 +107,7 @@ SOURCES src/ui/conversation_summary/message_populator.vala src/ui/conversation_summary/message_textview.vala src/ui/conversation_summary/slashme_message_display.vala + src/ui/conversation_titlebar/file_entry.vala src/ui/conversation_titlebar/menu_entry.vala src/ui/conversation_titlebar/occupants_entry.vala src/ui/conversation_titlebar/view.vala diff --git a/main/data/manage_accounts/dialog.ui b/main/data/manage_accounts/dialog.ui index 2ad3a6bc..9235f613 100644 --- a/main/data/manage_accounts/dialog.ui +++ b/main/data/manage_accounts/dialog.ui @@ -248,7 +248,7 @@ - No accounts configured + No accounts configured 0.5 0.5 True diff --git a/main/src/ui/chat_input/smiley_converter.vala b/main/src/ui/chat_input/smiley_converter.vala index f8d29225..6844222e 100644 --- a/main/src/ui/chat_input/smiley_converter.vala +++ b/main/src/ui/chat_input/smiley_converter.vala @@ -13,7 +13,7 @@ class SmileyConverter { private static HashMap smiley_translations = new HashMap(); static construct { - smiley_translations[":)"] = "โ˜บ"; + smiley_translations[":)"] = "๐Ÿ™‚"; smiley_translations[":D"] = "๐Ÿ˜€"; smiley_translations[";)"] = "๐Ÿ˜‰"; smiley_translations["O:)"] = "๐Ÿ˜‡"; @@ -22,7 +22,7 @@ class SmileyConverter { smiley_translations[":o"] = "๐Ÿ˜ฎ"; smiley_translations[":P"] = "๐Ÿ˜›"; smiley_translations[";P"] = "๐Ÿ˜œ"; - smiley_translations[":("] = "โ˜น"; + smiley_translations[":("] = "๐Ÿ™"; smiley_translations[":'("] = "๐Ÿ˜ข"; smiley_translations[":/"] = "๐Ÿ˜•"; } diff --git a/main/src/ui/conversation_titlebar/file_entry.vala b/main/src/ui/conversation_titlebar/file_entry.vala new file mode 100644 index 00000000..df173192 --- /dev/null +++ b/main/src/ui/conversation_titlebar/file_entry.vala @@ -0,0 +1,72 @@ +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui { + +public class FileEntry : Plugins.ConversationTitlebarEntry, Object { + public string id { get { return "send_files"; } } + + StreamInteractor stream_interactor; + + public FileEntry(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public double order { get { return 4; } } + public Plugins.ConversationTitlebarWidget get_widget(Plugins.WidgetType type) { + if (type == Plugins.WidgetType.GTK) { + return new FileWidget(stream_interactor) { visible=true }; + } + return null; + } +} + +public class FileWidget : Button, Plugins.ConversationTitlebarWidget { + + private Conversation? conversation; + private StreamInteractor stream_interactor; + + public FileWidget(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + image = new Image.from_icon_name("mail-attachment-symbolic", IconSize.MENU); + clicked.connect(on_clicked); + stream_interactor.get_module(FileManager.IDENTITY).upload_available.connect(on_upload_available); + } + + public void on_clicked() { + FileChooserNative chooser = new FileChooserNative ( + "Select file", get_toplevel() as Window, FileChooserAction.OPEN, + "Select", "Cancel"); +// long max_file_size = stream_interactor.get_module(Manager.IDENTITY).get_max_file_size(conversation.account); +// if (max_file_size != -1) { +// FileFilter filter = new FileFilter(); +// filter.add_custom(FileFilterFlags.URI, (filter_info) => { +// File file = File.new_for_uri(filter_info.uri); +// FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); +// return file_info.get_size() <= max_file_size; +// }); +// chooser.set_filter(filter); +// } + if (chooser.run() == Gtk.ResponseType.ACCEPT) { + string uri = chooser.get_filename(); + stream_interactor.get_module(FileManager.IDENTITY).send_file(uri, conversation); + } + } + + public void on_upload_available(Account account) { + Idle.add(() => { + if (conversation != null && conversation.account.equals(account)) { + visible = true; + } + return false; + }); + } + + public new void set_conversation(Conversation conversation) { + this.conversation = conversation; + visible = stream_interactor.get_module(FileManager.IDENTITY).is_upload_available(conversation); + } +} + +} diff --git a/main/src/ui/conversation_titlebar/view.vala b/main/src/ui/conversation_titlebar/view.vala index 32d829fb..bd8fe8c9 100644 --- a/main/src/ui/conversation_titlebar/view.vala +++ b/main/src/ui/conversation_titlebar/view.vala @@ -23,6 +23,7 @@ public class ConversationTitlebar : Gtk.HeaderBar { Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_contact_titlebar_entry(new MenuEntry(stream_interactor)); app.plugin_registry.register_contact_titlebar_entry(new OccupantsEntry(stream_interactor, window)); + app.plugin_registry.register_contact_titlebar_entry(new FileEntry(stream_interactor)); foreach(var e in app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget widget = e.get_widget(Plugins.WidgetType.GTK); diff --git a/plugins/http-files/CMakeLists.txt b/plugins/http-files/CMakeLists.txt index bbb2bf64..340ff5b2 100644 --- a/plugins/http-files/CMakeLists.txt +++ b/plugins/http-files/CMakeLists.txt @@ -9,7 +9,6 @@ find_packages(HTTP_FILES_PACKAGES REQUIRED vala_precompile(HTTP_FILES_VALA_C SOURCES - src/contact_titlebar_entry.vala src/file_provider.vala src/manager.vala src/plugin.vala diff --git a/plugins/http-files/src/contact_titlebar_entry.vala b/plugins/http-files/src/contact_titlebar_entry.vala deleted file mode 100644 index e5b82abe..00000000 --- a/plugins/http-files/src/contact_titlebar_entry.vala +++ /dev/null @@ -1,72 +0,0 @@ -using Gtk; - -using Dino.Entities; - -namespace Dino.Plugins.HttpFiles { - -public class ConversationsTitlebarEntry : Plugins.ConversationTitlebarEntry, Object { - public string id { get { return "send_files"; } } - - StreamInteractor stream_interactor; - - public ConversationsTitlebarEntry(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - } - - public double order { get { return 4; } } - public Plugins.ConversationTitlebarWidget get_widget(WidgetType type) { - if (type == WidgetType.GTK) { - return new ConversationTitlebarWidget(stream_interactor) { visible=true }; - } - return null; - } -} - -public class ConversationTitlebarWidget : Button, Plugins.ConversationTitlebarWidget { - - private Conversation? conversation; - private StreamInteractor stream_interactor; - - public ConversationTitlebarWidget(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - image = new Image.from_icon_name("mail-attachment-symbolic", IconSize.MENU); - clicked.connect(on_clicked); - stream_interactor.get_module(Manager.IDENTITY).upload_available.connect(on_upload_available); - } - - public void on_clicked() { - FileChooserNative chooser = new FileChooserNative ( - "Select file", get_toplevel() as Window, FileChooserAction.OPEN, - "Select", "Cancel"); - long max_file_size = stream_interactor.get_module(Manager.IDENTITY).get_max_file_size(conversation.account); - if (max_file_size != -1) { - FileFilter filter = new FileFilter(); - filter.add_custom(FileFilterFlags.URI, (filter_info) => { - File file = File.new_for_uri(filter_info.uri); - FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); - return file_info.get_size() <= max_file_size; - }); - chooser.set_filter(filter); - } - if (chooser.run() == Gtk.ResponseType.ACCEPT) { - string uri = chooser.get_filename(); - stream_interactor.get_module(Manager.IDENTITY).send(conversation, uri); - } - } - - public void on_upload_available(Account account) { - Idle.add(() => { - if (conversation != null && conversation.account.equals(account)) { - visible = true; - } - return false; - }); - } - - public new void set_conversation(Conversation conversation) { - this.conversation = conversation; - visible = stream_interactor.get_module(Manager.IDENTITY).is_upload_available(conversation.account); - } -} - -} diff --git a/plugins/http-files/src/file_provider.vala b/plugins/http-files/src/file_provider.vala index 69ae9218..d327ec5f 100644 --- a/plugins/http-files/src/file_provider.vala +++ b/plugins/http-files/src/file_provider.vala @@ -5,7 +5,7 @@ using Dino.Entities; namespace Dino.Plugins.HttpFiles { -public class FileProvider : Plugins.FileProvider, Object { +public class FileProvider : Dino.FileProvider, Object { public string id { get { return "http"; } } private StreamInteractor stream_interactor; @@ -19,15 +19,8 @@ public class FileProvider : Plugins.FileProvider, Object { this.url_regex = new Regex("""^(?i)\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?ยซยปโ€œโ€โ€˜โ€™]))$"""); this.file_ext_regex = new Regex("""\.(png|jpg|jpeg|svg|gif)"""); - Application app = GLib.Application.get_default() as Application; - app.plugin_registry.register_message_display(new FileMessageFilterDisplay(dino_db)); - stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect(check_message); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(check_message); - stream_interactor.get_module(Manager.IDENTITY).uploading.connect((file_transfer) => { - file_transfer.provider = 0; - file_incoming(file_transfer); - }); stream_interactor.get_module(Manager.IDENTITY).uploaded.connect((file_transfer, url) => { file_transfer.info = url; ignore_once.add(url); @@ -77,31 +70,4 @@ public class FileProvider : Plugins.FileProvider, Object { } } -public class FileMessageFilterDisplay : Plugins.MessageDisplayProvider, Object { - public string id { get; set; default="file_message_filter"; } - public double priority { get; set; default=10; } - - public Database db; - - public FileMessageFilterDisplay(Dino.Database db) { - this.db = db; - } - - public bool can_display(Entities.Message? message) { - return message_is_file(message); - } - - public Plugins.MetaConversationItem? get_item(Entities.Message message, Conversation conversation) { - return null; - } - - private bool message_is_file(Entities.Message message) { - Qlite.QueryBuilder builder = db.file_transfer.select() - .with(db.file_transfer.info, "=", message.body) - .with(db.file_transfer.account_id, "=", message.account.id) - .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(message.counterpart)); - return builder.count() > 0; - } -} - } diff --git a/plugins/http-files/src/manager.vala b/plugins/http-files/src/manager.vala index b1b7296c..9abf9843 100644 --- a/plugins/http-files/src/manager.vala +++ b/plugins/http-files/src/manager.vala @@ -4,44 +4,28 @@ using Gee; namespace Dino.Plugins.HttpFiles { -public class Manager : StreamInteractionModule, Object { +public class Manager : StreamInteractionModule, FileSender, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("http_files"); public string id { get { return IDENTITY.id; } } - public signal void upload_available(Account account); public signal void uploading(FileTransfer file_transfer); public signal void uploaded(FileTransfer file_transfer, string url); private StreamInteractor stream_interactor; private HashMap max_file_sizes = new HashMap(Account.hash_func, Account.equals_func); - private Manager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; + stream_interactor.get_module(FileManager.IDENTITY).add_sender(this); stream_interactor.stream_negotiated.connect(on_stream_negotiated); } - public void send(Conversation conversation, string file_uri) { - Xmpp.Core.XmppStream? stream = stream_interactor.get_stream(conversation.account); + public void send_file(Conversation conversation, FileTransfer file_transfer) { + Xmpp.Core.XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream != null) { - File file = File.new_for_path(file_uri); - FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); - - FileTransfer file_transfer = new FileTransfer(); - file_transfer.account = conversation.account; - file_transfer.counterpart = conversation.counterpart; - file_transfer.ourpart = conversation.account.bare_jid; - file_transfer.direction = FileTransfer.DIRECTION_SENT; - file_transfer.time = new DateTime.now_utc(); - file_transfer.local_time = new DateTime.now_utc(); - file_transfer.encryption = Encryption.NONE; - file_transfer.file_name = file_info.get_display_name(); - file_transfer.input_stream = file.read(); - file_transfer.mime_type = file_info.get_content_type(); - file_transfer.size = (int)file_info.get_size(); + file_transfer.provider = 0; uploading(file_transfer); - - stream_interactor.module_manager.get_module(conversation.account, UploadStreamModule.IDENTITY).upload(stream, file_uri, + stream_interactor.module_manager.get_module(file_transfer.account, UploadStreamModule.IDENTITY).upload(stream, Path.build_filename(FileManager.get_storage_dir(), file_transfer.path), (stream, url_down) => { uploaded(file_transfer, url_down); stream_interactor.get_module(MessageProcessor.IDENTITY).send_message(url_down, conversation); @@ -50,13 +34,16 @@ public class Manager : StreamInteractionModule, Object { file_transfer.state = FileTransfer.State.FAILED; } ); - } } - public bool is_upload_available(Account account) { + public bool can_send(Conversation conversation, FileTransfer file_transfer) { + return true; + } + + public bool is_upload_available(Conversation conversation) { lock (max_file_sizes) { - return max_file_sizes.has_key(account); + return max_file_sizes.has_key(conversation.account); } } @@ -81,4 +68,31 @@ public class Manager : StreamInteractionModule, Object { } } +public class FileMessageFilterDisplay : Plugins.MessageDisplayProvider, Object { + public string id { get; set; default="file_message_filter"; } + public double priority { get; set; default=10; } + + public Database db; + + public FileMessageFilterDisplay(Dino.Database db) { + this.db = db; + } + + public bool can_display(Entities.Message? message) { + return message_is_file(message); + } + + public Plugins.MetaConversationItem? get_item(Entities.Message message, Conversation conversation) { + return null; + } + + private bool message_is_file(Entities.Message message) { + Qlite.QueryBuilder builder = db.file_transfer.select() + .with(db.file_transfer.info, "=", message.body) + .with(db.file_transfer.account_id, "=", message.account.id) + .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(message.counterpart)); + return builder.count() > 0; + } +} + } diff --git a/plugins/http-files/src/plugin.vala b/plugins/http-files/src/plugin.vala index d91b0c97..7fc01e65 100644 --- a/plugins/http-files/src/plugin.vala +++ b/plugins/http-files/src/plugin.vala @@ -6,7 +6,6 @@ namespace Dino.Plugins.HttpFiles { public class Plugin : RootInterface, Object { public Dino.Application app; - public ConversationsTitlebarEntry conversations_titlebar_entry; public FileProvider file_provider; public void registered(Dino.Application app) { @@ -14,15 +13,14 @@ public class Plugin : RootInterface, Object { this.app = app; Manager.start(this.app.stream_interactor); - conversations_titlebar_entry = new ConversationsTitlebarEntry(app.stream_interactor); file_provider = new FileProvider(app.stream_interactor, app.db); - app.plugin_registry.register_contact_titlebar_entry(conversations_titlebar_entry); app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { list.add(new UploadStreamModule()); }); app.stream_interactor.get_module(FileManager.IDENTITY).add_provider(file_provider); + app.plugin_registry.register_message_display(new FileMessageFilterDisplay(app.db)); } catch (Error e) { print(@"Error initializing http-files: $(e.message)\n"); }