Commits

Olemis Lang  committed 333c639

Trac Rpc [ refs rpc-307 & closes rpc-308, rpc-305, rpc-306 ] Generic attachments RPC

Also expands WikiRPC API by adding listAttachmentsEx method providing attachment metadata

  • Participants
  • Parent commits 82d1502

Comments (0)

Files changed (3)

File trunk/tracrpc/attachment.py

+# -*- coding: utf-8 -*-
+"""
+License: BSD
+
+(c) 2014      ::: Olemis Lang (olemis+trac@gmail.com)
+"""
+
+from trac.attachment import Attachment
+from trac.resource import Resource, resource_exists, ResourceNotFound
+
+from tracrpc.api import Binary
+from trac.core import TracError
+from tracrpc.constants import NAME_RPC_TIMESTAMP
+from tracrpc.util import StringIO
+
+__all__ = ['AttachmentRPCDelegate']
+
+class AttachmentRPCDelegate(object):
+    """ Extend resource-specific RPC API with attachment methods. """
+
+    def __init__(self, env, realm, id_type):
+        """Setup attachment delegate context
+
+        :param env:     Environment object
+        :param realm:   Resource realm
+        """
+        self.env = env
+        self.realm = realm
+        self.id_type = id_type
+
+    # IXMLRPCHandler methods
+    def xmlrpc_methods(self, aliases=None):
+        aliases = aliases or {}
+        if 'listAttachments' in aliases:
+            yield (None, ((list, self.id_type),), self.listAttachments,
+                   aliases['listAttachments'])
+        else:
+            yield (None, ((list, self.id_type),), self.listAttachments)
+
+        if 'getAttachment' in aliases:
+            yield (None, ((Binary, self.id_type, str),), self.getAttachment,
+                   aliases['getAttachment'])
+        else:
+            yield (None, ((Binary, self.id_type, str),), self.getAttachment)
+
+        if 'putAttachment' in aliases:
+            yield (None,
+                   ((str, self.id_type, str, str, Binary, bool),
+                    (str, self.id_type, str, str, Binary)),
+               self.putAttachment, aliases['putAttachment'])
+        else:
+            yield (None,
+                   ((str, self.id_type, str, str, Binary, bool),
+                    (str, self.id_type, str, str, Binary)),
+               self.putAttachment)
+
+        if 'deleteAttachment' in aliases:
+            yield (None, ((bool, self.id_type, str),), self.deleteAttachment,
+                   aliases['deleteAttachment'])
+        else:
+            yield (None, ((bool, self.id_type, str),), self.deleteAttachment)
+
+    # Delegate methods
+    def listAttachments(self, req, resource_id):
+        """ Lists attachments for a given resource. Returns (filename,
+        description, size, time, author) for each attachment."""
+        for a in Attachment.select(self.env, self.realm, resource_id):
+            if 'ATTACHMENT_VIEW' in req.perm(a.resource):
+                yield (a.filename, a.description, a.size, a.date, a.author)
+
+    def getAttachment(self, req, resource_id, filename):
+        """ Returns the content of an attachment. """
+        attachment = Attachment(self.env, self.realm, resource_id, filename)
+        req.perm(attachment.resource).require('ATTACHMENT_VIEW')
+        return Binary(attachment.open().read())
+
+    def putAttachment(self, req, resource_id, filename, description, data,
+                      replace=True):
+        """ Add an attachment, optionally (and defaulting to) overwriting an
+        existing one. Returns filename."""
+        t = getattr(req, NAME_RPC_TIMESTAMP, None)
+        if not resource_exists(self.env, Resource(self.realm, resource_id)):
+            # This applies to both inexistent resource and not conclusive checks
+            raise ResourceNotFound('Resource "%s:%s" does not exist' % 
+                                   (self.realm, resource_id))
+        if replace:
+            try:
+                attachment = Attachment(self.env, self.realm, resource_id, 
+                                        filename)
+                req.perm(attachment.resource).require('ATTACHMENT_DELETE')
+                attachment.delete()
+            except TracError:
+                pass
+        t = getattr(req, NAME_RPC_TIMESTAMP, None)
+        attachment = Attachment(self.env, self.realm, resource_id)
+        req.perm(attachment.resource).require('ATTACHMENT_CREATE')
+        attachment.author = req.authname
+        attachment.description = description
+        attachment.insert(filename, StringIO(data.data), len(data.data), t=t)
+        return attachment.filename
+
+    def deleteAttachment(self, req, resource_id, filename):
+        """ Delete an attachment. """
+        if not resource_exists(self.env, Resource(self.realm, resource_id)):
+            raise ResourceNotFound('Resource "%s:%s" does not exist' % 
+                                   (self.realm, resource_id))
+        attachment = Attachment(self.env, self.realm, resource_id, filename)
+        req.perm(attachment.resource).require('ATTACHMENT_DELETE')
+        attachment.delete()
+        return True
+

File trunk/tracrpc/ticket.py

 from trac.util.datefmt import to_datetime, utc
 
 from tracrpc.api import IXMLRPCHandler, expose_rpc, Binary
+from tracrpc.attachment import AttachmentRPCDelegate
 from tracrpc.constants import NAME_RPC_TIMESTAMP
 from tracrpc.util import from_utimestamp, StringIO, to_utimestamp
 
 
     implements(IXMLRPCHandler)
 
