Passed
Pull Request — master (#18)
by
unknown
01:03
created

NextCloud.Share.get_shares_from_path()   A

Complexity

Conditions 3

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 20
rs 10
c 0
b 0
f 0
cc 3
nop 4
1
import re
2
import enum
3
import requests
4
import xml.etree.ElementTree as ET
5
import os
6
7
PUBLIC_API_NAME_CLASS_MAP = dict()
8
9
10
class Requester(object):
11
    def __init__(self, endpoint, user, passwd, js=False):
12
        self.query_components = []
13
14
        self.to_json = js
15
16
        self.base_url = endpoint
17
        # GroupFolders.url = endpoint + "/ocs/v2.php/apps/groupfolders/folders"
18
19
        self.h_get = {"OCS-APIRequest": "true"}
20
        self.h_post = {"OCS-APIRequest": "true",
21
                       "Content-Type": "application/x-www-form-urlencoded"}
22
        self.auth_pk = (user, passwd)
23
        self.API_URL = None
24
25
    def rtn(self, resp):
26
        if self.to_json:
27
            return resp.json()
28
        else:
29
            return resp.content.decode("UTF-8")
30
31
    def get(self, url="", params=None):
32
        url = self.get_full_url(url)
33
        res = requests.get(url, auth=self.auth_pk, headers=self.h_get, params=params)
34
        return self.rtn(res)
35
36
    def post(self, url="", data=None):
37
        url = self.get_full_url(url)
38
        res = requests.post(url, auth=self.auth_pk, data=data, headers=self.h_post)
39
        return self.rtn(res)
40
41
    def put(self, url="", data=None):
42
        url = self.get_full_url(url)
43
        res = requests.put(url, auth=self.auth_pk, data=data, headers=self.h_post)
44
        return self.rtn(res)
45
46
    def delete(self, url="", data=None):
47
        url = self.get_full_url(url)
48
        res = requests.delete(url, auth=self.auth_pk, data=data, headers=self.h_post)
49
        return self.rtn(res)
50
51
    def get_full_url(self, additional_url=""):
52
        """
53
        Build full url for request to NextCloud api
54
55
        Construct url from self.base_url, self.API_URL, additional_url (if given), add format=json param if self.json
56
57
        :param additional_url: str
58
            add to url after api_url
59
        :return: str
60
        """
61
        if additional_url and not str(additional_url).startswith("/"):
62
            additional_url = "/{}".format(additional_url)
63
64
        if self.to_json:
65
            self.query_components.append("format=json")
66
67
        ret = "{base_url}{api_url}{additional_url}".format(
68
            base_url=self.base_url, api_url=self.API_URL, additional_url=additional_url)
69
70
        if self.to_json:
71
            ret += "?format=json"
72
        return ret
73
74
75
class WebDAVRequester(Requester):
76
77
    def __init__(self, *args, **kwargs):
78
        super(WebDAVRequester, self).__init__(*args, **kwargs)
79
80
    def propfind(self, additional_url="", headers=None):
81
        url = self.get_full_url(additional_url=additional_url)
82
        res = requests.request('PROPFIND', url, auth=self.auth_pk, headers=headers)
83
        return self.rtn(res)
84
85
    def download(self, url="", params=None):
86
        url = self.get_full_url(url)
87
        res = requests.get(url, auth=self.auth_pk, headers=self.h_get, params=params)
88
        return res
89
90
    def make_collection(self, additional_url=""):
91
        url = self.get_full_url(additional_url=additional_url)
92
        res = requests.request("MKCOL", url=url, auth=self.auth_pk)
93
        return res
94
95
    def move(self, url, destination, overwrite=False):
96
        url = self.get_full_url(additional_url=url)
97
        destionation_url = self.get_full_url(additional_url=destination)
98
        headers = {
99
            "Destination": destionation_url,
100
            "Overwrite": "T" if overwrite else "F"
101
        }
102
        res = requests.request("MOVE", url=url, auth=self.auth_pk, headers=headers)
103
        return res
104
105
    def copy(self, url, destination, overwrite=False):
106
        url = self.get_full_url(additional_url=url)
107
        destionation_url = self.get_full_url(additional_url=destination)
108
        headers = {
109
            "Destination": destionation_url,
110
            "Overwrite": "T" if overwrite else "F"
111
        }
112
        res = requests.request("COPY", url=url, auth=self.auth_pk, headers=headers)
113
        return res
114
115
116
def nextcloud_method(method_to_wrap):
117
    class_name = method_to_wrap.__qualname__.split(".", 1)[0]
118
    PUBLIC_API_NAME_CLASS_MAP[method_to_wrap.__name__] = class_name
119
    return method_to_wrap
120
121
122
class NextCloud(object):
123
124
    def __init__(self, endpoint, user, password, js=False):
125
        self.query_components = []
126
127
        requester = Requester(endpoint, user, password, js)
128
        webdav_requester = WebDAVRequester(endpoint, user, password)
129
130
        self.functionality = {
131
            "Apps": Apps(requester),
132
            "Group": Group(requester),
133
            "GroupFolders": GroupFolders(requester),
134
            "Share": Share(requester),
135
            "User": User(requester),
136
            "FederatedCloudShare": FederatedCloudShare(requester),
137
            "WebDAV": WebDAV(webdav_requester, js=js),
138
        }
139
        for name, location in PUBLIC_API_NAME_CLASS_MAP.items():
140
            setattr(self, name, getattr(self.functionality[location], name))
141
142
143
class WithRequester(object):
144
145
    API_URL = NotImplementedError
146
147
    def __init__(self, requester):
148
        self._requester = requester
149
150
    @property
151
    def requester(self):
152
        """ Get requester instance """
153
        # dynamically set API_URL for requester
154
        self._requester.API_URL = self.API_URL
155
        return self._requester
156
157
158
class GroupFolders(WithRequester):
159
    API_URL = "/apps/groupfolders/folders"
160
161
    @nextcloud_method
162
    def get_group_folders(self):
163
        """
164
        Return a list of call configured folders and their settings
165
166
        Returns:
167
168
        """
169
        return self.requester.get()
170
171
    @nextcloud_method
172
    def get_group_folder(self, fid):
173
        """
174
        Return a specific configured folder and it's settings
175
176
        Args:
177
            fid (int/str): group folder id
178
179
        Returns:
180
181
        """
182
        return self.requester.get(fid)
183
184
    @nextcloud_method
185
    def create_group_folder(self, mountpoint):
186
        """
187
        Create a new group folder
188
189
        Args:
190
            mountpoint (str): name for the new folder
191
192
        Returns:
193
194
        """
195
        return self.requester.post(data={"mountpoint": mountpoint})
196
197
    @nextcloud_method
198
    def delete_group_folder(self, fid):
199
        """
200
        Delete a group folder
201
202
        Args:
203
            fid (int/str): group folder id
204
205
        Returns:
206
207
        """
208
        return self.requester.delete(fid)
209
210
    @nextcloud_method
211
    def grant_access_to_group_folder(self, fid, gid):
212
        """
213
        Give a group access to a folder
214
215
        Args:
216
            fid (int/str): group folder id
217
            gid (str): group to share with id
218
219
        Returns:
220
221
        """
222
        url = "/".join([str(fid), "groups"])
223
        return self.requester.post(url, data={"group": gid})
224
225
    @nextcloud_method
226
    def revoke_access_to_group_folder(self, fid, gid):
227
        """
228
        Remove access from a group to a folder
229
230
        Args:
231
            fid (int/str): group folder id
232
            gid (str): group id
233
234
        Returns:
235
236
        """
237
        url = "/".join([str(fid), "groups", gid])
238
        return self.requester.delete(url)
239
240
    @nextcloud_method
241
    def set_permissions_to_group_folder(self, fid, gid, permissions):
242
        """
243
        Set the permissions a group has in a folder
244
245
        Args:
246
            fid (int/str): group folder id
247
            gid (str): group id
248
            permissions (int): The new permissions for the group as attribute of Permission class
249
250
        Returns:
251
252
        """
253
        url = "/".join([str(fid), "groups", gid])
254
        return self.requester.post(url=url, data={"permissions": permissions})
255
256
    @nextcloud_method
257
    def set_quota_of_group_folder(self, fid, quota):
258
        """
259
        Set the quota for a folder in bytes
260
261
        Args:
262
            fid (int/str): group folder id
263
            quota (int/str): The new quota for the folder in bytes, user -3 for unlimited
264
265
        Returns:
266
267
        """
268
        url = "/".join([str(fid), "quota"])
269
        return self.requester.post(url, {"quota": quota})
270
271
    @nextcloud_method
272
    def rename_group_folder(self, fid, mountpoint):
273
        """
274
        Change the name of a folder
275
276
        Args:
277
            fid (int/str): group folder id
278
            mountpoint (str): The new name for the folder
279
280
        Returns:
281
282
        """
283
        url = "/".join([str(fid), "mountpoint"])
284
        return self.requester.post(url=url, data={"mountpoint": mountpoint})
285
286
287
class Share(WithRequester):
288
    API_URL = "/ocs/v2.php/apps/files_sharing/api/v1"
289
    LOCAL = "shares"
290
291
    def get_local_url(self, additional_url=""):
292
        if additional_url:
293
            return "/".join([self.LOCAL, additional_url])
294
        return self.LOCAL
295
296
    @staticmethod
297
    def validate_share_parameters(path, share_type, share_with):
298
        """
299
        Check if share parameters make sense
300
301
        Args:
302
            path (str): path to the file/folder which should be shared
303
            share_type (int): ShareType attribute
304
            share_with (str): user/group id with which the file should be shared
305
306
        Returns:
307
            bool: True if parameters make sense together, False otherwise
308
        """
309
        if (path is None or not isinstance(share_type, int)) \
310
                or (share_type in [ShareType.GROUP, ShareType.USER, ShareType.FEDERATED_CLOUD_SHARE]
311
                    and share_with is None):
312
            return False
313
        return True
314
315
    @nextcloud_method
316
    def get_shares(self):
317
        """ Get all shares from the user """
318
        return self.requester.get(self.get_local_url())
319
320
    @nextcloud_method
321
    def get_shares_from_path(self, path, reshares=None, subfiles=None):
322
        """
323
        Get all shares from a given file/folder
324
325
        Args:
326
            path (str): path to file/folder
327
            reshares (bool): (optional) return not only the shares from the current user but all shares from the given file
328
            subfiles (bool): (optional) return all shares within a folder, given that path defines a folder
329
330
        Returns:
331
332
        """
333
        url = self.get_local_url()
334
        params = {
335
            "path": path,
336
            "reshares": None if reshares is None else str(bool(reshares)).lower(),  # TODO: test reshares, subfiles
337
            "subfiles": None if subfiles is None else str(bool(subfiles)).lower(),
338
        }
339
        return self.requester.get(url, params=params)
340
341
    @nextcloud_method
342
    def get_share_info(self, sid):
343
        """
344
        Get information about a given share
345
346
        Args:
347
            sid (int): share id
348
349
        Returns:
350
        """
351
        return self.requester.get(self.get_local_url(sid))
352
353
    @nextcloud_method
354
    def create_share(
355
            self, path, share_type, share_with=None, public_upload=None,
356
            password=None, permissions=None):
357
        """
358
        Share a file/folder with a user/group or as public link
359
360
        Mandatory fields: share_type, path and share_with for share_type USER (0) or GROUP (1).
361
362
        Args:
363
            path (str): path to the file/folder which should be shared
364
            share_type (int): ShareType attribute
365
            share_with (str): user/group id with which the file should be shared
366
            public_upload (bool): bool, allow public upload to a public shared folder (true/false)
367
            password (str): password to protect public link Share with
368
            permissions (int): sum of selected Permission attributes
369
370
        Returns:
371
372
        """
373
        if not self.validate_share_parameters(path, share_type, share_with):
374
            return False
375
376
        url = self.get_local_url()
377
        if public_upload:
378
            public_upload = "true"
379
380
        data = {"path": path, "shareType": share_type}
381
        if share_type in [ShareType.GROUP, ShareType.USER, ShareType.FEDERATED_CLOUD_SHARE]:
382
            data["shareWith"] = share_with
383
        if public_upload:
384
            data["publicUpload"] = public_upload
385
        if share_type == ShareType.PUBLIC_LINK and password is not None:
386
            data["password"] = str(password)
387
        if permissions is not None:
388
            data["permissions"] = permissions
389
        return self.requester.post(url, data)
390
391
    @nextcloud_method
392
    def delete_share(self, sid):
393
        """
394
        Remove the given share
395
396
        Args:
397
            sid (str): share id
398
399
        Returns:
400
401
        """
402
        return self.requester.delete(self.get_local_url(sid))
403
404
    @nextcloud_method
405
    def update_share(self, sid, permissions=None, password=None, public_upload=None, expire_date=""):
406
        """
407
        Update a given share, only one value can be updated per request
408
409
        Args:
410
            sid (str): share id
411
            permissions (int): sum of selected Permission attributes
412
            password (str): password to protect public link Share with
413
            public_upload (bool): bool, allow public upload to a public shared folder (true/false)
414
            expire_date (str): set an expire date for public link shares. Format: ‘YYYY-MM-DD’
415
416
        Returns:
417
418
        """
419
        params = dict(
420
            permissions=permissions,
421
            password=password,
422
            expireDate=expire_date
423
        )
424
        if public_upload:
425
            params["publicUpload"] = "true"
426
        if public_upload is False:
427
            params["publicUpload"] = "false"
428
429
        # check if only one param specified
430
        specified_params_count = sum([int(bool(each)) for each in params.values()])
431
        if specified_params_count > 1:
432
            raise ValueError("Only one parameter for update can be specified per request")
433
434
        url = self.get_local_url(sid)
435
        return self.requester.put(url, data=params)
436
437
438
class FederatedCloudShare(WithRequester):
439
    API_URL = "/ocs/v2.php/apps/files_sharing/api/v1"
440
    FEDERATED = "remote_shares"
441
442
    def get_federated_url(self, additional_url=""):
443
        if additional_url:
444
            return "/".join([self.FEDERATED, additional_url])
445
        return self.FEDERATED
446
447
    @nextcloud_method
448
    def list_accepted_federated_cloudshares(self):
449
        url = self.get_federated_url()
450
        return self.requester.get(url)
451
452
    @nextcloud_method
453
    def get_known_federated_cloudshare(self, sid):
454
        url = self.get_federated_url(sid)
455
        return self.requester.get(url)
456
457
    @nextcloud_method
458
    def delete_accepted_federated_cloudshare(self, sid):
459
        url = self.get_federated_url(sid)
460
        return self.requester.delete(url)
461
462
    @nextcloud_method
463
    def list_pending_federated_cloudshares(self, sid):
464
        url = self.get_federated_url("pending")
465
        return self.requester.get(url)
466
467
    @nextcloud_method
468
    def accept_pending_federated_cloudshare(self, sid):
469
        url = self.get_federated_url("pending/{sid}".format(sid=sid))
470
        return self.requester.post(url)
471
472
    @nextcloud_method
473
    def decline_pending_federated_cloudshare(self, sid):
474
        url = self.get_federated_url("pending/{sid}".format(sid=sid))
475
        return self.requester.delete(url)
476
477
478
class Apps(WithRequester):
479
    API_URL = "/ocs/v1.php/cloud/apps"
480
481
    @nextcloud_method
482
    def get_apps(self, filter=None):
483
        """
484
        Get a list of apps installed on the Nextcloud server
485
486
        :param filter: str, optional "enabled" or "disabled"
487
        :return:
488
        """
489
        params = {
490
            "filter": filter
491
        }
492
        return self.requester.get(params=params)
493
494
    @nextcloud_method
495
    def get_app(self, app_id):
496
        """
497
        Provide information on a specific application
498
499
        :param app_id: str, app id
500
        :return:
501
        """
502
        return self.requester.get(app_id)
503
504
    @nextcloud_method
505
    def enable_app(self, app_id):
506
        """
507
        Enable an app
508
509
        :param app_id: str, app id
510
        :return:
511
        """
512
        return self.requester.post(app_id)
513
514
    @nextcloud_method
515
    def disable_app(self, app_id):
516
        """
517
        Disable the specified app
518
519
        :param app_id: str, app id
520
        :return:
521
        """
522
        return self.requester.delete(app_id)
523
524
525
class Group(WithRequester):
526
    API_URL = "/ocs/v1.php/cloud/groups"
527
528
    @nextcloud_method
529
    def get_groups(self, search=None, limit=None, offset=None):
530
        """
531
        Retrieve a list of groups from the Nextcloud server
532
533
        :param search: string, optional search string
534
        :param limit: int, optional limit value
535
        :param offset: int, optional offset value
536
        :return:
537
        """
538
        params = {
539
            'search': search,
540
            'limit': limit,
541
            'offset': offset
542
        }
543
        return self.requester.get(params=params)
544
545
    @nextcloud_method
546
    def add_group(self, gid):
547
        """
548
        Add a new group
549
550
        :param gid: str, group name
551
        :return:
552
        """
553
        msg = {"groupid": gid}
554
        return self.requester.post("", msg)
555
556
    @nextcloud_method
557
    def get_group(self, gid):
558
        """
559
        Retrieve a list of group members
560
561
        :param gid: str, group name
562
        :return:
563
        """
564
        return self.requester.get("{gid}".format(gid=gid))
565
566
    @nextcloud_method
567
    def get_subadmins(self, gid):
568
        """
569
        List subadmins of the group
570
571
        :param gid: str, group name
572
        :return:
573
        """
574
        return self.requester.get("{gid}/subadmins".format(gid=gid))
575
576
    @nextcloud_method
577
    def delete_group(self, gid):
578
        """
579
        Remove a group
580
581
        :param gid: str, group name
582
        :return:
583
        """
584
        return self.requester.delete("{gid}".format(gid=gid))
585
586
587
class User(WithRequester):
588
    API_URL = "/ocs/v1.php/cloud/users"
589
590
    @nextcloud_method
591
    def add_user(self, uid, passwd):
592
        """
593
        Create a new user on the Nextcloud server
594
595
        :param uid: str, uid of new user
596
        :param passwd: str, password of new user
597
        :return:
598
        """
599
        msg = {'userid': uid, 'password': passwd}
600
        return self.requester.post("", msg)
601
602
    @nextcloud_method
603
    def get_users(self, search=None, limit=None, offset=None):
604
        """
605
        Retrieve a list of users from the Nextcloud server
606
607
        :param search: string, optional search string
608
        :param limit: int, optional limit value
609
        :param offset: int, optional offset value
610
        :return:
611
        """
612
        params = {
613
            'search': search,
614
            'limit': limit,
615
            'offset': offset
616
        }
617
        return self.requester.get(params=params)
618
619
    @nextcloud_method
620
    def get_user(self, uid):
621
        """
622
        Retrieve information about a single user
623
624
        :param uid: str, uid of user
625
        :return:
626
        """
627
        return self.requester.get("{uid}".format(uid=uid))
628
629
    @nextcloud_method
630
    def edit_user(self, uid, what, value):
631
        """
632
        Edit attributes related to a user
633
634
        Users are able to edit email, displayname and password; admins can also edit the quota value
635
636
        :param uid: str, uid of user
637
        :param what: str, the field to edit
638
        :param value: str, the new value for the field
639
        :return:
640
        """
641
        what_to_key_map = dict(
642
            email="email", quota="quote", phone="phone", address="address", website="website",
643
            twitter="twitter", displayname="displayname", password="password",
644
        )
645
        assert what in what_to_key_map, (
646
            "You have chosen to edit user's '{what}', but you can choose only from: {choices}"
647
            .format(what=what, choices=", ".join(what_to_key_map.keys()))
648
        )
649
650
        url = "{uid}".format(uid=uid)
651
        msg = dict(
652
            key=what_to_key_map[what],
653
            value=value,
654
        )
655
        return self.requester.put(url, msg)
656
657
    @nextcloud_method
658
    def disable_user(self, uid):
659
        """
660
        Disable a user on the Nextcloud server so that the user cannot login anymore
661
662
        :param uid: str, uid of user
663
        :return:
664
        """
665
        return self.requester.put("{uid}/disable".format(uid=uid))
666
667
    @nextcloud_method
668
    def enable_user(self, uid):
669
        """
670
        Enable a user on the Nextcloud server so that the user can login again
671
672
        :param uid: str, uid of user
673
        :return:
674
        """
675
        return self.requester.put("{uid}/enable".format(uid=uid))
676
677
    @nextcloud_method
678
    def delete_user(self, uid):
679
        """
680
        Delete a user from the Nextcloud server
681
682
        :param uid: str, uid of user
683
        :return:
684
        """
685
        return self.requester.delete("{uid}".format(uid=uid))
686
687
    @nextcloud_method
688
    def add_to_group(self, uid, gid):
689
        """
690
        Add the specified user to the specified group
691
692
        :param uid: str, uid of user
693
        :param gid: str, name of group
694
        :return:
695
        """
696
        url = "{uid}/groups".format(uid=uid)
697
        msg = {'groupid': gid}
698
        return self.requester.post(url, msg)
699
700
    @nextcloud_method
701
    def remove_from_group(self, uid, gid):
702
        """
703
        Remove the specified user from the specified group
704
705
        :param uid: str, uid of user
706
        :param gid: str, name of group
707
        :return:
708
        """
709
        url = "{uid}/groups".format(uid=uid)
710
        msg = {'groupid': gid}
711
        return self.requester.delete(url, msg)
712
713
    @nextcloud_method
714
    def create_subadmin(self, uid, gid):
715
        """
716
        Make a user the subadmin of a group
717
718
        :param uid: str, uid of user
719
        :param gid: str, name of group
720
        :return:
721
        """
722
        url = "{uid}/subadmins".format(uid=uid)
723
        msg = {'groupid': gid}
724
        return self.requester.post(url, msg)
725
726
    @nextcloud_method
727
    def remove_subadmin(self, uid, gid):
728
        """
729
        Remove the subadmin rights for the user specified from the group specified
730
731
        :param uid: str, uid of user
732
        :param gid: str, name of group
733
        :return:
734
        """
735
        url = "{uid}/subadmins".format(uid=uid)
736
        msg = {'groupid': gid}
737
        return self.requester.delete(url, msg)
738
739
    @nextcloud_method
740
    def get_subadmin_groups(self, uid):
741
        """
742
        Get the groups in which the user is a subadmin
743
744
        :param uid: str, uid of user
745
        :return:
746
        """
747
        url = "{uid}/subadmins".format(uid=uid)
748
        return self.requester.get(url)
749
750
    @nextcloud_method
751
    def resend_welcome_mail(self, uid):
752
        """
753
        Trigger the welcome email for this user again
754
755
        :param uid: str, uid of user
756
        :return:
757
        """
758
        url = "{uid}/welcome".format(uid=uid)
759
        return self.requester.post(url)
760
761
762
class WebDAV(WithRequester):
763
764
    API_URL = "/remote.php/dav/files"
765
766
    def __init__(self, *args, **kwargs):
767
        super(WebDAV, self).__init__(*args)
768
        self.js = kwargs.get('js')
769
770
    @nextcloud_method
771
    def list_folders(self, uid, path=None, depth=1):
772
        """
773
        Get path files list with files properties for given user, with given depth
774
775
        Args:
776
            uid (str): uid of user
777
            path (str/None): files path
778
            depth: depth of listing files (directories content for example)
779
780
        Returns:
781
            list of dicts if js
782
            list of File objects if not js
783
        """
784
        # TODO: add possibility to request additional properties (in a request body)
785
        #  https://docs.nextcloud.com/server/14/developer_manual/client_apis/WebDAV/basic.html#requesting-properties
786
        additional_url = uid
787
        if path:
788
            additional_url = "{}/{}".format(additional_url, path)
789
        resp = self.requester.propfind(additional_url=additional_url, headers={"Depth": str(depth)})
790
        response_xml_data = ET.fromstring(resp)
791
        files_data = [File(single_file) for single_file in response_xml_data]
792
        return files_data if not self.js else [each.as_dict() for each in files_data]
793
794
    @nextcloud_method
795
    def download_file(self, uid, path):
796
        """
797
        Download file of given user by path
798
799
        File will be saved to working directory
800
        path argument must be valid file path
801
802
        Exception will be raised if:
803
            path doesn't exist
804
            path is a directory
805
            file with same name already exists in working directory
806
807
        Args:
808
            uid (str): uid of user
809
            path (str): file path
810
811
812
        Returns:
813
            None
814
815
        """
816
        additional_url = "/".join([uid, path])
817
        filename = path.split('/')[-1] if '/' in path else path
818
        file_data = self.list_folders(uid=uid, path=path, depth=0)[0]
819
        if file_data.status_code == 404:
820
            raise ValueError("Given path doesn't exist")
821
        file_resource_type = file_data.get('resource_type') if self.js else file_data.resource_type
822
        if file_resource_type == File.COLLECTION_RESOURCE_TYPE:
823
            raise ValueError("This is a collection, please specify file path")
824
        if filename in os.listdir('./'):
825
            raise ValueError("File with such name already exists in this directory")
826
        res = self.requester.download(additional_url)
827
        with open(filename, 'wb') as f:
828
            f.write(res.content)
829
830
    @nextcloud_method
831
    def upload_file(self, uid, local_filepath, remote_filepath):
832
        """
833
        Upload file to Nextcloud storage
834
835
        Args:
836
            uid (str): uid of user
837
            local_filepath (str): path to file on local storage
838
            remote_filepath (str): path where to upload file on Nextcloud storage
839
840
        Returns:
841
842
        """
843
        with open(local_filepath, 'rb') as f:
844
            file_content = f.read()
845
        additional_url = "/".join([uid, remote_filepath])
846
        return self.requester.put(additional_url, data=file_content)
847
848
    @nextcloud_method
849
    def create_folder(self, uid, folder_path):
850
        """
851
        Create folder on Nextcloud storage
852
853
        Args:
854
            uid (str): uid of user
855
            folder_path (str): folder path
856
857
        Returns:
858
859
        """
860
        return self.requester.make_collection(additional_url="/".join([uid, folder_path]))
861
862
    @nextcloud_method
863
    def delete_path(self, uid, path):
864
        """
865
        Delete file or folder with all content of given user by path
866
867
        Args:
868
            uid (str): uid of user
869
            path (str): file or folder path to delete
870
871
        Returns:
872
873
        """
874
        url = "/".join([uid, path])
875
        return self.requester.delete(url=url)
876
877
    @nextcloud_method
878
    def move_path(self, uid, path, destination_path, overwrite=False):
879
        """
880
        Move file or folder to destination
881
882
        Args:
883
            uid (str): uid of user
884
            path (str): file or folder path to move
885
            destionation_path (str): destination where to move
886
            overwrite (bool): allow destination path overriding
887
888
        Returns:
889
890
        """
891
        path_url = "/".join([uid, path])
892
        destination_path_url = "/".join([uid, destination_path])
893
        return self.requester.move(url=path_url, destination=destination_path_url, overwrite=overwrite)
894
895
    @nextcloud_method
896
    def copy_path(self, uid, path, destination_path, overwrite=False):
897
        """
898
        Copy file or folder to destination
899
900
        Args:
901
            uid (str): uid of user
902
            path (str): file or folder path to copy
903
            destionation_path (str): destination where to copy
904
            overwrite (bool): allow destination path overriding
905
906
        Returns:
907
908
        """
909
        path_url = "/".join([uid, path])
910
        destination_path_url = "/".join([uid, destination_path])
911
        return self.requester.copy(url=path_url, destination=destination_path_url, overwrite=overwrite)
912
913
    @nextcloud_method
914
    def set_favorites(self):
915
        pass
916
917
    @nextcloud_method
918
    def list_favorites(self):
919
        pass
920
921
922
class OCSCode(enum.IntEnum):
923
    OK = 100
924
    SERVER_ERROR = 996
925
    NOT_AUTHORIZED = 997
926
    NOT_FOUND = 998
927
    UNKNOWN_ERROR = 999
928
929
930
class ShareType(enum.IntEnum):
931
    USER = 0
932
    GROUP = 1
933
    PUBLIC_LINK = 3
934
    FEDERATED_CLOUD_SHARE = 6
935
936
937
class Permission(enum.IntEnum):
938
    """ Permission for Share have to be sum of selected permissions """
939
    READ = 1
940
    UPDATE = 2
941
    CREATE = 4
942
    DELETE = 8
943
    SHARE = 16
944
    ALL = 31
945
946
947
class File(object):
948
949
    SUCCESS_STATUS = 'HTTP/1.1 200 OK'
950
951
    FILE_PROPERTIES = {
952
        "getlastmodified": "last_modified",
953
        "getetag": "etag",
954
        "getcontenttype": "content_type",
955
        "resourcetype": "resource_type",
956
        "getcontentlength": "content_length",
957
        "id": "id",
958
        "fileid": "file_id",
959
        "favorite": "favorite",
960
        "comments-href": "comments_href",
961
        "comments-count": "comments_count",
962
        "comments-unread": "comments_unread",
963
        "owner-id": "owner_id",
964
        "owner-display-name": "owner_display_name",
965
        "share-types": "share_types",
966
        "checksums": "check_sums",
967
        "has-preview": "has_preview",
968
        "size": "size",
969
        "href": "href"
970
    }
971
    COLLECTION_RESOURCE_TYPE = 'collection'
972
973
    def __init__(self, xml_data):
974
        self.href = xml_data.find('{DAV:}href').text
975
        for propstat in xml_data.iter('{DAV:}propstat'):
976
            if propstat.find('{DAV:}status').text != self.SUCCESS_STATUS:
977
                continue
978
            for file_property in propstat.find('{DAV:}prop'):
979
                file_property_name = re.sub("{.*}", "", file_property.tag)
980
                if file_property_name not in self.FILE_PROPERTIES:
981
                    continue
982
                if file_property_name == 'resourcetype':
983
                    value = self._extract_resource_type(file_property)
984
                else:
985
                    value = file_property.text
986
                setattr(self, self.FILE_PROPERTIES[file_property_name], value)
987
988
    def _extract_resource_type(self, file_property):
989
        file_type = list(file_property)
990
        if file_type:
991
            return re.sub("{.*}", "", file_type[0].tag)
992
        return None
993
994
    def as_dict(self):
995
        return {key: value for key, value in self.__dict__.items() if key in self.FILE_PROPERTIES.values()}
996
997
998
QUOTA_UNLIMITED = -3
999
1000
1001
def datetime_to_expire_date(date):
1002
    return date.strftime("%Y-%m-%d")
1003