# # # add_dir "dws" # # add_file "dws/__init__.py" # content [293ba5fcc634271d47acf26056a75f1b2724c2bf] # # add_file "dws/dws.py" # content [22194ed1418156ea1bec068eb01ed3cf2b711feb] # # add_file "dws/dws_test.py" # content [a519b52ac41e1395c08507b3b0db7932c69e2926] # # add_file "fs_dws.py" # content [1df065db48b6c12048b949302ccde699294997b5] # # patch "fs.py" # from [736da00ad98cd9ef52574d9740e1374085fb2881] # to [a59535fb36d66b7d4f0392a2be44e132240031f4] # ============================================================ --- dws/__init__.py 293ba5fcc634271d47acf26056a75f1b2724c2bf +++ dws/__init__.py 293ba5fcc634271d47acf26056a75f1b2724c2bf @@ -0,0 +1,1 @@ +from dws import * ============================================================ --- dws/dws.py 22194ed1418156ea1bec068eb01ed3cf2b711feb +++ dws/dws.py 22194ed1418156ea1bec068eb01ed3cf2b711feb @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +import urllib2 +import httplib +import urlparse +import sys +import re + +class Connection: + def __init__(self, base, path=""): + self.base = base + self.path = path + (scheme, host, path, query, frag) = urlparse.urlsplit(self.base, "http") + if scheme not in ("http","https"): raise Exception("bad scheme: http,https are supported") + self.host = host + if scheme == "http": + self.conn = httplib.HTTPConnection(host) + else: + self.conn = httplib.HTTPSConnection(host) + self.baseUrl = "/" + path + + def request(self, what, method="GET", postData = "", **kwargs): + theUrl = self.baseUrl + args = {} + args.update(kwargs) + args['r'] = str(what) + if theUrl[-1].find('?') != -1: + d = "&" + else: + d = "?" + for k,v in args.iteritems(): + theUrl += d + k + "=" + str(v) + d = "&" + if postData: + self.verbose( "requesting %s (POST %i bytes)" % (theUrl, len(postData)) ) + else: + self.verbose( "requesting %s" % theUrl ) + if self.conn: + headers = { + 'Connection': 'keep-alive' + } + if postData: + self.conn.request("POST", theUrl, body = postData, headers = headers) + else: + self.conn.request("GET", theUrl, headers = headers) + req = self.conn.getresponse() + try: + return str(req.read()) + except Exception,e: + self.error( "request failed (%s): %s" %(theUrl, str(e)) ) + raise + else: + if postData: + req = urllib2.Request( url=theUrl, data = postData ) + else: + req = urllib2.Request( url=theUrl ) + + try: + f = urllib2.urlopen(req) + r = str(f.read()) + except Exception,e: + self.error( "request failed (%s): %s" %(theUrl, str(e)) ) + raise + return r + + def clear(self): + self.request("clear") + + def list(self): + allFiles = self.request("list_files").split("\n")[0:-1] + plen = len(self.path) + if plen == 0: return allFiles + r = [] + for ri in allFiles: + if ri[0:plen] == self.path: + r.append(ri[plen:]) + return r + + matchSimpleValue = re.compile('^\\s*(\\S+)\\s*:\\s*(\\S(.*))$') + + def stat(self, name): + realName = self.path + name + response = self.request("stat", n = realName) + result = {} + for line in response.split("\n"): + if line == "": continue + m = self.matchSimpleValue.match(line) + if not m: + self.warning( "stat: unknown line: '%s'" % line) + continue + result[m.group(1)] = m.group(2) + if not result: + raise Exception("DWS FATAL ERROR: bad 'stat' response for file '%s'" % name) + return result + + def get(self,name): + realName = self.path + name + return self.request("get", n = realName) + + def getParts(self, name, parts): + realName = self.path + name + partsArg = ",".join( [ "%i:%i" % (off, size) for off, size in parts ] ) + return self.request("getparts", n = realName, parts = partsArg) + + def put(self,name, content): + realName = self.path + name + return self.request("put", n = realName, postData=content) + + def append(self, name, content): + realName = self.path + name + return self.request("append", n = realName, postData=content) + + def error(self, msg): + print >> sys.stderr, "dws: error: %s" % msg + + def verbose(self, msg): + pass + + def warning(self, msg): + print >> sys.stderr, "dws: warning: %s" % msg + ============================================================ --- dws/dws_test.py a519b52ac41e1395c08507b3b0db7932c69e2926 +++ dws/dws_test.py a519b52ac41e1395c08507b3b0db7932c69e2926 @@ -0,0 +1,62 @@ +from dws import * +import unittest + +class TestSequenceFunctions(unittest.TestCase): + def setUp(self): + self.c = Connection("http://localhost/mtdumb/dws/impl/php/mtdumb.php","path/") + self.c.clear() + + def testList(self): + self.c.list() + + def __doTestContent(self,name,content): + self.c.put(name, content) + self.assert_(int(self.c.stat(name)['size']) == len(content)) + list = self.c.list() + self.assert_(name in self.c.list()) + readed = self.c.get(name) + + if readed != content: + self.assert_( name and False ) + + self.c.append(name, content) + self.assert_(int(self.c.stat(name)['size']) == len(content)*2) + + readed2 = self.c.get(name) + + content2 = content + content + + self.assert_( readed2 == content2 ) + + if len(content2) > 1: + lx = len(content) + self.assert_(self.c.getParts(name, [ ( 0, lx*2) ] ) == content2 ) + self.assert_(self.c.getParts(name, [(0, lx)] ) == content ) + self.assert_(self.c.getParts(name, [(lx, lx)] ) == content ) + self.assert_(self.c.getParts(name, [(0,1), (1, lx-1)] ) == content ) + self.assert_(self.c.getParts(name, [(0,lx), (lx, lx)] ) == content2 ) + self.assert_(self.c.getParts(name, [(0,lx-1), (lx-1, 1)] ) == content ) + if len(content) > 3 and len(content) % 2 == 0: + ly = lx/2 + self.assert_(self.c.getParts(name, [(0,ly), (ly, lx), (ly+lx,ly)] ) == content2 ) + self.assert_(self.c.getParts(name, [(0,ly), (ly+lx,ly)] ) == content ) + def testPutEmpty(self): + self.__doTestContent("f1", "") + def testPutZero(self): + self.__doTestContent("f2", "\0") + def testPutGeneric(self): + self.__doTestContent("f3", "jioia") + def testPutWeidCharacters(self): + self.__doTestContent("f4", "ZBiu\0\0xf0") + def testBig1(self): + z = "" + for x in xrange(0,10000): + z += " " + self.__doTestContent("f5", z) + + + def testGet(self): + pass + +if __name__ == '__main__': + unittest.main() ============================================================ --- fs_dws.py 1df065db48b6c12048b949302ccde699294997b5 +++ fs_dws.py 1df065db48b6c12048b949302ccde699294997b5 @@ -0,0 +1,119 @@ +# we need paramiko for sftp protocol support +import fs +import os.path +import posixpath +import base64 +import dws.dws +import sys +import StringIO + +class DWSReadableFS(fs.ReadableFS): + def __init__(self, hostspec): + pathIndex = hostspec.rfind(":") + if pathIndex == -1: + self.path = "" + self.hostspec = hostspec + else: + self.path = hostspec[pathIndex+1:] + self.hostspec = hostspec[:pathIndex] + self.conn = dws.Connection(self.hostspec,self.path) + self.version = 0 + + def list(self): + return self.conn.list() + + def get(self, name): + if name not in self.list(): raise IOError("dws: not found: %s" % name) + content = self.conn.get(name) + return content + + def put(self, name, content): + return self.conn.put(name, content) + + def open_read(self, filename): + content = self.get(filename) + return StringIO.StringIO(content) + + def fetch(self, filenames): + files = {} + list = self.list() + for fn in filenames: + try: + if fn not in list: raise IOError("not found: %s" % fn) + files[fn] = self.conn.get(fn) + except IOError,e: + files[fn] = None + return files + + def _real_fetch_bytes(self, filename, bytes): + fc = self.conn.getParts(filename, bytes) + resultOffset = 0 + for offset, length in bytes: + yield ((offset, length), fc[resultOffset:resultOffset+length]) + resultOffset += length + +class AppendableFileFake: + def __init__(self, conn,name): + self.conn = conn + self.name = name + self.content = "" + self._need_flush = False + + def write(self, a): + self.content += a + self._need_flush = True + return len(a) + + def flush(self): + return self._flush() + + def close(self): + self._flush() + return True + + def _flush(self): + if self._need_flush: + self.conn.append(self.name, self.content) + self._need_flush = False + return True + +class DWSWriteableFS(DWSReadableFS, fs.WriteableFS): + def open_append(self, filename): + return AppendableFileFake( self.conn, filename ) + + def size(self, filename): + try: + return int(self.conn.stat(filename)['size']) + except Exception,e: + return 0 + + def _exists(self, full_fn): + try: + return full_fn in self.list() + except IOError: + return False + return True + + def put(self, filenames): + for fn, data in filenames.iteritems(): + self.conn.put(fn, data) + + def rollback_interrupted_puts(self, filenames): + pass + + def mkdir(self, filename): + try: + pass + except IOError: + return 0 + return 1 + + def rmdir(self, filename): + try: + pass + except IOError: + pass + + def ensure_dir(self, absdir=None): + pass + ============================================================ --- fs.py 736da00ad98cd9ef52574d9740e1374085fb2881 +++ fs.py a59535fb36d66b7d4f0392a2be44e132240031f4 @@ -13,7 +13,12 @@ def readable_fs_for_url(url, **kwargs): (scheme, host, path, query, frag) = urlparse.urlsplit(url, "file") if scheme == "file": - assert not host + # win32 c:/ garbage hack + if host: + if len(host)>1 and host[0].isalpha() and host[1] == ':': + path = host + path + else: + assert not "host in local file" return LocalReadableFS(path) elif scheme in ("http", "https", "ftp"): import fs_read_httpftp @@ -21,17 +26,28 @@ elif scheme == "sftp": import fs_sftp return fs_sftp.SFTPReadableFS(host, path, **kwargs) + elif scheme == "dws": + import fs_dws + return fs_dws.DWSReadableFS(url.replace("dws://","http://")) else: raise BadURL, url def writeable_fs_for_url(url, **kwargs): (scheme, host, path, query, frag) = urlparse.urlsplit(url, "file") if scheme == "file": - assert not host + # win32 c:/ garbage hack + if host: + if len(host)>1 and host[0].isalpha() and host[1] == ':': + path = host + path + else: + assert not "host in local file" return LocalWriteableFs(path) elif scheme == "sftp": import fs_sftp return fs_sftp.SFTPWriteableFS(host, path, **kwargs) + elif scheme == "dws": + import fs_dws + return fs_dws.DWSWriteableFS(url.replace("dws://","http://")) else: raise BadURL, url @@ -173,7 +189,10 @@ tmph = open(tmpname, "wb") tmph.write(data) tmph.close() - os.rename(tmpname, self._fname(fn)) + realFn = self._fname(fn) + if os.access(realFn, os.R_OK): + os.remove(realFn) + os.rename(tmpname, realFn) def rollback_interrupted_puts(self, filenames): # we have atomic put