From 3ea00446fb5893804243f5b1a1aa89817b7bc19a Mon Sep 17 00:00:00 2001 From: bobufa Date: Tue, 19 Jun 2018 18:07:00 +0200 Subject: refactor conversation item management (accumulate them in libdino) --- libdino/CMakeLists.txt | 1 + libdino/src/application.vala | 1 + libdino/src/entity/file_transfer.vala | 20 +- libdino/src/plugin/interfaces.vala | 26 +-- libdino/src/plugin/registry.vala | 21 +- libdino/src/service/content_item_accumulator.vala | 224 ++++++++++++++++++++++ libdino/src/service/file_manager.vala | 37 +++- libdino/src/service/message_storage.vala | 32 ++-- 8 files changed, 302 insertions(+), 60 deletions(-) create mode 100644 libdino/src/service/content_item_accumulator.vala (limited to 'libdino') diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index 62c73eca..429fc1f3 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -29,6 +29,7 @@ SOURCES src/service/blocking_manager.vala src/service/chat_interaction.vala src/service/connection_manager.vala + src/service/content_item_accumulator.vala src/service/conversation_manager.vala src/service/counterpart_interaction_manager.vala src/service/database.vala diff --git a/libdino/src/application.vala b/libdino/src/application.vala index 370618b2..0edd6df6 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -38,6 +38,7 @@ public interface Dino.Application : GLib.Application { ChatInteraction.start(stream_interactor); FileManager.start(stream_interactor, db); NotificationEvents.start(stream_interactor); + ContentItemAccumulator.start(stream_interactor); create_actions(); diff --git a/libdino/src/entity/file_transfer.vala b/libdino/src/entity/file_transfer.vala index e2542e74..93ba782f 100644 --- a/libdino/src/entity/file_transfer.vala +++ b/libdino/src/entity/file_transfer.vala @@ -23,7 +23,21 @@ public class FileTransfer : Object { public DateTime? local_time { get; set; } public Encryption encryption { get; set; } - public InputStream input_stream { get; set; } + private InputStream? input_stream_ = null; + public InputStream input_stream { + get { + if (input_stream_ == null) { + File file = File.new_for_path(Path.build_filename(storage_dir, path ?? file_name)); + try { + input_stream_ = file.read(); + } catch (Error e) { } + } + return input_stream_; + } + set { + input_stream_ = value; + } + } public OutputStream output_stream { get; set; } public string file_name { get; set; } @@ -41,9 +55,11 @@ public class FileTransfer : Object { public string info { get; set; } private Database? db; + private string storage_dir; - public FileTransfer.from_row(Database db, Qlite.Row row) { + public FileTransfer.from_row(Database db, Qlite.Row row, string storage_dir) { this.db = db; + this.storage_dir = storage_dir; id = row[db.file_transfer.id]; account = db.get_account_by_id(row[db.file_transfer.account_id]); // TODO don’t have to generate acc new diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 62260076..2378feb7 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -75,15 +75,16 @@ public interface ConversationTitlebarWidget : Object { public abstract interface ConversationItemPopulator : Object { public abstract string id { get; } public abstract void init(Conversation conversation, ConversationItemCollection summary, WidgetType type); - public virtual void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } - public virtual void populate_between_widgets(Conversation conversation, DateTime from, DateTime to) { } public abstract void close(Conversation conversation); } +public abstract interface ConversationAdditionPopulator : ConversationItemPopulator { + public virtual void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } +} + public abstract class MetaConversationItem : Object { + public virtual string populator_id { get; set; } public virtual Jid? jid { get; set; default=null; } - public virtual string color { get; set; default=null; } - public virtual string display_name { get; set; default=null; } public virtual bool dim { get; set; default=false; } public virtual DateTime? sort_time { get; set; default=null; } public virtual double seccondary_sort_indicator { get; set; } @@ -103,21 +104,4 @@ public interface ConversationItemCollection : Object { public signal void remove_item(MetaConversationItem item); } -public interface MessageDisplayProvider : Object { - public abstract string id { get; set; } - public abstract double priority { get; set; } - public abstract bool can_display(Entities.Message? message); - public abstract MetaConversationItem? get_item(Entities.Message message, Entities.Conversation conversation); -} - -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/plugin/registry.vala b/libdino/src/plugin/registry.vala index 7b4410aa..2b496288 100644 --- a/libdino/src/plugin/registry.vala +++ b/libdino/src/plugin/registry.vala @@ -7,8 +7,7 @@ public class Registry { internal ArrayList account_settings_entries = new ArrayList(); internal ArrayList contact_details_entries = new ArrayList(); internal Map text_commands = new HashMap(); - internal Gee.List message_displays = new ArrayList(); - internal Gee.List conversation_item_populators = new ArrayList(); + internal Gee.List conversation_addition_populators = new ArrayList(); internal Gee.Collection conversation_titlebar_entries = new Gee.TreeSet((a, b) => { if (a.order < b.order) { return -1; @@ -70,22 +69,12 @@ public class Registry { } } - public bool register_message_display(MessageDisplayProvider provider) { - lock (message_displays) { - foreach(MessageDisplayProvider p in message_displays) { - if (p.id == provider.id) return false; - } - message_displays.add(provider); - return true; - } - } - - public bool register_conversation_item_populator(ConversationItemPopulator populator) { - lock (conversation_item_populators) { - foreach(ConversationItemPopulator p in conversation_item_populators) { + public bool register_conversation_addition_populator(ConversationAdditionPopulator populator) { + lock (conversation_addition_populators) { + foreach(ConversationItemPopulator p in conversation_addition_populators) { if (p.id == populator.id) return false; } - conversation_item_populators.add(populator); + conversation_addition_populators.add(populator); return true; } } diff --git a/libdino/src/service/content_item_accumulator.vala b/libdino/src/service/content_item_accumulator.vala new file mode 100644 index 00000000..9fc852b2 --- /dev/null +++ b/libdino/src/service/content_item_accumulator.vala @@ -0,0 +1,224 @@ +using Gee; + +using Dino.Entities; +using Xmpp; + +namespace Dino { + +public class ContentItemAccumulator : StreamInteractionModule, Object { + public static ModuleIdentity IDENTITY = new ModuleIdentity("content_item_accumulator"); + public string id { get { return IDENTITY.id; } } + + public signal void new_item(); + + private StreamInteractor stream_interactor; + private Gee.List filters = new ArrayList(); + private HashMap collection_conversations = new HashMap(); + + public static void start(StreamInteractor stream_interactor) { + ContentItemAccumulator m = new ContentItemAccumulator(stream_interactor); + stream_interactor.add_module(m); + } + + public ContentItemAccumulator(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect(on_new_message); + stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(on_new_message); + stream_interactor.get_module(FileManager.IDENTITY).received_file.connect(insert_file_transfer); + } + + public void init(Conversation conversation, ContentItemCollection item_collection) { + collection_conversations[item_collection] = conversation; + } + + public Gee.List populate_latest(ContentItemCollection item_collection, Conversation conversation, int n) { + Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare); + + Gee.List? messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation, n); + if (messages != null) { + foreach (Entities.Message message in messages) { + items.add(new MessageItem(message, conversation)); + } + } + Gee.List transfers = stream_interactor.get_module(FileManager.IDENTITY).get_latest_transfers(conversation.account, conversation.counterpart, n); + foreach (FileTransfer transfer in transfers) { + items.add(new FileItem(transfer)); + } + + BidirIterator iter = items.bidir_iterator(); + iter.last(); + int i = 0; + while (i < n && iter.has_previous()) { + iter.previous(); + i++; + } + Gee.List ret = new ArrayList(); + do { + ret.add(iter.get()); + } while(iter.next()); + return ret; + } + + public Gee.List populate_before(ContentItemCollection item_collection, Conversation conversation, ContentItem item, int n) { + Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare); + + Gee.List? messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(conversation, item.display_time, n); + if (messages != null) { + foreach (Entities.Message message in messages) { + items.add(new MessageItem(message, conversation)); + } + } + Gee.List transfers = stream_interactor.get_module(FileManager.IDENTITY).get_transfers_before(conversation.account, conversation.counterpart, item.display_time, n); + foreach (FileTransfer transfer in transfers) { + items.add(new FileItem(transfer)); + } + + BidirIterator iter = items.bidir_iterator(); + iter.last(); + int i = 0; + while (i < n && iter.has_previous()) { + iter.previous(); + i++; + } + Gee.List ret = new ArrayList(); + do { + ret.add(iter.get()); + } while(iter.next()); + return ret; + } + + public void populate_after(Conversation conversation, ContentItem item, int n) { + + } + + public void add_filter(ContentFilter content_filter) { + filters.add(content_filter); + } + + private void on_new_message(Message message, Conversation conversation) { + foreach (ContentItemCollection collection in collection_conversations.keys) { + if (collection_conversations[collection].equals(conversation)) { + MessageItem item = new MessageItem(message, conversation); + insert_item(collection, item); + } + } + } + + private void insert_file_transfer(FileTransfer file_transfer) { + foreach (ContentItemCollection collection in collection_conversations.keys) { + Conversation conversation = collection_conversations[collection]; + if (conversation.account.equals(file_transfer.account) && conversation.counterpart.equals_bare(file_transfer.counterpart)) { + FileItem item = new FileItem(file_transfer); + insert_item(collection, item); + } + } + } + + private void insert_item(ContentItemCollection item_collection, ContentItem content_item) { + bool insert = true; + foreach (ContentFilter filter in filters) { + if (filter.discard(content_item)) { + insert = false; + } + } + if (insert) { + item_collection.insert_item(content_item); + } + } +} + +public interface ContentItemCollection : Object { + public abstract void insert_item(ContentItem item); + public abstract void remove_item(ContentItem item); +} + +public interface ContentFilter : Object { + public abstract bool discard(ContentItem content_item); +} + +public abstract class ContentItem : Object { + public virtual string type_ { get; set; } + public virtual Jid? jid { get; set; default=null; } + public virtual DateTime? sort_time { get; set; default=null; } + public virtual double seccondary_sort_indicator { get; set; } + public virtual DateTime? display_time { get; set; default=null; } + public virtual Encryption? encryption { get; set; default=null; } + public virtual Entities.Message.Marked? mark { get; set; default=null; } + + public static int compare(ContentItem a, ContentItem b) { + int res = a.sort_time.compare(b.sort_time); + if (res == 0) { + res = a.display_time.compare(b.display_time); + } + if (res == 0) { + res = a.seccondary_sort_indicator - b.seccondary_sort_indicator > 0 ? 1 : -1; + } + return res; + } +} + +public class MessageItem : ContentItem { + public const string TYPE = "message"; + public override string type_ { get; set; default=TYPE; } + + public Message message; + public Conversation conversation; + + public MessageItem(Message message, Conversation conversation) { + this.message = message; + this.conversation = conversation; + + this.jid = message.from; + this.sort_time = message.local_time; + this.seccondary_sort_indicator = message.id + 0.0845; + this.display_time = message.time; + this.encryption = message.encryption; + this.mark = message.marked; + + WeakRef weak_message = WeakRef(message); + message.notify["marked"].connect(() => { + Message? m = weak_message.get() as Message; + if (m == null) return; + mark = m.marked; + }); + } +} + +public class FileItem : ContentItem { + public const string TYPE = "file"; + public override string type_ { get; set; default=TYPE; } + + public FileTransfer file_transfer; + public Conversation conversation; + + public FileItem(FileTransfer file_transfer) { + this.file_transfer = file_transfer; + + this.jid = file_transfer.direction == FileTransfer.DIRECTION_SENT ? file_transfer.account.bare_jid.with_resource(file_transfer.account.resourcepart) : file_transfer.counterpart; + this.sort_time = file_transfer.time; + this.seccondary_sort_indicator = file_transfer.id + 0.2903; + this.display_time = file_transfer.time; + this.encryption = file_transfer.encryption; + this.mark = file_to_message_state(file_transfer.state); + file_transfer.notify["state"].connect_after(() => { + this.mark = file_to_message_state(file_transfer.state); + }); + } + + private Entities.Message.Marked file_to_message_state(FileTransfer.State state) { + switch (state) { + case FileTransfer.State.IN_PROCESS: + return Entities.Message.Marked.UNSENT; + case FileTransfer.State.COMPLETE: + return Entities.Message.Marked.NONE; + case FileTransfer.State.NOT_STARTED: + return Entities.Message.Marked.UNSENT; + case FileTransfer.State.FAILED: + return Entities.Message.Marked.WONTSEND; + } + assert_not_reached(); + } +} + +} diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index 3def24af..667076dd 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -78,6 +78,37 @@ public class FileManager : StreamInteractionModule, Object { return false; } + public Gee.List get_latest_transfers(Account account, Jid counterpart, int n) { + 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) + .order_by(db.file_transfer.local_time, "DESC") + .limit(n); + + Gee.List ret = new ArrayList(); + foreach (Qlite.Row row in select) { + FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir()); + ret.insert(0, file_transfer); + } + return ret; + } + + public Gee.List get_transfers_before(Account account, Jid counterpart, DateTime before, int n) { + 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)before.to_unix()) + .order_by(db.file_transfer.local_time, "DESC") + .limit(n); + + Gee.List ret = new ArrayList(); + foreach (Qlite.Row row in select) { + FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir()); + ret.insert(0, file_transfer); + } + return ret; + } + 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)) @@ -88,11 +119,7 @@ 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.path ?? file_transfer.file_name)); - try { - file_transfer.input_stream = file.read(); - } catch (Error e) { } + FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir()); ret.insert(0, file_transfer); } return ret; diff --git a/libdino/src/service/message_storage.vala b/libdino/src/service/message_storage.vala index 35e05074..906693a3 100644 --- a/libdino/src/service/message_storage.vala +++ b/libdino/src/service/message_storage.vala @@ -51,23 +51,23 @@ public class MessageStorage : StreamInteractionModule, Object { return null; } - public Gee.List? get_messages_before_message(Conversation? conversation, Message message, int count = 20) { - SortedSet? before = messages[conversation].head_set(message); - if (before != null && before.size >= count) { - Gee.List ret = new ArrayList(Message.equals_func); - Iterator iter = before.iterator(); - iter.next(); - for (int from_index = before.size - count; iter.has_next() && from_index > 0; from_index--) iter.next(); - while(iter.has_next()) { - Message m = iter.get(); - ret.add(m); - iter.next(); - } - return ret; - } else { - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, message.local_time); + public Gee.List? get_messages_before_message(Conversation? conversation, DateTime before, int count = 20) { +// SortedSet? before = messages[conversation].head_set(message); +// if (before != null && before.size >= count) { +// Gee.List ret = new ArrayList(Message.equals_func); +// Iterator iter = before.iterator(); +// iter.next(); +// for (int from_index = before.size - count; iter.has_next() && from_index > 0; from_index--) iter.next(); +// while(iter.has_next()) { +// Message m = iter.get(); +// ret.add(m); +// iter.next(); +// } +// return ret; +// } else { + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before); return db_messages; - } +// } } public Message? get_message_by_id(string stanza_id, Conversation conversation) { -- cgit v1.2.3-54-g00ecf