diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala index 2b1567a0..a3f56652 100644 --- a/libdino/src/service/module_manager.vala +++ b/libdino/src/service/module_manager.vala @@ -62,6 +62,7 @@ public class ModuleManager { module_map[account].add(new Xep.ServiceDiscovery.Module.with_identity("client", "pc")); module_map[account].add(new Xep.PrivateXmlStorage.Module()); module_map[account].add(new Xep.Bookmarks.Module()); + module_map[account].add(new Xep.Bookmarks2.Module()); module_map[account].add(new Presence.Module()); module_map[account].add(new Xmpp.MessageModule()); module_map[account].add(new Xep.MessageArchiveManagement.Module()); diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index 392339c1..c1ef2d80 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -14,13 +14,15 @@ public class MucManager : StreamInteractionModule, Object { public signal void subject_set(Account account, Jid jid, string? subject); public signal void room_name_set(Account account, Jid jid, string? room_name); public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant); - public signal void bookmarks_updated(Account account, Gee.List conferences); public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason); + public signal void bookmarks_updated(Account account, Set conferences); + public signal void conference_added(Account account, Conference conference); + public signal void conference_removed(Account account, Jid jid); private StreamInteractor stream_interactor; private HashMap enter_errors = new HashMap(Jid.hash_func, Jid.equals_func); private ReceivedMessageListener received_message_listener; - + private HashMap bookmarks_provider = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor) { MucManager m = new MucManager(stream_interactor); stream_interactor.add_module(m); @@ -52,7 +54,7 @@ public class MucManager : StreamInteractionModule, Object { public void part(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; - unset_autojoin(stream, jid); + unset_autojoin(account, stream, jid); stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); @@ -148,29 +150,31 @@ public class MucManager : StreamInteractionModule, Object { return is_groupchat(jid.bare_jid, account) && jid.resourcepart != null; } - public void get_bookmarks(Account account, owned Xep.Bookmarks.Module.OnResult listener) { + public async Set? get_bookmarks(Account account) { XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (owned)listener); + if (stream == null) return null; + + return yield bookmarks_provider[account].get_conferences(stream); } - public void add_bookmark(Account account, Xep.Bookmarks.Conference conference) { + public void add_bookmark(Account account, Conference conference) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { - stream.get_module(Xep.Bookmarks.Module.IDENTITY).add_conference(stream, conference); + bookmarks_provider[account].add_conference.begin(stream, conference); } } - public void replace_bookmark(Account account, Xep.Bookmarks.Conference was, Xep.Bookmarks.Conference replace) { + public void replace_bookmark(Account account, Conference was, Conference replace) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { - stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference(stream, was, replace); + stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference.begin(stream, was, replace); } } - public void remove_bookmark(Account account, Xep.Bookmarks.Conference conference) { + public void remove_bookmark(Account account, Conference conference) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { - stream.get_module(Xep.Bookmarks.Module.IDENTITY).remove_conference(stream, conference); + bookmarks_provider[account].remove_conference.begin(stream, conference); } } @@ -276,29 +280,38 @@ public class MucManager : StreamInteractionModule, Object { private_room_occupant_updated(account, room, occupant); } }); - stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY).received_conferences.connect( (stream, conferences) => { + + bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY); + + bookmarks_provider[account].received_conferences.connect( (stream, conferences) => { sync_autojoin_active(account, conferences); bookmarks_updated(account, conferences); }); + bookmarks_provider[account].conference_added.connect( (stream, conference) => { + sync_autojoin_state(account, conference.jid, conference); + conference_added(account, conference); + }); + bookmarks_provider[account].conference_removed.connect( (stream, jid) => { + sync_autojoin_state(account, jid, null); + conference_removed(account, jid); + }); } - private void on_stream_negotiated(Account account, XmppStream stream) { - stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences) => { - if (conferences == null) { - join_all_active(account); - } else { - foreach (Xep.Bookmarks.Conference bookmark in conferences) { - if (bookmark.autojoin) { - join(account, bookmark.jid, bookmark.nick, bookmark.password); - } - } - } - }); + private async void on_stream_negotiated(Account account, XmppStream stream) { + if (bookmarks_provider[account] == null) return; + + Set? conferences = yield bookmarks_provider[account].get_conferences(stream); + + if (conferences == null) { + join_all_active(account); + } else { + sync_autojoin_active(account, conferences); + } } private void on_room_entred(Account account, XmppStream stream, Jid jid, string nick) { enter_errors.unset(jid); - set_autojoin(stream, jid, nick, null); // TODO password + set_autojoin(account, stream, jid, nick, null); // TODO password joined(account, jid, nick); stream_interactor.get_module(MessageProcessor.IDENTITY).send_unsent_messages(account, jid); Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.GROUPCHAT); @@ -315,65 +328,77 @@ public class MucManager : StreamInteractionModule, Object { } } - private void sync_autojoin_active(Account account, Gee.List conferences) { + private void sync_autojoin_active(Account account, Set conferences) { Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); - leave_non_autojoin(account, conferences, conversations); - join_autojoin(account, conferences, conversations); - } - - private void leave_non_autojoin(Account account, Gee.List conferences, Gee.List conversations) { - foreach (Conversation conversation in conversations) { - if (conversation.type_ != Conversation.Type.GROUPCHAT || !conversation.account.equals(account)) continue; - bool is_autojoin = false; - foreach (Xep.Bookmarks.Conference conference in conferences) { - if (conference.jid.equals(conversation.counterpart)) { - if (conference.autojoin) is_autojoin = true; - } - } - if (!is_autojoin) { - part(account, conversation.counterpart); - } + foreach (Conference conference in conferences) { + sync_autojoin_state(account, conference.jid, conference, conversations); } } - private void join_autojoin(Account account, Gee.List conferences, Gee.List conversations) { - foreach (Xep.Bookmarks.Conference conference in conferences) { - if (!conference.autojoin) continue; + private void sync_autojoin_state(Account account, Jid jid, Conference? conference, Gee.List? conversations_ = null) { + Gee.List conversations = conversations_ ?? stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); + + if (conference != null && conference.autojoin) { + // Join if we should join bool is_active = false; foreach (Conversation conversation in conversations) { if (conference.jid.equals(conversation.counterpart)) is_active = true; } - if (!is_active) { + if (!is_active || !is_joined(jid, account)) { join(account, conference.jid, conference.nick, conference.password); } + } else { + // Leave if we should leave + bool is_active = false; + foreach (Conversation conversation in conversations) { + if (conversation.type_ != Conversation.Type.GROUPCHAT || !conversation.account.equals(account)) continue; + if (jid.equals(conversation.counterpart)) { + is_active = true; + } + } + if (is_active) { + part(account, jid); + } } } - private void set_autojoin(XmppStream stream, Jid jid, string? nick, string? password) { - stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences) => { + private void set_autojoin(Account account, XmppStream stream, Jid jid, string? nick, string? password) { + bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { + Set? conferences = bookmarks_provider[account].get_conferences.end(res); if (conferences == null) return; - Xep.Bookmarks.Conference changed = new Xep.Bookmarks.Conference(jid) { nick=nick, password=password, autojoin=true }; - foreach (Xep.Bookmarks.Conference conference in conferences) { - if (conference.jid.equals_bare(jid) && conference.nick == nick && conference.password == password) { + + Conference changed = new Xep.Bookmarks.Bookmarks1Conference(jid) { nick=nick, password=password, autojoin=true }; + foreach (Conference conference in conferences) { + if (conference.jid.equals(jid)) { if (!conference.autojoin) { - conference.autojoin = true; - stream.get_module(Xep.Bookmarks.Module.IDENTITY).set_conferences(stream, conferences); + Conference new_conference = new Conference(); + new_conference.jid = jid; + new_conference.autojoin = true; + new_conference.nick = nick; + new_conference.password = password; + bookmarks_provider[account].replace_conference.begin(stream, conference, new_conference); } return; } } - stream.get_module(Xep.Bookmarks.Module.IDENTITY).add_conference(stream, changed); + bookmarks_provider[account].add_conference.begin(stream, changed); }); } - private void unset_autojoin(XmppStream stream, Jid jid) { - stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences) => { + private void unset_autojoin(Account account, XmppStream stream, Jid jid) { + bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { + Set? conferences = bookmarks_provider[account].get_conferences.end(res); if (conferences == null) return; - foreach (Xep.Bookmarks.Conference conference in conferences) { - if (conference.jid.equals_bare(jid)) { + + foreach (Conference conference in conferences) { + if (conference.jid.equals(jid)) { if (conference.autojoin) { - conference.autojoin = false; - stream.get_module(Xep.Bookmarks.Module.IDENTITY).set_conferences(stream, conferences); + Conference new_conference = new Conference(); + new_conference.jid = jid; + new_conference.autojoin = false; + new_conference.nick = conference.nick; + new_conference.password = conference.password; + bookmarks_provider[account].replace_conference.begin(stream, conference, new_conference); return; } } diff --git a/main/src/ui/add_conversation/add_groupchat_dialog.vala b/main/src/ui/add_conversation/add_groupchat_dialog.vala index 53359813..33a3a455 100644 --- a/main/src/ui/add_conversation/add_groupchat_dialog.vala +++ b/main/src/ui/add_conversation/add_groupchat_dialog.vala @@ -19,7 +19,7 @@ protected class AddGroupchatDialog : Gtk.Dialog { [GtkChild] private Entry nick_entry; private StreamInteractor stream_interactor; - private Xmpp.Xep.Bookmarks.Conference? edit_conference = null; + private Conference? edit_conference = null; private bool alias_entry_changed = false; public AddGroupchatDialog(StreamInteractor stream_interactor) { @@ -36,7 +36,7 @@ protected class AddGroupchatDialog : Gtk.Dialog { nick_entry.key_release_event.connect(check_ok); } - public AddGroupchatDialog.for_conference(StreamInteractor stream_interactor, Account account, Xmpp.Xep.Bookmarks.Conference conference) { + public AddGroupchatDialog.for_conference(StreamInteractor stream_interactor, Account account, Conference conference) { this(stream_interactor); edit_conference = conference; ok_button.label = _("Save"); @@ -65,7 +65,8 @@ protected class AddGroupchatDialog : Gtk.Dialog { } private void on_ok_button_clicked() { - Xmpp.Xep.Bookmarks.Conference conference = new Xmpp.Xep.Bookmarks.Conference(Jid.parse(jid_entry.text)); + Conference conference = new Conference(); + conference.jid = Jid.parse(jid_entry.text); conference.nick = nick_entry.text != "" ? nick_entry.text : null; conference.name = alias_entry.text; if (edit_conference == null) { diff --git a/main/src/ui/add_conversation/conference_list.vala b/main/src/ui/add_conversation/conference_list.vala index 2f52558d..435d42e0 100644 --- a/main/src/ui/add_conversation/conference_list.vala +++ b/main/src/ui/add_conversation/conference_list.vala @@ -2,6 +2,7 @@ using Gee; using Gtk; using Xmpp; +using Xmpp.Xep.Bookmarks; using Dino.Entities; namespace Dino.Ui { @@ -11,7 +12,8 @@ protected class ConferenceList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; - private HashMap> lists = new HashMap>(Account.hash_func, Account.equals_func); + private HashMap> lists = new HashMap>(Account.hash_func, Account.equals_func); + private HashMap> widgets = new HashMap>(Account.hash_func, Account.equals_func); public ConferenceList(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -26,20 +28,42 @@ protected class ConferenceList : FilterableList { }); foreach (Account account in stream_interactor.get_accounts()) { - stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks(account, (stream, conferences) => { on_conference_bookmarks_received(stream, account, conferences); }); + stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks.begin(account, (_, res) => { + Set? conferences = stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks.end(res); + set_bookmarks(account, conferences); + }); + } + + stream_interactor.get_module(MucManager.IDENTITY).conference_added.connect(add_conference); + stream_interactor.get_module(MucManager.IDENTITY).conference_removed.connect(remove_conference); + } + + private void add_conference(Account account, Conference conference) { + if (!widgets.has_key(account)) { + widgets[account] = new HashMap(Jid.hash_func, Jid.equals_func); + } + var widget = new ConferenceListRow(stream_interactor, conference, account); + widgets[account][conference.jid] = widget; + add(widget); + } + + private void remove_conference(Account account, Jid jid) { + if (widgets.has_key(account) && widgets[account].has_key(jid)) { + widgets[account][jid].destroy(); + widgets[account].unset(jid); } } public void refresh_conferences() { @foreach((widget) => { remove(widget); }); foreach (Account account in lists.keys) { - foreach (Xep.Bookmarks.Conference conference in lists[account]) { - add(new ConferenceListRow(stream_interactor, conference, account)); + foreach (Conference conference in lists[account]) { + add_conference(account, conference); } } } - private void on_conference_bookmarks_received(XmppStream stream, Account account, Gee.List? conferences) { + private void set_bookmarks(Account account, Set? conferences) { if (conferences == null) { lists.unset(account); } else { @@ -78,9 +102,9 @@ protected class ConferenceList : FilterableList { internal class ConferenceListRow : ListRow { - public Xep.Bookmarks.Conference bookmark; + public Conference bookmark; - public ConferenceListRow(StreamInteractor stream_interactor, Xep.Bookmarks.Conference bookmark, Account account) { + public ConferenceListRow(StreamInteractor stream_interactor, Conference bookmark, Account account) { this.jid = bookmark.jid; this.account = account; this.bookmark = bookmark; diff --git a/plugins/omemo/src/protocol/stream_module.vala b/plugins/omemo/src/protocol/stream_module.vala index 9a1bc75a..95ba4c85 100644 --- a/plugins/omemo/src/protocol/stream_module.vala +++ b/plugins/omemo/src/protocol/stream_module.vala @@ -75,7 +75,7 @@ public class StreamModule : XmppStreamModule { if (!am_on_devicelist) { debug("Not on device list, adding id"); node.put_node(new StanzaNode.build("device", NS_URI).put_attribute("id", store.local_registration_id.to_string())); - stream.get_module(Pubsub.Module.IDENTITY).publish(stream, jid, NODE_DEVICELIST, id, node, Xmpp.Xep.Pubsub.ACCESS_MODEL_OPEN); + stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, jid, NODE_DEVICELIST, id, node, Xmpp.Xep.Pubsub.ACCESS_MODEL_OPEN); } publish_bundles_if_needed(stream, jid); } @@ -281,7 +281,7 @@ public class StreamModule : XmppStreamModule { } bundle.put_node(prekeys); - stream.get_module(Pubsub.Module.IDENTITY).publish(stream, null, @"$NODE_BUNDLES:$device_id", "1", bundle, Xmpp.Xep.Pubsub.ACCESS_MODEL_OPEN); + stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, null, @"$NODE_BUNDLES:$device_id", "1", bundle, Xmpp.Xep.Pubsub.ACCESS_MODEL_OPEN); } public override string get_ns() { diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt index b945f9d0..5836f96f 100644 --- a/xmpp-vala/CMakeLists.txt +++ b/xmpp-vala/CMakeLists.txt @@ -19,6 +19,8 @@ SOURCES "src/core/xmpp_stream.vala" "src/module/bind.vala" + "src/module/bookmarks_provider.vala" + "src/module/conference.vala" "src/module/iq/module.vala" "src/module/iq/stanza.vala" "src/module/jid.vala" @@ -39,6 +41,9 @@ SOURCES "src/module/tls.vala" "src/module/util.vala" + "src/module/xep/0048_bookmarks.vala" + "src/module/xep/0048_conference.vala" + "src/module/xep/0402_bookmarks2.vala" "src/module/xep/0004_data_forms.vala" "src/module/xep/0030_service_discovery/flag.vala" "src/module/xep/0030_service_discovery/identity.vala" @@ -50,8 +55,6 @@ SOURCES "src/module/xep/0045_muc/module.vala" "src/module/xep/0045_muc/status_code.vala" "src/module/xep/0047_in_band_bytestreams.vala" - "src/module/xep/0048_bookmarks/conference.vala" - "src/module/xep/0048_bookmarks/module.vala" "src/module/xep/0049_private_xml_storage.vala" "src/module/xep/0054_vcard/module.vala" "src/module/xep/0060_pubsub.vala" diff --git a/xmpp-vala/src/module/bookmarks_provider.vala b/xmpp-vala/src/module/bookmarks_provider.vala new file mode 100644 index 00000000..69d2efa2 --- /dev/null +++ b/xmpp-vala/src/module/bookmarks_provider.vala @@ -0,0 +1,17 @@ +using Gee; + +namespace Xmpp { + +public interface BookmarksProvider : Object { + public signal void conference_added(XmppStream stream, Conference conferences); + public signal void conference_removed(XmppStream stream, Jid jid); + public signal void conference_changed(XmppStream stream, Conference conferences); + public signal void received_conferences(XmppStream stream, Set conferences); + + public async abstract async Set? get_conferences(XmppStream stream); + public async abstract void add_conference(XmppStream stream, Conference conference); + public async abstract void remove_conference(XmppStream stream, Conference conference); + public async abstract void replace_conference(XmppStream stream, Conference orig_conference, Conference modified_conference); +} + +} diff --git a/xmpp-vala/src/module/conference.vala b/xmpp-vala/src/module/conference.vala new file mode 100644 index 00000000..ee1be6b6 --- /dev/null +++ b/xmpp-vala/src/module/conference.vala @@ -0,0 +1,19 @@ +namespace Xmpp { + +public class Conference : Object { + public virtual Jid jid { get; set; } + public virtual bool autojoin { get; set; } + public virtual string? nick { get; set; } + public virtual string? name { get; set; } + public virtual string? password { get; set; } + + public static bool equal_func(Conference a, Conference b) { + return a.jid.equals(b.jid); + } + + public static uint hash_func(Conference a) { + return Jid.hash_func(a.jid); + } +} + +} diff --git a/xmpp-vala/src/module/xep/0048_bookmarks.vala b/xmpp-vala/src/module/xep/0048_bookmarks.vala new file mode 100644 index 00000000..8454d711 --- /dev/null +++ b/xmpp-vala/src/module/xep/0048_bookmarks.vala @@ -0,0 +1,80 @@ +using Gee; + +namespace Xmpp.Xep.Bookmarks { +private const string NS_URI = "storage:bookmarks"; + +public class Module : BookmarksProvider, XmppStreamModule { + public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0048_bookmarks_module"); + + public async Set? get_conferences(XmppStream stream) { + Set ret = new HashSet(Conference.hash_func, Conference.equal_func); + + StanzaNode get_node = new StanzaNode.build("storage", NS_URI).add_self_xmlns(); + stream.get_module(PrivateXmlStorage.Module.IDENTITY).retrieve(stream, get_node, (stream, node) => { + if (node != null) { + Gee.List conferences_node = node.get_subnode("storage", NS_URI).get_subnodes("conference", NS_URI); + foreach (StanzaNode conference_node in conferences_node) { + Conference? conference = Bookmarks1Conference.create_from_stanza_node(conference_node); + ret.add(conference); + } + } + Idle.add(get_conferences.callback); + }); + yield; + + return ret; + } + + private void set_conferences(XmppStream stream, Set conferences) { + StanzaNode storage_node = (new StanzaNode.build("storage", NS_URI)).add_self_xmlns(); + foreach (Conference conference in conferences) { + Bookmarks1Conference? bookmarks1conference = conference as Bookmarks1Conference; + if (bookmarks1conference != null) { + storage_node.put_node(bookmarks1conference.stanza_node); + } else { + StanzaNode conference_node = new StanzaNode.build("conference", NS_URI) + .put_attribute("jid", conference.jid.to_string()) + .put_attribute("autojoin", conference.autojoin ? "true" : "false"); + if (conference.name != null) { + conference_node.put_attribute("name", conference.name); + } + if (conference.nick != null) { + conference_node.put_node(new StanzaNode.build("nick", NS_URI) + .put_node(new StanzaNode.text(conference.nick))); + } + storage_node.put_node(conference_node); + } + } + stream.get_module(PrivateXmlStorage.Module.IDENTITY).store(stream, storage_node, (stream) => { + stream.get_module(Module.IDENTITY).received_conferences(stream, conferences); + }); + } + + public async void add_conference(XmppStream stream, Conference conference) { + Set? conferences = yield get_conferences(stream); + conferences.add(conference); + stream.get_module(Module.IDENTITY).set_conferences(stream, conferences); + } + + public async void replace_conference(XmppStream stream, Conference orig_conference, Conference modified_conference) { + Set? conferences = yield get_conferences(stream); + conferences.remove(orig_conference); + conferences.add(modified_conference); + stream.get_module(Module.IDENTITY).set_conferences(stream, conferences); + } + + public async void remove_conference(XmppStream stream, Conference conference_remove) { + Set? conferences = yield get_conferences(stream); + conferences.remove(conference_remove); + stream.get_module(Module.IDENTITY).set_conferences(stream, conferences); + } + + public override void attach(XmppStream stream) { } + + public override void detach(XmppStream stream) { } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } +} + +} diff --git a/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala b/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala deleted file mode 100644 index 7f80490b..00000000 --- a/xmpp-vala/src/module/xep/0048_bookmarks/conference.vala +++ /dev/null @@ -1,92 +0,0 @@ -namespace Xmpp.Xep.Bookmarks { - -public class Conference : Object { - - public const string ATTRIBUTE_AUTOJOIN = "autojoin"; - public const string ATTRIBUTE_JID = "jid"; - public const string ATTRIBUTE_NAME = "name"; - - public const string NODE_NICK = "nick"; - public const string NODE_PASSWORD = "password"; - - public StanzaNode stanza_node; - - public bool autojoin { - get { - string? attr = stanza_node.get_attribute(ATTRIBUTE_AUTOJOIN); - return attr == "true" || attr == "1"; - } - set { stanza_node.set_attribute(ATTRIBUTE_AUTOJOIN, value.to_string()); } - } - - private Jid jid_; - public Jid jid { - get { return jid_ ?? (jid_ = Jid.parse(stanza_node.get_attribute(ATTRIBUTE_JID))); } - set { stanza_node.set_attribute(ATTRIBUTE_JID, value.to_string()); } - } - - public string? name { - get { return stanza_node.get_attribute(ATTRIBUTE_NAME); } - set { - if (value == null) return; // TODO actually remove - stanza_node.set_attribute(ATTRIBUTE_NAME, value); - } - } - - public string? nick { - get { - StanzaNode? nick_node = stanza_node.get_subnode(NODE_NICK); - return nick_node == null ? null : nick_node.get_string_content(); - } - set { - StanzaNode? nick_node = stanza_node.get_subnode(NODE_NICK); - if (value == null) { - if (nick_node != null) stanza_node.sub_nodes.remove(nick_node); - return; - } - if (nick_node == null) { - nick_node = new StanzaNode.build(NODE_NICK, NS_URI); - stanza_node.put_node(nick_node); - } - nick_node.sub_nodes.clear(); - nick_node.put_node(new StanzaNode.text(value)); - } - } - - public string? password { - get { - StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD); - return password_node == null ? null : password_node.get_string_content(); - } - set { - StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD); - if (value == null) { - if (password_node != null) stanza_node.sub_nodes.remove(password_node); - return; - } - if (password_node == null) { - password_node = new StanzaNode.build(NODE_PASSWORD); - stanza_node.put_node(password_node); - } - password_node.put_node(new StanzaNode.text(value)); - } - } - - public Conference(Jid jid) { - this.stanza_node = new StanzaNode.build("conference", NS_URI); - this.jid = jid; - } - - public static Conference? create_from_stanza_node(StanzaNode stanza_node) { - if (stanza_node.get_attribute(ATTRIBUTE_JID) != null) { - return new Conference.from_stanza_node(stanza_node); - } - return null; - } - - private Conference.from_stanza_node(StanzaNode stanza_node) { - this.stanza_node = stanza_node; - } -} - -} diff --git a/xmpp-vala/src/module/xep/0048_bookmarks/module.vala b/xmpp-vala/src/module/xep/0048_bookmarks/module.vala deleted file mode 100644 index 2c16f5f0..00000000 --- a/xmpp-vala/src/module/xep/0048_bookmarks/module.vala +++ /dev/null @@ -1,89 +0,0 @@ -using Gee; - -namespace Xmpp.Xep.Bookmarks { -private const string NS_URI = "storage:bookmarks"; - -public class Module : XmppStreamModule { - public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0048_bookmarks_module"); - - public signal void received_conferences(XmppStream stream, Gee.List conferences); - - public delegate void OnResult(XmppStream stream, Gee.List? conferences); - public void get_conferences(XmppStream stream, owned OnResult listener) { - StanzaNode get_node = new StanzaNode.build("storage", NS_URI).add_self_xmlns(); - stream.get_module(PrivateXmlStorage.Module.IDENTITY).retrieve(stream, get_node, (stream, node) => { - if (node == null) { - listener(stream, null); - } else { - Gee.List conferences = get_conferences_from_stanza(node); - listener(stream, conferences); - } - }); - } - - public void set_conferences(XmppStream stream, Gee.List conferences) { - StanzaNode storage_node = (new StanzaNode.build("storage", NS_URI)).add_self_xmlns(); - foreach (Conference conference in conferences) { - storage_node.put_node(conference.stanza_node); - } - stream.get_module(PrivateXmlStorage.Module.IDENTITY).store(stream, storage_node, (stream) => { - stream.get_module(Module.IDENTITY).received_conferences(stream, conferences); - }); - } - - public void add_conference(XmppStream stream, Conference conference) { - get_conferences(stream, (stream, conferences) => { - conferences.add(conference); - stream.get_module(Module.IDENTITY).set_conferences(stream, conferences); - }); - } - - public void replace_conference(XmppStream stream, Conference orig_conference, Conference modified_conference) { - get_conferences(stream, (stream, conferences) => { - foreach (Conference conference in conferences) { - if (conference.autojoin == orig_conference.autojoin && conference.jid.equals(orig_conference.jid) && - conference.name == orig_conference.name && conference.nick == orig_conference.nick) { - conference.autojoin = modified_conference.autojoin; - conference.jid = modified_conference.jid; - conference.name = modified_conference.name; - conference.nick = modified_conference.nick; - break; - } - } - stream.get_module(Module.IDENTITY).set_conferences(stream, conferences); - }); - } - - public void remove_conference(XmppStream stream, Conference conference_remove) { - get_conferences(stream, (stream, conferences) => { - Conference? rem = null; - foreach (Conference conference in conferences) { - if (conference.name == conference_remove.name && conference.jid.equals(conference_remove.jid) && conference.autojoin == conference_remove.autojoin) { - rem = conference; - break; - } - } - if (rem != null) conferences.remove(rem); - stream.get_module(Module.IDENTITY).set_conferences(stream, conferences); - }); - } - - public override void attach(XmppStream stream) { } - - public override void detach(XmppStream stream) { } - - public override string get_ns() { return NS_URI; } - public override string get_id() { return IDENTITY.id; } - - private static Gee.List get_conferences_from_stanza(StanzaNode node) { - Gee.List conferences = new ArrayList(); - Gee.List conferenceNodes = node.get_subnode("storage", NS_URI).get_subnodes("conference", NS_URI); - foreach (StanzaNode conferenceNode in conferenceNodes) { - Conference? conference = Conference.create_from_stanza_node(conferenceNode); - conferences.add(conference); - } - return conferences; - } -} - -} diff --git a/xmpp-vala/src/module/xep/0048_conference.vala b/xmpp-vala/src/module/xep/0048_conference.vala new file mode 100644 index 00000000..d78a2685 --- /dev/null +++ b/xmpp-vala/src/module/xep/0048_conference.vala @@ -0,0 +1,92 @@ +namespace Xmpp.Xep.Bookmarks { + +public class Bookmarks1Conference : Conference { + + public const string ATTRIBUTE_AUTOJOIN = "autojoin"; + public const string ATTRIBUTE_JID = "jid"; + public const string ATTRIBUTE_NAME = "name"; + + public const string NODE_NICK = "nick"; + public const string NODE_PASSWORD = "password"; + + public StanzaNode stanza_node; + + public override bool autojoin { + get { + string? attr = stanza_node.get_attribute(ATTRIBUTE_AUTOJOIN); + return attr == "true" || attr == "1"; + } + set { stanza_node.set_attribute(ATTRIBUTE_AUTOJOIN, value.to_string()); } + } + + private Jid jid_; + public override Jid jid { + get { return jid_ ?? (jid_ = Jid.parse(stanza_node.get_attribute(ATTRIBUTE_JID))); } + set { stanza_node.set_attribute(ATTRIBUTE_JID, value.to_string()); } + } + + public override string? name { + get { return stanza_node.get_attribute(ATTRIBUTE_NAME); } + set { + if (value == null) return; // TODO actually remove + stanza_node.set_attribute(ATTRIBUTE_NAME, value); + } + } + + public override string? nick { + get { + StanzaNode? nick_node = stanza_node.get_subnode(NODE_NICK); + return nick_node == null ? null : nick_node.get_string_content(); + } + set { + StanzaNode? nick_node = stanza_node.get_subnode(NODE_NICK); + if (value == null) { + if (nick_node != null) stanza_node.sub_nodes.remove(nick_node); + return; + } + if (nick_node == null) { + nick_node = new StanzaNode.build(NODE_NICK, NS_URI); + stanza_node.put_node(nick_node); + } + nick_node.sub_nodes.clear(); + nick_node.put_node(new StanzaNode.text(value)); + } + } + + public override string? password { + get { + StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD); + return password_node == null ? null : password_node.get_string_content(); + } + set { + StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD); + if (value == null) { + if (password_node != null) stanza_node.sub_nodes.remove(password_node); + return; + } + if (password_node == null) { + password_node = new StanzaNode.build(NODE_PASSWORD); + stanza_node.put_node(password_node); + } + password_node.put_node(new StanzaNode.text(value)); + } + } + + public Bookmarks1Conference(Jid jid) { + this.stanza_node = new StanzaNode.build("conference", NS_URI); + this.jid = jid; + } + + public static Conference? create_from_stanza_node(StanzaNode stanza_node) { + if (stanza_node.get_attribute(ATTRIBUTE_JID) != null) { + return new Bookmarks1Conference.from_stanza_node(stanza_node); + } + return null; + } + + private Bookmarks1Conference.from_stanza_node(StanzaNode stanza_node) { + this.stanza_node = stanza_node; + } +} + +} diff --git a/xmpp-vala/src/module/xep/0060_pubsub.vala b/xmpp-vala/src/module/xep/0060_pubsub.vala index 91f5525d..434e9a7e 100644 --- a/xmpp-vala/src/module/xep/0060_pubsub.vala +++ b/xmpp-vala/src/module/xep/0060_pubsub.vala @@ -21,6 +21,24 @@ namespace Xmpp.Xep.Pubsub { event_listeners[node] = new EventListenerDelegate((owned)listener); } + public async Gee.List? request_all(XmppStream stream, Jid jid, string node) { // TODO multiple nodes gehen auch + Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node))); + request_iq.to = jid; + + Gee.List? ret = null; + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, request_iq, (stream, iq) => { + StanzaNode event_node = iq.stanza.get_subnode("pubsub", NS_URI); + if (event_node == null) return; + StanzaNode items_node = event_node.get_subnode("items", NS_URI); + if (items_node == null) return; + ret = items_node.get_subnodes("item", NS_URI); + Idle.add(request_all.callback); + }); + yield; + + return ret; + } + public delegate void OnResult(XmppStream stream, Jid jid, string? id, StanzaNode? node); public void request(XmppStream stream, Jid jid, string node, owned OnResult listener) { // TODO multiple nodes gehen auch Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node))); @@ -34,7 +52,7 @@ namespace Xmpp.Xep.Pubsub { }); } - public void publish(XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, string? access_model=null) { + public async bool publish(XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, string? access_model=null, int? max_items = null) { StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns(); StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id); pubsub_node.put_node(publish_node); @@ -43,7 +61,7 @@ namespace Xmpp.Xep.Pubsub { items_node.put_node(content); publish_node.put_node(items_node); - if (access_model != null) { + if (access_model != null || max_items != null) { StanzaNode publish_options_node = new StanzaNode.build("publish-options", NS_URI); pubsub_node.put_node(publish_options_node); @@ -51,14 +69,44 @@ namespace Xmpp.Xep.Pubsub { DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" }; form_type_field.set_value_string(NS_URI + "#publish-options"); data_form.add_field(form_type_field); - DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="pubsub#access_model" }; - field.set_value_string(access_model); - data_form.add_field(field); + if (access_model != null) { + DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="pubsub#access_model" }; + field.set_value_string(access_model); + data_form.add_field(field); + } + if (max_items != null) { + DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="pubsub#max_items" }; + field.set_value_string(max_items.to_string()); + data_form.add_field(field); + } publish_options_node.put_node(data_form.get_submit_node()); } Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); - stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, null); + bool ok = true; + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, result_iq) => { + ok = !result_iq.is_error(); + Idle.add(publish.callback); + }); + yield; + + return ok; + } + + public async bool retract_item(XmppStream stream, Jid? jid, string node_id, string item_id) { + StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns() + .put_node(new StanzaNode.build("retract", NS_URI).put_attribute("node", node_id).put_attribute("notify", "true") + .put_node(new StanzaNode.build("item", NS_URI).put_attribute("id", item_id))); + + Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); + bool ok = true; + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, result_iq) => { + ok = !result_iq.is_error(); + Idle.add(retract_item.callback); + }); + yield; + + return ok; } public void delete_node(XmppStream stream, Jid? jid, string node_id) { @@ -82,14 +130,30 @@ namespace Xmpp.Xep.Pubsub { public override string get_id() { return IDENTITY.id; } private void on_received_message(XmppStream stream, MessageStanza message) { - StanzaNode event_node = message.stanza.get_subnode("event", NS_URI_EVENT); if (event_node == null) return; - StanzaNode items_node = event_node.get_subnode("items", NS_URI_EVENT); if (items_node == null) return; - StanzaNode item_node = items_node.get_subnode("item", NS_URI_EVENT); if (item_node == null) return; + StanzaNode event_node = message.stanza.get_subnode("event", NS_URI_EVENT); + if (event_node == null) return; + StanzaNode items_node = event_node.get_subnode("items", NS_URI_EVENT); + if (items_node == null) return; string node = items_node.get_attribute("node", NS_URI_EVENT); - string id = item_node.get_attribute("id", NS_URI_EVENT); - if (event_listeners.has_key(node)) { - event_listeners[node].on_result(stream, message.from, id, item_node.sub_nodes[0]); + + StanzaNode? item_node = items_node.get_subnode("item", NS_URI_EVENT); + if (item_node != null) { + string id = item_node.get_attribute("id", NS_URI_EVENT); + + if (event_listeners.has_key(node)) { + event_listeners[node].on_result(stream, message.from, id, item_node); + } } + + StanzaNode? retract_node = items_node.get_subnode("retract", NS_URI_EVENT); + if (retract_node != null) { + string id = retract_node.get_attribute("id", NS_URI_EVENT); + + if (event_listeners.has_key(node)) { + event_listeners[node].on_result(stream, message.from, id, retract_node); + } + } + } } diff --git a/xmpp-vala/src/module/xep/0084_user_avatars.vala b/xmpp-vala/src/module/xep/0084_user_avatars.vala index 51c2a563..229b2e12 100644 --- a/xmpp-vala/src/module/xep/0084_user_avatars.vala +++ b/xmpp-vala/src/module/xep/0084_user_avatars.vala @@ -18,7 +18,7 @@ namespace Xmpp.Xep.UserAvatars { string sha1 = Checksum.compute_for_data(ChecksumType.SHA1, image); StanzaNode data_node = new StanzaNode.build("data", NS_URI_DATA).add_self_xmlns() .put_node(new StanzaNode.text(Base64.encode(image))); - stream.get_module(Pubsub.Module.IDENTITY).publish(stream, null, NS_URI_DATA, sha1, data_node); + stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, null, NS_URI_DATA, sha1, data_node); StanzaNode metadata_node = new StanzaNode.build("metadata", NS_URI_METADATA).add_self_xmlns(); StanzaNode info_node = new StanzaNode.build("info", NS_URI_METADATA) @@ -28,7 +28,7 @@ namespace Xmpp.Xep.UserAvatars { .put_attribute("height", height.to_string()) .put_attribute("type", "image/png"); metadata_node.put_node(info_node); - stream.get_module(Pubsub.Module.IDENTITY).publish(stream, null, NS_URI_METADATA, sha1, metadata_node); + stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, null, NS_URI_METADATA, sha1, metadata_node); } public override void attach(XmppStream stream) { diff --git a/xmpp-vala/src/module/xep/0402_bookmarks2.vala b/xmpp-vala/src/module/xep/0402_bookmarks2.vala new file mode 100644 index 00000000..cbe6ea45 --- /dev/null +++ b/xmpp-vala/src/module/xep/0402_bookmarks2.vala @@ -0,0 +1,116 @@ +using Gee; + +namespace Xmpp.Xep.Bookmarks2 { + +private const string NS_URI = "urn:xmpp:bookmarks:0"; + +public class Module : BookmarksProvider, XmppStreamModule { + public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0402_bookmarks2"); + + public async Set? get_conferences(XmppStream stream) { + HashMap? hm = null; + + Flag? flag = stream.get_flag(Flag.IDENTITY); + if (flag != null) { + hm = flag.conferences; + } else { + Gee.List? items = yield stream.get_module(Pubsub.Module.IDENTITY).request_all(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI); + + hm = new HashMap(Jid.hash_func, Jid.equals_func); + foreach (StanzaNode item_node in items) { + Conference? conference = parse_item_node(item_node, item_node.get_attribute("id")); + if (conference == null) continue; + hm[conference.jid] = conference; + } + stream.add_flag(new Flag(hm)); + } + + + var ret = new HashSet(); + foreach (var conference in hm.values) { + ret.add(conference); + } + return ret; + } + + public async void add_conference(XmppStream stream, Conference conference) { + StanzaNode conference_node = new StanzaNode.build("conference", NS_URI).add_self_xmlns() + .put_attribute("autojoin", conference.autojoin ? "true" : "false"); + if (conference.name != null) { + conference_node.put_attribute("name", conference.name); + } + if (conference.nick != null) { + conference_node.put_node((new StanzaNode.build("nick", NS_URI)).put_node(new StanzaNode.text(conference.nick))); + } + yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI, conference.jid.to_string(), conference_node, Xmpp.Xep.Pubsub.ACCESS_MODEL_WHITELIST, 128); + } + + public async void replace_conference(XmppStream stream, Conference orig_conference, Conference modified_conference) { + yield add_conference(stream, modified_conference); + } + + public async void remove_conference(XmppStream stream, Conference conference) { + yield stream.get_module(Pubsub.Module.IDENTITY).retract_item(stream, + stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, + NS_URI, + conference.jid.to_string()); + } + + private void on_pupsub_event(XmppStream stream, Jid jid, string id, StanzaNode? node) { + if (node.name == "item") { + Conference conference = parse_item_node(node, id); + Flag? flag = stream.get_flag(Flag.IDENTITY); + if (flag != null) { + flag.conferences[conference.jid] = conference; + } + conference_added(stream, conference); + } else if (node.name == "retract") { + string jid_str = node.get_attribute("id"); + Jid jid_parsed = Jid.parse(jid_str); + Flag? flag = stream.get_flag(Flag.IDENTITY); + if (flag != null) { + flag.conferences.unset(jid_parsed); + } + conference_removed(stream, jid_parsed); + } + } + + private Conference? parse_item_node(StanzaNode item_node, string id) { + Conference conference = new Conference(); + Jid? jid_parsed = Jid.parse(id); + if (jid_parsed == null || jid_parsed.resourcepart != null) return null; + conference.jid = jid_parsed; + + StanzaNode? conference_node = item_node.get_subnode("conference", NS_URI); + if (conference_node == null) return null; + + conference.name = conference_node.get_attribute("name", NS_URI); + conference.autojoin = conference_node.get_attribute("autojoin", NS_URI) == "true"; + conference.nick = conference_node.get_deep_string_content("nick"); + return conference; + } + + public override void attach(XmppStream stream) { + stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NS_URI, on_pupsub_event); + } + + public override void detach(XmppStream stream) { } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } +} + +public class Flag : XmppStreamFlag { + public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "bookmarks2"); + + public Flag(HashMap conferences) { + this.conferences = conferences; + } + + public HashMap conferences = new HashMap(Jid.hash_func, Jid.equals_func); + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } +} + +}