aboutsummaryrefslogtreecommitdiffstats
path: root/util/zotsh/easywebdav
diff options
context:
space:
mode:
Diffstat (limited to 'util/zotsh/easywebdav')
-rw-r--r--util/zotsh/easywebdav/LICENSE5
-rw-r--r--util/zotsh/easywebdav/__init__.py5
-rw-r--r--util/zotsh/easywebdav/__init__.pycbin0 -> 455 bytes
-rw-r--r--util/zotsh/easywebdav/__version__.py1
-rw-r--r--util/zotsh/easywebdav/__version__.pycbin0 -> 173 bytes
-rw-r--r--util/zotsh/easywebdav/client.py202
-rw-r--r--util/zotsh/easywebdav/client.pycbin0 -> 8846 bytes
7 files changed, 213 insertions, 0 deletions
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
--- /dev/null
+++ b/util/zotsh/easywebdav/__init__.pyc
Binary files 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
--- /dev/null
+++ b/util/zotsh/easywebdav/__version__.pyc
Binary files 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
--- /dev/null
+++ b/util/zotsh/easywebdav/client.pyc
Binary files differ