diff --git a/tests/res/test.txt b/tests/test.txt similarity index 100% rename from tests/res/test.txt rename to tests/test.txt diff --git a/tests/test_client.py b/tests/test_client.py index ca98aae..7b08ce8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,9 +1,18 @@ +import os.path +import shutil +from io import BytesIO, StringIO +from os import path from unittest import TestCase from webdav2.client import Client class ClientTestCase(TestCase): + remote_path_file = 'test_dir/test.txt' + remote_path_dir = 'test_dir' + local_path_file = 'test.txt' + local_path_dir = u'res/test_dir' + def setUp(self): options = { 'webdav_hostname': 'https://webdav.yandex.ru', @@ -11,16 +20,121 @@ class ClientTestCase(TestCase): 'webdav_password': 'Qwerty123!' } self.client = Client(options) + if path.exists(path=self.local_path_dir): + shutil.rmtree(path=self.local_path_dir) def test_list(self): file_list = self.client.list() self.assertIsNotNone(file_list, 'List of files should not be None') self.assertGreater(file_list.__len__(), 0, 'Expected that amount of files more then 0') + def test_free(self): + self.assertGreater(self.client.free(), 0, 'Expected that free space on WebDAV server is more then 0 bytes') + + def test_check(self): + self.assertTrue(self.client.check(), 'Expected that root directory is exist') + + def test_mkdir(self): + if self.client.check(remote_path=self.remote_path_dir): + self.client.clean(remote_path=self.remote_path_dir) + self.client.mkdir(remote_path=self.remote_path_dir) + self.assertTrue(self.client.check(remote_path=self.remote_path_dir), 'Expected the directory is created.') + + def test_download_to(self): + buff = BytesIO() + self.client.download_to(buff=buff, remote_path=self.remote_path_file) + self.assertEquals(buff.getvalue(), 'test content for testing of webdav client') + + def test_download(self): + self._prepare_for_downloading() + + self.client.download(local_path=self.local_path_dir, remote_path=self.remote_path_dir) + + self.assertTrue(path.exists(self.local_path_dir), 'Expected the directory is downloaded.') + self.assertTrue(path.isdir(self.local_path_dir), 'Expected this is a directory.') + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected the file is downloaded') + self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected this is a file') + + def test_download_sync(self): + self._prepare_for_downloading() + os.mkdir(self.local_path_dir) + + def callback(): + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected the file is downloaded') + self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected this is a file') + + self.client.download_sync(local_path=self.local_path_dir + os.path.sep + self.local_path_file, + remote_path=self.remote_path_file, callback=callback) + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected the file has already been downloaded') + + def test_download_async(self): + self._prepare_for_downloading() + os.mkdir(self.local_path_dir) + + def callback(): + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected the file is downloaded') + self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected this is a file') + + self.client.download_async(local_path=self.local_path_dir + os.path.sep + self.local_path_file, + remote_path=self.remote_path_file, callback=callback) + self.assertFalse(path.exists(self.local_path_dir + os.path.sep + self.local_path_file), + 'Expected the file has not been downloaded yet') + + def test_upload_from(self): + self._prepare_for_uploading() + buff = StringIO(u'test content for testing of webdav client') + self.client.upload_from(buff=buff, remote_path=self.remote_path_file) + self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.') + self.test_download_to() + + def test_upload(self): + self._prepare_for_uploading() + self.client.upload(remote_path=self.remote_path_file, local_path=self.local_path_dir) + self.assertTrue(self.client.check(self.remote_path_dir), 'Expected the directory is created.') + self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.') + def test_upload_file(self): - remote_path = 'test.txt' - local_path = './res/test.txt' - if self.client.check(remote_path=remote_path): - self.client.clean(remote_path=remote_path) - self.client.upload_file(remote_path=remote_path, local_path=local_path) - self.assertTrue(self.client.check(remote_path=remote_path)) + self._prepare_for_uploading() + self.client.upload_file(remote_path=self.remote_path_file, local_path=self.local_path_file) + self.assertTrue(self.client.check(remote_path=self.remote_path_file), 'Expected the file is uploaded.') + + def test_upload_sync(self): + self._prepare_for_uploading() + + def callback(): + self.assertTrue(self.client.check(self.remote_path_dir), 'Expected the directory is created.') + self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.') + + self.client.upload(remote_path=self.remote_path_file, local_path=self.local_path_dir) + + def test_upload_async(self): + self._prepare_for_uploading() + + def callback(): + self.assertTrue(self.client.check(self.remote_path_dir), 'Expected the directory is created.') + self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.') + + self.client.upload(remote_path=self.remote_path_file, local_path=self.local_path_dir) + + def _prepare_for_downloading(self): + if not self.client.check(remote_path=self.remote_path_dir): + self.client.mkdir(remote_path=self.remote_path_dir) + if not self.client.check(remote_path=self.remote_path_file): + self.client.upload_file(remote_path=self.remote_path_file, local_path=self.local_path_file) + + def _prepare_for_uploading(self): + if self.client.check(remote_path=self.remote_path_file): + self.client.clean(remote_path=self.remote_path_file) + if not self.client.check(remote_path=self.remote_path_dir): + self.client.mkdir(remote_path=self.remote_path_dir) + if not path.exists(path=self.local_path_dir): + os.mkdir(self.local_path_dir) + if not path.exists(path=self.local_path_dir + os.sep + self.local_path_file): + shutil.copy(src=self.local_path_file, dst=self.local_path_dir + os.sep + self.local_path_file) diff --git a/webdav2/client.py b/webdav2/client.py index f35d077..451002e 100644 --- a/webdav2/client.py +++ b/webdav2/client.py @@ -4,11 +4,12 @@ import functools import logging import os import shutil +import threading from io import BytesIO from re import sub -import requests import lxml.etree as etree +import requests from webdav2.connection import * from webdav2.exceptions import * @@ -19,12 +20,16 @@ try: except ImportError: from urllib import unquote -__version__ = "0.1" +__version__ = "0.2" log = logging.getLogger(__name__) def listdir(directory): + """Returns list of nested files and directories for local directory by path + :param directory: absolute or relative path to local directory + :return: list nested of file or directory names + """ file_names = list() for filename in os.listdir(directory): file_path = os.path.join(directory, filename) @@ -34,12 +39,18 @@ def listdir(directory): return file_names -def get_options(type, from_options): +def get_options(option_type, from_options): + """Extract options for specified option type from all options + :param option_type: the object of specified type of options + :param from_options: all options dictionary + :return: the dictionary of options for specified type, each option can be filled by value from all options + dictionary or blank in case the option for specified type is not exist in all options dictionary + """ _options = dict() - for key in type.keys: - key_with_prefix = "{prefix}{key}".format(prefix=type.prefix, key=key) + for key in option_type.keys: + key_with_prefix = "{prefix}{key}".format(prefix=option_type.prefix, key=key) if key not in from_options and key_with_prefix not in from_options: _options[key] = "" elif key in from_options: @@ -60,14 +71,20 @@ def wrap_connection_error(fn): raise NotConnection(self.webdav.hostname) else: return res + return _wrapper class Client(object): - + """The client for WebDAV servers provides an ability to control files on remote WebDAV server. + """ + # path to root directory of WebDAV root = '/' + + # Max size of file for uploading large_size = 2 * 1024 * 1024 * 1024 + # HTTP headers for different actions http_header = { 'list': ["Accept: */*", "Depth: 1"], 'free': ["Accept: */*", "Depth: 0", "Content-Type: text/xml"], @@ -81,13 +98,17 @@ class Client(object): 'set_metadata': ["Accept: */*", "Depth: 1", "Content-Type: application/x-www-form-urlencoded"] } - def get_header(self, method): + def get_header(self, action): + """Returns HTTP headers of specified WebDAV actions - if method in Client.http_header: + :param action: the identifier of action + :return: the dictionary of headers for specified action + """ + if action in Client.http_header: try: - header = Client.http_header[method].copy() + header = Client.http_header[action].copy() except AttributeError: - header = Client.http_header[method][:] + header = Client.http_header[action][:] else: header = list() @@ -96,7 +117,44 @@ class Client(object): header.append(webdav_token) return dict([map(lambda s: s.strip(), i.split(':')) for i in header]) + def get_url(self, path): + """Generates url by uri path. + + :param path: uri path. + :return: the url string. + """ + url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': path} + return "{hostname}{root}{path}".format(**url) + + def execute_request(self, action, path, data=None): + """Generate request to WebDAV server for specified action and path and execute it. + + :param action: the action for WebDAV server which should be executed. + :param path: the path to resource for action + :param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, + or file-like object to send in the body of the :class:`Request`. + :return: HTTP response of request. + """ + response = requests.request( + method=Client.requests[action], + url=self.get_url(path), + auth=(self.webdav.login, self.webdav.password), + headers=self.get_header(action), + data=data + ) + if response.status_code == 507: + raise NotEnoughSpace() + if response.status_code >= 400: + raise ResponseErrorCode(url=self.get_url(path), code=response.status_code, message=response.content) + return response + + # mapping of actions to WebDAV methods requests = { + 'download': "GET", + 'download_to': "GET", + 'download_file': "GET", + 'upload_from': "PUT", + 'upload_file': "PUT", 'copy': "COPY", 'move': "MOVE", 'mkdir': "MKCOL", @@ -117,25 +175,59 @@ class Client(object): } def __init__(self, options): + """Constructor of WebDAV client - webdav_options = get_options(type=WebDAVSettings, from_options=options) - proxy_options = get_options(type=ProxySettings, from_options=options) + :param options: the dictionary of connection options to WebDAV can include proxy server options. + WebDev settings: + `webdav_hostname`: url for WebDAV server should contain protocol and ip address or domain name. + Example: `https://webdav.server.com`. + `webdav_login`: (optional) login name for WebDAV server can be empty in case using of token auth. + `webdav_password`: (optional) password for WebDAV server can be empty in case using of token auth. + `webdav_token': (optional) token for WebDAV server can be empty in case using of login/password auth. + `webdav_root`: (optional) root directory of WebDAV server. Defaults is `/`. + `webdav_cert_path`: (optional) path to certificate. + `webdav_key_path`: (optional) path to private key. + `webdav_recv_speed`: (optional) rate limit data download speed in Bytes per second. + Defaults to unlimited speed. + `webdav_send_speed`: (optional) rate limit data upload speed in Bytes per second. + Defaults to unlimited speed. + `webdav_verbose`: (optional) set verbose mode on.off. By default verbose mode is off. + Proxy settings (optional): + `proxy_hostname`: url to proxy server should contain protocol and ip address or domain name and if needed + port. Example: `https://proxy.server.com:8383`. + `proxy_login`: login name for proxy server. + `proxy_password`: password for proxy server. + """ + webdav_options = get_options(option_type=WebDAVSettings, from_options=options) + proxy_options = get_options(option_type=ProxySettings, from_options=options) self.webdav = WebDAVSettings(webdav_options) self.proxy = ProxySettings(proxy_options) self.default_options = {} def valid(self): - return True if self.webdav.valid() and self.proxy.valid() else False + """Validates of WebDAV and proxy settings. + :return: True in case settings are valid and False otherwise. + """ + return True if self.webdav.valid() and self.proxy.valid() else False @wrap_connection_error def list(self, remote_path=root): + """Returns list of nested files and directories for remote WebDAV directory by path. - def parse(response): + :param remote_path: path to remote directory. + :return: list of nested file or directory names. + """ + def parse(list_response): + """Parses of response from WebDAV server and extract file and directory names. + + :param list_response: HTTP response from WebDAV server for getting list of files by remote path. + :return: list of extracted file or directory names. + """ try: - response_str = response.content + response_str = list_response.content tree = etree.fromstring(response_str) hrees = [unquote(hree.text) for hree in tree.findall(".//{DAV:}href")] return [Urn(hree) for hree in hrees] @@ -148,21 +240,7 @@ class Client(object): if not self.check(directory_urn.path()): raise RemoteResourceNotFound(directory_urn.path()) - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': directory_urn.quote()} - options = { - 'URL': "{hostname}{root}{path}".format(**url), - 'CUSTOMREQUEST': Client.requests['list'], - 'HTTPHEADER': self.get_header('list'), - 'NOBODY': 0 - } - - response = requests.request( - options["CUSTOMREQUEST"], - options["URL"], - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('list') - ) - + response = self.execute_request(action='list', path=directory_urn.quote()) urns = parse(response) path = "{root}{path}".format(root=self.webdav.root, path=directory_urn.path()) @@ -170,11 +248,19 @@ class Client(object): @wrap_connection_error def free(self): + """Returns an amount of free space on remote WebDAV server. - def parse(response): + :return: an amount of free space in bytes. + """ + def parse(free_response): + """Parses of response from WebDAV server and extract na amount of free space. + + :param free_response: HTTP response from WebDAV server for getting free space. + :return: an amount of free space in bytes. + """ try: - response_str = response.content + response_str = free_response.content tree = etree.fromstring(response_str) node = tree.find('.//{DAV:}quota-available-bytes') if node is not None: @@ -187,7 +273,6 @@ class Client(object): return str() def data(): - root = etree.Element("propfind", xmlns="DAV:") prop = etree.SubElement(root, "prop") etree.SubElement(prop, "quota-available-bytes") @@ -197,40 +282,22 @@ class Client(object): tree.write(buff) return buff.getvalue() - options = { - 'CUSTOMREQUEST': Client.requests['free'], - 'HTTPHEADER': self.get_header('free'), - 'POSTFIELDS': data(), - 'NOBODY': 0 - } - - response = requests.request( - options["CUSTOMREQUEST"], - options["URL"], - data=data(), - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('free') - ) + response = self.execute_request(action='free', path='', data=data()) return parse(response) @wrap_connection_error def check(self, remote_path=root): + """Checks an existence of remote resource on WebDAV server by remote path + + :param remote_path: (optional) path to resource on WebDAV server. Defaults is root directory of WebDAV. + :return: True if resource is exist or False otherwise + """ urn = Urn(remote_path) - response = BytesIO() - - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()} - options = { - 'URL': "{hostname}{root}{path}".format(**url), - 'CUSTOMREQUEST': Client.requests['check'], - } - - response = requests.request( - options["CUSTOMREQUEST"], - options["URL"], - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('check') - ) + try: + response = self.execute_request(action='check', path=urn.quote()) + except ResponseErrorCode: + return False if int(response.status_code) == 200: return True @@ -238,28 +305,28 @@ class Client(object): @wrap_connection_error def mkdir(self, remote_path): + """Makes new directory on WebDAV server. + :param remote_path: path to directory + :return: True if request executed with code 200 and False otherwise. + + """ directory_urn = Urn(remote_path, directory=True) if not self.check(directory_urn.parent()): raise RemoteParentNotFound(directory_urn.path()) - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': directory_urn.quote()} - options = { - 'URL': "{hostname}{root}{path}".format(**url), - 'CUSTOMREQUEST': Client.requests['mkdir'], - } + response = self.execute_request(action='mkdir', path=directory_urn.quote()) - response = requests.request( - options["CUSTOMREQUEST"], - options["URL"], - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('mkdir') - ) - # TODO: check response status + return response.status_code == 200 @wrap_connection_error def download_to(self, buff, remote_path): + """Downloads file from WebDAV and writes it in buffer. + + :param buff: buffer object for writing of downloaded file content. + :param remote_path: path to file on WebDAV server. + """ urn = Urn(remote_path) if self.is_dir(urn.path()): @@ -268,21 +335,17 @@ class Client(object): if not self.check(urn.path()): raise RemoteResourceNotFound(urn.path()) - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()} - options = { - 'URL': "{hostname}{root}{path}".format(**url), - } + response = self.execute_request(action='download_to', path=urn.quote()) - response = requests.request( - "GET", - options["URL"], - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('download_to') - ) buff.write(response.content) def download(self, remote_path, local_path, progress=None): + """Downloads remote resource from WebDAV and save it in local path. + :param remote_path: the path to remote resource for downloading can be file and directory. + :param local_path: the path to save resource locally. + :param progress: progress function. Not supported now. + """ urn = Urn(remote_path) if self.is_dir(urn.path()): self.download_directory(local_path=local_path, remote_path=remote_path, progress=progress) @@ -290,7 +353,13 @@ class Client(object): self.download_file(local_path=local_path, remote_path=remote_path, progress=progress) def download_directory(self, remote_path, local_path, progress=None): + """Downloads directory and downloads all nested files and directories from remote WebDAV to local. + If there is something on local path it deletes directories and files then creates new. + :param remote_path: the path to directory for downloading form WebDAV server. + :param local_path: the path to local directory for saving downloaded files and directories. + :param progress: Progress function. Not supported now. + """ urn = Urn(remote_path, directory=True) if not self.is_dir(urn.path()): @@ -307,8 +376,13 @@ class Client(object): self.download(local_path=_local_path, remote_path=_remote_path, progress=progress) @wrap_connection_error - def download_file(self, remote_path, local_path): + def download_file(self, remote_path, local_path, progress=None): + """Downloads file from WebDAV server and save it locally. + :param remote_path: the path to remote file for downloading. + :param local_path: the path to save file locally. + :param progress: progress function. Not supported now. + """ urn = Urn(remote_path) if self.is_dir(urn.path()): @@ -321,39 +395,40 @@ class Client(object): raise RemoteResourceNotFound(urn.path()) with open(local_path, 'wb') as local_file: + response = self.execute_request('download_file', urn.quote()) - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()} - options = { - 'URL': "{hostname}{root}{path}".format(**url), - 'HTTPHEADER': self.get_header('download_file'), - 'WRITEDATA': local_file, - 'NOBODY': 0 - } - - response = requests.request( - "GET", - options["URL"], - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('download_file') - ) for block in response.iter_content(1024): local_file.write(block) def download_sync(self, remote_path, local_path, callback=None): + """Downloads remote resources from WebDAV server synchronously. + :param remote_path: the path to remote resource on WebDAV server. Can be file and directory. + :param local_path: the path to save resource locally. + :param callback: the callback which will be invoked when downloading is complete. + """ self.download(local_path=local_path, remote_path=remote_path) if callback: callback() - # def download_async(self, remote_path, local_path, callback=None): + def download_async(self, remote_path, local_path, callback=None): + """Downloads remote resources from WebDAV server asynchronously - # target = (lambda: self.download_sync(local_path=local_path, remote_path=remote_path, callback=callback)) - # threading.Thread(target=target).start() + :param remote_path: the path to remote resource on WebDAV server. Can be file and directory. + :param local_path: the path to save resource locally. + :param callback: the callback which will be invoked when downloading is complete. + """ + target = (lambda: self.download_sync(local_path=local_path, remote_path=remote_path, callback=callback)) + threading.Thread(target=target).start() @wrap_connection_error def upload_from(self, buff, remote_path): + """Uploads file from buffer to remote path on WebDAV server. + :param buff: the buffer with content for file. + :param remote_path: the path to save file remotely on WebDAV server. + """ urn = Urn(remote_path) if urn.is_dir(): @@ -362,36 +437,30 @@ class Client(object): if not self.check(urn.parent()): raise RemoteParentNotFound(urn.path()) - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()} - options = { - 'URL': "{hostname}{root}{path}".format(**url), - } - # import ipdb; ipdb.set_trace(); - #FIXME - if buff.tell() == 0: - data = buff.read() - else: - data = buff - response = requests.request( - 'PUT', - options["URL"], - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('upload_from'), - data=data - ) - if response.status_code == 507: - raise NotEnoughSpace() - log.debug("Response: %s", response) + self.execute_request(action='upload_from', path=urn.quote(), data=buff) def upload(self, remote_path, local_path, progress=None): + """Uploads resource to remote path on WebDAV server. + In case resource is directory it will upload all nested files and directories. + :param remote_path: the path for uploading resources on WebDAV server. Can be file and directory. + :param local_path: the path to local resource for uploading. + :param progress: Progress function. Not supported now. + """ if os.path.isdir(local_path): self.upload_directory(local_path=local_path, remote_path=remote_path, progress=progress) else: self.upload_file(local_path=local_path, remote_path=remote_path) def upload_directory(self, remote_path, local_path, progress=None): + """Uploads directory to remote path on WebDAV server. + In case directory is exist on remote server it will delete it and then upload directory with nested files and + directories. + :param remote_path: the path to directory for uploading on WebDAV server. + :param local_path: the path to local directory for uploading. + :param progress: Progress function. Not supported now. + """ urn = Urn(remote_path, directory=True) if not urn.is_dir(): @@ -414,8 +483,14 @@ class Client(object): self.upload(local_path=_local_path, remote_path=_remote_path, progress=progress) @wrap_connection_error - def upload_file(self, remote_path, local_path): + def upload_file(self, remote_path, local_path, progress=None): + """Uploads file to remote path on WebDAV server. + File should be 2Gb or less. + :param remote_path: the path to uploading file on WebDAV server. + :param local_path: the path to local file for uploading. + :param progress: Progress function. Not supported now. + """ if not os.path.exists(local_path): raise LocalResourceNotFound(local_path) @@ -431,37 +506,44 @@ class Client(object): raise RemoteParentNotFound(urn.path()) with open(local_path, "rb") as local_file: - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()} - file_size = os.path.getsize(local_path) if file_size > self.large_size: - raise ResourceTooBig(path=local_path, size=file_size) + raise ResourceTooBig(path=local_path, size=file_size, max_size=self.large_size) - response = requests.request( - method='PUT', - url="{hostname}{root}{path}".format(**url), - auth=(self.webdav.login, self.webdav.password), - headers=self.get_header('upload_file'), - data=local_file - ) - if response.status_code == 507: - raise NotEnoughSpace() + self.execute_request(action='upload_file', path=urn.quote(), data=local_file) def upload_sync(self, remote_path, local_path, callback=None): + """Uploads resource to remote path on WebDAV server synchronously. + In case resource is directory it will upload all nested files and directories. + :param remote_path: the path for uploading resources on WebDAV server. Can be file and directory. + :param local_path: the path to local resource for uploading. + :param callback: the callback which will be invoked when downloading is complete. + """ self.upload(local_path=local_path, remote_path=remote_path) if callback: callback() - # def upload_async(self, remote_path, local_path, callback=None): + def upload_async(self, remote_path, local_path, callback=None): + """Uploads resource to remote path on WebDAV server asynchronously. + In case resource is directory it will upload all nested files and directories. - # target = (lambda: self.upload_sync(local_path=local_path, remote_path=remote_path, callback=callback)) - # threading.Thread(target=target).start() + :param remote_path: the path for uploading resources on WebDAV server. Can be file and directory. + :param local_path: the path to local resource for uploading. + :param callback: the callback which will be invoked when downloading is complete. + """ + target = (lambda: self.upload_sync(local_path=local_path, remote_path=remote_path, callback=callback)) + threading.Thread(target=target).start() + # TODO refactor code below and write tests for it. @wrap_connection_error def copy(self, remote_path_from, remote_path_to): + """Copies resource from one place to another on WebDAV server. + :param remote_path_from: the path to resource which will be copied, + :param remote_path_to: the path where resource will be copied. + """ def header(remote_path_to): path = Urn(remote_path_to).path() @@ -679,14 +761,12 @@ class Client(object): def get_property(self, remote_path, option): def parse(response, option): - response_str = response.content tree = etree.fromstring(response_str) xpath = "{xpath_prefix}{xpath_exp}".format(xpath_prefix=".//", xpath_exp=option['name']) return tree.findtext(xpath) def data(option): - root = etree.Element("propfind", xmlns="DAV:") prop = etree.SubElement(root, "prop") etree.SubElement(prop, option.get('name', ""), xmlns=option.get('namespace', "")) @@ -724,7 +804,6 @@ class Client(object): def set_property(self, remote_path, option): def data(option): - root_node = etree.Element("propertyupdate", xmlns="DAV:") root_node.set('xmlns:u', option.get('namespace', "")) set_node = etree.SubElement(root_node, "set") @@ -783,7 +862,8 @@ class Client(object): for local_resource_name in listdir(local_directory): local_path = os.path.join(local_directory, local_resource_name) - remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), resource_name=local_resource_name) + remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), + resource_name=local_resource_name) if os.path.isdir(local_path): if not self.check(remote_path=remote_path): @@ -816,7 +896,8 @@ class Client(object): for remote_resource_name in remote_resource_names: local_path = os.path.join(local_directory, remote_resource_name) - remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), resource_name=remote_resource_name) + remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), + resource_name=remote_resource_name) remote_urn = Urn(remote_path) @@ -836,7 +917,6 @@ class Client(object): class Resource(object): - def __init__(self, client, urn): self.client = client self.urn = urn @@ -848,7 +928,6 @@ class Resource(object): return self.client.is_dir(self.urn.path()) def rename(self, new_name): - old_path = self.urn.path() parent_path = self.urn.parent() new_name = Urn(new_name).filename() @@ -858,19 +937,16 @@ class Resource(object): self.urn = Urn(new_path) def move(self, remote_path): - new_urn = Urn(remote_path) self.client.move(remote_path_from=self.urn.path(), remote_path_to=new_urn.path()) self.urn = new_urn def copy(self, remote_path): - urn = Urn(remote_path) self.client.copy(remote_path_from=self.urn.path(), remote_path_to=remote_path) return Resource(self.client, urn) def info(self, params=None): - info = self.client.info(self.urn.path()) if not params: return info diff --git a/webdav2/connection.py b/webdav2/connection.py index fc1b729..b2ad8c9 100644 --- a/webdav2/connection.py +++ b/webdav2/connection.py @@ -1,10 +1,10 @@ +from os.path import exists from webdav2.exceptions import * from webdav2.urn import Urn -from os.path import exists + class ConnectionSettings: - def is_valid(self): pass @@ -17,11 +17,23 @@ class ConnectionSettings: else: return True -class WebDAVSettings(ConnectionSettings): +class WebDAVSettings(ConnectionSettings): ns = "webdav:" prefix = "webdav_" - keys = {'hostname', 'login', 'password', 'token', 'root', 'cert_path', 'key_path', 'recv_speed', 'send_speed', 'verbose'} + keys = {'hostname', 'login', 'password', 'token', 'root', 'cert_path', 'key_path', 'recv_speed', 'send_speed', + 'verbose'} + + hostname = None + login = None + password = None + token = None + root = None + cert_path = None + key_path = None + recv_speed = None + send_speed = None + verbose = None def __init__(self, options): @@ -57,11 +69,14 @@ class WebDAVSettings(ConnectionSettings): class ProxySettings(ConnectionSettings): - ns = "proxy:" prefix = "proxy_" keys = {'hostname', 'login', 'password'} + hostname = None + login = None + password = None + def __init__(self, options): self.options = dict() diff --git a/webdav2/exceptions.py b/webdav2/exceptions.py index 4318d84..7cfb02f 100644 --- a/webdav2/exceptions.py +++ b/webdav2/exceptions.py @@ -79,6 +79,17 @@ class NotConnection(WebDavException): return "Not connection with {hostname}".format(hostname=self.hostname) +class ResponseErrorCode(WebDavException): + def __init__(self, url, code, message): + self.url = url + self.code = code + self.message = message + + def __str__(self): + return "Request to {url} failed with code {code} and message: {message}".format(url=self.url, code=self.code, + message=self.message) + + class NotEnoughSpace(WebDavException): def __init__(self): pass