diff --git a/owncloud/owncloud.py b/owncloud/owncloud.py index 45ecceb..859c712 100644 --- a/owncloud/owncloud.py +++ b/owncloud/owncloud.py @@ -10,13 +10,12 @@ import datetime import time -import urllib -import urlparse +import urllib.request, urllib.parse, urllib.error +import urllib.parse import requests import xml.etree.ElementTree as ET import os - class ResponseError(Exception): def __init__(self, res): # TODO: how to retrieve the error message ? @@ -27,10 +26,8 @@ def __init__(self, res): Exception.__init__(self, "HTTP error: %i" % code) self.status_code = code - class PublicShare(): """Public share information""" - def __init__(self, share_id, target_file, link, token): self.share_id = share_id self.target_file = target_file @@ -39,41 +36,14 @@ def __init__(self, share_id, target_file, link, token): def __str__(self): return 'PublicShare(id=%i,path=%s,link=%s,token=%s)' % \ - (self.share_id, self.target_file, self.link, self.token) - - -class UserShare(): - """User share information""" - - def __init__(self, share_id, share, perms): - self.share_id = share_id - self.share = share - self.perms = perms - - def __str__(self): - return "UserShare(id=%i,path='%s',perms=%s)" % \ - (self.share_id, self.share, self.perms) - - -class GroupShare(): - """Group share information""" - - def __init__(self, share_id, share, perms): - self.share_id = share_id - self.share = share - self.perms = perms - - def __str__(self): - return "GroupShare(id=%i,path='%s',perms=%s)" % \ - (self.share_id, self.share, self.perms) - + (self.share_id, self.target_file, self.link, self.token) class FileInfo(): """File information""" __DATE_FORMAT = '%a, %d %b %Y %H:%M:%S %Z' - def __init__(self, path, file_type='file', attributes=None): + def __init__(self, path, file_type = 'file', attributes = None): self.path = path if path[-1] == '/': path = path[0:-1] @@ -82,26 +52,18 @@ def __init__(self, path, file_type='file', attributes=None): self.attributes = attributes or {} def get_name(self): - """Returns the base name of the file without path + """Returns the base name of the file (without path) :returns: name of the file """ return self.name - def get_path(self): - """Returns the full path to the file without name and without - trailing slash - - :returns: path to the file - """ - return os.path.dirname(self.path) - def get_size(self): """Returns the size of the file :returns: size of the file """ - if self.attributes.has_key('{DAV:}getcontentlength'): + if ('{DAV:}getcontentlength' in self.attributes): return int(self.attributes['{DAV:}getcontentlength']) return None @@ -117,7 +79,7 @@ def get_content_type(self): :returns: file content type """ - if self.attributes.has_key('{DAV:}getcontenttype'): + if '{DAV:}getcontenttype' in self.attributes: return self.attributes['{DAV:}getcontenttype'] if self.is_dir(): @@ -132,9 +94,9 @@ def get_last_modified(self): :rtype: datetime object """ return datetime.datetime.strptime( - self.attributes['{DAV:}getlastmodified'], - self.__DATE_FORMAT - ) + self.attributes['{DAV:}getlastmodified'], + self.__DATE_FORMAT + ) def is_dir(self): """Returns whether the file info is a directory @@ -145,50 +107,33 @@ def is_dir(self): def __str__(self): return 'File(path=%s,file_type=%s,attributes=%s)' % \ - (self.path, self.file_type, self.attributes) + (self.path, self.file_type, self.attributes) def __repr__(self): return self.__str__() - class Client(): """ownCloud client""" OCS_SERVICE_SHARE = 'apps/files_sharing/api/v1' OCS_SERVICE_PRIVATEDATA = 'privatedata' - OCS_SERVICE_CLOUD = 'cloud' - - # constants from lib/public/constants.php - OCS_PERMISSION_READ = 1 - OCS_PERMISSION_UPDATE = 2 - OCS_PERMISSION_CREATE = 4 - OCS_PERMISSION_DELETE = 8 - OCS_PERMISSION_SHARE = 16 - OCS_PERMISSION_ALL = 31 - # constants from lib/public/share.php - OCS_SHARE_TYPE_USER = 0 - OCS_SHARE_TYPE_GROUP = 1 - OCS_SHARE_TYPE_LINK = 3 def __init__(self, url, **kwargs): """Instantiates a client :param url: URL of the target ownCloud instance :param verify_certs: True (default) to verify SSL certificates, False otherwise - :param single_session: True to use a single session for every call - (default, recommended), False to reauthenticate every call (use with ownCloud 5) :param debug: set to True to print debugging messages to stdout, defaults to False """ if not url[-1] == '/': - url += '/' + url = url + '/' self.url = url self.__session = None self.__debug = kwargs.get('debug', False) self.__verify_certs = kwargs.get('verify_certs', True) - self.__single_session = kwargs.get('single_session', True) - url_components = urlparse.urlparse(url) + url_components = urllib.parse.urlparse(url) self.__davpath = url_components.path + 'remote.php/webdav' self.__webdav_url = url + 'remote.php/webdav' @@ -205,11 +150,11 @@ def login(self, user_id, password): self.__session.verify = self.__verify_certs self.__session.auth = (user_id, password) # TODO: use another path to prevent that the server renders the file list page - res = self.__session.get(self.url + 'index.php') + res = self.__session.get(self.url) if res.status_code == 200: - if self.__single_session: - # Keep the same session, no need to re-auth every call - self.__session.auth = None + # Remove auth, no need to re-auth every call + # so sending the auth every time for now + self.__session.auth = None return self.__session.close() self.__session = None @@ -269,7 +214,7 @@ def get_file_contents(self, path): return res.content return False - def get_file(self, remote_path, local_file=None): + def get_file(self, remote_path, local_file = None): """Downloads a remote file :param remote_path: path to the remote file @@ -280,16 +225,16 @@ def get_file(self, remote_path, local_file=None): """ remote_path = self.__normalize_path(remote_path) res = self.__session.get( - self.__webdav_url + remote_path, - stream=True - ) + self.__webdav_url + remote_path, + stream = True + ) if res.status_code == 200: - if local_file is None: + if local_file == None: # use downloaded file name from Content-Disposition # local_file = res.headers['content-disposition'] local_file = os.path.basename(remote_path) - file_handle = open(local_file, 'wb', 8192) + file_handle = open(local_file, 'w', 8192) for chunk in res.iter_content(8192): file_handle.write(chunk) file_handle.close() @@ -306,15 +251,18 @@ def get_directory_as_zip(self, remote_path, local_file): """ remote_path = self.__normalize_path(remote_path) url = self.url + 'index.php/apps/files/ajax/download.php?dir=' \ - + urllib.quote(remote_path) - res = self.__session.get(url, stream=True) + + urllib.parse.quote(remote_path) + res = self.__session.get( + url, + stream = True + ) if res.status_code == 200: - if local_file is None: + if local_file == None: # use downloaded file name from Content-Disposition # targetFile = res.headers['content-disposition'] local_file = os.path.basename(remote_path) - file_handle = open(local_file, 'wb', 8192) + file_handle = open(local_file, 'w', 8192) for chunk in res.iter_content(8192): file_handle.write(chunk) file_handle.close() @@ -329,7 +277,7 @@ def put_file_contents(self, remote_path, data): :returns: True if the operation succeeded, False otherwise :raises: ResponseError in case an HTTP error status was returned """ - return self.__make_dav_request('PUT', remote_path, data=data) + return self.__make_dav_request('PUT', remote_path, data = data) def put_file(self, remote_path, local_source_file, **kwargs): """Upload a file @@ -346,10 +294,10 @@ def put_file(self, remote_path, local_source_file, **kwargs): """ if kwargs.get('chunked', True): return self.__put_file_chunked( - remote_path, - local_source_file, - **kwargs - ) + remote_path, + local_source_file, + **kwargs + ) stat_result = os.stat(local_source_file) @@ -359,13 +307,13 @@ def put_file(self, remote_path, local_source_file, **kwargs): if remote_path[-1] == '/': remote_path += os.path.basename(local_source_file) - file_handle = open(local_source_file, 'rb', 8192) + file_handle = open(local_source_file, 'r', 8192) res = self.__make_dav_request( - 'PUT', - remote_path, - data=file_handle, - headers=headers - ) + 'PUT', + remote_path, + data = file_handle, + headers = headers + ) file_handle.close() return res @@ -390,13 +338,17 @@ def put_directory(self, target_path, local_directory, **kwargs): # gather files to upload for path, _, files in os.walk(local_directory): gathered_files.append( - (path, basedir + path[len(local_directory):], files) - ) + (path, basedir + path[len(local_directory):], files) + ) for path, remote_path, files in gathered_files: self.mkdir(target_path + remote_path + '/') for name in files: - if not self.put_file(target_path + remote_path + '/', path + '/' + name, **kwargs): + if not self.put_file( + target_path + remote_path + '/', + path + '/' + name, + **kwargs + ): return False return True @@ -421,45 +373,45 @@ def __put_file_chunked(self, remote_path, local_source_file, **kwargs): stat_result = os.stat(local_source_file) - file_handle = open(local_source_file, 'rb', 8192) + file_handle = open(local_source_file, 'r', 8192) file_handle.seek(0, os.SEEK_END) size = file_handle.tell() file_handle.seek(0) headers = {} if kwargs.get('keep_mtime', True): - headers['X-OC-MTIME'] = stat_result.st_mtime + headers['X-OC-MTIME'] = bytes(str(stat_result.st_mtime),'utf-8') if size == 0: return self.__make_dav_request( - 'PUT', - remote_path, - data='', - headers=headers - ) + 'PUT', + remote_path, + data = '', + headers = headers + ) - chunk_count = size / chunk_size - - if size % chunk_size > 0: - chunk_count += 1 + chunk_count = int(size / chunk_size) if chunk_count > 1: headers['OC-CHUNKED'] = 1 + if size % chunk_size > 0: + chunk_count += 1 + for chunk_index in range(0, chunk_count): data = file_handle.read(chunk_size) if chunk_count > 1: chunk_name = '%s-chunking-%s-%i-%i' % \ - (remote_path, transfer_id, chunk_count, chunk_index) + (remote_path, transfer_id, chunk_count, chunk_index) else: chunk_name = remote_path if not self.__make_dav_request( 'PUT', chunk_name, - data=data, - headers=headers - ): + data = data, + headers = headers + ): result = False break @@ -474,7 +426,7 @@ def mkdir(self, path): :raises: ResponseError in case an HTTP error status was returned """ if not path[-1] == '/': - path += '/' + path = path + '/' return self.__make_dav_request('MKCOL', path) def delete(self, path): @@ -486,88 +438,6 @@ def delete(self, path): """ return self.__make_dav_request('DELETE', path) - def delete_share(self, share_id): - """Unshares a file or directory - - :param share_id: Share ID (int) - :returns: True if the operation succeeded, False otherwise - :raises: ResponseError in case an HTTP error status was returned - """ - if not isinstance(share_id, int): - return False - - res = self.__make_ocs_request( - 'DELETE', - self.OCS_SERVICE_SHARE, - 'shares/' + str(share_id) - ) - if res.status_code == 200: - return res - raise ResponseError(res) - - def update_share(self, share_id, **kwargs): - """Updates a given share - - :param share_id: (int) Share ID - :param perms: (int) update permissions (see share_file_with_user() below) - :param password: (string) updated password for public link Share - :param public_upload: (boolean) enable/disable public upload for public shares - :returns: True if the operation succeeded, False otherwise - :raises: ResponseError in case an HTTP error status was returned - """ - - perms = kwargs.get('perms', None) - password = kwargs.get('password', None) - public_upload = kwargs.get('public_upload', None) - if (isinstance(perms, int)) and (perms > self.OCS_PERMISSION_ALL): - perms = None - if not (perms or password or (public_upload is not None)): - return False - if not isinstance(share_id, int): - return False - - data = {} - if perms: - data['permissions'] = perms - if isinstance(password, basestring): - data['password'] = password - if (public_upload is not None) and (isinstance(public_upload, bool)): - data['publicUpload'] = str(public_upload).lower() - - res = self.__make_ocs_request( - 'PUT', - self.OCS_SERVICE_SHARE, - 'shares/' + str(share_id), - data=data - ) - if res.status_code == 200: - return True - raise ResponseError(res) - - def move(self, remote_path_source, remote_path_target): - """Deletes a remote file or directory - - :param remote_path_source: source file or folder to move - :param remote_path_target: target file to which to move - the source file. A target directory can also be specified - instead by appending a "/" - :returns: True if the operation succeeded, False otherwise - :raises: ResponseError in case an HTTP error status was returned - """ - if remote_path_target[-1] == '/': - remote_path_target += os.path.basename(remote_path_source) - - remote_path_source = self.__normalize_path(remote_path_source) - headers = { - 'Destination': self.__webdav_url + urllib.quote(self.__encode_string(remote_path_target)) - } - - return self.__make_dav_request( - 'MOVE', - remote_path_source, - headers=headers - ) - def share_file_with_link(self, path): """Shares a remote file with link @@ -577,19 +447,16 @@ def share_file_with_link(self, path): :raises: ResponseError in case an HTTP error status was returned """ path = self.__normalize_path(path) - post_data = { - 'shareType': self.OCS_SHARE_TYPE_LINK, - 'path': self.__encode_string(path) - } + post_data = {'shareType': 3, 'path': path} res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_SHARE, - 'shares', - data=post_data - ) + 'POST', + self.OCS_SERVICE_SHARE, + 'shares', + data = post_data + ) if res.status_code == 200: - tree = ET.fromstring(res.content) + tree = ET.fromstring(res.text) self.__check_ocs_status(tree) data_el = tree.find('data') return PublicShare( @@ -600,378 +467,7 @@ def share_file_with_link(self, path): ) raise ResponseError(res) - def is_shared(self, path): - """Checks whether a path is already shared - - :param path: path to the share to be checked - :returns: True if the path is already shared, else False - :raises: ResponseError in case an HTTP error status was returned - """ - # make sure that the path exist - if not, raise ResponseError - self.file_info(path) - try: - result = self.get_shares(path) - if result: - return len(result) > 0 - except ResponseError as e: - if e.status_code != 404: - raise e - return False - return False - - def get_shares(self, path='', **kwargs): - """Returns array of shares - - :param path: path to the share to be checked - :param reshares: (optional, boolean) returns not only the shares from - the current user but all shares from the given file (default: False) - :param subfiles: (optional, boolean) returns all shares within - a folder, given that path defines a folder (default: False) - :returns: array of shares or empty array if the operation failed - :raises: ResponseError in case an HTTP error status was returned - """ - if not (isinstance(path, basestring)): - return None - - data = 'shares' - if path != '': - data += '?' - path = self.__encode_string(self.__normalize_path(path)) - args = {'path': path} - reshares = kwargs.get('reshares', False) - if isinstance(reshares, bool) and reshares: - args['reshares'] = reshares - subfiles = kwargs.get('subfiles', False) - if isinstance(subfiles, bool) and subfiles: - args['subfiles'] = subfiles - data += urllib.urlencode(args) - - res = self.__make_ocs_request( - 'GET', - self.OCS_SERVICE_SHARE, - data - ) - if res.status_code == 200: - tree = ET.fromstring(res.content) - self.__check_ocs_status(tree) - shares = [] - for element in tree.find('data').iter('element'): - share_attr = {} - for child in element: - key = child.tag - value = child.text - share_attr[key] = value - shares.append(share_attr) - if len(shares) > 0: - return shares - raise ResponseError(res) - - def create_user(self, user_name, initial_password): - """Create a new user with an initial password via provisioning API. - It is not an error, if the user already existed before. - If you get back an error 999, then the provisioning API is not enabled. - - :param user_name: name of user to be created - :param initial_password: password for user being created - :returns: True on success - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_CLOUD, - 'users', - data={'password': initial_password, 'userid': user_name} - ) - - # We get 200 when the user was just created. - if res.status_code == 200: - # We get an inner 102 although we have an outer 200 when the user already exists. - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree, [100, 102]) - return True - - raise ResponseError(res) - - def delete_user(self, user_name): - """Deletes a user via provisioning API. - If you get back an error 999, then the provisioning API is not enabled. - - :param user_name: name of user to be deleted - :returns: True on success - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'DELETE', - self.OCS_SERVICE_CLOUD, - 'users/' + user_name - ) - - # We get 200 when the user was deleted. - if res.status_code == 200: - return True - - raise ResponseError(res) - - def user_exists(self, user_name): - """Checks a user via provisioning API. - If you get back an error 999, then the provisioning API is not enabled. - - :param user_name: name of user to be checked - :returns: True if user found - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'GET', - self.OCS_SERVICE_CLOUD, - 'users?search=' + user_name - ) - - if res.status_code == 200: - tree = ET.fromstring(res.text) - code_el = tree.find('data/users/element') - - if code_el is not None and code_el.text == user_name: - return True - else: - return False - - raise ResponseError(res) - - def add_user_to_group(self, user_name, group_name): - """Adds a user to a group. - - :param user_name: name of user to be added - :param group_name: name of group user is to be added to - :returns: True if user added - :raises: ResponseError in case an HTTP error status was returned - - """ - - res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_CLOUD, - 'users/' + user_name + '/groups', - data={'groupid': group_name} - ) - - if res.status_code == 200: - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree, [100, 102]) - return True - - raise ResponseError(res) - - def remove_user_from_group(self, user_name, group_name): - """Removes a user from a group. - - :param user_name: name of user to be removed - :param group_name: name of group user is to be removed from - :returns: True if user removed - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'DELETE', - self.OCS_SERVICE_CLOUD, - 'users/' + user_name + '/groups', - data={'groupid': group_name} - ) - - if res.status_code == 200: - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree, [100, 102]) - return True - - raise ResponseError(res) - - def share_file_with_user(self, path, user, **kwargs): - """Shares a remote file with specified user - - :param path: path to the remote file to share - :param user: name of the user whom we want to share a file/folder - :param perms (optional): permissions of the shared object - defaults to read only (1) - http://doc.owncloud.org/server/6.0/admin_manual/sharing_api/index.html - :returns: instance of :class:`UserShare` with the share info - or False if the operation failed - :raises: ResponseError in case an HTTP error status was returned - """ - perms = kwargs.get('perms', self.OCS_PERMISSION_READ) - if (((not isinstance(perms, int)) or (perms > self.OCS_PERMISSION_ALL)) - or ((not isinstance(user, basestring)) or (user == ''))): - return False - - path = self.__normalize_path(path) - post_data = { - 'shareType': self.OCS_SHARE_TYPE_USER, - 'shareWith': user, - 'path': self.__encode_string(path), - 'permissions': perms - } - - res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_SHARE, - 'shares', - data=post_data - ) - - if self.__debug: - print( - 'OCS share_file request for file %s with permissions %i returned: %i' % (path, perms, res.status_code)) - if res.status_code == 200: - tree = ET.fromstring(res.content) - self.__check_ocs_status(tree) - data_el = tree.find('data') - return UserShare( - int(data_el.find('id').text), - path, - perms - ) - raise ResponseError(res) - - def create_group(self, group_name): - """Create a new group via provisioning API. - If you get back an error 999, then the provisioning API is not enabled. - - :param group_name: name of group to be created - :returns: True if group created - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_CLOUD, - 'groups', - data={'groupid': group_name} - ) - - # We get 200 when the group was just created. - if res.status_code == 200: - # We get an inner 102 although we have an outer 200 when the group already exists. - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree, [100, 102]) - return True - - raise ResponseError(res) - - def delete_group(self, group_name): - """Delete a group via provisioning API. - If you get back an error 999, then the provisioning API is not enabled. - - :param group_name: name of group to be deleted - :returns: True if group deleted - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'DELETE', - self.OCS_SERVICE_CLOUD, - 'groups/' + group_name - ) - - # We get 200 when the group was just deleted. - if res.status_code == 200: - return True - - raise ResponseError(res) - - def group_exists(self, group_name): - """Checks a group via provisioning API. - If you get back an error 999, then the provisioning API is not enabled. - - :param group_name: name of group to be checked - :returns: True if group exists - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request( - 'GET', - self.OCS_SERVICE_CLOUD, - 'groups?search=' + group_name - ) - - if res.status_code == 200: - tree = ET.fromstring(res.text) - code_el = tree.find('data/groups/element') - - if code_el is not None and code_el.text == group_name: - return True - else: - return False - - raise ResponseError(res) - - def share_file_with_group(self, path, group, **kwargs): - """Shares a remote file with specified group - - :param path: path to the remote file to share - :param user: name of the user whom we want to share a file/folder - :param perms (optional): permissions of the shared object - defaults to read only (1) - http://doc.owncloud.org/server/6.0/admin_manual/sharing_api/index.html - :returns: instance of :class:`GroupShare` with the share info - or False if the operation failed - :raises: ResponseError in case an HTTP error status was returned - """ - perms = kwargs.get('perms', self.OCS_PERMISSION_READ) - if (((not isinstance(perms, int)) or (perms > self.OCS_PERMISSION_ALL)) - or ((not isinstance(group, basestring)) or (group == ''))): - return False - - path = self.__normalize_path(path) - post_data = {'shareType': self.OCS_SHARE_TYPE_GROUP, 'shareWith': group, 'path': path, 'permissions': perms} - - res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_SHARE, - 'shares', - data=post_data - ) - if res.status_code == 200: - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree) - data_el = tree.find('data') - return GroupShare( - int(data_el.find('id').text), - path, - perms - ) - raise ResponseError(res) - - def get_config(self): - """Returns ownCloud config information - :returns: array of tuples (key, value) for each information - e.g. [('version', '1.7'), ('website', 'ownCloud'), ('host', 'cloud.example.com'), - ('contact', ''), ('ssl', 'false')] - :raises: ResponseError in case an HTTP error status was returned - """ - path = 'config' - res = self.__make_ocs_request( - 'GET', - '', - path - ) - if res.status_code == 200: - tree = ET.fromstring(res.content) - self.__check_ocs_status(tree) - values = [] - - element = tree.find('data') - if element is not None: - keys = ['version', 'website', 'host', 'contact', 'ssl'] - for key in keys: - text = element.find(key).text or '' - values.append(text) - return zip(keys, values) - else: - return None - raise ResponseError(res) - - def get_attribute(self, app=None, key=None): + def get_attribute(self, app = None, key = None): """Returns an application attribute :param app: application id @@ -982,32 +478,32 @@ def get_attribute(self, app=None, key=None): :raises: ResponseError in case an HTTP error status was returned """ path = 'getattribute' - if app is not None: - path += '/' + urllib.quote(app, '') - if key is not None: - path += '/' + urllib.quote(self.__encode_string(key), '') + if app != None: + path += '/' + urllib.parse.quote(app) + if key != None: + path += '/' + urllib.parse.quote(key) res = self.__make_ocs_request( - 'GET', - self.OCS_SERVICE_PRIVATEDATA, - path - ) + 'GET', + self.OCS_SERVICE_PRIVATEDATA, + path + ) if res.status_code == 200: - tree = ET.fromstring(res.content) + tree = ET.fromstring(res.text) self.__check_ocs_status(tree) values = [] for element in tree.find('data').iter('element'): app_text = element.find('app').text key_text = element.find('key').text value_text = element.find('value').text or '' - if key is None: - if app is None: + if key == None: + if app == None: values.append((app_text, key_text, value_text)) else: values.append((key_text, value_text)) else: return value_text - if len(values) == 0 and key is not None: + if len(values) == 0 and key != None: return None return values raise ResponseError(res) @@ -1021,15 +517,15 @@ def set_attribute(self, app, key, value): :returns: True if the operation succeeded, False otherwise :raises: ResponseError in case an HTTP error status was returned """ - path = 'setattribute/' + urllib.quote(app, '') + '/' + urllib.quote(self.__encode_string(key), '') + path = 'setattribute/' + urllib.parse.quote(app) + '/' + urllib.parse.quote(key) res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_PRIVATEDATA, - path, - data={'value': self.__encode_string(value)} - ) + 'POST', + self.OCS_SERVICE_PRIVATEDATA, + path, + data = {'value': value} + ) if res.status_code == 200: - tree = ET.fromstring(res.content) + tree = ET.fromstring(res.text) self.__check_ocs_status(tree) return True raise ResponseError(res) @@ -1042,73 +538,18 @@ def delete_attribute(self, app, key): :returns: True if the operation succeeded, False otherwise :raises: ResponseError in case an HTTP error status was returned """ - path = 'deleteattribute/' + urllib.quote(app, '') + '/' + urllib.quote(self.__encode_string(key), '') + path = 'deleteattribute/' + urllib.parse.quote(app) + '/' + urllib.parse.quote(key) res = self.__make_ocs_request( - 'POST', - self.OCS_SERVICE_PRIVATEDATA, - path - ) + 'POST', + self.OCS_SERVICE_PRIVATEDATA, + path + ) if res.status_code == 200: - tree = ET.fromstring(res.content) + tree = ET.fromstring(res.text) self.__check_ocs_status(tree) return True raise ResponseError(res) - def get_apps(self): - """ List all enabled apps through the provisioning api. - - :returns: a dict of apps, with values True/False, representing the enabled state. - :raises: ResponseError in case an HTTP error status was returned - """ - ena_apps = {} - - res = self.__make_ocs_request('GET', self.OCS_SERVICE_CLOUD, 'apps') - if res.status_code != 200: - raise ResponseError(res) - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree) - # filesactivity ... - for el in tree.findall('data/apps/element'): - ena_apps[el.text] = False - - res = self.__make_ocs_request('GET', self.OCS_SERVICE_CLOUD, 'apps?filter=enabled') - if res.status_code != 200: - raise ResponseError(res) - tree = ET.fromstring(res.text) - self.__check_ocs_status(tree) - for el in tree.findall('data/apps/element'): - ena_apps[el.text] = True - - return ena_apps - - def enable_app(self, appname): - """Enable an app through provisioning_api - - :param appname: Name of app to be enabled - :returns: True if the operation succeeded, False otherwise - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request('POST', self.OCS_SERVICE_CLOUD, 'apps/' + appname) - if res.status_code == 200: - return True - - raise ResponseError(res) - - def disable_app(self, appname): - """Disable an app through provisioning_api - - :param appname: Name of app to be disabled - :returns: True if the operation succeeded, False otherwise - :raises: ResponseError in case an HTTP error status was returned - - """ - res = self.__make_ocs_request('DELETE', self.OCS_SERVICE_CLOUD, 'apps/' + appname) - if res.status_code == 200: - return True - - raise ResponseError(res) - @staticmethod def __normalize_path(path): """Makes sure the path starts with a "/" @@ -1122,35 +563,15 @@ def __normalize_path(path): return path @staticmethod - def __encode_string(s): - """Encodes a unicode instance to utf-8. If a str is passed it will - simply be returned - - :param s: str or unicode to encode - :returns: encoded output as str - """ - if isinstance(s, unicode): - return s.encode('utf-8') - return s - - @staticmethod - def __check_ocs_status(tree, accepted_codes=[100]): + def __check_ocs_status(tree): """Checks the status code of an OCS request :param tree: response parsed with elementtree - :param accepted_codes: list of statuscodes we consider good. E.g. [100,102] can be used to accept a POST - returning an 'already exists' condition - :raises: ResponseError if the http status is not 200, or the webdav status is not one of the accepted_codes. + :raises: ResponseError if the status is not 200 """ code_el = tree.find('meta/statuscode') - if code_el is not None and int(code_el.text) not in accepted_codes: - r = requests.Response() - msg_el = tree.find('meta/message') - if msg_el is None: - msg_el = tree # fallback to the entire ocs response, if we find no message. - r._content = ET.tostring(msg_el) - r.status_code = int(code_el.text) - raise ResponseError(r) + if code_el is not None and code_el.text != '100': + raise ResponseError(int(code_el.text)) def __make_ocs_request(self, method, service, action, **kwargs): """Makes a OCS API request @@ -1161,16 +582,13 @@ def __make_ocs_request(self, method, service, action, **kwargs): :param \*\*kwargs: optional arguments that ``requests.Request.request`` accepts :returns :class:`requests.Response` instance """ - slash = '' - if service: - slash = '/' - path = 'ocs/v1.php/' + service + slash + action + path = 'ocs/v1.php/' + service + '/' + action if self.__debug: - print('OCS request: %s %s' % (method, self.url + path)) + print(('OCS request: ' +method+' '+ self.url + path)) attributes = kwargs.copy() - if not attributes.has_key('headers'): + if 'headers' not in attributes: attributes['headers'] = {} attributes['headers']['OCS-APIREQUEST'] = 'true' @@ -1189,18 +607,12 @@ def __make_dav_request(self, method, path, **kwargs): if it didn't """ if self.__debug: - print('DAV request: %s %s' % (method, path)) - if kwargs.get('headers'): - print('Headers: ', kwargs.get('headers')) + print(('DAV request: ' +method+' '+ path)); path = self.__normalize_path(path) - res = self.__session.request( - method, - self.__webdav_url + urllib.quote(self.__encode_string(path)), - **kwargs - ) + res = self.__session.request(method, self.__webdav_url + path, **kwargs) if self.__debug: - print('DAV status: %i' % res.status_code) + print(('DAV status: ' + res.status_code)); if res.status_code == 200 or res.status_code == 207: return self.__parse_dav_response(res) if res.status_code == 204 or res.status_code == 201: @@ -1215,7 +627,7 @@ def __parse_dav_response(self, res): the operation did not succeed """ if res.status_code == 207: - tree = ET.fromstring(res.content) + tree = ET.fromstring(res.text) items = [] for child in tree: items.append(self.__parse_dav_element(child)) @@ -1225,12 +637,12 @@ def __parse_dav_response(self, res): def __parse_dav_element(self, dav_response): """Parses a single DAV element - :param dav_response: DAV response + :param el: ElementTree element containing a single DAV response :returns :class:`FileInfo` """ - href = urllib.unquote( - self.__strip_dav_path(dav_response.find('{DAV:}href').text) - ).decode('utf-8') + href = urllib.parse.unquote( + self.__strip_dav_path(dav_response.find('{DAV:}href').text) + ) file_type = 'file' if href[-1] == '/': file_type = 'dir' @@ -1249,6 +661,7 @@ def __strip_dav_path(self, path): :param path: path containing the remote DAV path "remote.php/webdav" :returns: path stripped of the remote DAV path """ - if path.startswith(self.__davpath): + if (path.startswith(self.__davpath)): return path[len(self.__davpath):] return path +