mirror of
https://github.com/dino/dino.git
synced 2025-10-03 00:03:58 -04:00
Compare commits
5 Commits
2e8d33bdc4
...
1d44948343
Author | SHA1 | Date | |
---|---|---|---|
|
1d44948343 | ||
|
3482002dec | ||
|
d625058d76 | ||
|
fa678bd2cf | ||
|
58116203cd |
@ -64,6 +64,7 @@ sources = files(
|
||||
'src/service/module_manager.vala',
|
||||
'src/service/muc_manager.vala',
|
||||
'src/service/notification_events.vala',
|
||||
'src/service/occupant_id_store.vala',
|
||||
'src/service/presence_manager.vala',
|
||||
'src/service/replies.vala',
|
||||
'src/service/reactions.vala',
|
||||
|
@ -41,6 +41,7 @@ public interface Application : GLib.Application {
|
||||
BlockingManager.start(stream_interactor);
|
||||
Calls.start(stream_interactor, db);
|
||||
ConversationManager.start(stream_interactor, db);
|
||||
OccupantIdStore.start(stream_interactor, db);
|
||||
MucManager.start(stream_interactor);
|
||||
AvatarManager.start(stream_interactor, db);
|
||||
RosterManager.start(stream_interactor, db);
|
||||
|
@ -46,6 +46,7 @@ public class Message : Object {
|
||||
}
|
||||
public bool direction { get; set; }
|
||||
public Jid? real_jid { get; set; }
|
||||
public int occupant_db_id { get; set; default=-1; }
|
||||
public Type type_ { get; set; default = Type.UNKNOWN; }
|
||||
private string? body_;
|
||||
public string? body {
|
||||
@ -105,9 +106,12 @@ public class Message : Object {
|
||||
body = row[db.message.body];
|
||||
marked = (Message.Marked) row[db.message.marked];
|
||||
encryption = (Encryption) row[db.message.encryption];
|
||||
|
||||
string? real_jid_str = row[db.real_jid.real_jid];
|
||||
if (real_jid_str != null) real_jid = new Jid(real_jid_str);
|
||||
|
||||
occupant_db_id = row[db.message_occupant_id.occupant_id];
|
||||
|
||||
edit_to = row[db.message_correction.to_stanza_id];
|
||||
quoted_item_id = row[db.reply.quoted_content_item_id];
|
||||
|
||||
@ -140,6 +144,13 @@ public class Message : Object {
|
||||
.value(db.real_jid.real_jid, real_jid.to_string())
|
||||
.perform();
|
||||
}
|
||||
|
||||
if (occupant_db_id != -1) {
|
||||
db.message_occupant_id.insert()
|
||||
.value(db.message_occupant_id.occupant_id, occupant_db_id)
|
||||
.value(db.message_occupant_id.message_id, id)
|
||||
.perform();
|
||||
}
|
||||
notify.connect(on_update);
|
||||
}
|
||||
|
||||
@ -320,6 +331,13 @@ public class Message : Object {
|
||||
.value(db.reply.quoted_content_item_id, quoted_item_id)
|
||||
.perform();
|
||||
}
|
||||
|
||||
if (sp.get_name() == "message-occupant-id" && occupant_db_id != -1) {
|
||||
db.message_occupant_id.upsert()
|
||||
.value(db.message_occupant_id.occupant_id, occupant_db_id, true)
|
||||
.value(db.message_occupant_id.message_id, id)
|
||||
.perform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,31 +148,26 @@ public class ContentItemStore : StreamInteractionModule, Object {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ContentItem? get_content_item_for_message_id(Conversation conversation, string message_id) {
|
||||
Row? row = get_content_item_row_for_message_id(conversation, message_id);
|
||||
public ContentItem? get_content_item_for_referencing_id(Conversation conversation, string message_id) {
|
||||
Row? row = get_content_item_row_for_referencing_id(conversation, message_id);
|
||||
if (row != null) {
|
||||
return get_item_from_row(row, conversation);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int get_content_item_id_for_message_id(Conversation conversation, string message_id) {
|
||||
Row? row = get_content_item_row_for_message_id(conversation, message_id);
|
||||
public int get_content_item_id_for_referencing_id(Conversation conversation, string message_id) {
|
||||
Row? row = get_content_item_row_for_referencing_id(conversation, message_id);
|
||||
if (row != null) {
|
||||
return row[db.content_item.id];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private Row? get_content_item_row_for_message_id(Conversation conversation, string message_id) {
|
||||
private Row? get_content_item_row_for_referencing_id(Conversation conversation, string message_id) {
|
||||
var content_item_row = db.content_item.select();
|
||||
|
||||
Message? message = null;
|
||||
if (conversation.type_ == Conversation.Type.CHAT) {
|
||||
message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(message_id, conversation);
|
||||
} else {
|
||||
message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_server_id(message_id, conversation);
|
||||
}
|
||||
Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_referencing_id(message_id, conversation);
|
||||
if (message == null) return null;
|
||||
|
||||
RowOption file_transfer_row = db.file_transfer.select()
|
||||
@ -186,7 +181,11 @@ public class ContentItemStore : StreamInteractionModule, Object {
|
||||
content_item_row.with(db.content_item.foreign_id, "=", file_transfer_row[db.file_transfer.id])
|
||||
.with(db.content_item.content_type, "=", 2);
|
||||
} else {
|
||||
content_item_row.with(db.content_item.foreign_id, "=", message.id)
|
||||
// Check if this message has been corrected. In that case, the foreign id is the latest correction.
|
||||
int correction_message_db_id = stream_interactor.get_module(MessageCorrection.IDENTITY).get_latest_correction_message_id(conversation, message_id);
|
||||
int message_db_id = correction_message_db_id != -1 ? correction_message_db_id : message.id;
|
||||
|
||||
content_item_row.with(db.content_item.foreign_id, "=", message_db_id)
|
||||
.with(db.content_item.content_type, "=", 1);
|
||||
}
|
||||
RowOption content_item_row_option = content_item_row.single().row();
|
||||
|
@ -7,7 +7,7 @@ using Dino.Entities;
|
||||
namespace Dino {
|
||||
|
||||
public class Database : Qlite.Database {
|
||||
private const int VERSION = 29;
|
||||
private const int VERSION = 30;
|
||||
|
||||
public class AccountTable : Table {
|
||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||
@ -103,6 +103,19 @@ public class Database : Qlite.Database {
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageOccupantId : Table {
|
||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||
public Column<int> message_id = new Column.Integer("message_id") { not_null = true };
|
||||
public Column<int> occupant_id = new Column.Integer("occupant_id") { not_null = true };
|
||||
|
||||
internal MessageOccupantId(Database db) {
|
||||
base(db, "message_occupant_id");
|
||||
init({id, message_id, occupant_id});
|
||||
|
||||
index("message_id_occupant_id", { message_id, occupant_id });
|
||||
}
|
||||
}
|
||||
|
||||
public class BodyMeta : Table {
|
||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||
public Column<int> message_id = new Column.Integer("message_id");
|
||||
@ -440,6 +453,7 @@ public class Database : Qlite.Database {
|
||||
public EntityTable entity { get; private set; }
|
||||
public ContentItemTable content_item { get; private set; }
|
||||
public MessageTable message { get; private set; }
|
||||
public MessageOccupantId message_occupant_id { get; private set; }
|
||||
public BodyMeta body_meta { get; private set; }
|
||||
public ReplyTable reply { get; private set; }
|
||||
public MessageCorrectionTable message_correction { get; private set; }
|
||||
@ -473,6 +487,7 @@ public class Database : Qlite.Database {
|
||||
entity = new EntityTable(this);
|
||||
content_item = new ContentItemTable(this);
|
||||
message = new MessageTable(this);
|
||||
message_occupant_id = new MessageOccupantId(this);
|
||||
body_meta = new BodyMeta(this);
|
||||
message_correction = new MessageCorrectionTable(this);
|
||||
reply = new ReplyTable(this);
|
||||
@ -494,7 +509,7 @@ public class Database : Qlite.Database {
|
||||
settings = new SettingsTable(this);
|
||||
account_settings = new AccountSettingsTable(this);
|
||||
conversation_settings = new ConversationSettingsTable(this);
|
||||
init({ account, jid, entity, content_item, message, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, file_hashes, file_thumbnails, sfs_sources, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, account_settings, conversation_settings });
|
||||
init({ account, jid, entity, content_item, message, message_occupant_id, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, file_hashes, file_thumbnails, sfs_sources, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, account_settings, conversation_settings });
|
||||
|
||||
try {
|
||||
exec("PRAGMA journal_mode = WAL");
|
||||
|
@ -16,8 +16,9 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
|
||||
|
||||
private StreamInteractor stream_interactor;
|
||||
private Database db;
|
||||
private HashMap<Conversation, HashMap<Jid, Message>> last_messages = new HashMap<Conversation, HashMap<Jid, Message>>(Conversation.hash_func, Conversation.equals_func);
|
||||
public HashMap<Conversation, Gee.List<ContentItem>> unmatched_corrections = new HashMap<Conversation, Gee.List<ContentItem>>(Conversation.hash_func, Conversation.equals_func);
|
||||
|
||||
private HashMap<Conversation, HashMap<Jid, Message>> last_messages = new HashMap<Conversation, HashMap<Jid, Message>>(Conversation.hash_func, Conversation.equals_func);
|
||||
private HashMap<string, string> outstanding_correction_nodes = new HashMap<string, string>();
|
||||
|
||||
public static void start(StreamInteractor stream_interactor, Database db) {
|
||||
@ -37,10 +38,11 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
|
||||
if (last_messages.has_key(conversation)) last_messages[conversation].unset(jid);
|
||||
}
|
||||
});
|
||||
stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(cache_unmatched_correction);
|
||||
}
|
||||
|
||||
public void set_correction(Conversation conversation, Message message, Message old_message) {
|
||||
string reference_stanza_id = old_message.edit_to ?? old_message.stanza_id;
|
||||
string reference_stanza_id = MessageStorage.get_reference_id(old_message);
|
||||
|
||||
outstanding_correction_nodes[message.stanza_id] = reference_stanza_id;
|
||||
|
||||
@ -90,50 +92,110 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
|
||||
public override string[] after_actions { get { return after_actions_const; } }
|
||||
|
||||
public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
|
||||
if (conversation.type_ != Conversation.Type.CHAT) {
|
||||
// Don't process messages or corrections from MUC history or MUC MAM
|
||||
DateTime? mam_delay = Xep.DelayedDelivery.get_time_for_message(stanza, message.from.bare_jid);
|
||||
if (mam_delay != null) return false;
|
||||
if (Xmpp.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null) return false;
|
||||
if (unmatched_corrections.has_key(conversation) && unmatched_corrections[conversation].size > 0) {
|
||||
ContentItem? remove_from_list = null;
|
||||
bool? ret = null;
|
||||
foreach (var unmatched_correction_item in unmatched_corrections[conversation]) {
|
||||
MessageItem unmatched_correction_message_item = unmatched_correction_item as MessageItem;
|
||||
if (unmatched_correction_message_item != null) {
|
||||
if (MessageStorage.get_reference_id(message) == unmatched_correction_message_item.message.edit_to) {
|
||||
debug("Matching original message to correction retrospectively %s", unmatched_correction_message_item.message.edit_to);
|
||||
remove_from_list = unmatched_correction_item;
|
||||
ret = process_wrong_order_correction(conversation, message, unmatched_correction_message_item);
|
||||
} else if (unmatched_correction_message_item.message.edit_to == message.edit_to) {
|
||||
debug("Got another correction to the same (unknown) original message %s", message.edit_to);
|
||||
ret = process_wrong_order_correction(conversation, message, unmatched_correction_message_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (remove_from_list != null) unmatched_corrections[conversation].remove(remove_from_list);
|
||||
if (ret != null) return ret;
|
||||
}
|
||||
|
||||
string? replace_id = Xep.LastMessageCorrection.get_replace_id(stanza);
|
||||
if (replace_id == null) {
|
||||
if (!last_messages.has_key(conversation)) {
|
||||
last_messages[conversation] = new HashMap<Jid, Message>(Jid.hash_func, Jid.equals_func);
|
||||
}
|
||||
last_messages[conversation][message.from] = message;
|
||||
|
||||
// Store the latest message for every resource. This enables the corrections-allowed-check specified in the XEP.
|
||||
// This is only needed for MUCs - In case the MUC doesn't support occupant ids, the last message can still be corrected.
|
||||
if (replace_id == null && conversation.type_.is_muc_semantic()) {
|
||||
// Don't process messages or corrections from MUC history or MUC MAM
|
||||
if (Xep.DelayedDelivery.get_time_for_message(stanza, message.from.bare_jid) == null &&
|
||||
Xmpp.MessageArchiveManagement.MessageFlag.get_flag(stanza) == null) {
|
||||
if (!last_messages.has_key(conversation)) {
|
||||
last_messages[conversation] = new HashMap<Jid, Message>(Jid.hash_func, Jid.equals_func);
|
||||
}
|
||||
last_messages[conversation][message.from] = message;
|
||||
}
|
||||
}
|
||||
|
||||
if (replace_id != null) {
|
||||
var correction_message = message;
|
||||
correction_message.edit_to = replace_id; // This isn't persisted TODO
|
||||
|
||||
// Check if it's maybe accepted by the XEP rules (for MUCs without occupant id)
|
||||
if (conversation.type_.is_muc_semantic()) {
|
||||
if (last_messages.has_key(conversation) && last_messages[conversation].has_key(correction_message.from)) {
|
||||
var last_message = last_messages[conversation][correction_message.from];
|
||||
// TODO this should be the referencing id (-> in MUCs the server id), but this implementation is temporarily for backwards-compatibility.
|
||||
bool acceptable = last_message.stanza_id == replace_id || last_message.server_id == replace_id;
|
||||
if (acceptable) {
|
||||
return process_in_order_correction(conversation, last_messages[conversation][correction_message.from], correction_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message? original_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_referencing_id(replace_id, conversation);
|
||||
if (original_message != null && is_correction_acceptable(original_message, correction_message)) {
|
||||
return process_in_order_correction(conversation, original_message, correction_message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool process_wrong_order_correction(Conversation conversation, Message earlier_message, MessageItem correction_message) {
|
||||
if (!is_correction_acceptable(earlier_message, correction_message.message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!last_messages.has_key(conversation) || !last_messages[conversation].has_key(message.from)) return false;
|
||||
Message original_message = last_messages[conversation][message.from];
|
||||
if (original_message.stanza_id != replace_id) return false;
|
||||
db.content_item.update()
|
||||
.with(db.content_item.id, "=", correction_message.id)
|
||||
.set(db.content_item.time, (long) earlier_message.time.to_unix())
|
||||
.set(db.content_item.local_time, (long) earlier_message.local_time.to_unix())
|
||||
.perform();
|
||||
|
||||
int message_id_to_be_updated = get_latest_correction_message_id(conversation.account.id, replace_id, db.get_jid_id(message.counterpart), message.counterpart.resourcepart);
|
||||
correction_message.time = earlier_message.time;
|
||||
|
||||
on_received_correction(conversation, correction_message.message.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool process_in_order_correction(Conversation conversation, Message original_message, Message correction_message) {
|
||||
int message_id_to_be_updated = get_latest_correction_message_id(conversation, correction_message.edit_to);
|
||||
if (message_id_to_be_updated == -1) {
|
||||
message_id_to_be_updated = original_message.id;
|
||||
}
|
||||
|
||||
ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_for_referencing_id(conversation, correction_message.edit_to);
|
||||
|
||||
db.message_correction.insert()
|
||||
.value(db.message_correction.message_id, message.id)
|
||||
.value(db.message_correction.to_stanza_id, replace_id)
|
||||
.perform();
|
||||
.value(db.message_correction.message_id, correction_message.id)
|
||||
.value(db.message_correction.to_stanza_id, correction_message.edit_to)
|
||||
.perform();
|
||||
|
||||
int current_correction_message_id = get_latest_correction_message_id(conversation.account.id, replace_id, db.get_jid_id(message.counterpart), message.counterpart.resourcepart);
|
||||
|
||||
if (current_correction_message_id != message_id_to_be_updated) {
|
||||
int current_correction_message_id = get_latest_correction_message_id(conversation, correction_message.edit_to);
|
||||
if (content_item != null) {
|
||||
db.content_item.update()
|
||||
.with(db.content_item.foreign_id, "=", message_id_to_be_updated)
|
||||
.with(db.content_item.id, "=", content_item.id)
|
||||
.with(db.content_item.content_type, "=", 1)
|
||||
.set(db.content_item.foreign_id, current_correction_message_id)
|
||||
.perform();
|
||||
message.edit_to = replace_id;
|
||||
|
||||
on_received_correction(conversation, current_correction_message_id);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
warning("Got no content item for %s", correction_message.edit_to);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -146,16 +208,16 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
private int get_latest_correction_message_id(int account_id, string stanza_id, int counterpart_jid_id, string? counterpart_resource) {
|
||||
public int get_latest_correction_message_id(Conversation conversation, string stanza_ref_id) {
|
||||
var qry = db.message_correction.select({db.message.id})
|
||||
.join_with(db.message, db.message.id, db.message_correction.message_id)
|
||||
.with(db.message.account_id, "=", account_id)
|
||||
.with(db.message.counterpart_id, "=", counterpart_jid_id)
|
||||
.with(db.message_correction.to_stanza_id, "=", stanza_id)
|
||||
.with(db.message.account_id, "=", conversation.account.id)
|
||||
.with(db.message.counterpart_id, "=", db.get_jid_id(conversation.counterpart))
|
||||
.with(db.message_correction.to_stanza_id, "=", stanza_ref_id)
|
||||
.order_by(db.message.time, "DESC");
|
||||
|
||||
if (counterpart_resource != null) {
|
||||
qry.with(db.message.counterpart_resource, "=", counterpart_resource);
|
||||
if (conversation.counterpart.resourcepart != null) {
|
||||
qry.with(db.message.counterpart_resource, "=", conversation.counterpart.resourcepart);
|
||||
}
|
||||
RowOption row = qry.single().row();
|
||||
if (row.is_present()) {
|
||||
@ -180,6 +242,27 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
|
||||
last_messages[conversation] = last_conversation_messages;
|
||||
}
|
||||
}
|
||||
|
||||
private void cache_unmatched_correction(ContentItem content_item, Conversation conversation) {
|
||||
MessageItem message_item = content_item as MessageItem;
|
||||
if (message_item == null || message_item.message.edit_to == null) return;
|
||||
|
||||
// Check if this is an unmatched correction
|
||||
if (content_item.time != message_item.time) return;
|
||||
|
||||
debug(@"Caching unmatched correction $(message_item.message.server_id) $(message_item.id)");
|
||||
if (!unmatched_corrections.has_key(conversation)) unmatched_corrections[conversation] = new ArrayList<ContentItem>();
|
||||
unmatched_corrections[conversation].add(content_item);
|
||||
}
|
||||
}
|
||||
|
||||
// Accepts MUC corrections iff the occupant id matches
|
||||
// Accepts 1:1 corrections iff the bare jid matches
|
||||
private bool is_correction_acceptable(Message original_message, Message correction_message) {
|
||||
bool acceptable = (original_message.type_.is_muc_semantic() && original_message.occupant_db_id != -1 && original_message.occupant_db_id == correction_message.occupant_db_id) ||
|
||||
(original_message.type_ == Message.Type.CHAT && original_message.from.equals_bare(correction_message.from));
|
||||
if (!acceptable) warning("Got unacceptable correction (%i to %i from %s)", correction_message.id, original_message.id, correction_message.from.to_string());
|
||||
return acceptable;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -159,8 +159,8 @@ public class MessageProcessor : StreamInteractionModule, Object {
|
||||
Xmpp.MessageArchiveManagement.MessageFlag? mam_message_flag = Xmpp.MessageArchiveManagement.MessageFlag.get_flag(message);
|
||||
EntityInfo entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
|
||||
if (mam_message_flag != null && mam_message_flag.mam_id != null) {
|
||||
bool server_does_mam = entity_info.has_feature_cached(account, account.bare_jid, Xmpp.MessageArchiveManagement.NS_URI);
|
||||
if (server_does_mam) {
|
||||
bool sender_supports_mam = entity_info.has_feature_cached(account, mam_message_flag.sender_jid, Xmpp.MessageArchiveManagement.NS_URI);
|
||||
if (sender_supports_mam) {
|
||||
new_message.server_id = mam_message_flag.mam_id;
|
||||
}
|
||||
} else if (message.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) {
|
||||
|
@ -41,6 +41,7 @@ public class MessageStorage : StreamInteractionModule, Object {
|
||||
.with(db.message.counterpart_id, "=", db.get_jid_id(conversation.counterpart))
|
||||
.with(db.message.type_, "=", (int) Util.get_message_type_for_conversation(conversation))
|
||||
.order_by(db.message.time, "DESC")
|
||||
.outer_join_with(db.message_occupant_id, db.message_occupant_id.message_id, db.message.id)
|
||||
.outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id)
|
||||
.outer_join_with(db.reply, db.reply.message_id, db.message.id)
|
||||
.limit(count);
|
||||
@ -92,6 +93,7 @@ public class MessageStorage : StreamInteractionModule, Object {
|
||||
}
|
||||
|
||||
RowOption row_option = db.message.select().with(db.message.id, "=", id)
|
||||
.outer_join_with(db.message_occupant_id, db.message_occupant_id.message_id, db.message.id)
|
||||
.outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id)
|
||||
.outer_join_with(db.reply, db.reply.message_id, db.message.id)
|
||||
.row();
|
||||
@ -121,6 +123,7 @@ public class MessageStorage : StreamInteractionModule, Object {
|
||||
.with(db.message.type_, "=", (int) Util.get_message_type_for_conversation(conversation))
|
||||
.with(db.message.stanza_id, "=", stanza_id)
|
||||
.order_by(db.message.time, "DESC")
|
||||
.outer_join_with(db.message_occupant_id, db.message_occupant_id.message_id, db.message.id)
|
||||
.outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id)
|
||||
.outer_join_with(db.reply, db.reply.message_id, db.message.id);
|
||||
|
||||
@ -147,6 +150,7 @@ public class MessageStorage : StreamInteractionModule, Object {
|
||||
.with(db.message.type_, "=", (int) Util.get_message_type_for_conversation(conversation))
|
||||
.with(db.message.server_id, "=", server_id)
|
||||
.order_by(db.message.time, "DESC")
|
||||
.outer_join_with(db.message_occupant_id, db.message_occupant_id.message_id, db.message.id)
|
||||
.outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id)
|
||||
.outer_join_with(db.reply, db.reply.message_id, db.message.id);
|
||||
|
||||
|
88
libdino/src/service/occupant_id_store.vala
Normal file
88
libdino/src/service/occupant_id_store.vala
Normal file
@ -0,0 +1,88 @@
|
||||
using Xmpp;
|
||||
using Gee;
|
||||
using Qlite;
|
||||
|
||||
using Dino.Entities;
|
||||
|
||||
namespace Dino {
|
||||
|
||||
public class OccupantIdStore : StreamInteractionModule, Object {
|
||||
public static ModuleIdentity<OccupantIdStore> IDENTITY = new ModuleIdentity<OccupantIdStore>("occupant_id_cache");
|
||||
public string id { get { return IDENTITY.id; } }
|
||||
|
||||
private Database db;
|
||||
|
||||
// (Account, MUC JID, occupant id) -> occupant db id
|
||||
private HashMap<Account, HashMap<Jid, HashMap<string, int>>> occupant_db_ids = new HashMap<Account, HashMap<Jid, HashMap<string, int>>>(Account.hash_func, Account.equals_func);
|
||||
private HashMap<int, string> occupant_nicks = new HashMap<int, string>();
|
||||
|
||||
public static void start(StreamInteractor stream_interactor, Database db) {
|
||||
OccupantIdStore m = new OccupantIdStore(db);
|
||||
stream_interactor.add_module(m);
|
||||
}
|
||||
|
||||
private OccupantIdStore(Database db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public int get_occupant_db_id(Account account, string occupant_id, Jid muc_jid) {
|
||||
if (!occupant_db_ids.has_key(account) || !occupant_db_ids[account].has_key(muc_jid) || !occupant_db_ids[account][muc_jid].has_key(occupant_id)) {
|
||||
warning("Requested unknown occupant db id");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return occupant_db_ids[account][muc_jid][occupant_id];
|
||||
}
|
||||
|
||||
public int cache_occupant_id(Account account, string occupant_id, Jid occupant_jid) {
|
||||
string last_nick = occupant_jid.resourcepart;
|
||||
Jid muc_jid = occupant_jid.bare_jid;
|
||||
|
||||
if (occupant_db_ids.has_key(account) && occupant_db_ids[account].has_key(muc_jid) && occupant_db_ids[account][muc_jid].has_key(occupant_id)) {
|
||||
int occupant_db_id = occupant_db_ids[account][muc_jid][occupant_id];
|
||||
if (occupant_nicks[occupant_db_id] == last_nick) {
|
||||
return occupant_db_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!occupant_db_ids.has_key(account)) occupant_db_ids[account] = new HashMap<Jid, HashMap<string, int>>(Jid.hash_bare_func, Jid.equals_bare_func);
|
||||
if (!occupant_db_ids[account].has_key(muc_jid)) occupant_db_ids[account][muc_jid] = new HashMap<string, int>();
|
||||
|
||||
int muc_jid_id = db.get_jid_id(muc_jid);
|
||||
|
||||
RowOption row = db.occupantid.select()
|
||||
.with(db.occupantid.account_id, "=", account.id)
|
||||
.with(db.occupantid.jid_id, "=", muc_jid_id)
|
||||
.with(db.occupantid.occupant_id, "=", occupant_id)
|
||||
.single().row();
|
||||
|
||||
int occupant_db_id = -1;
|
||||
if (row.is_present()) {
|
||||
occupant_db_id = row[db.occupantid.id];
|
||||
|
||||
if (row[db.occupantid.last_nick] != last_nick) {
|
||||
db.occupantid.upsert()
|
||||
.value(db.occupantid.account_id, account.id, true)
|
||||
.value(db.occupantid.jid_id, muc_jid_id, true)
|
||||
.value(db.occupantid.occupant_id, occupant_id, true)
|
||||
.value(db.occupantid.last_nick, last_nick, false)
|
||||
.perform();
|
||||
}
|
||||
} else {
|
||||
occupant_db_id = (int)db.occupantid.upsert()
|
||||
.value(db.occupantid.account_id, account.id, true)
|
||||
.value(db.occupantid.jid_id, muc_jid_id, true)
|
||||
.value(db.occupantid.occupant_id, occupant_id, true)
|
||||
.value(db.occupantid.last_nick, muc_jid.resourcepart, false)
|
||||
.perform();
|
||||
}
|
||||
|
||||
occupant_db_ids[account][muc_jid][occupant_id] = occupant_db_id;
|
||||
occupant_nicks[occupant_db_id] = last_nick;
|
||||
|
||||
return occupant_db_id;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -251,7 +251,7 @@ public class Dino.Reactions : StreamInteractionModule, Object {
|
||||
Message reaction_message = yield stream_interactor.get_module(MessageProcessor.IDENTITY).parse_message_stanza(account, stanza);
|
||||
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(reaction_message);
|
||||
|
||||
int content_item_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_id_for_message_id(conversation, message_id);
|
||||
int content_item_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_id_for_referencing_id(conversation, message_id);
|
||||
var reaction_info = new ReactionInfo() { conversation=conversation, from_jid=from_jid, reactions=reactions, stanza=stanza, received_time=new DateTime.now() };
|
||||
|
||||
if (content_item_id != -1) {
|
||||
@ -425,23 +425,7 @@ public class Dino.Reactions : StreamInteractionModule, Object {
|
||||
}
|
||||
|
||||
if (occupant_id != null) {
|
||||
RowOption row = db.occupantid.select()
|
||||
.with(db.occupantid.account_id, "=", account.id)
|
||||
.with(db.occupantid.jid_id, "=", jid_id)
|
||||
.with(db.occupantid.occupant_id, "=", occupant_id)
|
||||
.single().row();
|
||||
|
||||
int occupant_db_id = -1;
|
||||
if (row.is_present()) {
|
||||
occupant_db_id = row[db.occupantid.id];
|
||||
} else {
|
||||
occupant_db_id = (int)db.occupantid.upsert()
|
||||
.value(db.occupantid.account_id, account.id, true)
|
||||
.value(db.occupantid.jid_id, jid_id, true)
|
||||
.value(db.occupantid.occupant_id, occupant_id, true)
|
||||
.value(db.occupantid.last_nick, jid.resourcepart, false)
|
||||
.perform();
|
||||
}
|
||||
int occupant_db_id = stream_interactor.get_module(OccupantIdStore.IDENTITY).cache_occupant_id(account, occupant_id, jid);
|
||||
builder.value(db.reaction.occupant_id, occupant_db_id, true);
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class Dino.Replies : StreamInteractionModule, Object {
|
||||
Xep.Replies.ReplyTo? reply_to = Xep.Replies.get_reply_to(stanza);
|
||||
if (reply_to == null) return;
|
||||
|
||||
ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_for_message_id(conversation, reply_to.to_message_id);
|
||||
ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_for_referencing_id(conversation, reply_to.to_message_id);
|
||||
if (quoted_content_item == null) return;
|
||||
|
||||
message.set_quoted_item(quoted_content_item.id);
|
||||
|
@ -46,6 +46,7 @@
|
||||
<file>icons/scalable/status/dino-bell-large-none-symbolic.svg</file>
|
||||
<file>icons/scalable/status/dino-bell-large-symbolic.svg</file>
|
||||
<file>icons/scalable/status/dino-block-symbolic.svg</file>
|
||||
<file>icons/scalable/status/dino-mention-symbolic.svg</file>
|
||||
<file>icons/scalable/status/dino-party-popper-symbolic.svg</file>
|
||||
<file>icons/scalable/status/dino-security-high-symbolic.svg</file>
|
||||
<file>icons/scalable/status/dino-status-away.svg</file>
|
||||
@ -72,6 +73,7 @@
|
||||
<file>menu_encryption.ui</file>
|
||||
<file>message_item_widget_edit_mode.ui</file>
|
||||
<file>muc_member_list_row.ui</file>
|
||||
<file>mute_toggle.ui</file>
|
||||
<file>occupant_list.ui</file>
|
||||
<file>occupant_list_item.ui</file>
|
||||
<file>quote.ui</file>
|
||||
|
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 7.664062 0.0078125 c -2.613281 0.1093755 -5.003906 1.4882815 -6.410156 3.6953125 c -1.90625 3 -1.605468 6.898437 0.734375 9.574219 c 2.347657 2.667968 6.175781 3.472656 9.398438 1.96875 c 0.5 -0.230469 0.714843 -0.828125 0.484375 -1.328125 c -0.234375 -0.5 -0.828125 -0.714844 -1.332032 -0.484375 c -2.414062 1.132812 -5.289062 0.53125 -7.046874 -1.476563 c -1.761719 -2.003906 -1.988282 -4.933593 -0.550782 -7.179687 c 1.429688 -2.253906 4.175782 -3.285156 6.738282 -2.539063 c 2.5625 0.746094 4.324218 3.09375 4.320312 5.761719 v 0.039062 v 0.960938 c 0 0.359375 -0.1875 0.683594 -0.5 0.863281 c -0.308594 0.179688 -0.6875 0.179688 -1 0 c -0.308594 -0.175781 -0.5 -0.507812 -0.5 -0.863281 v -1 c 0 -2.199219 -1.800781 -4 -4 -4 c -2.195312 0 -4 1.800781 -4 4 c 0 2.195312 1.804688 4 4 4 c 1.046875 0 1.992188 -0.417969 2.707031 -1.078125 c 0.222657 0.265625 0.488281 0.496094 0.792969 0.675781 c 0.929688 0.535156 2.074219 0.535156 3 0 c 0.929688 -0.535156 1.5 -1.527344 1.5 -2.597656 v -1 c 0 -3.554688 -2.347656 -6.683594 -5.761719 -7.683594 c -0.835937 -0.2421872 -1.703125 -0.347656 -2.574219 -0.3085935 z m 0.335938 5.9921875 c 1.117188 0 2 0.882812 2 2 c 0 1.113281 -0.882812 2 -2 2 c -1.113281 0 -2 -0.886719 -2 -2 c 0 -1.117188 0.886719 -2 2 -2 z m 0 0" fill="#222222"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
49
main/data/mute_toggle.ui
Normal file
49
main/data/mute_toggle.ui
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="Adw" version="1.0"/>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="AdwToggleGroup" id="mute_switch_chat">
|
||||
<child>
|
||||
<object class="AdwToggle">
|
||||
<property name="icon-name">dino-bell-large</property>
|
||||
<property name="name">notification.on</property>
|
||||
<property name="tooltip" translatable="yes">Enable notifications</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwToggle">
|
||||
<property name="icon-name">dino-bell-large-none</property>
|
||||
<property name="name">notification.off</property>
|
||||
<property name="tooltip" translatable="yes">Disable notifications</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="AdwToggleGroup" id="mute_switch_muc">
|
||||
<child>
|
||||
<object class="AdwToggle">
|
||||
<property name="icon-name">dino-bell-large</property>
|
||||
<property name="name">notification.on</property>
|
||||
<property name="tooltip" translatable="yes">Notify for all messages</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwToggle">
|
||||
<property name="icon-name">dino-mention-symbolic</property>
|
||||
<property name="name">notification.highlight</property>
|
||||
<property name="tooltip" translatable="yes">Notify only for mentions</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwToggle">
|
||||
<property name="icon-name">dino-bell-large-none</property>
|
||||
<property name="name">notification.off</property>
|
||||
<property name="tooltip" translatable="yes">Disable notifications</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkButton" id="reset_button">
|
||||
<property name="icon-name">edit-clear-symbolic</property>
|
||||
<property name="name">notification.default</property>
|
||||
<property name="tooltip-text" translatable="yes">Reset to default</property>
|
||||
</object>
|
||||
</interface>
|
@ -22,6 +22,8 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||
[GtkChild] public unowned Revealer main_revealer;
|
||||
|
||||
private PopoverMenu popover_menu;
|
||||
private Adw.ToggleGroup mute_switch;
|
||||
private Button mute_reset_button;
|
||||
|
||||
public Conversation conversation { get; private set; }
|
||||
|
||||
@ -95,6 +97,24 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||
});
|
||||
|
||||
popover_menu = new Gtk.PopoverMenu.from_model(get_popover_menu_model());
|
||||
|
||||
Builder builder = new Builder.from_resource("/im/dino/Dino/mute_toggle.ui");
|
||||
switch (conversation.type_) {
|
||||
case Conversation.Type.CHAT:
|
||||
case Conversation.Type.GROUPCHAT_PM:
|
||||
mute_switch = (Adw.ToggleGroup) builder.get_object("mute_switch_chat");
|
||||
break;
|
||||
case Conversation.Type.GROUPCHAT:
|
||||
mute_switch = (Adw.ToggleGroup) builder.get_object("mute_switch_muc");
|
||||
break;
|
||||
}
|
||||
mute_reset_button = (Button) builder.get_object("reset_button");
|
||||
Box mute_switch_wrapper = new Box(Orientation.HORIZONTAL, 0);
|
||||
Box separator = new Box(Orientation.HORIZONTAL, 0) { hexpand=true };
|
||||
mute_switch_wrapper.append(mute_switch);
|
||||
mute_switch_wrapper.append(separator);
|
||||
mute_switch_wrapper.append(mute_reset_button);
|
||||
popover_menu.add_child(mute_switch_wrapper, "mute-switch");
|
||||
popover_menu.set_parent(this);
|
||||
|
||||
GestureClick right_click = new GestureClick();
|
||||
@ -179,6 +199,10 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||
menu_item_close_conversation.set_action_and_target_value("app.close-conversation", new GLib.Variant.int32(conversation.id));
|
||||
menu.append_item(menu_item_close_conversation);
|
||||
|
||||
MenuItem menu_item_mute_switch = new MenuItem(null, null);
|
||||
menu_item_mute_switch.set_attribute_value("custom", new GLib.Variant.string("mute-switch"));
|
||||
menu.append_item(menu_item_mute_switch);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user