Completed
Push — master ( a9b8b6...971575 )
by Matěj
12s
created

WebDAV.list_folders()   A

Complexity

Conditions 5

Size

Total Lines 48
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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