+    def __init__(self):
+        self._attachment = AttachmentRPCDelegate(self.env, 'ticket', int)
+
     # IXMLRPCHandler methods
     def xmlrpc_namespace(self):
         return 'ticket'
                       self.update)
         yield (None, ((None, int),), self.delete)
         yield (None, ((dict, int), (dict, int, int)), self.changeLog)
-        yield (None, ((list, int),), self.listAttachments)
-        yield (None, ((Binary, int, str),), self.getAttachment)
-        yield (None,
-               ((str, int, str, str, Binary, bool),
-                (str, int, str, str, Binary)),
-               self.putAttachment)
-        yield (None, ((bool, int, str),), self.deleteAttachment)
+
+        for sig in self._attachment.xmlrpc_methods():
+            yield sig
+
         yield ('TICKET_VIEW', ((list,),), self.getTicketFields)
 
     # Exported methods

File trunk/tracrpc/wiki.py

 from trac.wiki.web_ui import InvalidWikiPage
 
 from tracrpc.api import IXMLRPCHandler, expose_rpc, Binary
+from tracrpc.attachment import AttachmentRPCDelegate
 from tracrpc.constants import NAME_RPC_TIMESTAMP
 from tracrpc.util import StringIO, to_utimestamp, from_utimestamp
 
 
     def __init__(self):
         self.wiki = WikiSystem(self.env)
+        self._attachment = AttachmentRPCDelegate(self.env, 'wiki', str)
 
     def xmlrpc_namespace(self):
         return 'wiki'
         yield (None, ((dict, str), (dict, str, int)), self.getPageInfo)
         yield (None, ((dict, str, int),), self.getPageInfo, 'getPageInfoVersion')
         yield (None, ((bool, str, str, dict),), self.putPage)
+
         yield (None, ((list, str),), self.listAttachments)
+        yield (None, ((list, str),), self._attachment.listAttachments, 
+               'listAttachmentsEx')
         yield (None, ((Binary, str),), self.getAttachment)
         yield (None, ((bool, str, Binary),), self.putAttachment)
-        yield (None, ((bool, str, str, str, Binary),
-                               (bool, str, str, str, Binary, bool)),
+        yield (None, ((str, str, str, str, Binary),
+                               (str, str, str, str, Binary, bool)),
                                self.putAttachmentEx)
+        yield (None, ((bool, str),), self.deleteAttachment)
+
         yield (None, ((bool, str),(bool, str, int)), self.deletePage)
-        yield (None, ((bool, str),), self.deleteAttachment)
         yield ('WIKI_VIEW', ((list, str),), self.listLinks)
         yield ('WIKI_VIEW', ((str, str),), self.wikiToHtml)
 
 
     def listAttachments(self, req, pagename):
         """ Lists attachments on a given page. """
-        for a in Attachment.select(self.env, 'wiki', pagename):
-            if 'ATTACHMENT_VIEW' in req.perm(a.resource):
-                yield pagename + '/' + a.filename
+        for a in self._attachment.listAttachments(req, pagename):
+            yield pagename + '/' + a[0]
 
     def getAttachment(self, req, path):
         """ returns the content of an attachment. """
         pagename, filename = os.path.split(path)
-        attachment = Attachment(self.env, 'wiki', pagename, filename)
-        req.perm(attachment.resource).require('ATTACHMENT_VIEW')
-        return Binary(attachment.open().read())
+        return self._attachment.getAttachment(req, pagename, filename)
 
     def putAttachment(self, req, path, data):
         """ (over)writes an attachment. Returns True if successful.
         filename of the attachment.
         
         Use this method if you don't care about WikiRPC compatibility. """
-        if not WikiPage(self.env, pagename).exists:
-            raise InvalidWikiPage, 'Wiki page "%s" does not exist' % pagename
-        if replace:
-            try:
-                attachment = Attachment(self.env, 'wiki', pagename, filename)
-                req.perm(attachment.resource).require('ATTACHMENT_DELETE')
-                attachment.delete()
-            except TracError:
-                pass
-        t = getattr(req, NAME_RPC_TIMESTAMP, None)
-        attachment = Attachment(self.env, 'wiki', pagename)
-        req.perm(attachment.resource).require('ATTACHMENT_CREATE')
-        attachment.author = req.authname
-        attachment.description = description
-        attachment.insert(filename, StringIO(data.data), len(data.data), t=t)
-        return attachment.filename
+        return self._attachment.putAttachment(req, pagename, filename,
+                                              description, data, replace)
 
     def deleteAttachment(self, req, path):
         """ Delete an attachment. """
         pagename, filename = os.path.split(path)
-        if not WikiPage(self.env, pagename).exists:
-            raise InvalidWikiPage, 'Wiki page "%s" does not exist' % pagename
-        attachment = Attachment(self.env, 'wiki', pagename, filename)
-        req.perm(attachment.resource).require('ATTACHMENT_DELETE')
-        attachment.delete()
-        return True
+        return self._attachment.deleteAttachment(req, pagename, filename)
 
     def listLinks(self, req, pagename):
         """ ''Not implemented'' """