aboutsummaryrefslogtreecommitdiff
path: root/libdino/src
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2020-04-29 21:31:23 +0200
committerfiaxh <git@lightrise.org>2020-06-03 21:50:40 +0200
commit71be2abb6a007fd9b4ed3164d70f5cb95d49ce69 (patch)
treef4dcdb63e08ce6dc0e4933e30e18cec27c604719 /libdino/src
parentb5066e0e2f4b482204f43402c797d8bf9f7ef582 (diff)
downloaddino-71be2abb6a007fd9b4ed3164d70f5cb95d49ce69.tar.gz
dino-71be2abb6a007fd9b4ed3164d70f5cb95d49ce69.zip
Store last read content item for conversations
fixes #495
Diffstat (limited to 'libdino/src')
-rw-r--r--libdino/src/application.vala2
-rw-r--r--libdino/src/entity/conversation.vala12
-rw-r--r--libdino/src/service/chat_interaction.vala85
-rw-r--r--libdino/src/service/content_item_store.vala17
-rw-r--r--libdino/src/service/counterpart_interaction_manager.vala7
-rw-r--r--libdino/src/service/database.vala19
-rw-r--r--libdino/src/service/file_manager.vala3
-rw-r--r--libdino/src/service/message_correction.vala3
-rw-r--r--libdino/src/service/notification_events.vala8
9 files changed, 99 insertions, 57 deletions
diff --git a/libdino/src/application.vala b/libdino/src/application.vala
index 1d32fcd1..80af3d3b 100644
--- a/libdino/src/application.vala
+++ b/libdino/src/application.vala
@@ -38,9 +38,9 @@ public interface Application : GLib.Application {
MucManager.start(stream_interactor);
AvatarManager.start(stream_interactor, db);
RosterManager.start(stream_interactor, db);
- ChatInteraction.start(stream_interactor);
FileManager.start(stream_interactor, db);
ContentItemStore.start(stream_interactor, db);
+ ChatInteraction.start(stream_interactor);
NotificationEvents.start(stream_interactor);
SearchProcessor.start(stream_interactor, db);
Register.start(stream_interactor, db);
diff --git a/libdino/src/entity/conversation.vala b/libdino/src/entity/conversation.vala
index 47ebd5d8..800a28a2 100644
--- a/libdino/src/entity/conversation.vala
+++ b/libdino/src/entity/conversation.vala
@@ -34,6 +34,7 @@ public class Conversation : Object {
}
public Encryption encryption { get; set; default = Encryption.NONE; }
public Message? read_up_to { get; set; }
+ public int read_up_to_item { get; set; default=-1; }
public enum NotifySetting { DEFAULT, ON, OFF, HIGHLIGHT }
public NotifySetting notify_setting { get; set; default = NotifySetting.DEFAULT; }
@@ -67,6 +68,7 @@ public class Conversation : Object {
encryption = (Encryption) row[db.conversation.encryption];
int? read_up_to = row[db.conversation.read_up_to];
if (read_up_to != null) this.read_up_to = db.get_message_by_id(read_up_to);
+ read_up_to_item = row[db.conversation.read_up_to_item];
notify_setting = (NotifySetting) row[db.conversation.notification];
send_typing = (Setting) row[db.conversation.send_typing];
send_marker = (Setting) row[db.conversation.send_marker];
@@ -88,6 +90,9 @@ public class Conversation : Object {
if (read_up_to != null) {
insert.value(db.conversation.read_up_to, read_up_to.id);
}
+ if (read_up_to_item != -1) {
+ insert.value(db.conversation.read_up_to_item, read_up_to_item);
+ }
if (nickname != null) {
insert.value(db.conversation.resource, nickname);
}
@@ -161,6 +166,13 @@ public class Conversation : Object {
update.set_null(db.conversation.read_up_to);
}
break;
+ case "read-up-to-item":
+ if (read_up_to_item != -1) {
+ update.set(db.conversation.read_up_to_item, read_up_to_item);
+ } else {
+ update.set_null(db.conversation.read_up_to_item);
+ }
+ break;
case "nickname":
update.set(db.conversation.resource, nickname); break;
case "active":
diff --git a/libdino/src/service/chat_interaction.vala b/libdino/src/service/chat_interaction.vala
index edfc3913..c832aeca 100644
--- a/libdino/src/service/chat_interaction.vala
+++ b/libdino/src/service/chat_interaction.vala
@@ -29,44 +29,14 @@ public class ChatInteraction : StreamInteractionModule, Object {
Timeout.add_seconds(30, update_interactions);
stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(stream_interactor));
stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(on_message_sent);
+ stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(new_item);
}
public bool has_unread(Conversation conversation) {
ContentItem? last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation);
if (last_content_item == null) return false;
- MessageItem? message_item = last_content_item as MessageItem;
- if (message_item != null) {
- Message last_message = message_item.message;
-
- // We are the message sender
- if (last_message.from.equals_bare(conversation.account.bare_jid)) return false;
- // We read up to the message
- if (conversation.read_up_to != null && last_message.equals(conversation.read_up_to)) return false;
-
- return true;
- }
-
- FileItem? file_item = last_content_item as FileItem;
- if (file_item != null) {
- FileTransfer file_transfer = file_item.file_transfer;
-
- // We are the file sender
- if (file_transfer.from.equals_bare(conversation.account.bare_jid)) return false;
-
- if (file_transfer.provider == 0) {
- // HTTP file transfer: Check if the associated message is the last one
- if (file_transfer.info == null) return false;
- Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation);
- if (message == null) return false;
- if (message.equals(conversation.read_up_to)) return false;
- }
- if (file_transfer.provider == 1) {
- if (file_transfer.state == FileTransfer.State.COMPLETE) return false;
- }
- return true;
- }
- return false;
+ return last_content_item.id != conversation.read_up_to_item;
}
public bool is_active_focus(Conversation? conversation = null) {
@@ -106,24 +76,58 @@ public class ChatInteraction : StreamInteractionModule, Object {
on_conversation_focused(conversation);
}
+ private void new_item(ContentItem item, Conversation conversation) {
+ bool mark_read = is_active_focus(conversation);
+
+ if (!mark_read) {
+ MessageItem? message_item = item as MessageItem;
+ if (message_item != null) {
+ if (message_item.message.direction == Message.DIRECTION_SENT) {
+ mark_read = true;
+ }
+ }
+ if (message_item == null) {
+ FileItem? file_item = item as FileItem;
+ if (file_item != null) {
+ if (file_item.file_transfer.direction == FileTransfer.DIRECTION_SENT) {
+ mark_read = true;
+ }
+ }
+ }
+ }
+ if (mark_read) {
+ ContentItem? read_up_to = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, conversation.read_up_to_item);
+ if (read_up_to != null) {
+ if (read_up_to.compare(item) < 0) {
+ conversation.read_up_to_item = item.id;
+ }
+ } else {
+ conversation.read_up_to_item = item.id;
+ }
+ }
+ }
+
private void on_message_sent(Entities.Message message, Conversation conversation) {
last_input_interaction.unset(conversation);
last_interface_interaction.unset(conversation);
- conversation.read_up_to = message;
}
private void on_conversation_focused(Conversation? conversation) {
focus_in = true;
if (conversation == null) return;
- focused_in(selected_conversation);
+ focused_in(conversation);
check_send_read();
- selected_conversation.read_up_to = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(conversation);
+
+ ContentItem? latest_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation);
+ if (latest_item != null) {
+ conversation.read_up_to_item = latest_item.id;
+ }
}
private void on_conversation_unfocused(Conversation? conversation) {
focus_in = false;
if (conversation == null) return;
- focused_out(selected_conversation);
+ focused_out(conversation);
if (last_input_interaction.has_key(conversation)) {
send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED);
last_input_interaction.unset(conversation);
@@ -133,8 +137,7 @@ public class ChatInteraction : StreamInteractionModule, Object {
private void check_send_read() {
if (selected_conversation == null) return;
Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(selected_conversation);
- if (message != null && message.direction == Entities.Message.DIRECTION_RECEIVED && !message.equals(selected_conversation.read_up_to)) {
- selected_conversation.read_up_to = message;
+ if (message != null && message.direction == Entities.Message.DIRECTION_RECEIVED) {
send_chat_marker(message, null, selected_conversation, Xep.ChatMarkers.MARKER_DISPLAYED);
}
}
@@ -163,7 +166,7 @@ public class ChatInteraction : StreamInteractionModule, Object {
private class ReceivedMessageListener : MessageListener {
- public string[] after_actions_const = new string[]{ "DEDUPLICATE", "FILTER_EMPTY" };
+ public string[] after_actions_const = new string[]{ "DEDUPLICATE", "FILTER_EMPTY", "STORE_CONTENT_ITEM" };
public override string action_group { get { return "OTHER_NODES"; } }
public override string[] after_actions { get { return after_actions_const; } }
@@ -183,7 +186,6 @@ public class ChatInteraction : StreamInteractionModule, Object {
if (message.direction == Entities.Message.DIRECTION_SENT) return false;
if (outer.is_active_focus(conversation)) {
outer.check_send_read();
- conversation.read_up_to = message;
outer.send_chat_marker(message, stanza, conversation, Xep.ChatMarkers.MARKER_DISPLAYED);
} else {
outer.send_chat_marker(message, stanza, conversation, Xep.ChatMarkers.MARKER_RECEIVED);
@@ -206,6 +208,9 @@ public class ChatInteraction : StreamInteractionModule, Object {
break;
case Xep.ChatMarkers.MARKER_DISPLAYED:
if (conversation.get_send_marker_setting(stream_interactor) == Conversation.Setting.ON) {
+ if (message.equals(conversation.read_up_to)) return;
+ conversation.read_up_to = message;
+
if (message.type_ == Message.Type.GROUPCHAT || message.type_ == Message.Type.GROUPCHAT_PM) {
if (message.server_id == null) return;
stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.from.bare_jid, message.server_id, message.get_type_string(), Xep.ChatMarkers.MARKER_DISPLAYED);
diff --git a/libdino/src/service/content_item_store.vala b/libdino/src/service/content_item_store.vala
index 1ea0275e..640c3fda 100644
--- a/libdino/src/service/content_item_store.vala
+++ b/libdino/src/service/content_item_store.vala
@@ -40,7 +40,7 @@ public class ContentItemStore : StreamInteractionModule, Object {
}
public Gee.List<ContentItem> get_items_from_query(QueryBuilder select, Conversation conversation) {
- Gee.TreeSet<ContentItem> items = new Gee.TreeSet<ContentItem>(ContentItem.compare);
+ Gee.TreeSet<ContentItem> items = new Gee.TreeSet<ContentItem>(ContentItem.compare_func);
foreach (var row in select) {
int provider = row[db.content_item.content_type];
@@ -113,6 +113,15 @@ public class ContentItemStore : StreamInteractionModule, Object {
return item.size > 0 ? item[0] : null;
}
+ public ContentItem? get_item_by_id(Conversation conversation, int id) {
+ QueryBuilder select = db.content_item.select()
+ .with(db.content_item.id, "=", id);
+
+ Gee.List<ContentItem> item = get_items_from_query(select, conversation);
+
+ return item.size > 0 ? item[0] : null;
+ }
+
public ContentItem? get_latest(Conversation conversation) {
Gee.List<ContentItem> items = get_n_latest(conversation, 1);
if (items.size > 0) {
@@ -246,7 +255,11 @@ public abstract class ContentItem : Object {
this.mark = mark;
}
- public static int compare(ContentItem a, ContentItem b) {
+ public int compare(ContentItem c) {
+ return compare_func(this, c);
+ }
+
+ public static int compare_func(ContentItem a, ContentItem b) {
int res = a.sort_time.compare(b.sort_time);
if (res == 0) {
res = a.display_time.compare(b.display_time);
diff --git a/libdino/src/service/counterpart_interaction_manager.vala b/libdino/src/service/counterpart_interaction_manager.vala
index 10fbacd6..1e5fd0e2 100644
--- a/libdino/src/service/counterpart_interaction_manager.vala
+++ b/libdino/src/service/counterpart_interaction_manager.vala
@@ -150,8 +150,13 @@ public class CounterpartInteractionManager : StreamInteractionModule, Object {
}
if (message == null) return;
// Don't move read marker backwards because we get old info from another client
- if (conversation.read_up_to == null || conversation.read_up_to.local_time.compare(message.local_time) > 0) return;
+ if (conversation.read_up_to != null && conversation.read_up_to.local_time.compare(message.local_time) > 0) return;
conversation.read_up_to = message;
+
+ ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message.id);
+ ContentItem? read_up_to_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, conversation.read_up_to_item);
+ if (read_up_to_item != null && read_up_to_item.sort_time.compare(content_item.sort_time) > 0) return;
+ conversation.read_up_to_item = content_item.id;
} else {
// We can't currently handle chat markers in MUCs
if (conversation.type_ == Conversation.Type.GROUPCHAT) return;
diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala
index 4dfcb5b4..a24822d2 100644
--- a/libdino/src/service/database.vala
+++ b/libdino/src/service/database.vala
@@ -7,7 +7,7 @@ using Dino.Entities;
namespace Dino {
public class Database : Qlite.Database {
- private const int VERSION = 14;
+ private const int VERSION = 15;
public class AccountTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
@@ -166,13 +166,14 @@ public class Database : Qlite.Database {
public Column<int> type_ = new Column.Integer("type");
public Column<int> encryption = new Column.Integer("encryption");
public Column<int> read_up_to = new Column.Integer("read_up_to");
+ public Column<int> read_up_to_item = new Column.Integer("read_up_to_item") { not_null=true, default="-1", min_version=15 };
public Column<int> notification = new Column.Integer("notification") { min_version=3 };
public Column<int> send_typing = new Column.Integer("send_typing") { min_version=3 };
public Column<int> send_marker = new Column.Integer("send_marker") { min_version=3 };
internal ConversationTable(Database db) {
base(db, "conversation");
- init({id, account_id, jid_id, resource, active, last_active, type_, encryption, read_up_to, notification, send_typing, send_marker});
+ init({id, account_id, jid_id, resource, active, last_active, type_, encryption, read_up_to, read_up_to_item, notification, send_typing, send_marker});
}
}
@@ -365,6 +366,20 @@ public class Database : Qlite.Database {
error("Failed to upgrade to database version 12: %s", e.message);
}
}
+ if (oldVersion < 15) {
+ // Initialize `conversation.read_up_to_item` with the content item id corresponding to the `read_up_to` message.
+ try {
+ exec("
+ update conversation
+ set read_up_to_item=ifnull((
+ select content_item.id
+ from content_item
+ where content_item.foreign_id=conversation.read_up_to and content_type=1)
+ , -1);");
+ } catch (Error e) {
+ error("Failed to upgrade to database version 15: %s", e.message);
+ }
+ }
}
public ArrayList<Account> get_accounts() {
diff --git a/libdino/src/service/file_manager.vala b/libdino/src/service/file_manager.vala
index 00abe517..abeaabc7 100644
--- a/libdino/src/service/file_manager.vala
+++ b/libdino/src/service/file_manager.vala
@@ -216,9 +216,10 @@ public class FileManager : StreamInteractionModule, Object {
}
public bool is_sender_trustworthy(FileTransfer file_transfer, Conversation conversation) {
+ if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return true;
Jid relevant_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(file_transfer.from, conversation.account) ?? conversation.counterpart;
bool in_roster = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(conversation.account, relevant_jid) != null;
- return file_transfer.direction == FileTransfer.DIRECTION_SENT || in_roster;
+ return in_roster;
}
private async FileMeta get_file_meta(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation, FileReceiveData receive_data_) throws FileReceiveError {
diff --git a/libdino/src/service/message_correction.vala b/libdino/src/service/message_correction.vala
index eb0b9287..9bedf15b 100644
--- a/libdino/src/service/message_correction.vala
+++ b/libdino/src/service/message_correction.vala
@@ -47,9 +47,6 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
outstanding_correction_nodes[out_message.stanza_id] = stanza_id;
stream_interactor.get_module(MessageStorage.IDENTITY).add_message(out_message, conversation);
stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(out_message, conversation);
- if (conversation.read_up_to != null && conversation.read_up_to.equals(old_message)) { // TODO nicer
- conversation.read_up_to = out_message;
- }
db.message_correction.insert()
.value(db.message_correction.message_id, out_message.id)
diff --git a/libdino/src/service/notification_events.vala b/libdino/src/service/notification_events.vala
index 3f4e3c6d..5e81ff89 100644
--- a/libdino/src/service/notification_events.vala
+++ b/libdino/src/service/notification_events.vala
@@ -33,15 +33,9 @@ public class NotificationEvents : StreamInteractionModule, Object {
}
private void on_content_item_received(ContentItem item, Conversation conversation) {
-
ContentItem last_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation);
- bool not_read_up_to = true;
- MessageItem message_item = item as MessageItem;
- if (message_item != null) {
- not_read_up_to = conversation.read_up_to != null && !conversation.read_up_to.equals(message_item.message);
- }
- if (item.id != last_item.id && not_read_up_to) return;
+ if (item.id != last_item.id && last_item.id != conversation.read_up_to_item) return;
if (!should_notify(item, conversation)) return;
if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus()) return;