FTP protocol support - initial read support

Rafael Villar Burke pachi at rvburke.com
Mon Feb 5 20:07:44 CST 2007


I've tried to understand how FTP protocol support could be added to 
Mercurial and, so far, I've taken the static-http support and tried to 
tweak it to support read-only operation for FTP.
It looks like that, for adding a new protocol, the following is needed:
- a new key in the schemes dictionary in hg.py so the new protocol is 
recognized, and a new repo class is provided as value for that key.
- the repo class must implement certain API, depending on its 
capabilities. The existing statichttprepo.py and sshrepo.py classes look 
like the most basic and most complete implementations in the current 
code. A new ftprepo.py has been provided, that's based on statichttprepo.py.
- the repo class uses an opener to manage range reads using the provided 
byterange module. This last already has a FTPRangeHandler, and is used 
in the new ftprangereader.py module in a similar way to the 
HTTPRangeHandler used for the static-http protocol used in 
httprangereader.py.

I haven't been able to test this (working on win32 ATM), but I would 
like to know if it works for anyone, and wouldn't mind others trying to 
develop it further ;).

The attached diff  has my changes so far.

Regards,

Rafael Villar Burke
www.rvburke.com

diff -r 82eb0fafb56d mercurial/ftprangereader.py
--- /dev/null    Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/ftprangereader.py    Tue Feb 06 02:32:54 2007 +0100
@@ -0,0 +1,29 @@
+# ftprangereader.py - just what it says
+#
+# Copyright 2005, 2006 Matt Mackall <mpm at selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import byterange, urllib2
+from ftplib import FTP
+
+class ftprangereader(object):
+    def __init__(self, url):
+        self.url = url
+        self.pos = 0
+    def seek(self, pos):
+        self.pos = pos
+    def read(self, bytes=None):
+        opener = urllib2.build_opener(byterange.FTPRangeHandler())
+        urllib2.install_opener(opener)
+        req = urllib2.Request(self.url)
+        end = ''
+        if bytes:
+            end = self.pos + bytes - 1
+        req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
+        f = urllib2.urlopen(req)
+        data = f.read()
+        if bytes:
+            data = data[:bytes]
+        return data
diff -r 82eb0fafb56d mercurial/ftprepo.py
--- /dev/null    Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/ftprepo.py    Tue Feb 06 02:34:30 2007 +0100
@@ -0,0 +1,78 @@
+# statichttprepo.py - simple http repository class for mercurial
+#
+# This provides read-only repo access to repositories exported via 
static http
+#
+# Copyright 2005, 2006 Matt Mackall <mpm at selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from i18n import _
+import changelog, filelog, httprangereader
+import repo, localrepo, manifest, os, urllib, urllib2, util
+
+class rangereader(httprangereader.httprangereader):
+    def read(self, size=None):
+        try:
+            return httprangereader.httprangereader.read(self, size)
+        except urllib2.URLError, inst:
+            raise IOError(None, inst.reason[1])
+
+def opener(base):
+    """return a function that opens files over http"""
+    p = base
+    def o(path, mode="r"):
+        f = "/".join((p, urllib.quote(path)))
+        return rangereader(f)
+    return o
+
+class ftprepository(localrepo.localrepository):
+    def __init__(self, ui, path):
+        self._url = path
+        self.ui = ui
+        self.revlogversion = 0
+
+        self.path = (path + "/.hg")
+        self.opener = opener(self.path)
+        # find requirements
+        try:
+            requirements = self.opener("requires").read().splitlines()
+        except IOError:
+            requirements = []
+        # check them
+        for r in requirements:
+            if r not in self.supported:
+                raise repo.RepoError(_("requirement '%s' not 
supported") % r)
+
+        # setup store
+        if "store" in requirements:
+            self.encodefn = util.encodefilename
+            self.decodefn = util.decodefilename
+            self.spath = self.path + "/store"
+        else:
+            self.encodefn = lambda x: x
+            self.decodefn = lambda x: x
+            self.spath = self.path
+        self.sopener = util.encodedopener(opener(self.spath), 
self.encodefn)
+
+        self.manifest = manifest.manifest(self.sopener)
+        self.changelog = changelog.changelog(self.sopener)
+        self.tagscache = None
+        self.nodetagscache = None
+        self.encodepats = None
+        self.decodepats = None
+
+    def url(self):
+        return self._url
+
+    def dev(self):
+        return -1
+
+    def local(self):
+        return False
+
+def instance(ui, path, create):
+    if create:
+        #TODO: should create repo here
+        raise util.Abort(_('cannot create new ftp repository'))
+    return ftprepository(ui, path)
diff -r 82eb0fafb56d mercurial/hg.py
--- a/mercurial/hg.py    Tue Jan 30 21:11:10 2007 -0200
+++ b/mercurial/hg.py    Tue Feb 06 02:35:04 2007 +0100
@@ -9,7 +9,7 @@ from node import *
 from node import *
 from repo import *
 from i18n import _
-import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
+import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, ftprepo
 import errno, lock, os, shutil, util
 import merge as _merge
 import verify as _verify
@@ -27,6 +27,7 @@ schemes = {
     'old-http': statichttprepo,
     'ssh': sshrepo,
     'static-http': statichttprepo,
+    'ftp': ftprepo,
     }
 
 def _lookup(path):

-------------- next part --------------
diff -r 82eb0fafb56d mercurial/ftprangereader.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/ftprangereader.py	Tue Feb 06 02:32:54 2007 +0100
@@ -0,0 +1,29 @@
+# ftprangereader.py - just what it says
+#
+# Copyright 2005, 2006 Matt Mackall <mpm at selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import byterange, urllib2
+from ftplib import FTP
+
+class ftprangereader(object):
+    def __init__(self, url):
+        self.url = url
+        self.pos = 0
+    def seek(self, pos):
+        self.pos = pos
+    def read(self, bytes=None):
+        opener = urllib2.build_opener(byterange.FTPRangeHandler())
+        urllib2.install_opener(opener)
+        req = urllib2.Request(self.url)
+        end = ''
+        if bytes:
+            end = self.pos + bytes - 1
+        req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
+        f = urllib2.urlopen(req)
+        data = f.read()
+        if bytes:
+            data = data[:bytes]
+        return data
diff -r 82eb0fafb56d mercurial/ftprepo.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/ftprepo.py	Tue Feb 06 02:34:30 2007 +0100
@@ -0,0 +1,78 @@
+# statichttprepo.py - simple http repository class for mercurial
+#
+# This provides read-only repo access to repositories exported via static http
+#
+# Copyright 2005, 2006 Matt Mackall <mpm at selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from i18n import _
+import changelog, filelog, httprangereader
+import repo, localrepo, manifest, os, urllib, urllib2, util
+
+class rangereader(httprangereader.httprangereader):
+    def read(self, size=None):
+        try:
+            return httprangereader.httprangereader.read(self, size)
+        except urllib2.URLError, inst:
+            raise IOError(None, inst.reason[1])
+
+def opener(base):
+    """return a function that opens files over http"""
+    p = base
+    def o(path, mode="r"):
+        f = "/".join((p, urllib.quote(path)))
+        return rangereader(f)
+    return o
+
+class ftprepository(localrepo.localrepository):
+    def __init__(self, ui, path):
+        self._url = path
+        self.ui = ui
+        self.revlogversion = 0
+
+        self.path = (path + "/.hg")
+        self.opener = opener(self.path)
+        # find requirements
+        try:
+            requirements = self.opener("requires").read().splitlines()
+        except IOError:
+            requirements = []
+        # check them
+        for r in requirements:
+            if r not in self.supported:
+                raise repo.RepoError(_("requirement '%s' not supported") % r)
+
+        # setup store
+        if "store" in requirements:
+            self.encodefn = util.encodefilename
+            self.decodefn = util.decodefilename
+            self.spath = self.path + "/store"
+        else:
+            self.encodefn = lambda x: x
+            self.decodefn = lambda x: x
+            self.spath = self.path
+        self.sopener = util.encodedopener(opener(self.spath), self.encodefn)
+
+        self.manifest = manifest.manifest(self.sopener)
+        self.changelog = changelog.changelog(self.sopener)
+        self.tagscache = None
+        self.nodetagscache = None
+        self.encodepats = None
+        self.decodepats = None
+
+    def url(self):
+        return self._url
+
+    def dev(self):
+        return -1
+
+    def local(self):
+        return False
+
+def instance(ui, path, create):
+    if create:
+        #TODO: should create repo here
+        raise util.Abort(_('cannot create new ftp repository'))
+    return ftprepository(ui, path)
diff -r 82eb0fafb56d mercurial/hg.py
--- a/mercurial/hg.py	Tue Jan 30 21:11:10 2007 -0200
+++ b/mercurial/hg.py	Tue Feb 06 02:35:04 2007 +0100
@@ -9,7 +9,7 @@ from node import *
 from node import *
 from repo import *
 from i18n import _
-import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
+import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, ftprepo
 import errno, lock, os, shutil, util
 import merge as _merge
 import verify as _verify
@@ -27,6 +27,7 @@ schemes = {
     'old-http': statichttprepo,
     'ssh': sshrepo,
     'static-http': statichttprepo,
+    'ftp': ftprepo,
     }
 
 def _lookup(path):


More information about the Mercurial-devel mailing list