Passed
Pull Request — master (#44)
by Matěj
01:09
created

WebDAV.list_folders()   A

Complexity

Conditions 5

Size

Total Lines 51
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 51
rs 9.0333
c 0
b 0
f 0
cc 5
nop 5

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
import re
3
import os
4
import pathlib
5
6
import xml.etree.ElementTree as ET
7
8
from nextcloud.base import WithRequester
9
10
11
class WebDAV(WithRequester):
12
13
    API_URL = "/remote.php/dav/files"
14
15
    def __init__(self, *args, **kwargs):
16
        super(WebDAV, self).__init__(*args)
17
        self.json_output = kwargs.get('json_output')
18
19
    def list_folders(self, uid, path=None, depth=1, all_properties=False):
20
        """
21
        Get path files list with files properties for given user, with given depth
22
23
        Args:
24
            uid (str): uid of user
25
            path (str/None): files path
26
            depth (int): depth of listing files (directories content for example)
27
            all_properties (bool): list all available file properties in Nextcloud
28
29
        Returns:
30
            list of dicts if json_output
31
            list of File objects if not json_output
32
        """
33
        if all_properties:
34
            data = """<?xml version="1.0"?>
35
                <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"
36
                            xmlns:nc="http://nextcloud.org/ns">
37
                  <d:prop>
38
                        <d:getlastmodified />
39
                        <d:getetag />
40
                        <d:getcontenttype />
41
                        <d:resourcetype />
42
                        <oc:fileid />
43
                        <oc:permissions />
44
                        <oc:size />
45
                        <d:getcontentlength />
46
                        <nc:has-preview />
47
                        <oc:favorite />
48
                        <oc:comments-unread />
49
                        <oc:owner-display-name />
50
                        <oc:share-types />
51
                  </d:prop>
52
                </d:propfind>
53
            """
54
        else:
55
            data = None
56
        additional_url = uid
57
        if path:
58
            additional_url = "{}/{}".format(additional_url, path)
59
        resp = self.requester.propfind(additional_url=additional_url,
60
                                       headers={"Depth": str(depth)},
61
                                       data=data)
62
        if not resp.is_ok:
63
            resp.data = None
64
            return resp
65
        response_data = resp.data
66
        response_xml_data = ET.fromstring(response_data)
67
        files_data = [File(single_file) for single_file in response_xml_data]
68
        resp.data = files_data if not self.json_output else [each.as_dict() for each in files_data]
69
        return resp
70
71
    def download_file(self, uid, path):
72
        """
73
        Download file of given user by path
74
        File will be saved to working directory
75
        path argument must be valid file path
76
77
        Exception will be raised if:
78
            * path doesn't exist,
79
            * path is a directory, or if
80
            * file with same name already exists in working directory
81
82
        Args:
83
            uid (str): uid of user
84
            path (str): file path
85
86
        Returns:
87
            None
88
        """
89
        additional_url = "/".join([uid, path])
90
        filename = path.split('/')[-1] if '/' in path else path
91
        file_data = self.list_folders(uid=uid, path=path, depth=0)
92
        if not file_data:
93
            raise ValueError("Given path doesn't exist")
94
        file_resource_type = (file_data.data[0].get('resource_type')
95
                              if self.json_output
96
                              else file_data.data[0].resource_type)
97
        if file_resource_type == File.COLLECTION_RESOURCE_TYPE:
98
            raise ValueError("This is a collection, please specify file path")
99
        if filename in os.listdir('./'):
100
            raise ValueError("File with such name already exists in this directory")
101
        res = self.requester.download(additional_url)
102
        with open(filename, 'wb') as f:
103
            f.write(res.data)
104
105
    def upload_file(self, uid, local_filepath, remote_filepath):
106
        """
107
        Upload file to Nextcloud storage
108
109
        Args:
110
            uid (str): uid of user
111
            local_filepath (str): path to file on local storage
112
            remote_filepath (str): path where to upload file on Nextcloud storage
113
        """
114
        with open(local_filepath, 'rb') as f:
115
            file_contents = f.read()
116
        return self.upload_file_contents(uid, file_contents, remote_filepath)
117
118
    def upload_file_contents(self, uid, file_contents, remote_filepath):
119
        """
120
        Upload file to Nextcloud storage
121
122
        Args:
123
            uid (str): uid of user
124
            file_contents (bytes): Bytes the file to be uploaded consists of
125
            remote_filepath (str): path where to upload file on Nextcloud storage
126
        """
127
        additional_url = "/".join([uid, remote_filepath])
128
        return self.requester.put(additional_url, data=file_contents)
129
130
    def create_folder(self, uid, folder_path):
131
        """
132
        Create folder on Nextcloud storage
133
134
        Args:
135
            uid (str): uid of user
136
            folder_path (str): folder path
137
        """
138
        return self.requester.make_collection(additional_url="/".join([uid, folder_path]))
139
140
    def assure_folder_exists(self, uid, folder_path):
141
        """
142
        Create folder on Nextcloud storage, don't do anything if the folder already exists.
143
        Args:
144
            uid (str): uid of user
145
            folder_path (str): folder path
146
        Returns:
147
        """
148
        ret = self.create_folder(uid, folder_path)
149
        return True
150
151
    def assure_tree_exists(self, uid, tree_path):
152
        """
153
        Make sure that the folder structure on Nextcloud storage exists
154
        Args:
155
            uid (str): uid of user
156
            folder_path (str): The folder tree
157
        Returns:
158
        """
159
        tree = pathlib.PurePath(tree_path)
160
        parents = [f for f in tree.parents]
161
        for subf in parents[::-1]:
162
            ret = self.assure_folder_exists(uid, subf)
163
        return ret
0 ignored issues
show
introduced by
The variable ret does not seem to be defined in case the for loop on line 161 is not entered. Are you sure this can never be the case?
Loading history...
164
165
    def delete_path(self, uid, path):
166
        """
167
        Delete file or folder with all content of given user by path
168
169
        Args:
170
            uid (str): uid of user
171
            path (str): file or folder path to delete
172
        """
173
        url = "/".join([uid, path])
174
        return self.requester.delete(url=url)
175
176
    def move_path(self, uid, path, destination_path, overwrite=False):
177
        """
178
        Move file or folder to destination
179
180
        Args:
181
            uid (str): uid of user
182
            path (str): file or folder path to move
183
            destionation_path (str): destination where to move
184
            overwrite (bool): allow destination path overriding
185
        """
186
        path_url = "/".join([uid, path])
187
        destination_path_url = "/".join([uid, destination_path])
188
        return self.requester.move(url=path_url,
189
                                   destination=destination_path_url, overwrite=overwrite)
190
191
    def copy_path(self, uid, path, destination_path, overwrite=False):
192
        """
193
        Copy file or folder to destination
194
195
        Args:
196
            uid (str): uid of user
197
            path (str): file or folder path to copy
198
            destionation_path (str): destination where to copy
199
            overwrite (bool): allow destination path overriding
200
        """
201
        path_url = "/".join([uid, path])
202
        destination_path_url = "/".join([uid, destination_path])
203
        return self.requester.copy(url=path_url,
204
                                   destination=destination_path_url, overwrite=overwrite)
205
206
    def set_favorites(self, uid, path):
207
        """
208
        Set files of a user favorite
209
210
        Args:
211
            uid (str): uid of user
212
            path (str): file or folder path to make favorite
213
        """
214
        data = """<?xml version="1.0"?>
215
        <d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
216
          <d:set>
217
                <d:prop>
218
                  <oc:favorite>1</oc:favorite>
219
                </d:prop>
220
          </d:set>
221
        </d:propertyupdate>
222
        """
223
        url = "/".join([uid, path])
224
        return self.requester.proppatch(additional_url=url, data=data)
225
226
    def list_favorites(self, uid, path=""):
227
        """
228
        Set files of a user favorite
229
230
        Args:
231
            uid (str): uid of user
232
            path (str): file or folder path to make favorite
233
        """
234
        data = """<?xml version="1.0"?>
235
        <oc:filter-files xmlns:d="DAV:"
236
                         xmlns:oc="http://owncloud.org/ns"
237
                         xmlns:nc="http://nextcloud.org/ns">
238
                 <oc:filter-rules>
239
                         <oc:favorite>1</oc:favorite>
240
                 </oc:filter-rules>
241
         </oc:filter-files>
242
        """
243
        url = "/".join([uid, path])
244
        res = self.requester.report(additional_url=url, data=data)
245
        if not res.is_ok:
246
            res.data = None
247
            return res
248
        response_xml_data = ET.fromstring(res.data)
249
        files_data = [File(single_file) for single_file in response_xml_data]
250
        res.data = files_data if not self.json_output else [each.as_dict() for each in files_data]
251
        return res
252
253
254
class File(object):
255
256
    SUCCESS_STATUS = 'HTTP/1.1 200 OK'
257
258
    # key is NextCloud property, value is python variable name
259
    FILE_PROPERTIES = {
260
        # d:
261
        "getlastmodified": "last_modified",
262
        "getetag": "etag",
263
        "getcontenttype": "content_type",
264
        "resourcetype": "resource_type",
265
        "getcontentlength": "content_length",
266
        # oc:
267
        "id": "id",
268
        "fileid": "file_id",
269
        "favorite": "favorite",
270
        "comments-href": "comments_href",
271
        "comments-count": "comments_count",
272
        "comments-unread": "comments_unread",
273
        "owner-id": "owner_id",
274
        "owner-display-name": "owner_display_name",
275
        "share-types": "share_types",
276
        "checksums": "check_sums",
277
        "size": "size",
278
        "href": "href",
279
        # nc:
280
        "has-preview": "has_preview",
281
    }
282
    xml_namespaces_map = {
283
        "d": "DAV:",
284
        "oc": "http://owncloud.org/ns",
285
        "nc": "http://nextcloud.org/ns"
286
    }
287
    COLLECTION_RESOURCE_TYPE = 'collection'
288
289
    def __init__(self, xml_data):
290
        self.href = xml_data.find('d:href', self.xml_namespaces_map).text
291
        for propstat in xml_data.iter('{DAV:}propstat'):
292
            if propstat.find('d:status', self.xml_namespaces_map).text != self.SUCCESS_STATUS:
293
                continue
294
            for file_property in propstat.find('d:prop', self.xml_namespaces_map):
295
                file_property_name = re.sub("{.*}", "", file_property.tag)
296
                if file_property_name not in self.FILE_PROPERTIES:
297
                    continue
298
                if file_property_name == 'resourcetype':
299
                    value = self._extract_resource_type(file_property)
300
                else:
301
                    value = file_property.text
302
                setattr(self, self.FILE_PROPERTIES[file_property_name], value)
303
304
    def _extract_resource_type(self, file_property):
305
        file_type = list(file_property)
306
        if file_type:
307
            return re.sub("{.*}", "", file_type[0].tag)
308
        return None
309
310
    def as_dict(self):
311
        return {key: value
312
                for key, value in self.__dict__.items()
313
                if key in self.FILE_PROPERTIES.values()}
314
315
316
class WebDAVStatusCodes(object):
317
    CREATED_CODE = 201
318
    NO_CONTENT_CODE = 204
319
    MULTISTATUS_CODE = 207
320
    ALREADY_EXISTS_CODE = 405
321
    PRECONDITION_FAILED_CODE = 412
322