aboutsummaryrefslogtreecommitdiff
path: root/libdino/src
diff options
context:
space:
mode:
Diffstat (limited to 'libdino/src')
-rw-r--r--libdino/src/application.vala2
-rw-r--r--libdino/src/service/contact_model.vala2
-rw-r--r--libdino/src/service/database.vala34
-rw-r--r--libdino/src/service/muc_affiliations.vala82
-rw-r--r--libdino/src/service/muc_manager.vala68
5 files changed, 170 insertions, 18 deletions
diff --git a/libdino/src/application.vala b/libdino/src/application.vala
index ef2d883e..19650495 100644
--- a/libdino/src/application.vala
+++ b/libdino/src/application.vala
@@ -42,7 +42,7 @@ public interface Application : GLib.Application {
Calls.start(stream_interactor, db);
ConversationManager.start(stream_interactor, db);
OccupantIdStore.start(stream_interactor, db);
- MucManager.start(stream_interactor);
+ MucManager.start(stream_interactor, db);
AvatarManager.start(stream_interactor, db);
RosterManager.start(stream_interactor, db);
FileManager.start(stream_interactor, db);
diff --git a/libdino/src/service/contact_model.vala b/libdino/src/service/contact_model.vala
index 312df4f7..33f6126b 100644
--- a/libdino/src/service/contact_model.vala
+++ b/libdino/src/service/contact_model.vala
@@ -27,7 +27,7 @@ namespace Dino {
stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect((account, jid) => {
check_update_models(account, jid, Conversation.Type.GROUPCHAT);
});
- stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => {
+ stream_interactor.get_module(MucManager.IDENTITY).private_room_occupants_updated.connect((account, room) => {
check_update_models(account, room, Conversation.Type.GROUPCHAT);
});
stream_interactor.get_module(MucManager.IDENTITY).subject_set.connect((account, jid, subject) => {
diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala
index 54f5ab42..7a5b940a 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 = 31;
+ private const int VERSION = 32;
public class AccountTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
@@ -385,6 +385,32 @@ public class Database : Qlite.Database {
}
}
+ public class MucAffiliationTable : Table {
+ public Column<int> account_id = new Column.Integer("account_id");
+ public Column<int> muc_jid_id = new Column.Integer("muc_jid_id");
+ public Column<int> real_jid_id = new Column.Integer("real_jid_id");
+ public Column<int> affiliation = new Column.Integer("affiliation");
+
+ internal MucAffiliationTable(Database db) {
+ base(db, "muc_affiliation");
+ init({account_id, muc_jid_id, real_jid_id, affiliation});
+ index("muc_affiliation_idx", {muc_jid_id});
+ unique({account_id, muc_jid_id, real_jid_id}, "REPLACE");
+ }
+ }
+
+ public class MucAffiliationVersionTable : Table {
+ public Column<int> account_id = new Column.Integer("account_id");
+ public Column<int> muc_jid_id = new Column.Integer("muc_jid_id");
+ public Column<string> version = new Column.Text("version");
+
+ internal MucAffiliationVersionTable(Database db) {
+ base(db, "muc_affiliation_version");
+ init({account_id, muc_jid_id, version});
+ unique({account_id, muc_jid_id}, "REPLACE");
+ }
+ }
+
public class ReactionTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
public Column<int> account_id = new Column.Integer("account_id") { not_null = true };
@@ -473,6 +499,8 @@ public class Database : Qlite.Database {
public RosterTable roster { get; private set; }
public MamCatchupTable mam_catchup { get; private set; }
public ReactionTable reaction { get; private set; }
+ public MucAffiliationTable muc_affiliation { get; private set; }
+ public MucAffiliationVersionTable muc_affiliation_version { get; private set; }
public SettingsTable settings { get; private set; }
public AccountSettingsTable account_settings { get; private set; }
public ConversationSettingsTable conversation_settings { get; private set; }
@@ -507,10 +535,12 @@ public class Database : Qlite.Database {
roster = new RosterTable(this);
mam_catchup = new MamCatchupTable(this);
reaction = new ReactionTable(this);
+ muc_affiliation = new MucAffiliationTable(this);
+ muc_affiliation_version = new MucAffiliationVersionTable(this);
settings = new SettingsTable(this);
account_settings = new AccountSettingsTable(this);
conversation_settings = new ConversationSettingsTable(this);
- 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 });
+ 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, muc_affiliation, muc_affiliation_version, settings, account_settings, conversation_settings });
try {
exec("PRAGMA journal_mode = WAL");
diff --git a/libdino/src/service/muc_affiliations.vala b/libdino/src/service/muc_affiliations.vala
new file mode 100644
index 00000000..41107a93
--- /dev/null
+++ b/libdino/src/service/muc_affiliations.vala
@@ -0,0 +1,82 @@
+using Gee;
+
+using Dino.Entities;
+using Xmpp;
+using Xmpp.Xep;
+
+namespace Dino {
+ public class MucAffiliations {
+
+ private Database db;
+
+ public MucAffiliations(Database db) {
+ this.db = db;
+ }
+
+ public string? get_mav_version(Account account, Jid muc_jid) {
+ Qlite.RowOption version_rowopt = db.muc_affiliation_version.select()
+ .with(db.muc_affiliation_version.account_id, "=", account.id)
+ .with(db.muc_affiliation_version.muc_jid_id, "=", db.get_jid_id(muc_jid))
+ .single()
+ .row();
+
+ return version_rowopt.is_present() ? version_rowopt.inner[db.muc_affiliation_version.version] : null;
+ }
+
+ public void clear_affiliations(Account account, Jid muc_jid) {
+ db.muc_affiliation.delete()
+ .with(db.muc_affiliation.account_id, "=", account.id)
+ .with(db.muc_affiliation.muc_jid_id, "=", db.get_jid_id(muc_jid))
+ .perform();
+ }
+
+ public void clear_mav_version(Account account, Jid muc_jid) {
+ db.muc_affiliation_version.delete()
+ .with(db.muc_affiliation_version.account_id, "=", account.id)
+ .with(db.muc_affiliation_version.muc_jid_id, "=", db.get_jid_id(muc_jid))
+ .perform();
+ }
+
+ public void update_affiliations(Account account, Jid muc_jid, HashMap<Jid, Muc.Affiliation> affiliations, string? mav_since, string? mav_until) {
+ // Update version (if MUC affiliation versioning is used)
+ if (mav_until != null && mav_since == null) {
+ clear_affiliations(account, muc_jid);
+ } else if (mav_until != null) {
+ string? previous_version = get_mav_version(account, muc_jid);
+ if (mav_since != previous_version) {
+ warning("Got an unexpected affiliations version 'since'. Expected %s; got %s", previous_version, mav_since);
+ clear_affiliations(account, muc_jid);
+ clear_mav_version(account, muc_jid);
+ }
+ }
+
+ if (mav_until != null) {
+ db.muc_affiliation_version.upsert()
+ .value(db.muc_affiliation_version.account_id, account.id, true)
+ .value(db.muc_affiliation_version.muc_jid_id, db.get_jid_id(muc_jid), true)
+ .value(db.muc_affiliation_version.version, mav_until)
+ .perform();
+ }
+
+ // Update affiliations
+ foreach (Jid real_jid in affiliations.keys) {
+ Muc.Affiliation affiliation = affiliations[real_jid];
+
+ if (affiliation == Muc.Affiliation.NONE) {
+ db.muc_affiliation.delete()
+ .with(db.muc_affiliation.account_id, "=", account.id)
+ .with(db.muc_affiliation.muc_jid_id, "=", db.get_jid_id(muc_jid))
+ .with(db.muc_affiliation.real_jid_id, "=", db.get_jid_id(real_jid))
+ .perform();
+ } else {
+ db.muc_affiliation.insert()
+ .value(db.muc_affiliation.account_id, account.id)
+ .value(db.muc_affiliation.muc_jid_id, db.get_jid_id(muc_jid))
+ .value(db.muc_affiliation.real_jid_id, db.get_jid_id(real_jid))
+ .value(db.muc_affiliation.affiliation, (int)affiliation)
+ .perform();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala
index 29c6c5ca..b47a52bf 100644
--- a/libdino/src/service/muc_manager.vala
+++ b/libdino/src/service/muc_manager.vala
@@ -12,7 +12,7 @@ public class MucManager : StreamInteractionModule, Object {
public signal void left(Account account, Jid jid);
public signal void subject_set(Account account, Jid jid, string? subject);
public signal void room_info_updated(Account account, Jid muc_jid);
- public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant);
+ public signal void private_room_occupants_updated(Account account, Jid room);
public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason);
public signal void voice_request_received(Account account, Jid room_jid, Jid from_jid, string nick);
public signal void received_occupant_role(Account account, Jid jid, Xep.Muc.Role? role);
@@ -21,6 +21,9 @@ public class MucManager : StreamInteractionModule, Object {
public signal void conference_removed(Account account, Jid jid);
private StreamInteractor stream_interactor;
+ private Database db;
+ private MucAffiliations muc_affiliations;
+
private HashMap<Account, HashSet<Jid>> mucs_joined = new HashMap<Account, HashSet<Jid>>(Account.hash_func, Account.equals_func);
private HashMap<Account, HashSet<Jid>> mucs_joining = new HashMap<Account, HashSet<Jid>>(Account.hash_func, Account.equals_func);
private HashMap<Account, HashMap<Jid, Cancellable>> mucs_sync_cancellables = new HashMap<Account, HashMap<Jid, Cancellable>>(Account.hash_func, Account.equals_func);
@@ -31,13 +34,16 @@ public class MucManager : StreamInteractionModule, Object {
public HashMap<Account, Jid> default_muc_server = new HashMap<Account, Jid>(Account.hash_func, Account.equals_func);
private HashMap<Account, HashMap<Jid, string>> own_occupant_ids = new HashMap<Account, HashMap<Jid, string>>(Account.hash_func, Account.equals_func);
- public static void start(StreamInteractor stream_interactor) {
- MucManager m = new MucManager(stream_interactor);
+ public static void start(StreamInteractor stream_interactor, Database db) {
+ MucManager m = new MucManager(stream_interactor, db);
stream_interactor.add_module(m);
}
- private MucManager(StreamInteractor stream_interactor) {
+ private MucManager(StreamInteractor stream_interactor, Database db) {
this.stream_interactor = stream_interactor;
+ this.db = db;
+ this.muc_affiliations = new MucAffiliations(db);
+
this.received_message_listener = new ReceivedMessageListener(stream_interactor);
stream_interactor.account_added.connect(on_account_added);
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
@@ -86,7 +92,9 @@ public class MucManager : StreamInteractionModule, Object {
}
mucs_joining[account].add(jid);
- Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null);
+ string? mav_version = muc_affiliations.get_mav_version(account, jid);
+
+ Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null, mav_version);
mucs_joining[account].remove(jid);
@@ -100,6 +108,14 @@ public class MucManager : StreamInteractionModule, Object {
joined_conversation.nickname = nick;
stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(joined_conversation);
+ bool can_do_affiliation_versioning = yield entity_info.has_feature(account, jid, Xmpp.Xep.MucAffiliationsVersioning.NS_URI);
+ if (!can_do_affiliation_versioning) {
+ stream.get_module(Xep.Muc.Module.IDENTITY).fetch_affiliations.begin(stream, jid.bare_jid, () => {
+ muc_affiliations.clear_affiliations(account, jid);
+ persist_muc_affiliations(account, jid.bare_jid);
+ });
+ }
+
if (can_do_mam) {
var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync;
if (conversation == null) {
@@ -371,11 +387,16 @@ public class MucManager : StreamInteractionModule, Object {
}
public Gee.List<Jid>? get_offline_members(Jid jid, Account account) {
- Xep.Muc.Flag? flag = get_muc_flag(account);
- if (flag != null) {
- return flag.get_offline_members(jid);
+ Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func);
+
+ var qry = db.muc_affiliation.select()
+ .with(db.muc_affiliation.account_id, "=", account.id)
+ .with(db.muc_affiliation.muc_jid_id, "=", db.get_jid_id(jid));
+
+ foreach (Qlite.Row affiliation_row in qry) {
+ ret.add(db.get_jid_by_id(affiliation_row[db.muc_affiliation.real_jid_id]));
}
- return null;
+ return ret;
}
public Gee.List<Jid>? get_other_offline_members(Jid jid, Account account) {
@@ -444,14 +465,15 @@ public class MucManager : StreamInteractionModule, Object {
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_role.connect( (stream, from_jid, role) => {
received_occupant_role(account, from_jid, role);
});
+ stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_affiliations.connect( (stream, muc_jid, affiliations, mav_since, mav_until) => {
+ muc_affiliations.update_affiliations(account, muc_jid, affiliations, mav_since, mav_until);
+ if (is_private_room(account, muc_jid)) {
+ private_room_occupants_updated(account, muc_jid);
+ }
+ });
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_info_updated.connect( (stream, muc_jid) => {
room_info_updated(account, muc_jid);
});
- stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_jid.connect( (stream, room, occupant) => {
- if (is_private_room(account, room.bare_jid)) {
- private_room_occupant_updated(account, room, occupant);
- }
- });
stream_interactor.module_manager.get_module(account, Xep.OccupantIds.Module.IDENTITY).received_own_occupant_id.connect( (stream, jid, occupant_id) => {
if (!own_occupant_ids.has_key(account)) {
own_occupant_ids[account] = new HashMap<Jid, string>(Jid.hash_bare_func, Jid.equals_bare_func);
@@ -555,6 +577,24 @@ public class MucManager : StreamInteractionModule, Object {
});
}
+ private void persist_muc_affiliations(Account account, Jid muc_jid) {
+ Xep.Muc.Flag? flag = get_muc_flag(account);
+ if (flag == null) return;
+
+ var affiliations = new HashMap<Jid, Muc.Affiliation>();
+
+ Gee.List<Jid> offline_members = flag.get_offline_members(muc_jid);
+ foreach (Jid offline_member in offline_members) {
+ Xep.Muc.Affiliation affiliation = flag.get_affiliation(muc_jid, offline_member);
+ affiliations[offline_member] = affiliation;
+ }
+
+ muc_affiliations.update_affiliations(account, muc_jid, affiliations, null, null);
+ if (is_private_room(account, muc_jid)) {
+ private_room_occupants_updated(account, muc_jid);
+ }
+ }
+
private void join_all_active(Account account) {
Gee.List<Conversation> conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account);
foreach (Conversation conversation in conversations) {