diff --git a/libdino/src/service/content_item_accumulator.vala b/libdino/src/service/content_item_accumulator.vala index 9fc852b2..9f9e672c 100644 --- a/libdino/src/service/content_item_accumulator.vala +++ b/libdino/src/service/content_item_accumulator.vala @@ -46,50 +46,73 @@ public class ContentItemAccumulator : StreamInteractionModule, Object { items.add(new FileItem(transfer)); } + Gee.List ret = new ArrayList(); + if (items.size == 0) return ret; + BidirIterator iter = items.bidir_iterator(); iter.last(); int i = 0; - while (i < n && iter.has_previous()) { + while (i < n - 1 && iter.has_previous()) { iter.previous(); i++; } - Gee.List ret = new ArrayList(); do { ret.add(iter.get()); - } while(iter.next()); + } 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); + int before_id = item as MessageItem != null ? (int)Math.floor(item.seccondary_sort_indicator) : -1; + Gee.List? messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(conversation, item.display_time, before_id, 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); + Gee.List transfers = stream_interactor.get_module(FileManager.IDENTITY).get_transfers_before(conversation.account, conversation.counterpart, item.sort_time, n); foreach (FileTransfer transfer in transfers) { items.add(new FileItem(transfer)); } + Gee.List ret = new ArrayList(); + if (items.size == 0) return ret; + BidirIterator iter = items.bidir_iterator(); iter.last(); int i = 0; - while (i < n && iter.has_previous()) { + while (i < n - 1 && iter.has_previous()) { iter.previous(); i++; } - Gee.List ret = new ArrayList(); do { ret.add(iter.get()); - } while(iter.next()); + } while (iter.next()); return ret; } - public void populate_after(Conversation conversation, ContentItem item, int n) { + public Gee.List populate_after(ContentItemCollection item_collection, Conversation conversation, ContentItem item, int n) { + Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare); + int after_id = item as MessageItem != null ? (int)Math.floor(item.seccondary_sort_indicator) : -1; + Gee.List? messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(conversation, item.sort_time, after_id, 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_after(conversation.account, conversation.counterpart, item.sort_time, n); + foreach (FileTransfer transfer in transfers) { + items.add(new FileItem(transfer)); + } + + Gee.List ret = new ArrayList(); + foreach (ContentItem content_item in items) { + ret.add(content_item); + } + return ret; } public void add_filter(ContentFilter content_filter) { @@ -196,7 +219,7 @@ public class FileItem : ContentItem { 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.sort_time = file_transfer.local_time; this.seccondary_sort_indicator = file_transfer.id + 0.2903; this.display_time = file_transfer.time; this.encryption = file_transfer.encryption; diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 2dca686f..d02e4c71 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -236,11 +236,10 @@ public class Database : Qlite.Database { } } - public Gee.List get_messages(Xmpp.Jid jid, Account account, Message.Type? type, int count, DateTime? before) { + public Gee.List get_messages(Xmpp.Jid jid, Account account, Message.Type? type, int count, DateTime? before, DateTime? after, int id) { QueryBuilder select = message.select() .with(message.counterpart_id, "=", get_jid_id(jid)) .with(message.account_id, "=", account.id) - .order_by(message.id, "DESC") .limit(count); if (jid.resourcepart != null) { select.with(message.counterpart_resource, "=", jid.resourcepart); @@ -250,6 +249,17 @@ public class Database : Qlite.Database { } if (before != null) { select.with(message.local_time, "<", (long) before.to_unix()); + if (id > 0) { + select.with(message.id, "<", id); + } + } + if (after != null) { + select.with(message.local_time, ">", (long) after.to_unix()); + if (id > 0) { + select.with(message.id, ">", id); + } + } else { + select.order_by(message.id, "DESC"); } LinkedList ret = new LinkedList(); diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index 667076dd..18f1735d 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -84,13 +84,7 @@ public class FileManager : StreamInteractionModule, Object { .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; + return get_transfers_from_qry(select); } public Gee.List get_transfers_before(Account account, Jid counterpart, DateTime before, int n) { @@ -100,23 +94,19 @@ public class FileManager : StreamInteractionModule, Object { .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; + return get_transfers_from_qry(select); } - public Gee.List get_file_transfers(Account account, Jid counterpart, DateTime after, DateTime before) { + public Gee.List get_transfers_after(Account account, Jid counterpart, DateTime after, 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)after.to_unix()) - .with(db.file_transfer.local_time, "<", (long)before.to_unix()) - .order_by(db.file_transfer.id, "DESC"); + .limit(n); + return get_transfers_from_qry(select); + } + private Gee.List get_transfers_from_qry(Qlite.QueryBuilder select) { Gee.List ret = new ArrayList(); foreach (Qlite.Row row in select) { FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir()); diff --git a/libdino/src/service/message_storage.vala b/libdino/src/service/message_storage.vala index 906693a3..e3869e41 100644 --- a/libdino/src/service/message_storage.vala +++ b/libdino/src/service/message_storage.vala @@ -51,7 +51,7 @@ public class MessageStorage : StreamInteractionModule, Object { return null; } - public Gee.List? get_messages_before_message(Conversation? conversation, DateTime before, int count = 20) { + public Gee.List? get_messages_before_message(Conversation? conversation, DateTime before, int id, int count = 20) { // SortedSet? before = messages[conversation].head_set(message); // if (before != null && before.size >= count) { // Gee.List ret = new ArrayList(Message.equals_func); @@ -65,11 +65,16 @@ public class MessageStorage : StreamInteractionModule, Object { // } // return ret; // } else { - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before); + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before, null, id); return db_messages; // } } + public Gee.List? get_messages_after_message(Conversation? conversation, DateTime after, int id, int count = 20) { + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, null, after, id); + return db_messages; + } + public Message? get_message_by_id(string stanza_id, Conversation conversation) { init_conversation(conversation); foreach (Message message in messages[conversation]) { @@ -100,7 +105,7 @@ public class MessageStorage : StreamInteractionModule, Object { } return res; }); - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), 50, null); + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), 50, null, null, -1); messages[conversation].add_all(db_messages); } } diff --git a/main/src/ui/conversation_summary/content_populator.vala b/main/src/ui/conversation_summary/content_populator.vala index 9fb83419..cec54c7b 100644 --- a/main/src/ui/conversation_summary/content_populator.vala +++ b/main/src/ui/conversation_summary/content_populator.vala @@ -53,6 +53,18 @@ public class ContentProvider : ContentItemCollection, Object { } return ret; } + + public Gee.List populate_after(Conversation conversation, Plugins.MetaConversationItem before_item, int n) { + Gee.List ret = new ArrayList(); + ContentMetaItem? content_meta_item = before_item as ContentMetaItem; + if (content_meta_item != null) { + Gee.List items = stream_interactor.get_module(ContentItemAccumulator.IDENTITY).populate_after(this, conversation, content_meta_item.content_item, n); + foreach (ContentItem item in items) { + ret.add(new ContentMetaItem(item, widget_factory)); + } + } + return ret; + } } public class ContentMetaItem : Plugins.MetaConversationItem { diff --git a/main/src/ui/conversation_summary/conversation_view.vala b/main/src/ui/conversation_summary/conversation_view.vala index bb696572..008909e4 100644 --- a/main/src/ui/conversation_summary/conversation_view.vala +++ b/main/src/ui/conversation_summary/conversation_view.vala @@ -18,7 +18,8 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { [GtkChild] private Stack stack; private StreamInteractor stream_interactor; - private Gee.TreeSet meta_items = new TreeSet(sort_meta_items); + private Gee.TreeSet content_items = new Gee.TreeSet(compare_meta_items); + private Gee.TreeSet meta_items = new TreeSet(compare_meta_items); private Gee.HashMap item_item_skeletons = new Gee.HashMap(); private Gee.HashMap widgets = new Gee.HashMap(); private Gee.List item_skeletons = new Gee.ArrayList(); @@ -32,6 +33,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { private Mutex reloading_mutex = Mutex(); private bool animate = false; private bool firstLoad = true; + private bool at_current_content = true; public ConversationView(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -41,8 +43,8 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { content_populator = new ContentProvider(stream_interactor); subscription_notification = new SubscriptionNotitication(stream_interactor); - insert_item.connect(on_insert_item); - remove_item.connect(on_remove_item); + insert_item.connect(do_insert_item); + remove_item.connect(do_remove_item); Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_conversation_addition_populator(new ChatStatePopulator(stream_interactor)); @@ -82,49 +84,57 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { } this.conversation = conversation; stack.set_visible_child_name("void"); + + foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) { + populator.init(conversation, this, Plugins.WidgetType.GTK); + } + content_populator.init(this, conversation, Plugins.WidgetType.GTK); + subscription_notification.init(conversation, this); + + display_latest(); + + stack.set_visible_child_name("main"); + } + + private void display_latest() { clear(); was_upper = null; was_page_size = null; animate = false; Timeout.add(20, () => { animate = true; return false; }); - foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) { - populator.init(conversation, this, Plugins.WidgetType.GTK); - } - content_populator.init(this, conversation, Plugins.WidgetType.GTK); Gee.List items = content_populator.populate_latest(conversation, 40); foreach (ContentMetaItem item in items) { - on_insert_item(item); + do_insert_item(item); } Idle.add(() => { on_value_notify(); return false; }); - - subscription_notification.init(conversation, this); - - stack.set_visible_child_name("main"); } - public void on_insert_item(Plugins.MetaConversationItem item) { + public void do_insert_item(Plugins.MetaConversationItem item) { lock (meta_items) { if (!item.can_merge || !merge_back(item)) { insert_new(item); } } + if (item as ContentMetaItem != null) { + content_items.add(item); + } + meta_items.add(item); } - private void on_remove_item(Plugins.MetaConversationItem item) { - lock (meta_items) { - ConversationItemSkeleton? skeleton = item_item_skeletons[item]; - if (skeleton.items.size > 1) { - skeleton.remove_meta_item(item); - } else { - widgets[item].destroy(); - widgets.unset(item); - skeleton.destroy(); - item_skeletons.remove(skeleton); - item_item_skeletons.unset(item); - } - meta_items.remove(item); + private void do_remove_item(Plugins.MetaConversationItem item) { + ConversationItemSkeleton? skeleton = item_item_skeletons[item]; + if (skeleton.items.size > 1) { + skeleton.remove_meta_item(item); + } else { + widgets[item].destroy(); + widgets.unset(item); + skeleton.destroy(); + item_skeletons.remove(skeleton); + item_item_skeletons.unset(item); } + content_items.remove(item); + meta_items.remove(item); } public void add_notification(Widget widget) { @@ -154,8 +164,8 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { lower_skeleton.add_meta_item(item); force_alloc_width(lower_skeleton, main.get_allocated_width()); + widgets[item] = widgets[lower_start_item]; item_item_skeletons[item] = lower_skeleton; - meta_items.add(item); return true; } @@ -182,7 +192,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { item_item_skeletons[item] = item_skeleton; int index = lower_item != null ? item_skeletons.index_of(item_item_skeletons[lower_item]) + 1 : 0; item_skeletons.insert(index, item_skeleton); - meta_items.add(item); // Insert widget Widget insert = item_skeleton; @@ -220,12 +229,12 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { while(i < split_skeleton.items.size) { Plugins.MetaConversationItem meta_item = split_skeleton.items[i]; if (time.compare(meta_item.display_time) < 0) { - remove_item(meta_item); + do_remove_item(meta_item); if (!already_divided) { insert_new(meta_item); already_divided = true; } else { - insert_item(meta_item); + do_insert_item(meta_item); } } i++; @@ -235,19 +244,24 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { private void on_upper_notify() { if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1 || scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size - scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down + if (at_current_content) { + scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down + } } else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1) { scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content } was_upper = scrolled.vadjustment.upper; was_page_size = scrolled.vadjustment.page_size; + was_value = scrolled.vadjustment.value; reloading_mutex.trylock(); reloading_mutex.unlock(); } private void on_value_notify() { - if (scrolled.vadjustment.value < 200) { + if (scrolled.vadjustment.value < 400) { load_earlier_messages(); + } else if (scrolled.vadjustment.upper - (scrolled.vadjustment.value + scrolled.vadjustment.page_size) < 400) { + load_later_messages(); } } @@ -255,14 +269,39 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { was_value = scrolled.vadjustment.value; if (!reloading_mutex.trylock()) return; if (meta_items.size > 0) { - Gee.List items = content_populator.populate_before(conversation, meta_items.first(), 20); + Gee.List items = content_populator.populate_before(conversation, content_items.first(), 20); foreach (ContentMetaItem item in items) { - on_insert_item(item); + do_insert_item(item); } + } else { + reloading_mutex.unlock(); } } - private static int sort_meta_items(Plugins.MetaConversationItem a, Plugins.MetaConversationItem b) { + private void load_later_messages() { + if (!reloading_mutex.trylock()) return; + if (meta_items.size > 0 && !at_current_content) { + foreach (Plugins.MetaConversationItem a in content_items) { + ContentMetaItem b = a as ContentMetaItem; + MessageItem c = b.content_item as MessageItem; + } + Gee.List items = content_populator.populate_after(conversation, content_items.last(), 20); + + ContentMetaItem b = content_items.last() as ContentMetaItem; + MessageItem c = b.content_item as MessageItem; + + if (items.size == 0) { + at_current_content = true; + } + foreach (ContentMetaItem item in items) { + do_insert_item(item); + } + } else { + reloading_mutex.unlock(); + } + } + + private static int compare_meta_items(Plugins.MetaConversationItem a, Plugins.MetaConversationItem b) { int res = a.sort_time.compare(b.sort_time); if (res == 0) { if (a.seccondary_sort_indicator < b.seccondary_sort_indicator) res = -1; @@ -281,6 +320,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { } private void clear() { + content_items.clear(); meta_items.clear(); item_skeletons.clear(); item_item_skeletons.clear();