← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~xaav/loggerhead/export-tarball into lp:loggerhead

 

You have been requested to review the proposed merge of lp:~xaav/loggerhead/export-tarball into lp:loggerhead.

For more details, see:
https://code.launchpad.net/~xaav/loggerhead/export-tarball/+merge/63931

This branch **may** accomplish exporting the tarball using chunked transfer encoding. The code all looks to be correct, but I have not tested it, so I would like your opinion.

Thanks!

-- 
https://code.launchpad.net/~xaav/loggerhead/export-tarball/+merge/63931
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~xaav/loggerhead/export-tarball into lp:loggerhead.
=== modified file '.bzrignore'
--- .bzrignore	2011-03-10 14:22:12 +0000
+++ .bzrignore	2011-06-13 02:18:26 +0000
@@ -8,3 +8,4 @@
 loggerhead-memprofile
 ./docs/_build/
 tags
+loggerhead/static/downloads/*

=== modified file 'loggerhead/apps/branch.py'
--- loggerhead/apps/branch.py	2011-03-16 11:40:05 +0000
+++ loggerhead/apps/branch.py	2011-06-13 02:18:26 +0000
@@ -33,7 +33,7 @@
 from loggerhead.controllers.atom_ui import AtomUI
 from loggerhead.controllers.changelog_ui import ChangeLogUI
 from loggerhead.controllers.diff_ui import DiffUI
-from loggerhead.controllers.download_ui import DownloadUI
+from loggerhead.controllers.download_ui import DownloadUI, DownloadTarballUI
 from loggerhead.controllers.filediff_ui import FileDiffUI
 from loggerhead.controllers.inventory_ui import InventoryUI
 from loggerhead.controllers.revision_ui import RevisionUI
@@ -49,7 +49,7 @@
 
     def __init__(self, branch, friendly_name=None, config={},
                  graph_cache=None, branch_link=None, is_root=False,
-                 served_url=_DEFAULT, use_cdn=False):
+                 served_url=_DEFAULT, use_cdn=False, export_tarballs=True):
         self.branch = branch
         self._config = config
         self.friendly_name = friendly_name
@@ -61,6 +61,7 @@
         self.is_root = is_root
         self.served_url = served_url
         self.use_cdn = use_cdn
+        self.export_tarballs = export_tarballs
 
     def get_history(self):
         file_cache = None
@@ -126,6 +127,7 @@
         'revision': RevisionUI,
         'search': SearchUI,
         'view': ViewUI,
+        'tarball': DownloadTarballUI,
         }
 
     def last_updated(self):
@@ -171,10 +173,14 @@
         try:
             try:
                 c = cls(self, self.get_history)
-                return c(environ, start_response)
+                to_ret = c(environ, start_response)
             except:
                 environ['exc_info'] = sys.exc_info()
                 environ['branch'] = self
                 raise
+            if type(to_ret) == type(httpexceptions.HTTPSeeOther('/')):
+                raise to_ret
+            else:
+                return to_ret
         finally:
             self.branch.unlock()

=== modified file 'loggerhead/config.py'
--- loggerhead/config.py	2010-05-12 14:38:05 +0000
+++ loggerhead/config.py	2011-06-13 02:18:26 +0000
@@ -36,6 +36,7 @@
         use_cdn=False,
         sql_dir=None,
         allow_writes=False,
+        export_tarballs=True,
         )
     parser.add_option("--user-dirs", action="store_true",
                       help="Serve user directories as ~user.")
@@ -75,6 +76,8 @@
                       help="The directory to place the SQL cache in")
     parser.add_option("--allow-writes", action="store_true",
                       help="Allow writing to the Bazaar server.")
+    parser.add_option("--export-tarballs", action="store_true",
+                      help="Allow exporting revisions to tarballs.")
     return parser
 
 

=== modified file 'loggerhead/controllers/__init__.py'
--- loggerhead/controllers/__init__.py	2011-03-16 11:40:05 +0000
+++ loggerhead/controllers/__init__.py	2011-06-13 02:18:26 +0000
@@ -20,7 +20,7 @@
 import bzrlib.errors
 import time
 
-from paste.httpexceptions import HTTPNotFound
+from paste.httpexceptions import HTTPNotFound, HTTPSeeOther
 from paste.request import path_info_pop, parse_querystring
 
 from loggerhead import util

=== modified file 'loggerhead/controllers/download_ui.py'
--- loggerhead/controllers/download_ui.py	2010-05-05 19:03:40 +0000
+++ loggerhead/controllers/download_ui.py	2011-06-13 02:18:26 +0000
@@ -19,17 +19,23 @@
 
 import logging
 import mimetypes
+import os
 import urllib
 
 from paste import httpexceptions
 from paste.request import path_info_pop
 
 from loggerhead.controllers import TemplatedBranchView
+from loggerhead.exporter import export_tarball
 
 log = logging.getLogger("loggerhead.controllers")
 
 
 class DownloadUI (TemplatedBranchView):
+    
+    def encode_filename(self, filename):
+        
+        return urllib.quote(filename.encode('utf-8'))
 
     def __call__(self, environ, start_response):
         # /download/<rev_id>/<file_id>/[filename]
@@ -58,7 +64,7 @@
                       path,
                       h.get_revno(revid),
                       len(content))
-        encoded_filename = urllib.quote(filename.encode('utf-8'))
+        encoded_filename = self.encode_filename(filename)
         headers = [
             ('Content-Type', mime_type),
             ('Content-Length', str(len(content))),
@@ -67,3 +73,34 @@
             ]
         start_response('200 OK', headers)
         return [content]
+
+class DownloadTarballUI(DownloadUI):
+     
+    def __call__(self, environ, start_response):
+        """Stream a tarball from a bazaar branch."""
+        
+        #Tried to re-use code from downloadui, not very successful
+        
+        format = ".tar.gz"
+        
+        history = self._history
+        if len(self.args):
+            revid = history.fix_revid(self.args[0])
+        else:
+            revid = self.get_revid()
+            
+        if self._branch.export_tarballs:
+            
+            encoded_filename = self.encode_filename("branch" + format)
+            
+            headers = [
+                ('Content-Type', 'application/octet-stream'),
+                ('Content-Disposition',
+                 "attachment; filename*=utf-8''%s" % (encoded_filename,)),
+                ]
+            
+            start_response('200 OK', headers)
+            
+            return export_tarball(history, revid, format)
+        else:
+            raise httpexceptions.HTTPSeeOther('/')

=== modified file 'loggerhead/controllers/revision_ui.py'
--- loggerhead/controllers/revision_ui.py	2011-03-02 14:07:21 +0000
+++ loggerhead/controllers/revision_ui.py	2011-06-13 02:18:26 +0000
@@ -110,7 +110,7 @@
                 self._branch.friendly_name,
                 self._branch.is_root,
                 'changes'))
-
+        can_export = self._branch.export_tarballs
         return {
             'branch': self._branch,
             'revid': revid,
@@ -132,4 +132,5 @@
             'compare_revid': compare_revid,
             'url': self._branch.context_url,
             'directory_breadcrumbs': directory_breadcrumbs,
+            'can_export': can_export,
         }

=== added file 'loggerhead/exporter.py'
--- loggerhead/exporter.py	1970-01-01 00:00:00 +0000
+++ loggerhead/exporter.py	2011-06-13 02:18:26 +0000
@@ -0,0 +1,40 @@
+"""Exports an archive from a bazaar branch"""
+
+from bzrlib.export import get_export_generator
+
+class ExporterFileObject(object):
+    
+    def __init__(self):
+        self._buffer = []
+        
+    def write(self, str):
+        self._buffer.append(str)
+        
+    def get_buffer(self):
+        try:
+            return ''.join(self._buffer)
+        finally:
+            self._buffer = []
+        
+def export_archive(history, revid, format=".tar.gz"):
+    """Export tree contents to an archive
+
+    :param history: Instance of history to export
+    :param revid: Revision to export
+    :param format: Format of the archive
+    """
+    
+    fileobj = ExporterFileObject()
+    
+    tree = history._branch.repository.revision_tree(revid)
+    
+    for _ in get_export_generator(tree=tree, fileobj=fileobj, format=format):
+        
+        yield fileobj.get_buffer()
+    
+    # Might have additonal contents written
+        
+    yield fileobj.get_buffer()
+
+    
+        
\ No newline at end of file

=== modified file 'loggerhead/history.py'
--- loggerhead/history.py	2011-03-25 13:09:10 +0000
+++ loggerhead/history.py	2011-06-13 02:18:26 +0000
@@ -33,6 +33,7 @@
 import re
 import textwrap
 import threading
+import tarfile
 
 from bzrlib import tag
 import bzrlib.branch
@@ -44,6 +45,7 @@
 from loggerhead import search
 from loggerhead import util
 from loggerhead.wholehistory import compute_whole_history_data
+from bzrlib.export.tar_exporter import export_tarball
 
 
 def is_branch(folder):
@@ -817,3 +819,4 @@
             removed=sorted(reporter.removed, key=lambda x: x.filename),
             modified=sorted(reporter.modified, key=lambda x: x.filename),
             text_changes=sorted(reporter.text_changes, key=lambda x: x.filename))
+

=== added directory 'loggerhead/static/downloads'
=== modified file 'loggerhead/templates/revision.pt'
--- loggerhead/templates/revision.pt	2011-02-23 03:01:15 +0000
+++ loggerhead/templates/revision.pt	2011-06-13 02:18:26 +0000
@@ -84,7 +84,10 @@
                tal:attributes="href python:url(['/diff', change.revno], clear=1)">download diff</a>
             <a tal:condition="python:compare_revid is not None"
                tal:attributes="href python:url(['/diff', change.revno, history.get_revno(compare_revid)], clear=1)">download diff</a>
-          </li>
+           </li>
+           <li tal:condition="python:can_export">
+            <a tal:attributes="href python:url(['/tarball', change.revno])">download tarball</a>
+           </li>
           <li id="last"><a tal:attributes="href python:url(['/changes', change.revno]);
                                            title string:view history from revision ${change/revno}"
                            tal:content="string:view history from revision ${change/revno}"></a></li>

=== modified file 'loggerhead/tests/test_controllers.py'
--- loggerhead/tests/test_controllers.py	2011-03-15 02:56:59 +0000
+++ loggerhead/tests/test_controllers.py	2011-06-13 02:18:26 +0000
@@ -12,6 +12,9 @@
 from loggerhead.tests.test_simple import BasicTests
 from loggerhead import util
 
+from os import tmpnam
+from tarfile import is_tarfile
+
 
 class TestInventoryUI(BasicTests):
 
@@ -125,3 +128,14 @@
         self.assertEqual(2, len(annotated))
         self.assertEqual('2', annotated[0].change.revno)
         self.assertEqual('1', annotated[1].change.revno)
+
+class TestDownloadTarballUI(BasicTests):
+    
+    def test_download_tarball(self):
+        app = self.setUpLoggerhead()
+        res = app.get('/tarball')
+        tmpname = tmpnam()
+        f = open(tmpname, 'w')
+        f.write(res)
+        f.close()
+        self.failIf(not is_tarfile(tmpname))