Patchwork D2791: hgweb: refactor fake file object proxy for archiving

login
register
mail settings
Submitter phabricator
Date March 12, 2018, 9:34 p.m.
Message ID <3106fe4e9999dca01734df9118793186@localhost.localdomain>
Download mbox | patch
Permalink /patch/29371/
State Not Applicable
Headers show

Comments

phabricator - March 12, 2018, 9:34 p.m.
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG16499427f6de: hgweb: refactor fake file object proxy for archiving (authored by indygreg, committed by ).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2791?vs=6852&id=6927

REVISION DETAIL
  https://phab.mercurial-scm.org/D2791

AFFECTED FILES
  mercurial/archival.py
  mercurial/hgweb/request.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS




To: indygreg, #hg-reviewers, durin42
Cc: mercurial-devel

Patch

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -24,6 +24,9 @@ 
     paritygen,
     staticfile,
 )
+from . import (
+    request as requestmod,
+)
 
 from .. import (
     archival,
@@ -1215,7 +1218,9 @@ 
     req.headers.extend(headers)
     req.respond(HTTP_OK, mimetype)
 
-    archival.archive(web.repo, req, cnode, artype, prefix=name,
+    bodyfh = requestmod.offsettrackingwriter(req.write)
+
+    archival.archive(web.repo, bodyfh, cnode, artype, prefix=name,
                      matchfn=match,
                      subrepos=web.configbool("web", "archivesubrepos"))
     return []
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -290,6 +290,37 @@ 
                          headers=headers,
                          bodyfh=bodyfh)
 
+class offsettrackingwriter(object):
+    """A file object like object that is append only and tracks write count.
+
+    Instances are bound to a callable. This callable is called with data
+    whenever a ``write()`` is attempted.
+
+    Instances track the amount of written data so they can answer ``tell()``
+    requests.
+
+    The intent of this class is to wrap the ``write()`` function returned by
+    a WSGI ``start_response()`` function. Since ``write()`` is a callable and
+    not a file object, it doesn't implement other file object methods.
+    """
+    def __init__(self, writefn):
+        self._write = writefn
+        self._offset = 0
+
+    def write(self, s):
+        res = self._write(s)
+        # Some Python objects don't report the number of bytes written.
+        if res is None:
+            self._offset += len(s)
+        else:
+            self._offset += res
+
+    def flush(self):
+        pass
+
+    def tell(self):
+        return self._offset
+
 class wsgiresponse(object):
     """Represents a response to a WSGI request.
 
diff --git a/mercurial/archival.py b/mercurial/archival.py
--- a/mercurial/archival.py
+++ b/mercurial/archival.py
@@ -195,34 +195,11 @@ 
         if self.fileobj:
             self.fileobj.close()
 
-class tellable(object):
-    '''provide tell method for zipfile.ZipFile when writing to http
-    response file object.'''
-
-    def __init__(self, fp):
-        self.fp = fp
-        self.offset = 0
-
-    def __getattr__(self, key):
-        return getattr(self.fp, key)
-
-    def write(self, s):
-        self.fp.write(s)
-        self.offset += len(s)
-
-    def tell(self):
-        return self.offset
-
 class zipit(object):
     '''write archive to zip file or stream.  can write uncompressed,
     or compressed with deflate.'''
 
     def __init__(self, dest, mtime, compress=True):
-        if not isinstance(dest, bytes):
-            try:
-                dest.tell()
-            except (AttributeError, IOError):
-                dest = tellable(dest)
         self.z = zipfile.ZipFile(pycompat.fsdecode(dest), r'w',
                                  compress and zipfile.ZIP_DEFLATED or
                                  zipfile.ZIP_STORED)