From a257b163376174e4f5efcbc82c9fdd56463c3191 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Wed, 30 Aug 2017 00:03:37 +0200 Subject: Download & inline display images --- libdino/CMakeLists.txt | 2 + libdino/src/application.vala | 1 + libdino/src/entity/file_transfer.vala | 120 +++++++++++++++++++++++++++++ libdino/src/plugin/interfaces.vala | 19 +++++ libdino/src/service/database.vala | 35 ++++++++- libdino/src/service/file_manager.vala | 74 ++++++++++++++++++ libdino/src/service/message_processor.vala | 2 +- 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 libdino/src/entity/file_transfer.vala create mode 100644 libdino/src/service/file_manager.vala (limited to 'libdino') diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index 96abdd62..54cb1932 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -18,6 +18,7 @@ SOURCES src/entity/account.vala src/entity/conversation.vala src/entity/encryption.vala + src/entity/file_transfer.vala src/entity/jid.vala src/entity/message.vala src/entity/settings.vala @@ -34,6 +35,7 @@ SOURCES src/service/counterpart_interaction_manager.vala src/service/database.vala src/service/entity_capabilities_storage.vala + src/service/file_manager.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 0359957e..c18b28f9 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -35,6 +35,7 @@ public interface Dino.Application : GLib.Application { RosterManager.start(stream_interactor, db); ConversationManager.start(stream_interactor, db); ChatInteraction.start(stream_interactor); + FileManager.start(stream_interactor, db); activate.connect(() => { stream_interactor.connection_manager.log_options = print_xmpp; diff --git a/libdino/src/entity/file_transfer.vala b/libdino/src/entity/file_transfer.vala new file mode 100644 index 00000000..7a752518 --- /dev/null +++ b/libdino/src/entity/file_transfer.vala @@ -0,0 +1,120 @@ +namespace Dino.Entities { + +public class FileTransfer : Object { + + public const bool DIRECTION_SENT = true; + public const bool DIRECTION_RECEIVED = false; + + public enum State { + COMPLETE, + IN_PROCESS, + NOT_STARTED, + FAILED + } + + public int id { get; set; default=-1; } + public Account account { get; set; } + public Jid counterpart { get; set; } + public Jid ourpart { get; set; } + public bool direction { get; set; } + public DateTime time { get; set; } + public DateTime? local_time { get; set; } + public Encryption encryption { get; set; } + + public InputStream input_stream { get; set; } + public OutputStream output_stream { get; set; } + + public string file_name { get; set; } + public string path { get; set; } + public string mime_type { get; set; } + public int size { get; set; } + + public State state { get; set; } + public int provider { get; set; } + public string info { get; set; } + + private Database? db; + + public FileTransfer.from_row(Database db, Qlite.Row row) { + this.db = db; + + id = row[db.file_transfer.id]; + account = db.get_account_by_id(row[db.file_transfer.account_id]); // TODO dont have to generate acc new + + string counterpart_jid = db.get_jid_by_id(row[db.file_transfer.counterpart_id]); + string counterpart_resource = row[db.file_transfer.counterpart_resource]; + counterpart = counterpart_resource != null ? new Jid.with_resource(counterpart_jid, counterpart_resource) : new Jid(counterpart_jid); + + string our_resource = row[db.file_transfer.our_resource]; + if (our_resource != null) { + ourpart = new Jid.with_resource(account.bare_jid.to_string(), our_resource); + } else { + ourpart = account.bare_jid; + } + direction = row[db.file_transfer.direction]; + time = new DateTime.from_unix_local(row[db.file_transfer.time]); + local_time = new DateTime.from_unix_local(row[db.file_transfer.time]); + encryption = (Encryption) row[db.file_transfer.encryption]; + file_name = row[db.file_transfer.file_name]; + path = row[db.file_transfer.path]; + mime_type = row[db.file_transfer.mime_type]; + size = row[db.file_transfer.size]; + state = (State) row[db.file_transfer.state]; + provider = row[db.file_transfer.provider]; + info = row[db.file_transfer.info]; + + notify.connect(on_update); + } + + public void persist(Database db) { + if (id != -1) return; + + this.db = db; + Qlite.InsertBuilder builder = db.file_transfer.insert() + .value(db.file_transfer.account_id, account.id) + .value(db.file_transfer.counterpart_id, db.get_jid_id(counterpart)) + .value(db.file_transfer.counterpart_resource, counterpart.resourcepart) + .value(db.file_transfer.our_resource, ourpart.resourcepart) + .value(db.file_transfer.direction, direction) + .value(db.file_transfer.time, (long) time.to_unix()) + .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); + id = (int) builder.perform(); + notify.connect(on_update); + } + + private void on_update(Object o, ParamSpec sp) { + Qlite.UpdateBuilder update_builder = db.file_transfer.update().with(db.file_transfer.id, "=", id); + switch (sp.name) { + case "counterpart": + update_builder.set(db.file_transfer.counterpart_id, db.get_jid_id(counterpart)); + update_builder.set(db.file_transfer.counterpart_resource, counterpart.resourcepart); break; + case "ourpart": + update_builder.set(db.file_transfer.our_resource, ourpart.resourcepart); break; + case "direction": + update_builder.set(db.file_transfer.direction, direction); break; + case "time": + update_builder.set(db.file_transfer.time, (long) time.to_unix()); break; + case "local-time": + update_builder.set(db.file_transfer.local_time, (long) local_time.to_unix()); break; + case "encryption": + update_builder.set(db.file_transfer.encryption, encryption); break; + case "state": + update_builder.set(db.file_transfer.state, state); break; + case "provider": + update_builder.set(db.file_transfer.provider, provider); break; + case "info": + update_builder.set(db.file_transfer.info, info); break; + } + update_builder.perform(); + } +} + +} diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 5ffd491f..0e0ad27c 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -108,4 +108,23 @@ 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); +} + +public interface FileDisplayProvider : Object { + public abstract double priority { get; } + public abstract bool can_display(Entities.Message? message); + public abstract FileWidget? get_item(Entities.Message? message); +} + } diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 51d16e59..1678c077 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -6,7 +6,7 @@ using Dino.Entities; namespace Dino { public class Database : Qlite.Database { - private const int VERSION = 5; + private const int VERSION = 6; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -53,6 +53,7 @@ public class Database : Qlite.Database { base(db, "message"); init({id, stanza_id, account_id, counterpart_id, our_resource, counterpart_resource, direction, type_, time, local_time, body, encryption, marked}); + index("message_localtime_counterpart_idx", {local_time, counterpart_id}); } } @@ -77,6 +78,32 @@ public class Database : Qlite.Database { } } + public class FileTransferTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column account_id = new Column.Integer("account_id") { not_null = true }; + public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; + public Column counterpart_resource = new Column.Text("counterpart_resource"); + public Column our_resource = new Column.Text("our_resource"); + public Column direction = new Column.BoolInt("direction") { not_null = true }; + public Column time = new Column.Long("time"); + public Column local_time = new Column.Long("local_time"); + public Column encryption = new Column.Integer("encryption"); + public Column file_name = new Column.Text("file_name"); + public Column path = new Column.Text("path"); + public Column mime_type = new Column.Text("mime_type"); + public Column size = new Column.Integer("size"); + public Column state = new Column.Integer("state"); + public Column provider = new Column.Integer("provider"); + public Column info = new Column.Text("info"); + + internal FileTransferTable(Database db) { + base(db, "file_transfer"); + init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time, + encryption, file_name, path, mime_type, size, state, provider, info}); + index("filetransfer_localtime_counterpart_idx", {local_time, counterpart_id}); + } + } + public class ConversationTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; @@ -148,6 +175,7 @@ public class Database : Qlite.Database { public JidTable jid { get; private set; } public MessageTable message { get; private set; } public RealJidTable real_jid { get; private set; } + public FileTransferTable file_transfer { get; private set; } public ConversationTable conversation { get; private set; } public AvatarTable avatar { get; private set; } public EntityFeatureTable entity_feature { get; private set; } @@ -164,12 +192,13 @@ public class Database : Qlite.Database { jid = new JidTable(this); message = new MessageTable(this); real_jid = new RealJidTable(this); + file_transfer = new FileTransferTable(this); conversation = new ConversationTable(this); avatar = new AvatarTable(this); entity_feature = new EntityFeatureTable(this); roster = new RosterTable(this); settings = new SettingsTable(this); - init({ account, jid, message, real_jid, conversation, avatar, entity_feature, roster, settings }); + init({ account, jid, message, real_jid, file_transfer, conversation, avatar, entity_feature, roster, settings }); exec("PRAGMA synchronous=0"); } @@ -214,7 +243,7 @@ public class Database : Qlite.Database { select.with(message.type_, "=", (int) type); } if (before != null) { - select.with(message.time, "<", (long) before.to_unix()); + select.with(message.local_time, "<", (long) before.to_unix()); } LinkedList ret = new LinkedList(); diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala new file mode 100644 index 00000000..b165039f --- /dev/null +++ b/libdino/src/service/file_manager.vala @@ -0,0 +1,74 @@ +using Gdk; +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + +public class FileManager : StreamInteractionModule, Object { + public static ModuleIdentity IDENTITY = new ModuleIdentity("file"); + public string id { get { return IDENTITY.id; } } + + public signal void received_file(FileTransfer file_transfer); + + private StreamInteractor stream_interactor; + private Database db; + private Gee.List file_transfers = new ArrayList(); + + public static void start(StreamInteractor stream_interactor, Database db) { + FileManager m = new FileManager(stream_interactor, db); + stream_interactor.add_module(m); + } + + public static string get_storage_dir() { + return Path.build_filename(Dino.get_storage_dir(), "files"); + } + + private FileManager(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + 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; + } + file_transfer.persist(db); + file_transfer.input_stream = file.read(); + received_file(file_transfer); + }); + } + + public Gee.List get_file_transfers(Account account, Jid counterpart, DateTime after, DateTime before) { + Qlite.QueryBuilder select = db.file_transfer.select() + .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(counterpart)) + .with(db.file_transfer.account_id, "=", account.id) + .with(db.file_transfer.local_time, ">", (long)after.to_unix()) + .with(db.file_transfer.local_time, "<", (long)before.to_unix()) + .order_by(db.file_transfer.id, "DESC"); + + 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(); + ret.insert(0, file_transfer); + } + return ret; + } + +} + +} diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 2bf3d615..4bb30ce6 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -6,7 +6,7 @@ using Dino.Entities; namespace Dino { public class MessageProcessor : StreamInteractionModule, Object { - public static ModuleIdentity IDENTITY = new ModuleIdentity("message_manager"); + public static ModuleIdentity IDENTITY = new ModuleIdentity("message_processor"); public string id { get { return IDENTITY.id; } } public signal void pre_message_received(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation); -- cgit v1.2.3-70-g09d2