commit 7c3b06a44c10fb7c3d1e8c5572b039848110475c Author: Sven Slootweg Date: Mon Nov 5 15:48:04 2012 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/pytahoe/__init__.py b/pytahoe/__init__.py new file mode 100644 index 0000000..b35ca08 --- /dev/null +++ b/pytahoe/__init__.py @@ -0,0 +1,237 @@ +import urllib, urllib2, json, time, os, re, requests + +try: + from fs.contrib.tahoelafs import TahoeLAFS +except ImportError: + fs_available = False +else: + try: + from fs.expose import fuse, dokan + except ImportError: + fs_available = false + else: + fs_available = True + + +class FilesystemException(Exception): + pass + +class ObjectException(Exception): + pass + +class UploadException(Exception): + pass + +class Filesystem: + def __init__(self, url="http://localhost:3456/"): + if url.strip() == "": + raise FilesystemException("You must specify a Tahoe-LAFS WAPI URL.") + + # Ensure there is no trailing slash in the WAPI URL + if url[-1:] == "/": + url = url[:-1] + + try: + result = urllib.urlopen("%s/statistics?t=json" % url).read() + except urllib.URLError: + raise FilesystemException("The provided WAPI URL is not reachable.") + + try: + data = json.loads(result) + except ValueError: + raise FilesystemException("The provided WAPI URL is either not running Tahoe-LAFS, or running a version that is too old.") + + if "node.uptime" not in data['stats']: + raise FilesystemException("The provided WAPI URL is either not running Tahoe-LAFS, or running a version that is too old.") + + self.url = url + self.start_date = time.time() - data['stats']['node.uptime'] + + def standard_request(self, destination, data=None): + return self.do_json_request("/%s?t=json" % destination, data) + + def do_request(self, destination, data=None): + if data is not None: + post_data = urllib.urlencode(data) + + try: + if data is None: + return urllib.urlopen("%s%s" % (self.url, destination)).read() + else: + return urllib.urlopen("%s%s" % (self.url, destination), post_data).read() + except urllib.URLError: + raise FilesystemException("The WAPI could not be reached.") + + def do_json_request(self, destination, data=None): + results = self.do_request(destination, data) + + try: + return json.loads(results) + except ValueError: + raise FilesystemException("Corrupted data was received from the WAPI") + + def do_put_request(self, destination, data, headers): + request = urllib2.Request("%s%s" % (self.url, destination), data, headers) + return urllib2.urlopen(request).read() + + def Directory(self, uri, data=None): + if data == None: + data = self.standard_request("uri/%s" % urllib.quote(uri)) + + return Directory(self, uri, data) + + def File(self, uri, data=None): + if data == None: + data = self.standard_request("uri/%s" % urllib.quote(uri)) + + return File(self, uri, data) + + def Object(self, uri, data=None): + if data == None: + data = self.standard_request("uri/%s" % urllib.quote(uri)) + + if "filenode" in data: + return self.File(uri, data) + elif "dirnode" in data: + return self.Directory(uri, data) + else: + raise ObjectException("The specified object does not appear to exist.") + + def create_directory(self): + result = self.do_request("/uri?t=mkdir", {}) + return self.Directory(result) + + def _sanitize_filename(self, name): + return re.sub("[^a-zA-Z0-9 $_.+!*'(),-]+", "", name) + + def upload(self, filedata): + if type(filedata) is str: + try: + filedata = open(filedata, "rb") + except IOError: + raise UploadException("The given path is not a valid file path.") + elif type(filedata) is not file: + raise UploadException("Cannot upload the file because the given file is not a valid file object or path.") + + file_uri = requests.put("%s/uri" % self.url, data=filedata.read()).text + return self.File(file_uri) + +class Directory: + mutable = False + writeable = False + children = {} + + def __init__(self, filesystem, uri, data=None): + self.filesystem = filesystem + self.uri = uri + + if data == None: + data = self.filesystem.standard_request("uri/%s" % urllib.quote(uri)) + + if "dirnode" in data: + details = data[1] + + self.mutable = details['mutable'] + self.readcap = details['ro_uri'] + + if "verify_uri" in details: + self.verifycap = details['verify_uri'] + + if "rw_uri" in details: + self.writable = True + self.writecap = details['rw_uri'] + + for child_name, child_data in details['children'].iteritems(): + if "rw_uri" in child_data[1]: + child_uri = child_data[1]['rw_uri'] + else: + child_uri = child_data[1]['ro_uri'] + + self.children[child_name] = self.filesystem.Object(child_uri, child_data) + elif "unknown" in data: + raise ObjectException("The specified object does not appear to exist.") + else: + raise ObjectException("The specified object is not a directory.") + + def __repr__(self): + if self.writable == True: + state = "writable" + else: + state = "read-only" + + return "" % (self.uri, state) + + def mount(self, mountpoint): + global fs_available + + if fs_available == False: + raise DependencyException("Could not mount the directory because the 'fs' module was not found.") + + fs = TahoeLAFS(self.uri) + + try: + return fuse.mount(fs, mountpoint) + except OSError: + try: + return dokan.mount(fs, mountpoint) + except OSError: + raise DependencyException("Could not mount the directory because both the FUSE and dokan libraries are unavailable.") + + def upload(self, filedata, filename=None): + if filename is None: + if type(filedata) is str: + filename = self._sanitize_filename(os.path.basename(filedata)) + elif type(filedata) is file: + if type(filedata.name) is str: + filename = self._sanitize_filename(filedata.name) + else: + # We could not determine the filename for the input... let's generate something. + filename = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(15)) + else: + raise UploadException("The given file is not a valid string or file object.") + +class File: + mutable = False + writable = False + + def __init__(self, filesystem, uri, data=None): + self.filesystem = filesystem + self.uri = uri + + if data == None: + data = self.filesystem.standard_request("uri/%s" % urllib.quote(uri)) + + if "filenode" in data: + details = data[1] + + self.mutable = details['mutable'] + self.readcap = details['ro_uri'] + self.size = details['size'] + + if "metadata" in details and "tahoe" in details['metadata']: + self.creation_date = details['metadata']['tahoe']['linkcrtime'] + self.modification_date = details['metadata']['tahoe']['linkmotime'] + + if "verify_uri" in details: + self.verifycap = details['verify_uri'] + + if "rw_uri" in details: + self.writable = True + self.writecap = details['rw_uri'] + elif "unknown" in data: + raise ObjectException("The specified object does not appear to exist.") + else: + raise ObjectException("The specified object is not a file.") + + def __repr__(self): + if self.writable == True: + state = "writable" + else: + state = "read-only" + + if self.mutable == True: + mutable = "mutable" + else: + mutable = "immutable" + + return "" % (self.uri, mutable, state) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a328c6e --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup(name='pytahoe', + version='1.0', + description='Python module for working with the Tahoe-LAFS filesystem.', + author='Sven Slootweg', + author_email='pytahoe@cryto.net', + url='http://cryto.net/pytahoe', + packages=['pytahoe'], + provides=['pytahoe'] + ) diff --git a/test.py b/test.py new file mode 100644 index 0000000..b3e9d01 --- /dev/null +++ b/test.py @@ -0,0 +1,10 @@ +import pytahoe + +fs = pytahoe.Filesystem("http://localhost:3456/") +#tdir = fs.Directory("URI:DIR2:jjw572jvowd473fo2n7rw6uiai:hloglrouhwgjpubcpyq5nrb4ezyijdfiboe3hquadgzjrmkdikxa") +#print tdir +#for name, item in tdir.children.iteritems(): +# print "%s: %s" % (name, item) + +#print fs.create_directory().mount("test") +print fs.upload("test.py")