Esempio n. 1
0
class Connection(log.Logger, log.LogProxy):
    '''API for agency to call against the database.'''

    implements(IDatabaseClient, ITimeProvider, IRevisionStore)

    def __init__(self, database):
        log.Logger.__init__(self, database)
        log.LogProxy.__init__(self, database)
        self._database = IDatabaseDriver(database)
        self._serializer = json.Serializer()
        self._unserializer = json.PaisleyUnserializer()

        # listner_id -> doc_ids
        self._listeners = dict()
        self._change_cb = None
        # Changed to use a normal dictionary.
        # It will grow boundless up to the number of documents
        # modified by the connection. It is a kind of memory leak
        # done to temporarily resolve the problem of notifications
        # received after the expiration time due to reconnection
        # killing agents.
        self._known_revisions = {} # {DOC_ID: (REV_INDEX, REV_HASH)}

    ### IRevisionStore ###

    @property
    def known_revisions(self):
        return self._known_revisions

    ### ITimeProvider ###

    def get_time(self):
        return time.time()

    ### IDatabaseClient ###

    def create_database(self):
        return self._database.create_db()

    def save_document(self, doc):
        serialized = self._serializer.convert(doc)
        d = self._database.save_doc(serialized, doc.doc_id)
        d.addCallback(self._update_id_and_rev, doc)
        return d

    def get_document(self, doc_id):
        d = self._database.open_doc(doc_id)
        d.addCallback(self._unserializer.convert)
        d.addCallback(self._notice_doc_revision)
        return d

    def reload_document(self, doc):
        assert isinstance(doc, document.Document)
        return self.get_document(doc.doc_id)

    def delete_document(self, doc):
        assert isinstance(doc, document.Document)
        d = self._database.delete_doc(doc.doc_id, doc.rev)
        d.addCallback(self._update_id_and_rev, doc)
        return d

    def changes_listener(self, filter_, callback, **kwargs):
        assert callable(callback)

        r = RevisionAnalytic(self, callback)
        d = self._database.listen_changes(filter_, r.on_change, kwargs)

        def set_listener_id(l_id, filter_):
            self._listeners[l_id] = filter_

        d.addCallback(set_listener_id, filter_)
        return d

    def cancel_listener(self, doc_id):
        for l_id, doc_ids in self._listeners.items():
            if doc_id in doc_ids:
                self._cancel_listener(l_id)

    def query_view(self, factory, **options):
        factory = IViewFactory(factory)
        d = self._database.query_view(factory, **options)
        d.addCallback(self._parse_view_results, factory, options)
        return d

    def disconnect(self):
        for l_id in self._listeners.keys():
            self._cancel_listener(l_id)

    ### private

    def _cancel_listener(self, lister_id):
        self._database.cancel_listener(lister_id)
        try:
            del(self._listeners[lister_id])
        except KeyError:
            self.warning('Tried to remove nonexistining listener id %r.',
                         lister_id)

    def _parse_view_results(self, rows, factory, options):
        '''
        rows here should be a list of tuples (key, value)
        rendered by the view
        '''
        reduced = factory.use_reduce and options.get('reduce', True)
        return map(lambda row: factory.parse(row[0], row[1], reduced), rows)

    def _update_id_and_rev(self, resp, doc):
        doc.doc_id = unicode(resp.get('id', None))
        doc.rev = unicode(resp.get('rev', None))
        self._notice_doc_revision(doc)
        return doc

    def _notice_doc_revision(self, doc):
        self.log('Storing knowledge about doc rev. ID: %r, REV: %r',
                 doc.doc_id, doc.rev)
        self._known_revisions[doc.doc_id] = _parse_doc_revision(doc.rev)
        return doc
Esempio n. 2
0
class Connection(log.Logger):
    """API for agency to call against the database."""

    implements(IDatabaseClient, ITimeProvider)

    def __init__(self, database):
        log.Logger.__init__(self, database)
        self.database = IDatabaseDriver(database)
        self.serializer = json.Serializer()
        self.unserializer = json.PaisleyUnserializer()

        self.listener_id = None
        self.change_cb = None
        # rev -> doc_id
        self.known_revisions = container.ExpDict(self)

    ### ITimeProvider

    def get_time(self):
        return time.time()

    ### IDatabaseClient

    def create_database(self):
        return self.database.create_db()

    def save_document(self, doc):
        serialized = self.serializer.convert(doc)
        d = self.database.save_doc(serialized, doc.doc_id)
        d.addCallback(self._update_id_and_rev, doc)
        return d

    def get_document(self, id):
        d = self.database.open_doc(id)
        d.addCallback(self.unserializer.convert)
        d.addCallback(self._notice_doc_revision)
        return d

    def reload_document(self, doc):
        assert isinstance(doc, document.Document)
        return self.get_document(doc.doc_id)

    def delete_document(self, doc):
        assert isinstance(doc, document.Document)
        d = self.database.delete_doc(doc.doc_id, doc.rev)
        d.addCallback(self._update_id_and_rev, doc)
        return d

    def changes_listener(self, doc_ids, callback):
        assert isinstance(doc_ids, (tuple, list))
        assert callable(callback)
        self.change_cb = callback
        d = self.database.listen_changes(doc_ids, self._on_change)

        def set_listener_id(l_id):
            self.listener_id = l_id

        d.addCallback(set_listener_id)
        return d

    def query_view(self, factory, **options):
        factory = IViewFactory(factory)
        d = self.database.query_view(factory, **options)
        d.addCallback(self._parse_view_results, factory, options)
        return d

    def disconnect(self):
        if self.listener_id:
            self.database.cancel_listener(self.listener_id)
            self.listener_id = None
            self.change_cb = None

    ### private

    def _parse_view_results(self, rows, factory, options):
        """
        rows here should be a list of tuples (key, value)
        rendered by the view
        """
        reduced = factory.use_reduce and options.get("reduce", True)
        return map(lambda row: factory.parse(row[0], row[1], reduced), rows)

    def _on_change(self, doc_id, rev):
        self.log("Change notification received doc_id: %r, rev: %r", doc_id, rev)
        key = (doc_id, rev)
        known = self.known_revisions.get(key, False)
        if known:
            self.log("Ignoring change notification, it is ours.")
        elif callable(self.change_cb):
            self.change_cb(doc_id, rev)

    def _update_id_and_rev(self, resp, doc):
        doc.doc_id = unicode(resp.get("id", None))
        doc.rev = unicode(resp.get("rev", None))
        # store information about rev and doc_id in ExpDict for 1 second
        # so that we can ignore change callback which we trigger
        self._notice_doc_revision(doc)
        return doc

    def _notice_doc_revision(self, doc):
        self.log("Storing knowledge about doc rev. ID: %r, REV: %r", doc.doc_id, doc.rev)
        self.known_revisions.set((doc.doc_id, doc.rev), True, expiration=5, relative=True)
        return doc