From ab0bc7f04d156db9e398c3f8c4f8bdfc11dd7458 Mon Sep 17 00:00:00 2001 From: bobufa Date: Wed, 20 Jun 2018 16:22:03 +0200 Subject: enable fts for the body of messages --- libdino/src/service/database.vala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'libdino/src/service/database.vala') diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 25db82f8..2dca686f 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 = 6; + private const int VERSION = 7; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -54,6 +54,7 @@ public class Database : Qlite.Database { 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}); + fts({body}); } } @@ -206,6 +207,9 @@ public class Database : Qlite.Database { public override void migrate(long oldVersion) { // new table columns are added, outdated columns are still present + if (oldVersion < 7) { + message.fts_rebuild(); + } } public ArrayList get_accounts() { -- cgit v1.2.3-54-g00ecf From 760fd4cb26340165fb85c2e3aee8390e46dc8b63 Mon Sep 17 00:00:00 2001 From: bobufa Date: Sat, 23 Jun 2018 11:59:21 +0200 Subject: load+display later messages when scrolling down --- libdino/src/service/content_item_accumulator.vala | 43 ++++++-- libdino/src/service/database.vala | 14 ++- libdino/src/service/file_manager.vala | 24 ++--- libdino/src/service/message_storage.vala | 11 ++- .../ui/conversation_summary/content_populator.vala | 12 +++ .../ui/conversation_summary/conversation_view.vala | 110 ++++++++++++++------- 6 files changed, 147 insertions(+), 67 deletions(-) (limited to 'libdino/src/service/database.vala') 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) { + do_insert_item(item); + } + } else { + reloading_mutex.unlock(); + } + } + + 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) { - 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 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(); -- cgit v1.2.3-54-g00ecf From 2e2a9a239000509488f1a369ea4eaf4cdda9c0b1 Mon Sep 17 00:00:00 2001 From: bobufa Date: Mon, 16 Jul 2018 21:26:39 +0200 Subject: accumulate conversation content in meta db table --- libdino/CMakeLists.txt | 2 +- libdino/src/application.vala | 2 +- libdino/src/service/content_item_accumulator.vala | 247 --------------------- libdino/src/service/content_item_store.vala | 246 ++++++++++++++++++++ .../service/counterpart_interaction_manager.vala | 6 +- libdino/src/service/database.vala | 48 +++- libdino/src/service/message_storage.vala | 10 +- .../ui/conversation_summary/content_populator.vala | 35 ++- .../ui/conversation_summary/conversation_view.vala | 8 +- plugins/http-files/src/plugin.vala | 2 +- 10 files changed, 330 insertions(+), 276 deletions(-) delete mode 100644 libdino/src/service/content_item_accumulator.vala create mode 100644 libdino/src/service/content_item_store.vala (limited to 'libdino/src/service/database.vala') diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index de44195d..054e2bab 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -29,7 +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/content_item_store.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 80e474ac..7f278fa0 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -38,7 +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); + ContentItemStore.start(stream_interactor, db); SearchProcessor.start(stream_interactor, db); create_actions(); diff --git a/libdino/src/service/content_item_accumulator.vala b/libdino/src/service/content_item_accumulator.vala deleted file mode 100644 index 9f9e672c..00000000 --- a/libdino/src/service/content_item_accumulator.vala +++ /dev/null @@ -1,247 +0,0 @@ -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)); - } - - Gee.List ret = new ArrayList(); - if (items.size == 0) return ret; - - BidirIterator iter = items.bidir_iterator(); - iter.last(); - int i = 0; - while (i < n - 1 && iter.has_previous()) { - iter.previous(); - i++; - } - 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); - - 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.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 - 1 && iter.has_previous()) { - iter.previous(); - i++; - } - do { - ret.add(iter.get()); - } while (iter.next()); - return ret; - } - - 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) { - 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.local_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/content_item_store.vala b/libdino/src/service/content_item_store.vala new file mode 100644 index 00000000..39bdfdde --- /dev/null +++ b/libdino/src/service/content_item_store.vala @@ -0,0 +1,246 @@ +using Gee; + +using Dino.Entities; +using Qlite; +using Xmpp; + +namespace Dino { + +public class ContentItemStore : StreamInteractionModule, Object { + public static ModuleIdentity IDENTITY = new ModuleIdentity("content_item_store"); + public string id { get { return IDENTITY.id; } } + + public signal void new_item(ContentItem item, Conversation conversation); + + private StreamInteractor stream_interactor; + private Database db; + private Gee.List filters = new ArrayList(); + private HashMap collection_conversations = new HashMap(Conversation.hash_func, Conversation.equals_func); + + public static void start(StreamInteractor stream_interactor, Database db) { + ContentItemStore m = new ContentItemStore(stream_interactor, db); + stream_interactor.add_module(m); + } + + public ContentItemStore(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + + 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[conversation] = item_collection; + } + + public void uninit(Conversation conversation, ContentItemCollection item_collection) { + collection_conversations.unset(conversation); + } + + public Gee.List get_items_from_query(QueryBuilder select, Conversation conversation) { + Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare); + + foreach (var row in select) { + int provider = row[db.content.content_type]; + int foreign_id = row[db.content.foreign_id]; + switch (provider) { + case 1: + RowOption row_option = db.message.select().with(db.message.id, "=", foreign_id).row(); + if (row_option.is_present()) { + Message message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(foreign_id, conversation); + if (message == null) { + message = new Message.from_row(db, row_option.inner); + } + items.add(new MessageItem(message, conversation, row[db.content.id])); + } + break; + case 2: + RowOption row_option = db.file_transfer.select().with(db.file_transfer.id, "=", foreign_id).row(); + if (row_option.is_present()) { + string storage_dir = stream_interactor.get_module(FileManager.IDENTITY).get_storage_dir(); + FileTransfer file_transfer = new FileTransfer.from_row(db, row_option.inner, storage_dir); + items.add(new FileItem(file_transfer, row[db.content.id])); + } + break; + } + } + + Gee.List ret = new ArrayList(); + foreach (ContentItem item in items) { + ret.add(item); + } + return ret; + } + + public Gee.List get_latest(Conversation conversation, int count) { + QueryBuilder select = db.content.select() + .with(db.content.conversation_id, "=", conversation.id) + .order_by(db.content.local_time, "DESC") + .order_by(db.content.time, "DESC") + .limit(count); + + return get_items_from_query(select, conversation); + } + + public Gee.List get_before(Conversation conversation, ContentItem item, int count) { + long local_time = (long) item.sort_time.to_unix(); + long time = (long) item.display_time.to_unix(); + QueryBuilder select = db.content.select() + .where(@"local_time < ? OR (local_time = ? AND time < ?) OR (local_time = ? AND time = ? AND id < ?)", { local_time.to_string(), local_time.to_string(), time.to_string(), local_time.to_string(), time.to_string(), item.id.to_string() }) + .with(db.content.conversation_id, "=", conversation.id) + .order_by(db.content.local_time, "DESC") + .order_by(db.content.time, "DESC") + .limit(count); + + return get_items_from_query(select, conversation); + } + + public Gee.List get_after(Conversation conversation, ContentItem item, int count) { + long local_time = (long) item.sort_time.to_unix(); + long time = (long) item.display_time.to_unix(); + QueryBuilder select = db.content.select() + .where(@"local_time > ? OR (local_time = ? AND time > ?) OR (local_time = ? AND time = ? AND id > ?)", { local_time.to_string(), local_time.to_string(), time.to_string(), local_time.to_string(), time.to_string(), item.id.to_string() }) + .with(db.content.conversation_id, "=", conversation.id) + .order_by(db.content.local_time, "ASC") + .order_by(db.content.time, "ASC") + .limit(count); + + return get_items_from_query(select, conversation); + } + + public void add_filter(ContentFilter content_filter) { + filters.add(content_filter); + } + + private void on_new_message(Message message, Conversation conversation) { + MessageItem item = new MessageItem(message, conversation, -1); + if (!discard(item)) { + item.id = db.add_content_item(conversation, message.time, message.local_time, 1, message.id); + + if (collection_conversations.has_key(conversation)) { + collection_conversations.get(conversation).insert_item(item); + } + new_item(item, conversation); + } + } + + private void insert_file_transfer(FileTransfer file_transfer) { + FileItem item = new FileItem(file_transfer, -1); + if (!discard(item)) { + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart, file_transfer.account); + item.id = db.add_content_item(conversation, file_transfer.time, file_transfer.local_time, 2, file_transfer.id); + + if (collection_conversations.has_key(conversation)) { + collection_conversations.get(conversation).insert_item(item); + } + new_item(item, conversation); + } + } + + private bool discard(ContentItem content_item) { + foreach (ContentFilter filter in filters) { + if (filter.discard(content_item)) { + return true; + } + } + return false; + } +} + +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 int id { get; set; } + public string type_ { get; set; } + public Jid? jid { get; set; default=null; } + public DateTime? sort_time { get; set; default=null; } + public double seccondary_sort_indicator { get; set; } + public DateTime? display_time { get; set; default=null; } + public Encryption? encryption { get; set; default=null; } + public Entities.Message.Marked? mark { get; set; default=null; } + + public ContentItem(int id, string ty, Jid jid, DateTime sort_time, double seccondary_sort_indicator, DateTime display_time, Encryption encryption, Entities.Message.Marked mark) { + this.id = id; + this.type_ = ty; + this.jid = jid; + this.sort_time = sort_time; + this.seccondary_sort_indicator = seccondary_sort_indicator; + this.display_time = display_time; + this.encryption = encryption; + this.mark = mark; + } + + 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 Message message; + public Conversation conversation; + + public MessageItem(Message message, Conversation conversation, int id) { + base(id, TYPE, message.from, message.local_time, message.id + 0.0845, message.time, message.encryption, message.marked); + this.message = message; + this.conversation = conversation; + + 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 FileTransfer file_transfer; + public Conversation conversation; + + public FileItem(FileTransfer file_transfer, int id) { + Jid jid = file_transfer.direction == FileTransfer.DIRECTION_SENT ? file_transfer.account.bare_jid.with_resource(file_transfer.account.resourcepart) : file_transfer.counterpart; + base(id, TYPE, jid, file_transfer.local_time, file_transfer.id + 0.0845, file_transfer.time, file_transfer.encryption, file_to_message_state(file_transfer.state)); + + this.file_transfer = file_transfer; + + file_transfer.notify["state"].connect_after(() => { + this.mark = file_to_message_state(file_transfer.state); + }); + } + + private static 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/counterpart_interaction_manager.vala b/libdino/src/service/counterpart_interaction_manager.vala index fb10d20c..b4df9b8d 100644 --- a/libdino/src/service/counterpart_interaction_manager.vala +++ b/libdino/src/service/counterpart_interaction_manager.vala @@ -9,7 +9,7 @@ public class CounterpartInteractionManager : StreamInteractionModule, Object { public string id { get { return IDENTITY.id; } } public signal void received_state(Account account, Jid jid, string state); - public signal void received_marker(Account account, Jid jid, Entities.Message message, string marker); + public signal void received_marker(Account account, Jid jid, Entities.Message message, Entities.Message.Marked marker); public signal void received_message_received(Account account, Jid jid, Entities.Message message); public signal void received_message_displayed(Account account, Jid jid, Entities.Message message); @@ -69,12 +69,12 @@ public class CounterpartInteractionManager : StreamInteractionModule, Object { if (marker != Xep.ChatMarkers.MARKER_DISPLAYED && marker != Xep.ChatMarkers.MARKER_ACKNOWLEDGED) return; Conversation? conversation = stream_interactor.get_module(MessageStorage.IDENTITY).get_conversation_for_stanza_id(account, stanza_id); if (conversation == null) return; - Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(stanza_id, conversation); + Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); if (message == null) return; conversation.read_up_to = message; } else { foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_conversations(jid, account)) { - Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(stanza_id, conversation); + Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); if (message != null) { switch (marker) { case Xep.ChatMarkers.MARKER_RECEIVED: diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index d02e4c71..01cc2f52 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 = 7; + private const int VERSION = 8; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -34,6 +34,20 @@ public class Database : Qlite.Database { } } + public class ContentTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column conversation_id = new Column.Integer("conversation_id") { not_null = true }; + public Column time = new Column.Long("time") { not_null = true }; + public Column local_time = new Column.Long("local_time") { not_null = true }; + public Column content_type = new Column.Integer("content_type") { not_null = true }; + public Column foreign_id = new Column.Integer("foreign_id") { not_null = true }; + + internal ContentTable(Database db) { + base(db, "content"); + init({id, conversation_id, time, local_time, content_type, foreign_id}); + } + } + public class MessageTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column stanza_id = new Column.Text("stanza_id"); @@ -174,6 +188,7 @@ public class Database : Qlite.Database { public AccountTable account { get; private set; } public JidTable jid { get; private set; } + public ContentTable content { get; private set; } public MessageTable message { get; private set; } public RealJidTable real_jid { get; private set; } public FileTransferTable file_transfer { get; private set; } @@ -191,6 +206,7 @@ public class Database : Qlite.Database { base(fileName, VERSION); account = new AccountTable(this); jid = new JidTable(this); + content = new ContentTable(this); message = new MessageTable(this); real_jid = new RealJidTable(this); file_transfer = new FileTransferTable(this); @@ -199,7 +215,7 @@ public class Database : Qlite.Database { entity_feature = new EntityFeatureTable(this); roster = new RosterTable(this); settings = new SettingsTable(this); - init({ account, jid, message, real_jid, file_transfer, conversation, avatar, entity_feature, roster, settings }); + init({ account, jid, content, message, real_jid, file_transfer, conversation, avatar, entity_feature, roster, settings }); try { exec("PRAGMA synchronous=0"); } catch (Error e) { } @@ -209,6 +225,24 @@ public class Database : Qlite.Database { // new table columns are added, outdated columns are still present if (oldVersion < 7) { message.fts_rebuild(); + } else if (oldVersion < 8) { + exec(""" + insert into content (conversation_id, time, local_time, content_type, foreign_id) + select conversation.id, message.time, message.local_time, 1, message.id + from message join conversation on + message.account_id=conversation.account_id and + message.counterpart_id=conversation.jid_id and + message.type=conversation.type+1 and + (message.counterpart_resource=conversation.resource or message.type != 3) + where + message.body not in (select info from file_transfer where info not null) and + message.id not in (select info from file_transfer where info not null) + union + select conversation.id, file_transfer.time, file_transfer.local_time, 2, file_transfer.id + from file_transfer join conversation on + file_transfer.account_id=conversation.account_id and + file_transfer.counterpart_id=conversation.jid_id + order by message.local_time, message.time"""); } } @@ -236,6 +270,16 @@ public class Database : Qlite.Database { } } + public int add_content_item(Conversation conversation, DateTime time, DateTime local_time, int content_type, int foreign_id) { + return (int) content.insert() + .value(content.conversation_id, conversation.id) + .value(content.local_time, (long) local_time.to_unix()) + .value(content.time, (long) time.to_unix()) + .value(content.content_type, content_type) + .value(content.foreign_id, foreign_id) + .perform(); + } + 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)) diff --git a/libdino/src/service/message_storage.vala b/libdino/src/service/message_storage.vala index abc8acb4..9fb6ab19 100644 --- a/libdino/src/service/message_storage.vala +++ b/libdino/src/service/message_storage.vala @@ -76,7 +76,15 @@ public class MessageStorage : StreamInteractionModule, Object { return db_messages; } - public Message? get_message_by_id(string stanza_id, Conversation conversation) { + public Message? get_message_by_id(int id, Conversation conversation) { + init_conversation(conversation); + foreach (Message message in messages[conversation]) { + if (message.id == id) return message; + } + return null; + } + + public Message? get_message_by_stanza_id(string stanza_id, Conversation conversation) { init_conversation(conversation); foreach (Message message in messages[conversation]) { if (message.stanza_id == stanza_id) return message; diff --git a/main/src/ui/conversation_summary/content_populator.vala b/main/src/ui/conversation_summary/content_populator.vala index cec54c7b..9ebb9159 100644 --- a/main/src/ui/conversation_summary/content_populator.vala +++ b/main/src/ui/conversation_summary/content_populator.vala @@ -19,13 +19,14 @@ public class ContentProvider : ContentItemCollection, Object { } public void init(Plugins.ConversationItemCollection item_collection, Conversation conversation, Plugins.WidgetType type) { + if (current_conversation != null) { + stream_interactor.get_module(ContentItemStore.IDENTITY).uninit(current_conversation, this); + } current_conversation = conversation; this.item_collection = item_collection; - stream_interactor.get_module(ContentItemAccumulator.IDENTITY).init(conversation, this); + stream_interactor.get_module(ContentItemStore.IDENTITY).init(conversation, this); } - public void close(Conversation conversation) { } - public void insert_item(ContentItem item) { item_collection.insert_item(new ContentMetaItem(item, widget_factory)); } @@ -34,7 +35,7 @@ public class ContentProvider : ContentItemCollection, Object { public Gee.List populate_latest(Conversation conversation, int n) { - Gee.List items = stream_interactor.get_module(ContentItemAccumulator.IDENTITY).populate_latest(this, conversation, n); + Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation, n); Gee.List ret = new ArrayList(); foreach (ContentItem item in items) { ret.add(new ContentMetaItem(item, widget_factory)); @@ -42,29 +43,27 @@ public class ContentProvider : ContentItemCollection, Object { return ret; } - public Gee.List populate_before(Conversation conversation, Plugins.MetaConversationItem before_item, int n) { + public Gee.List populate_before(Conversation conversation, ContentItem 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_before(this, conversation, content_meta_item.content_item, n); - foreach (ContentItem item in items) { - ret.add(new ContentMetaItem(item, widget_factory)); - } + Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_before(conversation, before_item, n); + foreach (ContentItem item in items) { + ret.add(new ContentMetaItem(item, widget_factory)); } return ret; } - public Gee.List populate_after(Conversation conversation, Plugins.MetaConversationItem before_item, int n) { + public Gee.List populate_after(Conversation conversation, ContentItem after_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)); - } + Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_after(conversation, after_item, n); + foreach (ContentItem item in items) { + ret.add(new ContentMetaItem(item, widget_factory)); } return ret; } + + public ContentMetaItem get_content_meta_item(ContentItem content_item) { + return new ContentMetaItem(content_item, widget_factory); + } } 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 870b6ee3..a1863cf4 100644 --- a/main/src/ui/conversation_summary/conversation_view.vala +++ b/main/src/ui/conversation_summary/conversation_view.vala @@ -270,9 +270,13 @@ 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, content_items.first(), 20); + Gee.List items = content_populator.populate_before(conversation, (content_items.first() as ContentMetaItem).content_item, 20); foreach (ContentMetaItem item in items) { do_insert_item(item); + if (content_items.size > 50) { + do_remove_item(content_items.last()); + at_current_content = false; + } } } else { reloading_mutex.unlock(); @@ -286,7 +290,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { ContentMetaItem b = a as ContentMetaItem; MessageItem c = b.content_item as MessageItem; } - Gee.List items = content_populator.populate_after(conversation, content_items.last(), 20); + Gee.List items = content_populator.populate_after(conversation, (content_items.last() as ContentMetaItem).content_item, 20); ContentMetaItem b = content_items.last() as ContentMetaItem; MessageItem c = b.content_item as MessageItem; diff --git a/plugins/http-files/src/plugin.vala b/plugins/http-files/src/plugin.vala index c57ee3dc..bd136f31 100644 --- a/plugins/http-files/src/plugin.vala +++ b/plugins/http-files/src/plugin.vala @@ -19,7 +19,7 @@ public class Plugin : RootInterface, Object { }); app.stream_interactor.get_module(FileManager.IDENTITY).add_provider(file_provider); - app.stream_interactor.get_module(ContentItemAccumulator.IDENTITY).add_filter(new FileMessageFilter(app.db)); + app.stream_interactor.get_module(ContentItemStore.IDENTITY).add_filter(new FileMessageFilter(app.db)); } public void shutdown() { -- cgit v1.2.3-54-g00ecf From e376a577b6bfcdd9bdc0cc6ca283d99199a0197a Mon Sep 17 00:00:00 2001 From: bobufa Date: Wed, 25 Jul 2018 20:41:51 +0200 Subject: improve sidebar UI - only display messages that are content items - only display messages for active accounts - "fix" textview issue - add empty states (no search, no results) --- libdino/src/service/database.vala | 5 +- libdino/src/service/message_storage.vala | 18 ++- libdino/src/service/search_processor.vala | 21 ++-- main/data/global_search.ui | 137 +++++++++++++++++++-- main/data/theme.css | 16 +++ main/src/ui/application.vala | 2 +- .../ui/conversation_summary/conversation_view.vala | 93 ++++++++++---- .../ui/conversation_summary/message_textview.vala | 1 - .../src/ui/conversation_titlebar/search_entry.vala | 4 +- main/src/ui/global_search.vala | 129 +++++++++++-------- main/src/ui/unified_window.vala | 23 +++- main/src/ui/util/helper.vala | 13 +- 12 files changed, 341 insertions(+), 121 deletions(-) (limited to 'libdino/src/service/database.vala') diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 01cc2f52..bea07dda 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -43,8 +43,9 @@ public class Database : Qlite.Database { public Column foreign_id = new Column.Integer("foreign_id") { not_null = true }; internal ContentTable(Database db) { - base(db, "content"); + base(db, "contentx"); init({id, conversation_id, time, local_time, content_type, foreign_id}); + unique({content_type, foreign_id}, "IGNORE"); } } @@ -227,7 +228,7 @@ public class Database : Qlite.Database { message.fts_rebuild(); } else if (oldVersion < 8) { exec(""" - insert into content (conversation_id, time, local_time, content_type, foreign_id) + insert into contentx (conversation_id, time, local_time, content_type, foreign_id) select conversation.id, message.time, message.local_time, 1, message.id from message join conversation on message.account_id=conversation.account_id and diff --git a/libdino/src/service/message_storage.vala b/libdino/src/service/message_storage.vala index 9fb6ab19..50fc94b3 100644 --- a/libdino/src/service/message_storage.vala +++ b/libdino/src/service/message_storage.vala @@ -52,7 +52,7 @@ public class MessageStorage : StreamInteractionModule, Object { return null; } - public Gee.List? get_messages_before_message(Conversation? conversation, DateTime before, int id, 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); @@ -66,14 +66,22 @@ 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, null, id); - return db_messages; + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before, null, id); + Gee.List ret = new ArrayList(); + foreach (Message message in db_messages) { + ret.add(new MessageItem(message, conversation, -1)); + } + return ret; // } } - public Gee.List? get_messages_after_message(Conversation? conversation, DateTime after, int id, int count = 20) { + 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; + Gee.List ret = new ArrayList(); + foreach (Message message in db_messages) { + ret.add(new MessageItem(message, conversation, -1)); + } + return ret; } public Message? get_message_by_id(int id, Conversation conversation) { diff --git a/libdino/src/service/search_processor.vala b/libdino/src/service/search_processor.vala index 6962a7c1..e56efa41 100644 --- a/libdino/src/service/search_processor.vala +++ b/libdino/src/service/search_processor.vala @@ -23,7 +23,7 @@ public class SearchProcessor : StreamInteractionModule, Object { this.db = db; } - private QueryBuilder prepare_search(string query) { + private QueryBuilder prepare_search(string query, bool join_content) { string words = ""; string? with = null; string? in_ = null; @@ -60,7 +60,12 @@ public class SearchProcessor : StreamInteractionModule, Object { .order_by(db.message.id, "DESC") .join_with(db.jid, db.jid.id, db.message.counterpart_id) .join_with(db.account, db.account.id, db.message.account_id) - .outer_join_with(db.real_jid, db.real_jid.message_id, db.message.id); + .outer_join_with(db.real_jid, db.real_jid.message_id, db.message.id) + .with(db.account.enabled, "=", true); + if (join_content) { + rows.join_on(db.content, "message.id=contentx.foreign_id AND contentx.content_type=1") + .with(db.content.content_type, "=", 1); + } if (with != null) { if (with.index_of("/") > 0) { rows.with(db.message.type_, "=", Message.Type.GROUPCHAT_PM) @@ -85,20 +90,22 @@ public class SearchProcessor : StreamInteractionModule, Object { return rows; } - public Gee.List match_messages(string query, int offset = -1) { - Gee.List ret = new ArrayList(Message.equals_func); - var rows = prepare_search(query).limit(10); + public Gee.List match_messages(string query, int offset = -1) { + Gee.List ret = new ArrayList(); + var rows = prepare_search(query, true).limit(10); if (offset > 0) { rows.offset(offset); } foreach (Row row in rows) { - ret.add(new Message.from_row(db, row)); + Message message = new Message.from_row(db, row); + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); + ret.add(new MessageItem(message, conversation, row[db.content.id])); } return ret; } public int count_match_messages(string query) { - return (int)prepare_search(query).select({db.message.id}).count(); + return (int)prepare_search(query, false).select({db.message.id}).count(); } } diff --git a/main/data/global_search.ui b/main/data/global_search.ui index cc5f043b..3c4597c1 100644 --- a/main/data/global_search.ui +++ b/main/data/global_search.ui @@ -10,24 +10,135 @@ - - 0 - True - 17 - True - - - - - True + True - + + vertical + 10 + center + True + + + True + system-search-symbolic + 4 + 72 + + + + + + No active search + 0.5 + 0.5 + True + + + + + + + + + + Type to start a search + 0.5 + 0.5 + True + + + + + + empty + + + + + vertical + 10 + center + True + + + True + face-uncertain-symbolic + 4 + 72 + + + + + + No matching messages + 0.5 + 0.5 + True + + + + + + + + + + Check the spelling or try to remove filters + 0.5 + 0.5 + True + + + + + + no-result + + z + + vertical - 25 - 10 True + + + 0 + True + 17 + True + + + + + never + True + True + + + vertical + 25 + 10 + True + + + + + + results + diff --git a/main/data/theme.css b/main/data/theme.css index 61f15af4..42988c42 100644 --- a/main/data/theme.css +++ b/main/data/theme.css @@ -17,6 +17,22 @@ window.dino-main .dino-conversation undershoot { background: none; } +@keyframes highlight { + from { background: alpha(@warning_color, 0.5) } + to { background: transparent } +} + +window.dino-main .dino-conversation .highlight-once { + animation-duration: 3s; + animation-timing-function: ease-out; + animation-iteration-count: 1; + animation-name: highlight; +} + +window.dino-main .dino-conversation textview, window.dino-main .dino-conversation textview text { + background: transparent; +} + window.dino-main .dino-sidebar frame { background: @insensitive_bg_color; border-left: 1px solid @borders; diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 22d6d93d..86a4e288 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -32,7 +32,7 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application { window = new UnifiedWindow(this, stream_interactor); notifications = new Notifications(stream_interactor, window); notifications.start(); - notifications.conversation_selected.connect(window.on_conversation_selected); + notifications.conversation_selected.connect((conversation) => window.on_conversation_selected(conversation)); } window.present(); }); diff --git a/main/src/ui/conversation_summary/conversation_view.vala b/main/src/ui/conversation_summary/conversation_view.vala index a1863cf4..c74884a4 100644 --- a/main/src/ui/conversation_summary/conversation_view.vala +++ b/main/src/ui/conversation_summary/conversation_view.vala @@ -34,6 +34,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { private bool animate = false; private bool firstLoad = true; private bool at_current_content = true; + private bool reload_messages = true; public ConversationView init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -57,7 +58,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { return true; }); - Util.force_base_background(this); return this; } @@ -66,14 +66,71 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { if (firstLoad) { int timeout = firstLoad ? 1000 : 0; Timeout.add(timeout, () => { + stack.set_visible_child_name("void"); initialize_for_conversation_(conversation); + display_latest(); + stack.set_visible_child_name("main"); return false; }); firstLoad = false; } else { + stack.set_visible_child_name("void"); initialize_for_conversation_(conversation); + display_latest(); + stack.set_visible_child_name("main"); + } + } + + public void initialize_around_message(Conversation conversation, ContentItem content_item) { + stack.set_visible_child_name("void"); + clear(); + initialize_for_conversation_(conversation); + Gee.List before_items = content_populator.populate_before(conversation, content_item, 40); + foreach (ContentMetaItem item in before_items) { + do_insert_item(item); + } + ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item); + meta_item.can_merge = false; + Widget w = insert_new(meta_item); + content_items.add(meta_item); + meta_items.add(meta_item); + + Gee.List after_items = content_populator.populate_after(conversation, content_item, 40); + foreach (ContentMetaItem item in after_items) { + do_insert_item(item); + } + if (after_items.size == 40) { + at_current_content = false; + } + { + int h = 0, i = 0; + main.@foreach((widget) => { + if (i >= before_items.size) return; + ConversationItemSkeleton? sk = widget as ConversationItemSkeleton; + i += sk != null ? sk.items.size : 1; + int minimum_height, natural_height; + widget.get_preferred_height_for_width(main.get_allocated_width() - 2 * main.margin, out minimum_height, out natural_height); + h += minimum_height + 15; + }); + print(@"height_for_w: $(h)\n"); } + reload_messages = false; + Timeout.add(700, () => { + int h = 0, i = 0; + main.@foreach((widget) => { + if (i >= before_items.size) return; + ConversationItemSkeleton? sk = widget as ConversationItemSkeleton; + i += sk != null ? sk.items.size : 1; + h += widget.get_allocated_height() + 15; + }); + print(@"timeout: $(h)\n"); + scrolled.vadjustment.value = h - scrolled.vadjustment.page_size * 1/3; + w.get_style_context().add_class("highlight-once"); + reload_messages = true; + stack.set_visible_child_name("main"); + return false; + }); } private void initialize_for_conversation_(Conversation? conversation) { @@ -84,7 +141,6 @@ 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); @@ -92,17 +148,12 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { content_populator.init(this, conversation, Plugins.WidgetType.GTK); subscription_notification.init(conversation, this); - display_latest(); - - stack.set_visible_child_name("main"); + animate = false; + Timeout.add(20, () => { animate = true; return false; }); } private void display_latest() { clear(); - was_upper = null; - was_page_size = null; - animate = false; - Timeout.add(20, () => { animate = true; return false; }); Gee.List items = content_populator.populate_latest(conversation, 40); foreach (ContentMetaItem item in items) { @@ -163,7 +214,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { lower_start_item.encryption == item.encryption && (item.mark == Message.Marked.WONTSEND) == (lower_start_item.mark == Message.Marked.WONTSEND)) { lower_skeleton.add_meta_item(item); - force_alloc_width(lower_skeleton, main.get_allocated_width()); + Util.force_alloc_width(lower_skeleton, main.get_allocated_width()); widgets[item] = widgets[lower_start_item]; item_item_skeletons[item] = lower_skeleton; @@ -174,7 +225,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { return false; } - private void insert_new(Plugins.MetaConversationItem item) { + private Widget insert_new(Plugins.MetaConversationItem item) { Plugins.MetaConversationItem? lower_item = meta_items.lower(item); // Does another skeleton need to be split? @@ -206,7 +257,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { main.add(insert); } widgets[item] = insert; - force_alloc_width(insert, main.get_allocated_width()); + Util.force_alloc_width(insert, main.get_allocated_width()); main.reorder_child(insert, index); // If an item from the past was added, add everything between that item and the (post-)first present item @@ -222,6 +273,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { } } } + return insert; } private void split_at_time(ConversationItemSkeleton split_skeleton, DateTime time) { @@ -273,10 +325,6 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { Gee.List items = content_populator.populate_before(conversation, (content_items.first() as ContentMetaItem).content_item, 20); foreach (ContentMetaItem item in items) { do_insert_item(item); - if (content_items.size > 50) { - do_remove_item(content_items.last()); - at_current_content = false; - } } } else { reloading_mutex.unlock(); @@ -310,21 +358,14 @@ public class ConversationView : Box, Plugins.ConversationItemCollection { int res = a.sort_time.compare(b.sort_time); if (res == 0) { if (a.seccondary_sort_indicator < b.seccondary_sort_indicator) res = -1; - else if (a.seccondary_sort_indicator > b.seccondary_sort_indicator) res = 1; + else if (a.seccondary_sort_indicator > b.seccondary_sort_indicator) res = 1; } return res; } - // Workaround GTK TextView issues - private void force_alloc_width(Widget widget, int width) { - Allocation alloc = Allocation(); - widget.get_preferred_width(out alloc.width, null); - widget.get_preferred_height(out alloc.height, null); - alloc.width = width; - widget.size_allocate(alloc); - } - private void clear() { + was_upper = null; + was_page_size = null; content_items.clear(); meta_items.clear(); item_skeletons.clear(); diff --git a/main/src/ui/conversation_summary/message_textview.vala b/main/src/ui/conversation_summary/message_textview.vala index 0c38c269..71ca35f8 100644 --- a/main/src/ui/conversation_summary/message_textview.vala +++ b/main/src/ui/conversation_summary/message_textview.vala @@ -24,7 +24,6 @@ public class MessageTextView : TextView { motion_notify_event.connect(change_cursor_over_url); update_display_style(); - Util.force_base_background(this, "textview, text:not(:selected)"); style_updated.connect(update_display_style); populate_popup.connect(populate_context_menu); } diff --git a/main/src/ui/conversation_titlebar/search_entry.vala b/main/src/ui/conversation_titlebar/search_entry.vala index e80e5954..b452bdce 100644 --- a/main/src/ui/conversation_titlebar/search_entry.vala +++ b/main/src/ui/conversation_titlebar/search_entry.vala @@ -24,9 +24,7 @@ public class SearchMenuEntry : Plugins.ConversationTitlebarEntry, Object { } public class GlobalSearchButton : Plugins.ConversationTitlebarWidget, Gtk.ToggleButton { - public new void set_conversation(Conversation conversation) { - active = false; - } + public new void set_conversation(Conversation conversation) { } } } diff --git a/main/src/ui/global_search.vala b/main/src/ui/global_search.vala index cadee9c1..8bd13e6f 100644 --- a/main/src/ui/global_search.vala +++ b/main/src/ui/global_search.vala @@ -1,3 +1,4 @@ +using Gee; using Gtk; using Pango; @@ -7,6 +8,8 @@ namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/global_search.ui")] class GlobalSearch : Box { + public signal void selected_item(MessageItem item); + private StreamInteractor stream_interactor; private string search = ""; private int loaded_results = -1; @@ -16,6 +19,7 @@ class GlobalSearch : Box { [GtkChild] public Label entry_number_label; [GtkChild] public ScrolledWindow results_scrolled; [GtkChild] public Box results_box; + [GtkChild] public Stack results_empty_stack; public GlobalSearch init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -27,7 +31,7 @@ class GlobalSearch : Box { results_scrolled.vadjustment.notify["value"].connect(() => { if (results_scrolled.vadjustment.upper - (results_scrolled.vadjustment.value + results_scrolled.vadjustment.page_size) < 100) { if (!reloading_mutex.trylock()) return; - Gee.List new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results); + Gee.List new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results); if (new_messages.size == 0) { reloading_mutex.unlock(); return; @@ -51,37 +55,47 @@ class GlobalSearch : Box { clear_search(); this.search = search; - int match_count = stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search); - entry_number_label.label = "" + _("%i search results").printf(match_count) + ""; - Gee.List messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search); - loaded_results += messages.size; - append_messages(messages); + if (get_keywords(search).is_empty) { + results_empty_stack.set_visible_child_name("empty"); + return; + } + + Gee.List messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search); + if (messages.size == 0) { + results_empty_stack.set_visible_child_name("no-result"); + } else { + results_empty_stack.set_visible_child_name("results"); + + int match_count = messages.size < 10 ? messages.size : stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search); + entry_number_label.label = "" + _("%i search results").printf(match_count) + ""; + loaded_results += messages.size; + append_messages(messages); + } } - private void append_messages(Gee.List messages) { - foreach (Message message in messages) { - if (message.from == null) { - print("wtf null\n"); - continue; - } - Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); - Gee.List before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(conversation, message.local_time, message.id, 1); - Gee.List after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(conversation, message.local_time, message.id, 1); + private void append_messages(Gee.List messages) { + foreach (MessageItem item in messages) { + Gee.List before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(item.conversation, item.message.local_time, item.message.id, 1); + Gee.List after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(item.conversation, item.message.local_time, item.message.id, 1); Box context_box = new Box(Orientation.VERTICAL, 5) { visible=true }; if (before_message != null && before_message.size > 0) { context_box.add(get_context_message_widget(before_message.first())); } - context_box.add(get_match_message_widget(message)); + + Widget match_widget = get_match_message_widget(item); + Util.force_alloc_width(match_widget, results_empty_stack.get_allocated_width() - results_box.margin * 2); + context_box.add(match_widget); + if (after_message != null && after_message.size > 0) { context_box.add(get_context_message_widget(after_message.first())); } - Label date_label = new Label(ConversationSummary.DefaultSkeletonHeader.get_relative_time(message.time)) { xalign=0, visible=true }; + Label date_label = new Label(ConversationSummary.DefaultSkeletonHeader.get_relative_time(item.display_time)) { xalign=0, visible=true }; date_label.get_style_context().add_class("dim-label"); - string display_name = Util.get_conversation_display_name(stream_interactor, conversation); - string title = message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name); + string display_name = Util.get_conversation_display_name(stream_interactor, item.conversation); + string title = item.message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name); Box header_box = new Box(Orientation.HORIZONTAL, 10) { margin_left=7, visible=true }; header_box.add(new Label(@"$(Markup.escape_text(title))") { ellipsize=EllipsizeMode.END, xalign=0, use_markup=true, visible=true }); header_box.add(date_label); @@ -94,21 +108,12 @@ class GlobalSearch : Box { } } - // Workaround GTK TextView issues - private void force_alloc_width(Widget widget, int width) { - Allocation alloc = Allocation(); - widget.get_preferred_width(out alloc.width, null); - widget.get_preferred_height(out alloc.height, null); - alloc.width = width; - widget.size_allocate(alloc); - } - - private Widget get_match_message_widget(Message message) { - Grid grid = get_skeleton(message); + private Widget get_match_message_widget(MessageItem item) { + Grid grid = get_skeleton(item); grid.margin_top = 3; grid.margin_bottom = 3; - string text = message.body.replace("\n", "").replace("\r", ""); + string text = item.message.body.replace("\n", "").replace("\r", ""); if (text.length > 200) { int index = text.index_of(search); if (index + search.length <= 100) { @@ -123,52 +128,68 @@ class GlobalSearch : Box { tv.buffer.text = text; TextTag link_tag = tv.buffer.create_tag("hit", background: "yellow"); - Regex url_regex = new Regex(search.down()); - MatchInfo match_info; - url_regex.match(text.down(), 0, out match_info); - for (; match_info.matches(); match_info.next()) { - int start; - int end; - match_info.fetch_pos(0, out start, out end); - start = text[0:start].char_count(); - end = text[0:end].char_count(); - TextIter start_iter; - TextIter end_iter; - tv.buffer.get_iter_at_offset(out start_iter, start); - tv.buffer.get_iter_at_offset(out end_iter, end); - tv.buffer.apply_tag(link_tag, start_iter, end_iter); + Gee.List keywords = get_keywords(Regex.escape_string(search.down())); + foreach (string keyword in keywords) { + Regex url_regex = new Regex(keyword.down()); + MatchInfo match_info; + url_regex.match(text.down(), 0, out match_info); + for (; match_info.matches(); match_info.next()) { + int start; + int end; + match_info.fetch_pos(0, out start, out end); + start = text[0:start].char_count(); + end = text[0:end].char_count(); + TextIter start_iter; + TextIter end_iter; + tv.buffer.get_iter_at_offset(out start_iter, start); + tv.buffer.get_iter_at_offset(out end_iter, end); + tv.buffer.apply_tag(link_tag, start_iter, end_iter); + } } - grid.attach(tv, 1, 1, 1, 1); - // force_alloc_width(tv, this.width_request); + grid.attach(tv, 1, 1, 1, 1); Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; + button.clicked.connect(() => { + selected_item(item); + }); button.add(grid); return button; } - private Grid get_context_message_widget(Message message) { - Grid grid = get_skeleton(message); + private Grid get_context_message_widget(MessageItem item) { + Grid grid = get_skeleton(item); grid.margin_left = 7; - Label label = new Label(message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true }; + Label label = new Label(item.message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true }; grid.attach(label, 1, 1, 1, 1); grid.opacity = 0.55; return grid; } - private Grid get_skeleton(Message message) { + private Grid get_skeleton(MessageItem item) { AvatarImage image = new AvatarImage() { height=32, width=32, margin_right=7, valign=Align.START, visible=true, allow_gray = false }; - image.set_jid(stream_interactor, message.from, message.account); + image.set_jid(stream_interactor, item.jid, item.message.account); Grid grid = new Grid() { row_homogeneous=false, visible=true }; grid.attach(image, 0, 0, 1, 2); - string display_name = Util.get_display_name(stream_interactor, message.from, message.account); - string color = Util.get_name_hex_color(stream_interactor, message.account, message.from, false); // TODO Util.is_dark_theme(name_label) + string display_name = Util.get_display_name(stream_interactor, item.jid, item.message.account); + string color = Util.get_name_hex_color(stream_interactor, item.message.account, item.jid, false); // TODO Util.is_dark_theme(name_label) Label name_label = new Label("") { use_markup=true, xalign=0, visible=true }; name_label.label = @"$display_name"; grid.attach(name_label, 1, 0, 1, 1); return grid; } + + private static Gee.List get_keywords(string search_string) { + Gee.List ret = new ArrayList(); + foreach (string search in search_string.split(" ")) { + bool is_filter = search.has_prefix("from:") || search.has_prefix("in:") || search.has_prefix("with:"); + if (!is_filter && search != "") { + ret.add(search); + } + } + return ret; + } } } diff --git a/main/src/ui/unified_window.vala b/main/src/ui/unified_window.vala index cfcd2bff..60aeb832 100644 --- a/main/src/ui/unified_window.vala +++ b/main/src/ui/unified_window.vala @@ -39,7 +39,10 @@ public class UnifiedWindow : Window { setup_unified(); setup_stack(); - conversation_titlebar.search_button.bind_property("active", search_revealer, "reveal-child", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + + conversation_titlebar.search_button.clicked.connect(() => { + search_revealer.reveal_child = conversation_titlebar.search_button.active; + }); search_revealer.notify["child-revealed"].connect(() => { if (search_revealer.child_revealed) { if (conversation_frame.conversation != null) { @@ -58,6 +61,10 @@ public class UnifiedWindow : Window { search_box.search_entry.text = ""; } }); + search_box.selected_item.connect((item) => { + on_conversation_selected(item.conversation, false, false); + conversation_frame.initialize_around_message(item.conversation, item); + }); paned.bind_property("position", headerbar_paned, "position", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); @@ -71,8 +78,8 @@ public class UnifiedWindow : Window { accounts_placeholder.primary_button.clicked.connect(() => { get_application().activate_action("accounts", null); }); conversations_placeholder.primary_button.clicked.connect(() => { get_application().activate_action("add_chat", null); }); conversations_placeholder.secondary_button.clicked.connect(() => { get_application().activate_action("add_conference", null); }); - filterable_conversation_list.conversation_list.conversation_selected.connect(on_conversation_selected); - conversation_list_titlebar.conversation_opened.connect(on_conversation_selected); + filterable_conversation_list.conversation_list.conversation_selected.connect((conversation) => on_conversation_selected(conversation)); + conversation_list_titlebar.conversation_opened.connect((conversation) => on_conversation_selected(conversation)); check_stack(); } @@ -89,15 +96,21 @@ public class UnifiedWindow : Window { search_revealer.valign = Align.FILL; } - public void on_conversation_selected(Conversation conversation) { + public void on_conversation_selected(Conversation conversation, bool close_search = true, bool default_initialize_conversation = true) { if (this.conversation == null || !this.conversation.equals(conversation)) { this.conversation = conversation; stream_interactor.get_module(ChatInteraction.IDENTITY).on_conversation_selected(conversation); conversation.active = true; // only for conversation_selected filterable_conversation_list.conversation_list.on_conversation_selected(conversation); // only for conversation_opened + if (close_search) { + conversation_titlebar.search_button.active = false; + search_revealer.reveal_child = false; + } chat_input.initialize_for_conversation(conversation); - conversation_frame.initialize_for_conversation(conversation); + if (default_initialize_conversation) { + conversation_frame.initialize_for_conversation(conversation); + } conversation_titlebar.initialize_for_conversation(conversation); } } diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala index 3cadfffb..4e9e942d 100644 --- a/main/src/ui/util/helper.vala +++ b/main/src/ui/util/helper.vala @@ -118,10 +118,6 @@ public static void force_background(Gtk.Widget widget, string color, string sele force_css(widget, force_background_css.printf(selector, color)); } -public static void force_base_background(Gtk.Widget widget, string selector = "*") { - force_background(widget, "@theme_base_color", selector); -} - public static void force_color(Gtk.Widget widget, string color, string selector = "*") { force_css(widget, force_color_css.printf(selector, color)); } @@ -142,4 +138,13 @@ public static bool is_24h_format() { return settings_format == "24h" || p_format == " "; } +// Workaround GTK TextView issues +public static void force_alloc_width(Widget widget, int width) { + Allocation alloc = Allocation(); + widget.get_preferred_width(out alloc.width, null); + widget.get_preferred_height(out alloc.height, null); + alloc.width = width; + widget.size_allocate(alloc); +} + } -- cgit v1.2.3-54-g00ecf From 2992a12f9fac1a7bf93a7aece5f85285c3b927ff Mon Sep 17 00:00:00 2001 From: bobufa Date: Sat, 11 Aug 2018 13:19:58 +0200 Subject: fix non-adjacent messages being shown before/after hit --- libdino/src/service/database.vala | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'libdino/src/service/database.vala') diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index bea07dda..4d8f0bd6 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -282,24 +282,21 @@ public class Database : Qlite.Database { } 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) - .limit(count); - if (jid.resourcepart != null) { - select.with(message.counterpart_resource, "=", jid.resourcepart); - } - if (type != null) { - select.with(message.type_, "=", (int) type); - } + QueryBuilder select = message.select(); + if (before != null) { - select.with(message.local_time, "<", (long) before.to_unix()); if (id > 0) { + select.where(@"local_time < ? OR (local_time = ? AND id < ?)", { before.to_unix().to_string(), before.to_unix().to_string(), id.to_string() }); + } else { select.with(message.id, "<", id); } } if (after != null) { - select.with(message.local_time, ">", (long) after.to_unix()); + if (id > 0) { + select.where(@"local_time > ? OR (local_time = ? AND id > ?)", { after.to_unix().to_string(), after.to_unix().to_string(), id.to_string() }); + } else { + select.with(message.local_time, ">", (long) after.to_unix()); + } if (id > 0) { select.with(message.id, ">", id); } @@ -307,6 +304,16 @@ public class Database : Qlite.Database { select.order_by(message.id, "DESC"); } + select.with(message.counterpart_id, "=", get_jid_id(jid)) + .with(message.account_id, "=", account.id) + .limit(count); + if (jid.resourcepart != null) { + select.with(message.counterpart_resource, "=", jid.resourcepart); + } + if (type != null) { + select.with(message.type_, "=", (int) type); + } + LinkedList ret = new LinkedList(); foreach (Row row in select) { ret.insert(0, new Message.from_row(this, row)); -- cgit v1.2.3-54-g00ecf From 1d9ce7f471c2aa3a70a9b0255ca39c0ec7a367e2 Mon Sep 17 00:00:00 2001 From: bobufa Date: Mon, 13 Aug 2018 15:50:50 +0200 Subject: sort file transfers into correct conversation --- libdino/src/service/content_item_store.vala | 3 +-- libdino/src/service/database.vala | 12 ++++++++---- libdino/src/service/file_manager.vala | 10 +++++----- plugins/http-files/src/file_provider.vala | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) (limited to 'libdino/src/service/database.vala') diff --git a/libdino/src/service/content_item_store.vala b/libdino/src/service/content_item_store.vala index 39bdfdde..83ba1da0 100644 --- a/libdino/src/service/content_item_store.vala +++ b/libdino/src/service/content_item_store.vala @@ -126,10 +126,9 @@ public class ContentItemStore : StreamInteractionModule, Object { } } - private void insert_file_transfer(FileTransfer file_transfer) { + private void insert_file_transfer(FileTransfer file_transfer, Conversation conversation) { FileItem item = new FileItem(file_transfer, -1); if (!discard(item)) { - Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart, file_transfer.account); item.id = db.add_content_item(conversation, file_transfer.time, file_transfer.local_time, 2, file_transfer.id); if (collection_conversations.has_key(conversation)) { diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 4d8f0bd6..8475cc1b 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -239,11 +239,15 @@ public class Database : Qlite.Database { message.body not in (select info from file_transfer where info not null) and message.id not in (select info from file_transfer where info not null) union - select conversation.id, file_transfer.time, file_transfer.local_time, 2, file_transfer.id - from file_transfer join conversation on + select conversation.id, message.time, message.local_time, 2, file_transfer.id + from file_transfer + join message on + file_transfer.info=message.id + join conversation on file_transfer.account_id=conversation.account_id and - file_transfer.counterpart_id=conversation.jid_id - order by message.local_time, message.time"""); + file_transfer.counterpart_id=conversation.jid_id and + message.type=conversation.type+1 and + (message.counterpart_resource=conversation.resource or message.type != 3)"""); } } diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala index 18f1735d..340205af 100644 --- a/libdino/src/service/file_manager.vala +++ b/libdino/src/service/file_manager.vala @@ -11,7 +11,7 @@ public class FileManager : StreamInteractionModule, Object { public string id { get { return IDENTITY.id; } } public signal void upload_available(Account account); - public signal void received_file(FileTransfer file_transfer); + public signal void received_file(FileTransfer file_transfer, Conversation conversation); private StreamInteractor stream_interactor; private Database db; @@ -68,7 +68,7 @@ public class FileManager : StreamInteractionModule, Object { file_sender.send_file(conversation, file_transfer); } } - received_file(file_transfer); + received_file(file_transfer, conversation); } public bool is_upload_available(Conversation conversation) { @@ -134,7 +134,7 @@ public class FileManager : StreamInteractionModule, Object { outgoing_processors.add(processor); } - private void handle_incomming_file(FileTransfer file_transfer) { + private void handle_incomming_file(FileTransfer file_transfer, Conversation conversation) { foreach (IncommingFileProcessor processor in incomming_processors) { if (processor.can_process(file_transfer)) { processor.process(file_transfer); @@ -148,7 +148,7 @@ public class FileManager : StreamInteractionModule, Object { } catch (Error e) { } file_transfer.persist(db); - received_file(file_transfer); + received_file(file_transfer, conversation); } private void save_file(FileTransfer file_transfer) { @@ -169,7 +169,7 @@ public class FileManager : StreamInteractionModule, Object { } public interface FileProvider : Object { - public signal void file_incoming(FileTransfer file_transfer); + public signal void file_incoming(FileTransfer file_transfer, Conversation conversation); } public interface FileSender : Object { diff --git a/plugins/http-files/src/file_provider.vala b/plugins/http-files/src/file_provider.vala index b647cdbb..7616d535 100644 --- a/plugins/http-files/src/file_provider.vala +++ b/plugins/http-files/src/file_provider.vala @@ -90,7 +90,7 @@ public class FileProvider : Dino.FileProvider, Object { file_transfer.state = FileTransfer.State.NOT_STARTED; file_transfer.provider = 0; file_transfer.info = message.id.to_string(); - file_incoming(file_transfer); + file_incoming(file_transfer, conversation); success = true; Idle.add((owned)callback); }); -- cgit v1.2.3-54-g00ecf From f5547076d2397cec5c9d7374bd161f93327488c4 Mon Sep 17 00:00:00 2001 From: bobufa Date: Mon, 13 Aug 2018 23:48:15 +0200 Subject: rename content db table -> content_item --- libdino/src/service/database.vala | 4 ++-- libdino/src/service/search_processor.vala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'libdino/src/service/database.vala') diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 8475cc1b..8a470d12 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -43,7 +43,7 @@ public class Database : Qlite.Database { public Column foreign_id = new Column.Integer("foreign_id") { not_null = true }; internal ContentTable(Database db) { - base(db, "contentx"); + base(db, "content_item"); init({id, conversation_id, time, local_time, content_type, foreign_id}); unique({content_type, foreign_id}, "IGNORE"); } @@ -228,7 +228,7 @@ public class Database : Qlite.Database { message.fts_rebuild(); } else if (oldVersion < 8) { exec(""" - insert into contentx (conversation_id, time, local_time, content_type, foreign_id) + insert into content_item (conversation_id, time, local_time, content_type, foreign_id) select conversation.id, message.time, message.local_time, 1, message.id from message join conversation on message.account_id=conversation.account_id and diff --git a/libdino/src/service/search_processor.vala b/libdino/src/service/search_processor.vala index 3f746981..5a14dfe7 100644 --- a/libdino/src/service/search_processor.vala +++ b/libdino/src/service/search_processor.vala @@ -63,7 +63,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .outer_join_with(db.real_jid, db.real_jid.message_id, db.message.id) .with(db.account.enabled, "=", true); if (join_content) { - rows.join_on(db.content, "message.id=contentx.foreign_id AND contentx.content_type=1") + rows.join_on(db.content, "message.id=content_item.foreign_id AND content_item.content_type=1") .with(db.content.content_type, "=", 1); } if (with != null) { @@ -226,7 +226,7 @@ public class SearchProcessor : StreamInteractionModule, Object { public Gee.List match_messages(string query, int offset = -1) { Gee.List ret = new ArrayList(); - QueryBuilder rows = prepare_search(query, false).limit(10); + QueryBuilder rows = prepare_search(query, true).limit(10); if (offset > 0) { rows.offset(offset); } -- cgit v1.2.3-54-g00ecf