1# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
2#
3# This file is part of nbxmpp.
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 3
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; If not, see <http://www.gnu.org/licenses/>.
17
18from nbxmpp.namespaces import Namespace
19from nbxmpp.protocol import Iq
20from nbxmpp.protocol import Node
21from nbxmpp.protocol import JID
22from nbxmpp.structs import AnnotationNote
23from nbxmpp.errors import StanzaError
24from nbxmpp.errors import MalformedStanzaError
25from nbxmpp.task import iq_request_task
26from nbxmpp.modules.base import BaseModule
27from nbxmpp.modules.util import process_response
28from nbxmpp.modules.date_and_time import parse_datetime
29
30
31class Annotations(BaseModule):
32    def __init__(self, client):
33        BaseModule.__init__(self, client)
34
35        self._client = client
36        self.handlers = []
37
38    @property
39    def domain(self):
40        return self._client.get_bound_jid().domain
41
42    @iq_request_task
43    def request_annotations(self):
44        _task = yield
45
46        response = yield _make_request()
47        if response.isError():
48            raise StanzaError(response)
49
50        query = response.getQuery()
51        storage = query.getTag('storage', namespace=Namespace.ROSTERNOTES)
52        if storage is None:
53            raise MalformedStanzaError('storage node missing', response)
54
55        notes = []
56        for note in storage.getTags('note'):
57            try:
58                jid = JID.from_string(note.getAttr('jid'))
59            except Exception as error:
60                self._log.warning('Invalid JID: %s, %s',
61                                  note.getAttr('jid'), error)
62                continue
63
64            cdate = note.getAttr('cdate')
65            if cdate is not None:
66                cdate = parse_datetime(cdate, epoch=True)
67
68            mdate = note.getAttr('mdate')
69            if mdate is not None:
70                mdate = parse_datetime(mdate, epoch=True)
71
72            data = note.getData()
73            notes.append(AnnotationNote(jid=jid, cdate=cdate,
74                                        mdate=mdate, data=data))
75
76        self._log.info('Received annotations from %s:', self.domain)
77        for note in notes:
78            self._log.info(note)
79        yield notes
80
81    @iq_request_task
82    def set_annotations(self, notes):
83        _task = yield
84
85        self._log.info('Set annotations for %s:', self.domain)
86
87        for note in notes:
88            self._log.info(note)
89
90        response = yield _make_set_request(notes)
91        yield process_response(response)
92
93
94def _make_request():
95    payload = Node('storage', attrs={'xmlns': Namespace.ROSTERNOTES})
96    return Iq(typ='get', queryNS=Namespace.PRIVATE, payload=payload)
97
98def _make_set_request(notes):
99    storage = Node('storage', attrs={'xmlns': Namespace.ROSTERNOTES})
100    for note in notes:
101        node = Node('note', attrs={'jid': note.jid})
102        node.setData(note.data)
103        if note.cdate is not None:
104            node.setAttr('cdate', note.cdate)
105        if note.mdate is not None:
106            node.setAttr('mdate', note.mdate)
107        storage.addChild(node=node)
108    return Iq(typ='set', queryNS=Namespace.PRIVATE, payload=storage)
109