diff src/plugins/plugin_xep_0048.py @ 982:0e80ee1fe9af

plugin XEP-0048: bookmarks (first draft)
author Goffi <goffi@goffi.org>
date Mon, 07 Apr 2014 16:22:35 +0200
parents
children c34e0b2bbf08
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/plugin_xep_0048.py	Mon Apr 07 16:22:35 2014 +0200
@@ -0,0 +1,280 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# SAT plugin for Bookmarks (xep-0048)
+# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.core.i18n import _, D_
+from sat.core import exceptions
+from sat.memory.persistent import PersistentBinaryDict
+from sat.tools import xml_tools
+from logging import debug, info, warning, error
+from twisted.words.xish import domish
+from twisted.words.protocols.jabber import jid
+
+from twisted.internet import defer
+
+NS_BOOKMARKS = 'storage:bookmarks'
+
+PLUGIN_INFO = {
+    "name": "Bookmarks",
+    "import_name": "XEP-0048",
+    "type": "XEP",
+    "protocols": ["XEP-0048"],
+    "dependencies": ["XEP-0045"],
+    "recommendations": ["XEP-0049"],
+    "main": "XEP_0048",
+    "handler": "no",
+    "description": _("""Implementation of bookmarks""")
+}
+
+
+class XEP_0048(object):
+    MUC_TYPE = 'muc'
+    URL_TYPE = 'url'
+    MUC_KEY = 'jid'
+    URL_KEY = 'url'
+    MUC_ATTRS = ('autojoin', 'name')
+    URL_ATTRS = ('name',)
+
+    def __init__(self, host):
+        info(_("Bookmarks plugin initialization"))
+        self.host = host
+        # self.__menu_id = host.registerCallback(self._bookmarksMenu, with_data=True)
+        self.__bm_save_id = host.registerCallback(self._bookmarksSaveCb, with_data=True)
+        host.importMenu((D_("Communication"), D_("bookmarks")), self._bookmarksMenu, security_limit=0, help_string=D_("Use and manage bookmarks"))
+        try:
+            self.private_plg = self.host.plugins["XEP-0049"]
+        except KeyError:
+            self.private_plg = None
+
+    @defer.inlineCallbacks
+    def profileConnected(self, profile):
+        client = self.host.getClient(profile)
+        local = client.bookmarks_local = PersistentBinaryDict(NS_BOOKMARKS, profile)
+        yield local.load()
+        if not local:
+            local[XEP_0048.MUC_TYPE] = dict()
+            local[XEP_0048.URL_TYPE] = dict()
+        private = yield self._getServerBookmarks('private', profile)
+        pubsub = client.bookmarks_pubsub = None
+
+        for bookmarks in (local, private, pubsub):
+            if bookmarks is not None:
+                for (room_jid, data) in bookmarks[XEP_0048.MUC_TYPE].items():
+                    if data.get('autojoin', 'false') == 'true':
+                        nick = data.get('nick', client.jid.user)
+                        self.host.plugins['XEP-0045'].join(room_jid, nick, {}, profile_key=client.profile)
+
+    @defer.inlineCallbacks
+    def _getServerBookmarks(self, storage_type, profile):
+        """Get distants bookmarks
+
+        update also the client.bookmarks_[type] key, with None if service is not available
+        @param storage_type: storage type, can be:
+            - 'private': XEP-0049 storage
+            - 'pubsub': XEP-0223 storage
+        @param profile: %(doc_profile)s
+        @return: data dictionary, or None if feature is not available
+
+        @raise: exception.FeatureNotFound
+        """
+        client = self.host.getClient(profile)
+        if storage_type == 'private':
+            try:
+                bookmarks_private_xml = yield self.private_plg.privateXMLGet('storage', NS_BOOKMARKS, profile)
+                data = client.bookmarks_private = self._bookmarkElt2Dict(bookmarks_private_xml)
+            except (exceptions.FeatureNotFound, AttributeError):
+                info(_("Private XML storage not available"))
+                data = client.bookmarks_private = None
+        elif storage_type == 'pubsub':
+            raise NotImplementedError
+        else:
+            raise ValueError("storage_type must be 'private' or 'pubsub'")
+        defer.returnValue(data)
+
+    @defer.inlineCallbacks
+    def _setServerBookmarks(self, storage_type, bookmarks_elt, profile):
+        """Save bookmarks on server
+
+        @param storage_type: storage type, can be:
+            - 'private': XEP-0049 storage
+            - 'pubsub': XEP-0223 storage
+        @param bookmarks_elt (domish.Element): bookmarks XML
+        @param profile: %(doc_profile)s
+        """
+        if storage_type == 'private':
+            yield self.private_plg.privateXMLStore(bookmarks_elt, profile)
+        elif storage_type == 'pubsub':
+            raise NotImplementedError
+        else:
+            raise ValueError("storage_type must be 'private' or 'pubsub'")
+
+    def _bookmarkElt2Dict(self, storage_elt):
+        """Parse bookmarks to get dictionary
+        @param storage_elt (domish.Element): bookmarks storage
+        @return (dict): bookmark data (key: bookmark type, value: list) where key can be:
+            - XEP_0048.MUC_TYPE
+            - XEP_0048.URL_TYPE
+            - value (dict): data as for addBookmark
+        """
+        conf_data = {}
+        url_data = {}
+
+        conference_elts = storage_elt.elements(NS_BOOKMARKS, 'conference')
+        for conference_elt in conference_elts:
+            try:
+                room_jid = jid.JID(conference_elt[XEP_0048.MUC_KEY])
+            except KeyError:
+                warning ("invalid bookmark found, igoring it:\n%s" % conference_elt.toXml())
+                continue
+
+            data = conf_data[room_jid] = {}
+
+            for attr in XEP_0048.MUC_ATTRS:
+                if conference_elt.hasAttribute(attr):
+                    data[attr] = conference_elt[attr]
+            try:
+                data['nick'] = unicode(conference_elt.elements(NS_BOOKMARKS, 'nick').next())
+            except StopIteration:
+                pass
+            # TODO: manage password (need to be secured, see XEP-0049 §4)
+
+        url_elts = storage_elt.elements(NS_BOOKMARKS, 'url')
+        for url_elt in url_elts:
+            try:
+                url = url_elt[XEP_0048.URL_KEY]
+            except KeyError:
+                warning ("invalid bookmark found, igoring it:\n%s" % url_elt.toXml())
+                continue
+            data = url_data[url] = {}
+            for attr in XEP_0048.URL_ATTRS:
+                if url_elt.hasAttribute(attr):
+                    data[attr] = url_elt[attr]
+
+        return {XEP_0048.MUC_TYPE: conf_data, XEP_0048.URL_TYPE: url_data}
+
+    def _dict2BookmarkElt(self, type_, data):
+        """Construct a bookmark element from a data dict
+        @param data (dict): bookmark data (key: bookmark type, value: list) where key can be:
+            - XEP_0048.MUC_TYPE
+            - XEP_0048.URL_TYPE
+            - value (dict): data as for addBookmark
+        @return (domish.Element): bookmark element
+        """
+        rooms_data = data.get(XEP_0048.MUC_TYPE, {})
+        urls_data = data.get(XEP_0048.URL_TYPE, {})
+        storage_elt = domish.Element((NS_BOOKMARKS, 'storage'))
+        for room_jid in rooms_data:
+            conference_elt = storage_elt.addElement('conference')
+            conference_elt[XEP_0048.MUC_KEY] = room_jid.full()
+            for attr in XEP_0048.MUC_ATTRS:
+                try:
+                    conference_elt[attr] = rooms_data[room_jid][attr]
+                except KeyError:
+                    pass
+            try:
+                conference_elt.addElement('nick', content=rooms_data[room_jid]['nick'])
+            except KeyError:
+                pass
+
+        for url in urls_data:
+            url_elt = storage_elt.addElement('url')
+            url_elt[XEP_0048.URL_KEY] = url
+            for attr in XEP_0048.URL_ATTRS:
+                try:
+                    url_elt[attr] = url[attr]
+                except KeyError:
+                    pass
+
+        return storage_elt
+
+    def _bookmarksMenu(self, data, profile):
+        """ XMLUI activated by menu: return Gateways UI
+        @param profile: %(doc_profile)s
+
+        """
+        client = self.host.getClient(profile)
+        xmlui = xml_tools.XMLUI(title=_('Bookmarks manager'))
+        xmlui.addText(_("add a bookmark"))
+        xmlui.changeContainer("pairs")
+        xmlui.addLabel(_('Name'))
+        xmlui.addString('name')
+        xmlui.addLabel(_('jid'))
+        xmlui.addString('jid')
+        xmlui.addLabel(_('Nickname'))
+        xmlui.addString('nick', client.jid.user)
+        xmlui.addLabel(_('Autojoin'))
+        xmlui.addBool('autojoin')
+        xmlui.changeContainer("vertical")
+        xmlui.addButton(self.__bm_save_id, _("Save"), ('name', 'jid', 'nick', 'autojoin'))
+        return {'xmlui': xmlui.toXml()}
+
+    def _bookmarksSaveCb(self, data, profile):
+        bm_data = xml_tools.XMLUIResult2DataFormResult(data)
+        try:
+            location = jid.JID(bm_data.pop('jid'))
+        except KeyError:
+            raise exceptions.InternalError("Can't find mandatory key")
+        d = self.addBookmark(XEP_0048.MUC_TYPE, location, bm_data, profile_key=profile)
+        d.addCallback(lambda dummy: {})
+        return d
+
+    @defer.inlineCallbacks
+    def addBookmark(self, type_, location, data, storage_type="auto", profile_key="@NONE@"):
+        """ Store a new bookmark
+        @param type_: bookmark type, one of:
+            - XEP_0048.MUC_TYPE: Multi-User chat room
+            - XEP_0048.URL_TYPE: web page URL
+        @param location: dependeding on type_, can be a MUC room jid or an url
+        @param data (dict): depending on type_, can contains the following keys:
+            - name: human readable name of the bookmark
+            - nick: user preferred room nick (default to user part of profile's jid)
+            - autojoin: "true" if room must be automatically joined on connection
+            - password: unused yet TODO
+        @param storage_type: where the bookmark will be stored, can be:
+            - "auto": find best available option: pubsub, private, local in that order
+            - "pubsub": PubSub private storage (XEP-0223)
+            - "private": Private XML storage (XEP-0049)
+            - "local": Store in SàT database
+        @param profile_key: %(doc_profile_key)s
+
+        """
+        assert storage_type in ('auto', 'pubsub', 'private', 'local')
+        client = self.host.getClient(profile_key)
+        if storage_type == 'auto':
+            if client.bookmarks_pubsub is not None:
+                storage_type = 'pubsub'
+            elif client.bookmarks_private is not None:
+                storage_type = 'private'
+            else:
+                storage_type = 'local'
+                warning(_("Bookmarks will be local only"))
+            info(_('Type selected for "auto" storage: %s') % storage_type)
+
+        if storage_type == 'local':
+            client.bookmarks_local[type_][location] = data
+            yield client.bookmarks_local.force(type_)
+        else:
+            bookmarks = yield self._getServerBookmarks(storage_type, client.profile)
+            bookmarks[type_][location] = data
+            bookmark_elt = self._dict2BookmarkElt(type_, bookmarks)
+            yield self._setServerBookmarks(storage_type, bookmark_elt, client.profile)
+
+
+
+
+