From 8d6e3f5390cfe81fd74a3d7b0a9529d834d4cf5e Mon Sep 17 00:00:00 2001 From: friendica Date: Sun, 15 Feb 2015 16:29:12 -0800 Subject: add zotsh to util --- util/zotsh/easywebdav/LICENSE | 5 + util/zotsh/easywebdav/__init__.py | 5 + util/zotsh/easywebdav/__init__.pyc | Bin 0 -> 455 bytes util/zotsh/easywebdav/__version__.py | 1 + util/zotsh/easywebdav/__version__.pyc | Bin 0 -> 173 bytes util/zotsh/easywebdav/client.py | 202 ++++++++++++++++++++++++++++++++++ util/zotsh/easywebdav/client.pyc | Bin 0 -> 8846 bytes 7 files changed, 213 insertions(+) create mode 100644 util/zotsh/easywebdav/LICENSE create mode 100644 util/zotsh/easywebdav/__init__.py create mode 100644 util/zotsh/easywebdav/__init__.pyc create mode 100644 util/zotsh/easywebdav/__version__.py create mode 100644 util/zotsh/easywebdav/__version__.pyc create mode 100644 util/zotsh/easywebdav/client.py create mode 100644 util/zotsh/easywebdav/client.pyc (limited to 'util/zotsh/easywebdav') diff --git a/util/zotsh/easywebdav/LICENSE b/util/zotsh/easywebdav/LICENSE new file mode 100644 index 000000000..4f24108f8 --- /dev/null +++ b/util/zotsh/easywebdav/LICENSE @@ -0,0 +1,5 @@ +Copyright (c) 2012 year, Amnon Grossman + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/util/zotsh/easywebdav/__init__.py b/util/zotsh/easywebdav/__init__.py new file mode 100644 index 000000000..3bc2cad2a --- /dev/null +++ b/util/zotsh/easywebdav/__init__.py @@ -0,0 +1,5 @@ +from .client import * + +def connect(*args, **kwargs): + """connect(host, port=0, auth=None, username=None, password=None, protocol='http', path="/")""" + return Client(*args, **kwargs) diff --git a/util/zotsh/easywebdav/__init__.pyc b/util/zotsh/easywebdav/__init__.pyc new file mode 100644 index 000000000..b609b06c1 Binary files /dev/null and b/util/zotsh/easywebdav/__init__.pyc differ diff --git a/util/zotsh/easywebdav/__version__.py b/util/zotsh/easywebdav/__version__.py new file mode 100644 index 000000000..d293e3a30 --- /dev/null +++ b/util/zotsh/easywebdav/__version__.py @@ -0,0 +1 @@ +__version__ = "1.2.0" diff --git a/util/zotsh/easywebdav/__version__.pyc b/util/zotsh/easywebdav/__version__.pyc new file mode 100644 index 000000000..5da5a28ec Binary files /dev/null and b/util/zotsh/easywebdav/__version__.pyc differ diff --git a/util/zotsh/easywebdav/client.py b/util/zotsh/easywebdav/client.py new file mode 100644 index 000000000..da7bf325a --- /dev/null +++ b/util/zotsh/easywebdav/client.py @@ -0,0 +1,202 @@ +import requests +import platform +from numbers import Number +import xml.etree.cElementTree as xml +from collections import namedtuple + +py_majversion, py_minversion, py_revversion = platform.python_version_tuple() + +if py_majversion == '2': + from httplib import responses as HTTP_CODES + from urlparse import urlparse +else: + from http.client import responses as HTTP_CODES + from urllib.parse import urlparse + +DOWNLOAD_CHUNK_SIZE_BYTES = 1 * 1024 * 1024 + +class WebdavException(Exception): + pass + +class ConnectionFailed(WebdavException): + pass + + +def codestr(code): + return HTTP_CODES.get(code, 'UNKNOWN') + + +File = namedtuple('File', ['name', 'size', 'mtime', 'ctime', 'contenttype']) + + +def prop(elem, name, default=None): + child = elem.find('.//{DAV:}' + name) + return default if child is None else child.text + + +def elem2file(elem): + return File( + prop(elem, 'href'), + int(prop(elem, 'getcontentlength', 0)), + prop(elem, 'getlastmodified', ''), + prop(elem, 'creationdate', ''), + prop(elem, 'getcontenttype', ''), + ) + + +class OperationFailed(WebdavException): + _OPERATIONS = dict( + HEAD = "get header", + GET = "download", + PUT = "upload", + DELETE = "delete", + MKCOL = "create directory", + PROPFIND = "list directory", + ) + + def __init__(self, method, path, expected_code, actual_code): + self.method = method + self.path = path + self.expected_code = expected_code + self.actual_code = actual_code + operation_name = self._OPERATIONS[method] + self.reason = 'Failed to {operation_name} "{path}"'.format(**locals()) + expected_codes = (expected_code,) if isinstance(expected_code, Number) else expected_code + expected_codes_str = ", ".join('{0} {1}'.format(code, codestr(code)) for code in expected_codes) + actual_code_str = codestr(actual_code) + msg = '''\ +{self.reason}. + Operation : {method} {path} + Expected code : {expected_codes_str} + Actual code : {actual_code} {actual_code_str}'''.format(**locals()) + super(OperationFailed, self).__init__(msg) + +class Client(object): + def __init__(self, host, port=0, auth=None, username=None, password=None, + protocol='http', verify_ssl=True, path=None, cert=None, session=None): + if not port: + port = 443 if protocol == 'https' else 80 + self.baseurl = '{0}://{1}:{2}'.format(protocol, host, port) + if path: + self.baseurl = '{0}/{1}'.format(self.baseurl, path) + self.cwd = '/' + if session is None: + self.session = requests.session() + else: + self.session = session + self.session.verify = verify_ssl + self.session.stream = True + + if cert: + self.session.cert = cert + + if auth: + self.session.auth = auth + elif username and password: + self.session.auth = (username, password) + + def _send(self, method, path, expected_code, **kwargs): + url = self._get_url(path).strip(".") + #~ print self.session + #~ print self.session.verify + #~ print self.session.params + #~ print self.session.cookies + response = self.session.request(method, url, allow_redirects=False, **kwargs) + #~ print response.request.method + #~ print response.request.url + if isinstance(expected_code, Number) and response.status_code != expected_code \ + or not isinstance(expected_code, Number) and response.status_code not in expected_code: + raise OperationFailed(method, path, expected_code, response.status_code) + return response + + def _get_url(self, path): + path = str(path).strip() + if path.startswith('/'): + return self.baseurl + path + return "".join((self.baseurl, self.cwd, path)) + + def cd(self, path): + path = path.strip() + if not path: + return + stripped_path = '/'.join(part for part in path.split('/') if part) + '/' + + if stripped_path == '/': + self.cwd = stripped_path + elif path.startswith('/'): + self.cwd = '/' + stripped_path + elif stripped_path == "./": + return + elif stripped_path == "../": + self.cwd ='/'.join( self.cwd.split('/')[:-2] ) + '/' + else: + self.cwd += stripped_path + + def mkdir(self, path, safe=False): + expected_codes = 201 if not safe else (201, 301, 405) + self._send('MKCOL', path, expected_codes) + + def mkdirs(self, path): + dirs = [d for d in path.split('/') if d] + if not dirs: + return + if path.startswith('/'): + dirs[0] = '/' + dirs[0] + old_cwd = self.cwd + try: + for dir in dirs: + try: + self.mkdir(dir, safe=True) + except Exception as e: + if e.actual_code == 409: + raise + finally: + self.cd(dir) + finally: + self.cd(old_cwd) + + def rmdir(self, path, safe=False): + path = str(path).rstrip('/') + '/' + expected_codes = 204 if not safe else (204, 404) + self._send('DELETE', path, expected_codes) + + def delete(self, path): + self._send('DELETE', path, 204) + + def upload(self, local_path_or_fileobj, remote_path): + if isinstance(local_path_or_fileobj, basestring): + with open(local_path_or_fileobj, 'rb') as f: + self._upload(f, remote_path) + else: + self._upload(local_path_or_fileobj, remote_path) + + def _upload(self, fileobj, remote_path): + self._send('PUT', remote_path, (200, 201, 204), data=fileobj) + + def download(self, remote_path, local_path_or_fileobj): + response = self._send('GET', remote_path, 200, stream=True) + if isinstance(local_path_or_fileobj, basestring): + with open(local_path_or_fileobj, 'wb') as f: + self._download(f, response) + else: + self._download(local_path_or_fileobj, response) + + def _download(self, fileobj, response): + for chunk in response.iter_content(DOWNLOAD_CHUNK_SIZE_BYTES): + fileobj.write(chunk) + + def ls(self, remote_path='.'): + headers = {'Depth': '1'} + response = self._send('PROPFIND', remote_path, (207, 301), headers=headers) + + # Redirect + if response.status_code == 301: + url = urlparse(response.headers['location']) + return self.ls(url.path) + + tree = xml.fromstring(response.content) + return [elem2file(elem) for elem in tree.findall('{DAV:}response')] + + def exists(self, remote_path): + response = self._send('HEAD', remote_path, (200, 301, 404)) + return True if response.status_code != 404 else False diff --git a/util/zotsh/easywebdav/client.pyc b/util/zotsh/easywebdav/client.pyc new file mode 100644 index 000000000..4c42d2603 Binary files /dev/null and b/util/zotsh/easywebdav/client.pyc differ -- cgit v1.2.3