From 3ea00446fb5893804243f5b1a1aa89817b7bc19a Mon Sep 17 00:00:00 2001
From: bobufa <bobufa@users.noreply.github.com>
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<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
     internal ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
     internal Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
-    internal Gee.List<MessageDisplayProvider> message_displays = new ArrayList<MessageDisplayProvider>();
-    internal Gee.List<ConversationItemPopulator> conversation_item_populators = new ArrayList<ConversationItemPopulator>();
+    internal Gee.List<ConversationAdditionPopulator> conversation_addition_populators = new ArrayList<ConversationAdditionPopulator>();
     internal Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((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<ContentItemAccumulator> IDENTITY = new ModuleIdentity<ContentItemAccumulator>("content_item_accumulator");
+    public string id { get { return IDENTITY.id; } }
+
+    public signal void new_item();
+
+    private StreamInteractor stream_interactor;
+    private Gee.List<ContentFilter> filters = new ArrayList<ContentFilter>();
+    private HashMap<ContentItemCollection, Conversation> collection_conversations = new HashMap<ContentItemCollection, Conversation>();
+
+    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<ContentItem> populate_latest(ContentItemCollection item_collection, Conversation conversation, int n) {
+        Gee.TreeSet<ContentItem> items = new Gee.TreeSet<ContentItem>(ContentItem.compare);
+
+        Gee.List<Entities.Message>? 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<FileTransfer> 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<ContentItem> iter = items.bidir_iterator();
+        iter.last();
+        int i = 0;
+        while (i < n && iter.has_previous()) {
+            iter.previous();
+            i++;
+        }
+        Gee.List<ContentItem> ret = new ArrayList<ContentItem>();
+        do {
+            ret.add(iter.get());
+        } while(iter.next());
+        return ret;
+    }
+
+    public Gee.List<ContentItem> populate_before(ContentItemCollection item_collection, Conversation conversation, ContentItem item, int n) {
+        Gee.TreeSet<ContentItem> items = new Gee.TreeSet<ContentItem>(ContentItem.compare);
+
+        Gee.List<Entities.Message>? 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<FileTransfer> 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<ContentItem> iter = items.bidir_iterator();
+        iter.last();
+        int i = 0;
+        while (i < n && iter.has_previous()) {
+            iter.previous();
+            i++;
+        }
+        Gee.List<ContentItem> ret = new ArrayList<ContentItem>();
+        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<FileTransfer> 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<FileTransfer> ret = new ArrayList<FileTransfer>();
+        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<FileTransfer> 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<FileTransfer> ret = new ArrayList<FileTransfer>();
+        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<FileTransfer> 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<FileTransfer> ret = new ArrayList<FileTransfer>();
         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<Message>? get_messages_before_message(Conversation? conversation, Message message, int count = 20) {
-        SortedSet<Message>? before = messages[conversation].head_set(message);
-        if (before != null && before.size >= count) {
-            Gee.List<Message> ret = new ArrayList<Message>(Message.equals_func);
-            Iterator<Message> 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<Message> db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, message.local_time);
+    public Gee.List<Message>? get_messages_before_message(Conversation? conversation, DateTime before, int count = 20) {
+//        SortedSet<Message>? before = messages[conversation].head_set(message);
+//        if (before != null && before.size >= count) {
+//            Gee.List<Message> ret = new ArrayList<Message>(Message.equals_func);
+//            Iterator<Message> 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<Message> 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-70-g09d2