ufload3 2.0__tar.gz → 2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ufload3-2.0/ufload3.egg-info → ufload3-2.2}/PKG-INFO +1 -1
- {ufload3-2.0 → ufload3-2.2}/ufload3/__init__.py +1 -1
- {ufload3-2.0 → ufload3-2.2}/ufload3/cli/main.py +14 -10
- {ufload3-2.0 → ufload3-2.2}/ufload3/cloud.py +16 -21
- {ufload3-2.0 → ufload3-2.2}/ufload3/db.py +30 -7
- ufload3-2.2/ufload3/webdav.py +229 -0
- {ufload3-2.0 → ufload3-2.2/ufload3.egg-info}/PKG-INFO +1 -1
- ufload3-2.0/ufload3/webdav.py +0 -280
- {ufload3-2.0 → ufload3-2.2}/LICENSE +0 -0
- {ufload3-2.0 → ufload3-2.2}/README.md +0 -0
- {ufload3-2.0 → ufload3-2.2}/pyproject.toml +0 -0
- {ufload3-2.0 → ufload3-2.2}/setup.cfg +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3/cli/__init__.py +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3/cli/test_main.py +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3/test_cloud.py +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3/test_db.py +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3.egg-info/SOURCES.txt +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3.egg-info/dependency_links.txt +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3.egg-info/entry_points.txt +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3.egg-info/requires.txt +0 -0
- {ufload3-2.0 → ufload3-2.2}/ufload3.egg-info/top_level.txt +0 -0
|
@@ -233,9 +233,6 @@ def _dirRestore(args):
|
|
|
233
233
|
return 2, None
|
|
234
234
|
|
|
235
235
|
def _multiRestore(args):
|
|
236
|
-
if not _required(args, [ 'user', 'pw' ]):
|
|
237
|
-
ufload3.progress('With no -file or -dir argument, cloud credentials are mandatory.')
|
|
238
|
-
return 2, None
|
|
239
236
|
|
|
240
237
|
if args.i is None:
|
|
241
238
|
if not _required(args, [ 'oc' ]):
|
|
@@ -308,7 +305,7 @@ def _multiRestore(args):
|
|
|
308
305
|
|
|
309
306
|
ufload3.progress("Instances to be restored: %s" % ", ".join(list(instances.keys())))
|
|
310
307
|
dbs=[]
|
|
311
|
-
pattern = re.compile('.*-[A-Z]{1}[a-z]{2}\.zip$')
|
|
308
|
+
pattern = re.compile(r'.*-[A-Z]{1}[a-z]{2}\.zip$')
|
|
312
309
|
|
|
313
310
|
for i in instances:
|
|
314
311
|
|
|
@@ -491,8 +488,14 @@ def _syncLink(args, dbs, sdb):
|
|
|
491
488
|
|
|
492
489
|
|
|
493
490
|
def _cmdLs(args):
|
|
494
|
-
if not _required(args, [ '
|
|
491
|
+
if not _required(args, [ 'tenant', 'client_id', 'oc' ]):
|
|
492
|
+
return 2
|
|
493
|
+
if not args.cert_path and not args.cert_content:
|
|
494
|
+
ufload3.progress('Argument --cert-content or --cert-path is required for this sub-command.')
|
|
495
495
|
return 2
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
496
499
|
if args.subdir is None:
|
|
497
500
|
args.subdir = ''
|
|
498
501
|
|
|
@@ -563,14 +566,14 @@ def _cmdUpgrade(args):
|
|
|
563
566
|
return 1
|
|
564
567
|
|
|
565
568
|
#Download the patch
|
|
566
|
-
patches.sort(key=lambda s: list(map(int, re.split('\.|-|p',re.search('uf(.+?)\.patch\.zip', s[1], re.I).group(1)))))
|
|
569
|
+
patches.sort(key=lambda s: list(map(int, re.split(r'\.|-|p',re.search(r'uf(.+?)\.patch\.zip', s[1], re.I).group(1)))))
|
|
567
570
|
i = 0
|
|
568
571
|
for j in patches:
|
|
569
572
|
filename = dav.download(j[2], j[1])
|
|
570
573
|
|
|
571
574
|
#Set patch and version args
|
|
572
575
|
args.patch = filename
|
|
573
|
-
m = re.search('(.+?)\.patch\.zip', filename)
|
|
576
|
+
m = re.search(r'(.+?)\.patch\.zip', filename)
|
|
574
577
|
if m:
|
|
575
578
|
args.version = m.group(1)
|
|
576
579
|
|
|
@@ -727,7 +730,7 @@ def _cmdUpgrade(args):
|
|
|
727
730
|
if len(patches) == 0:
|
|
728
731
|
ufload3.progress("No User Rights found.")
|
|
729
732
|
return 1
|
|
730
|
-
patches.sort(key=lambda s: list(map(int, re.split('\.|-|p',re.search('User Rights v(.+?).zip', s[1], re.I).group(1)))))
|
|
733
|
+
patches.sort(key=lambda s: list(map(int, re.split(r'\.|-|p',re.search('User Rights v(.+?).zip', s[1], re.I).group(1)))))
|
|
731
734
|
|
|
732
735
|
urfilename = None
|
|
733
736
|
for j in patches:
|
|
@@ -788,8 +791,9 @@ spinner = spinning_cursor()
|
|
|
788
791
|
def parse():
|
|
789
792
|
parser = argparse.ArgumentParser(prog='ufload3')
|
|
790
793
|
|
|
791
|
-
parser.add_argument("-
|
|
792
|
-
parser.add_argument("-
|
|
794
|
+
parser.add_argument("-tenant", help="Cloud Tenant")
|
|
795
|
+
parser.add_argument("-client-id", help="Cloud App-Id")
|
|
796
|
+
parser.add_argument("-cert-path", help="Cloud CertPath")
|
|
793
797
|
parser.add_argument("-oc", help="OC (OCG, OCA and OCB accepted) - optional for the restore command (if not provided, ufload will try and deduce the right OC(s) from the name of the requested instances)")
|
|
794
798
|
|
|
795
799
|
parser.add_argument("-syncuser", help="username to access the sync server backup")
|
|
@@ -49,9 +49,6 @@ def instance_to_dir(instance):
|
|
|
49
49
|
|
|
50
50
|
def get_cloud_info(args, sub_dir=''):
|
|
51
51
|
|
|
52
|
-
#Cloud password is encrypted
|
|
53
|
-
pword = _decrypt(args.pw)
|
|
54
|
-
|
|
55
52
|
#Cloud path depends on the OC
|
|
56
53
|
if args.oc:
|
|
57
54
|
dir = '/personal/UF_' + args.oc.upper() + '_msf_geneva_msf_org/'
|
|
@@ -67,17 +64,18 @@ def get_cloud_info(args, sub_dir=''):
|
|
|
67
64
|
except:
|
|
68
65
|
#The argument cloudpath is not defined, forget about it (this is not the upgrade process)
|
|
69
66
|
pass
|
|
67
|
+
|
|
68
|
+
if args.cert_path:
|
|
69
|
+
with open(args.cert_path, 'r') as c:
|
|
70
|
+
args.cert_content = c.read()
|
|
70
71
|
|
|
71
72
|
ret = {
|
|
72
73
|
'url': args.cloud_url,
|
|
73
74
|
'dir': dir + sub,
|
|
74
75
|
'site': dir,
|
|
75
76
|
'path': args.cloud_path,
|
|
76
|
-
'login': args.user,
|
|
77
|
-
'password': pword,
|
|
78
77
|
'tenant': args.tenant,
|
|
79
78
|
'client_id': args.client_id,
|
|
80
|
-
'thumbprint': args.thumbprint,
|
|
81
79
|
'cert_content': args.cert_content,
|
|
82
80
|
}
|
|
83
81
|
|
|
@@ -88,10 +86,12 @@ def get_onedrive_connection(args):
|
|
|
88
86
|
info = get_cloud_info(args)
|
|
89
87
|
if not info.get('url'):
|
|
90
88
|
ufload3.progress('URL is not set!')
|
|
91
|
-
if not info.get('
|
|
92
|
-
ufload3.progress('
|
|
93
|
-
if not info.get('
|
|
94
|
-
ufload3.progress('
|
|
89
|
+
if not info.get('tenant'):
|
|
90
|
+
ufload3.progress('Tenant is not set!')
|
|
91
|
+
if not info.get('client_id'):
|
|
92
|
+
ufload3.progress('Client_id is not set!')
|
|
93
|
+
if not info.get('cert_content'):
|
|
94
|
+
ufload3.progress('Cert is not set!')
|
|
95
95
|
|
|
96
96
|
url = urlparse(info['url'])
|
|
97
97
|
if not url.netloc:
|
|
@@ -100,8 +100,7 @@ def get_onedrive_connection(args):
|
|
|
100
100
|
path = info.get('site') + url.path
|
|
101
101
|
|
|
102
102
|
try:
|
|
103
|
-
dav = webdav.Client(url.netloc,
|
|
104
|
-
password=info['password'], tenant=info['tenant'], client_id= info['client_id'], thumbprint=info['thumbprint'], cert_content=info['cert_content'], path=path)
|
|
103
|
+
dav = webdav.Client(url.netloc, tenant=info['tenant'], client_id= info['client_id'], cert_content=info['cert_content'], path=path)
|
|
105
104
|
return dav
|
|
106
105
|
except webdav.ConnectionFailed as e:
|
|
107
106
|
ufload3.progress('Unable to connect: {}'.format(e))
|
|
@@ -124,24 +123,20 @@ def _get_all_files_and_timestamp(dav, d):
|
|
|
124
123
|
ret = []
|
|
125
124
|
for f in all_zip:
|
|
126
125
|
#if not f['Name'] or f['Name'][-1] == '/':
|
|
127
|
-
if not f
|
|
126
|
+
if not f.name:
|
|
128
127
|
continue
|
|
129
128
|
|
|
130
|
-
# We try to extract a timestamp to get an idea of the creation date
|
|
131
|
-
# Format: Mon, 14 Mar 2016 03:31:40 GMT
|
|
132
|
-
t = time.strptime(f['TimeLastModified'], '%Y-%m-%dT%H:%M:%SZ')
|
|
133
|
-
|
|
134
129
|
# We don't take into consideration backups that are too recent.
|
|
135
130
|
# Otherwise they could be half uploaded (=> corrupted)
|
|
136
|
-
if abs(time.time() -
|
|
131
|
+
if abs(time.time() - f.time_last_modified.timestamp()) < 900:
|
|
137
132
|
continue
|
|
138
133
|
|
|
139
134
|
# ufload3.progress('File found: %s' % f['Name'])
|
|
140
135
|
|
|
141
|
-
if f
|
|
142
|
-
logging.warn("Ignoring non-zipfile: %s" % f
|
|
136
|
+
if f.name.split(".")[-1] != "zip":
|
|
137
|
+
logging.warn("Ignoring non-zipfile: %s" % f.name)
|
|
143
138
|
continue
|
|
144
|
-
ret.append((
|
|
139
|
+
ret.append((f.time_last_modified, f.name, f.serverRelativeUrl))
|
|
145
140
|
return ret
|
|
146
141
|
|
|
147
142
|
# returns True if x has instance as a substring
|
|
@@ -361,7 +361,7 @@ def instantiate(args, db):
|
|
|
361
361
|
connection = transport.make_connection(host)
|
|
362
362
|
connection.timeout = 4
|
|
363
363
|
sock = xmlrpc.client.ServerProxy('http://%s:%s/xmlrpc/common' % (host, port), transport=transport)
|
|
364
|
-
|
|
364
|
+
sock.login(db, args.adminuser.lower(), args.adminpw)
|
|
365
365
|
except Exception as e:
|
|
366
366
|
ufload3.progress("non blocking error at first connection %s" % e)
|
|
367
367
|
|
|
@@ -498,8 +498,8 @@ def delive(args, db):
|
|
|
498
498
|
if args.db_prefix:
|
|
499
499
|
db_name = db_name.split(args.db_prefix+'_', 1)[1]
|
|
500
500
|
new_pass_dict = []
|
|
501
|
-
for pass_part in re.split(
|
|
502
|
-
m = re.search('\[(\d+)\+(\d+)\]', pass_part)
|
|
501
|
+
for pass_part in re.split(r'(\[\d+\+\d+\])', args.newuserspw):
|
|
502
|
+
m = re.search(r'\[(\d+)\+(\d+)\]', pass_part)
|
|
503
503
|
if m:
|
|
504
504
|
pos = int(m.group(1)) - 1
|
|
505
505
|
add = int(m.group(2))
|
|
@@ -511,7 +511,17 @@ def delive(args, db):
|
|
|
511
511
|
|
|
512
512
|
for new_user_info in args.createusers.split(';'):
|
|
513
513
|
new_user_data = new_user_info.split(':')
|
|
514
|
-
|
|
514
|
+
new_user_dpt = False
|
|
515
|
+
new_user_name = False
|
|
516
|
+
new_user_email = False
|
|
517
|
+
if len(new_user_data) == 6:
|
|
518
|
+
new_user = new_user_data[0]
|
|
519
|
+
new_user_name = new_user_data[1]
|
|
520
|
+
new_user_email = new_user_data[2]
|
|
521
|
+
new_user_dpt = new_user_data[3]
|
|
522
|
+
new_user_pass = new_user_data[4]
|
|
523
|
+
groups = new_user_data[5]
|
|
524
|
+
elif len(new_user_data) == 3:
|
|
515
525
|
new_user= new_user_data[0]
|
|
516
526
|
new_user_pass = new_user_data[1]
|
|
517
527
|
groups = new_user_data[2]
|
|
@@ -519,12 +529,25 @@ def delive(args, db):
|
|
|
519
529
|
new_user= new_user_data[0]
|
|
520
530
|
new_user_pass = newpass
|
|
521
531
|
groups = new_user_data[1]
|
|
532
|
+
|
|
533
|
+
if not new_user_name:
|
|
534
|
+
new_user_name = new_user
|
|
522
535
|
rc, new_userid = psql(args, """ insert into res_users (name, active, login, password, context_lang, company_id, view, menu_id) values
|
|
523
536
|
('%s', 't', '%s', '%s', 'en_MF', 1, 'simple', 1) returning id;"""
|
|
524
|
-
% (
|
|
537
|
+
% (new_user_name, new_user.lower(), new_user_pass), db, silent=True)
|
|
525
538
|
if rc != 0:
|
|
526
539
|
return rc
|
|
527
|
-
|
|
540
|
+
if new_user_dpt:
|
|
541
|
+
psql(args, """ update res_users u set context_department_id = d.id
|
|
542
|
+
from hr_department d
|
|
543
|
+
where d.name = '%s' and u.id = %s """ % (new_user_dpt, new_userid), db)
|
|
544
|
+
|
|
545
|
+
if new_user_email:
|
|
546
|
+
rc, address_id = psql(args, """ insert into res_partner_address (name, email) values ('%s', '%s') returning id """ % (new_user_name, new_user_email), db, silent=True)
|
|
547
|
+
if address_id:
|
|
548
|
+
psql(args,"update res_users set address_id=%s where id=%s" % (address_id, new_userid), db)
|
|
549
|
+
|
|
550
|
+
for new_group in groups.split(','):
|
|
528
551
|
rc = psql(args, " insert into res_groups_users_rel (uid, gid) (select %s, id from res_groups where name='%s');" % (new_userid, new_group), db)
|
|
529
552
|
if rc != 0:
|
|
530
553
|
return rc
|
|
@@ -608,7 +631,7 @@ def get_hwid(args):
|
|
|
608
631
|
import winreg
|
|
609
632
|
try:
|
|
610
633
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
|
611
|
-
"SYSTEM\ControlSet001\services\eventlog\Application\openerp-web-6.0",
|
|
634
|
+
r"SYSTEM\ControlSet001\services\eventlog\Application\openerp-web-6.0",
|
|
612
635
|
0, winreg.KEY_READ) as registry_key:
|
|
613
636
|
hwid, regtype = winreg.QueryValueEx(registry_key, "HardwareId")
|
|
614
637
|
ufload3.progress("Hardware id from registry key: %s" % hwid)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from office365.sharepoint.client_context import ClientContext
|
|
5
|
+
from office365.runtime.client_runtime_context import ClientRuntimeContext
|
|
6
|
+
from office365.runtime.client_request_exception import ClientRequestException
|
|
7
|
+
from office365.sharepoint.files.file import File
|
|
8
|
+
from office365.sharepoint.folders.folder import Folder
|
|
9
|
+
from office365.runtime.queries.service_operation import ServiceOperationQuery
|
|
10
|
+
from cryptography.x509 import load_pem_x509_certificate
|
|
11
|
+
from cryptography.hazmat.primitives import hashes
|
|
12
|
+
|
|
13
|
+
def move_to_newname(self, destination, newname, flag):
|
|
14
|
+
"""Moves the file to the specified destination url.
|
|
15
|
+
|
|
16
|
+
:param str or office365.sharepoint.folders.folder.Folder destination: Specifies the existing folder or folder
|
|
17
|
+
site relative url
|
|
18
|
+
:param str new file name
|
|
19
|
+
:param int flag: Specifies the kind of move operation.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def _moveto(destination_folder):
|
|
23
|
+
# type: (Folder) -> None
|
|
24
|
+
file_url = "/".join([str(destination_folder.serverRelativeUrl), newname])
|
|
25
|
+
|
|
26
|
+
params = {"newurl": file_url, "flags": flag}
|
|
27
|
+
qry = ServiceOperationQuery(self, "moveto", params)
|
|
28
|
+
self.context.add_query(qry)
|
|
29
|
+
|
|
30
|
+
def _update_file(return_type):
|
|
31
|
+
self.set_property("ServerRelativeUrl", file_url)
|
|
32
|
+
|
|
33
|
+
self.context.after_query_execute(_update_file)
|
|
34
|
+
|
|
35
|
+
def _source_file_resolved():
|
|
36
|
+
if isinstance(destination, Folder):
|
|
37
|
+
destination.ensure_property("ServerRelativeUrl", _moveto, destination)
|
|
38
|
+
else:
|
|
39
|
+
self.context.web.ensure_folder_path(destination).get().after_execute(
|
|
40
|
+
_moveto
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.ensure_properties(["ServerRelativeUrl", "Name"], _source_file_resolved)
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
File.move_to_newname = move_to_newname
|
|
47
|
+
|
|
48
|
+
import logging
|
|
49
|
+
import os
|
|
50
|
+
import posixpath
|
|
51
|
+
from urllib.parse import urlparse, urljoin
|
|
52
|
+
from time import sleep
|
|
53
|
+
|
|
54
|
+
def execute_query_retry(
|
|
55
|
+
self,
|
|
56
|
+
max_retry=5,
|
|
57
|
+
timeout_secs=5,
|
|
58
|
+
success_callback=None,
|
|
59
|
+
failure_callback=None,
|
|
60
|
+
exceptions=(ClientRequestException,),
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Executes the current set of data retrieval queries and method invocations and retries it if needed.
|
|
64
|
+
|
|
65
|
+
:param int max_retry: Number of times to retry the request
|
|
66
|
+
:param int timeout_secs: Seconds to wait before retrying the request.
|
|
67
|
+
:param (office365.runtime.client_object.ClientObject)-> None success_callback:
|
|
68
|
+
:param (int, requests.exceptions.RequestException)-> None failure_callback:
|
|
69
|
+
:param exceptions: tuple of exceptions that we retry
|
|
70
|
+
"""
|
|
71
|
+
retry = 1
|
|
72
|
+
while True:
|
|
73
|
+
try:
|
|
74
|
+
self.execute_query()
|
|
75
|
+
if callable(success_callback):
|
|
76
|
+
success_callback(self.current_query.return_type)
|
|
77
|
+
break
|
|
78
|
+
except exceptions as e:
|
|
79
|
+
if retry > max_retry:
|
|
80
|
+
raise
|
|
81
|
+
retry += 1
|
|
82
|
+
self.add_query(self.current_query)
|
|
83
|
+
if callable(failure_callback):
|
|
84
|
+
failure_callback(retry, e)
|
|
85
|
+
sleep(timeout_secs)
|
|
86
|
+
ClientRuntimeContext.execute_query_retry=execute_query_retry
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ConnectionFailed(Exception):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
class PasswordFailed(Exception):
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
class Client(object):
|
|
96
|
+
def __init__(self, host, tenant, client_id, cert_content=False, cert_path=False, max_retry=1, path=None):
|
|
97
|
+
self.tenant = tenant
|
|
98
|
+
self.client_id = client_id
|
|
99
|
+
if not cert_content and cert_path:
|
|
100
|
+
with open(cert_path, 'r') as c:
|
|
101
|
+
cert_content = c.read()
|
|
102
|
+
self.cert_content = cert_content
|
|
103
|
+
cert = load_pem_x509_certificate(bytes(self.cert_content, 'utf8'))
|
|
104
|
+
self.thumbprint = cert.fingerprint(hashes.SHA1()).hex()
|
|
105
|
+
|
|
106
|
+
self.path = path or ''
|
|
107
|
+
|
|
108
|
+
self.url = 'https://{0}'.format(host)
|
|
109
|
+
|
|
110
|
+
self.max_retry = max_retry
|
|
111
|
+
self.login()
|
|
112
|
+
|
|
113
|
+
def login(self):
|
|
114
|
+
full_path = urljoin(self.url, self.path)
|
|
115
|
+
self.request = ClientContext(full_path)
|
|
116
|
+
|
|
117
|
+
self.request.with_client_certificate(
|
|
118
|
+
tenant=self.tenant,
|
|
119
|
+
client_id=self.client_id,
|
|
120
|
+
private_key=self.cert_content,
|
|
121
|
+
thumbprint=self.thumbprint,
|
|
122
|
+
)
|
|
123
|
+
self.baseurl = self.request._get_context_web_information().WebFullUrl
|
|
124
|
+
self.request._auth_context.url = self.baseurl
|
|
125
|
+
if not self.baseurl:
|
|
126
|
+
raise requests.exceptions.RequestException("Full Url not found %s" % self.path)
|
|
127
|
+
if not self.baseurl.endswith('/'):
|
|
128
|
+
self.baseurl = '%s/' % self.baseurl
|
|
129
|
+
self.path = urlparse(full_path).path
|
|
130
|
+
|
|
131
|
+
if not self.path.startswith('/'):
|
|
132
|
+
self.path = '/%s' % self.path
|
|
133
|
+
if not self.path.endswith('/') and len(self.path) > 1:
|
|
134
|
+
self.path = '%s/' % (self.path, )
|
|
135
|
+
|
|
136
|
+
def create_folder(self, remote_path):
|
|
137
|
+
if not self.folder_exists(remote_path):
|
|
138
|
+
self.request.web.get_folder_by_server_relative_url(self.path).add(remote_path).execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
def delete(self, remote_path):
|
|
142
|
+
webUri = '%s%s' % (self.path, remote_path)
|
|
143
|
+
return self.request.web.get_file_by_server_relative_url(webUri).delete_object().execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
144
|
+
|
|
145
|
+
def move(self, remote_path, dest, retry=True):
|
|
146
|
+
# Move file to folder
|
|
147
|
+
webUri = '%s%s' % (self.path, remote_path)
|
|
148
|
+
|
|
149
|
+
to_folder_dest = posixpath.join(self.path, dest)
|
|
150
|
+
self.request.web.get_file_by_server_relative_path(webUri).moveto(to_folder_dest, 1).execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
def move_to_file(self, remote_path, dest):
|
|
154
|
+
webUri = '%s%s' % (self.path, remote_path)
|
|
155
|
+
full_name_dest = '/'.join([self.path, dest])
|
|
156
|
+
|
|
157
|
+
dest_file_name = full_name_dest.split('/')[-1]
|
|
158
|
+
dest_folder = '/'.join(full_name_dest.split('/')[0:-1])
|
|
159
|
+
self.request.web.get_file_by_server_relative_path(webUri).move_to_newname(dest_folder, dest_file_name, 1).execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
def upload(self, fileobj, remote_path, buffer_size=None, log=False, progress_obj=False, continuation=False):
|
|
163
|
+
split_name = remote_path.split('/')
|
|
164
|
+
new_file = split_name.pop()
|
|
165
|
+
split_name.insert(0, self.path)
|
|
166
|
+
path = '/'.join(split_name)
|
|
167
|
+
if path[-1] != '/':
|
|
168
|
+
path += '/'
|
|
169
|
+
|
|
170
|
+
if buffer_size is None:
|
|
171
|
+
buffer_size = 10 * 1024 * 1024
|
|
172
|
+
|
|
173
|
+
if progress_obj:
|
|
174
|
+
log = True
|
|
175
|
+
|
|
176
|
+
report_method = None
|
|
177
|
+
def report_progress(uploaded, **a):
|
|
178
|
+
percent_txt = ''
|
|
179
|
+
if size:
|
|
180
|
+
percent = round(uploaded*100/size)
|
|
181
|
+
percent_txt = '%d%%' % percent
|
|
182
|
+
if progress_obj:
|
|
183
|
+
progress_obj.write({'name': percent})
|
|
184
|
+
|
|
185
|
+
if logger:
|
|
186
|
+
logger.info('OneDrive: %d bytes sent on %s bytes %s' % (uploaded, size or 'unknown', percent_txt))
|
|
187
|
+
|
|
188
|
+
if log:
|
|
189
|
+
logger = logging.getLogger('cloud.backup')
|
|
190
|
+
try:
|
|
191
|
+
size = os.path.getsize(fileobj.name)
|
|
192
|
+
except:
|
|
193
|
+
size = None
|
|
194
|
+
report_method = report_progress
|
|
195
|
+
|
|
196
|
+
target_folder = self.request.web.get_folder_by_server_relative_url(path)
|
|
197
|
+
return target_folder.files.create_upload_session(
|
|
198
|
+
fileobj, buffer_size, chunk_uploaded=report_method, file_name=new_file
|
|
199
|
+
).execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
200
|
+
|
|
201
|
+
def list(self, remote_path):
|
|
202
|
+
if not remote_path.startswith(self.path):
|
|
203
|
+
remote_path = posixpath.join(self.path, remote_path)
|
|
204
|
+
return (
|
|
205
|
+
self.request.web.get_folder_by_server_relative_path(remote_path)
|
|
206
|
+
.get_files()
|
|
207
|
+
.expand(["TimeLastModified"])
|
|
208
|
+
.execute_query_retry(max_retry=self.max_retry)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def folder_exists(self, remote_path):
|
|
212
|
+
webUri = '%s%s' % (self.path, remote_path)
|
|
213
|
+
try:
|
|
214
|
+
self.request.web.get_folder_by_server_relative_url(webUri).get().execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
215
|
+
except ClientRequestException as e:
|
|
216
|
+
if e.response.status_code == 404:
|
|
217
|
+
return False
|
|
218
|
+
raise
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
def download(self, remote_path, filename):
|
|
222
|
+
if not remote_path.startswith(self.path):
|
|
223
|
+
remote_path = posixpath.join(self.path, remote_path)
|
|
224
|
+
|
|
225
|
+
src = self.request.web.get_file_by_server_relative_path(remote_path)
|
|
226
|
+
with open(filename, 'wb') as file:
|
|
227
|
+
src.download_session(file).execute_query_with_incremental_retry(max_retry=self.max_retry)
|
|
228
|
+
return filename
|
|
229
|
+
|
ufload3-2.0/ufload3/webdav.py
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
import requests
|
|
4
|
-
from office365.sharepoint.client_context import ClientContext
|
|
5
|
-
from office365.runtime.http.request_options import RequestOptions
|
|
6
|
-
from office365.runtime.http.http_method import HttpMethod
|
|
7
|
-
from office365.runtime.auth.providers.saml_token_provider import SamlTokenProvider
|
|
8
|
-
import uuid
|
|
9
|
-
import logging
|
|
10
|
-
import os
|
|
11
|
-
import posixpath
|
|
12
|
-
import time
|
|
13
|
-
from urllib.parse import urlparse, urljoin
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ConnectionFailed(Exception):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
class PasswordFailed(Exception):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
class Client(object):
|
|
23
|
-
def __init__(self, host, port=0, auth=None, username=None, password=None, tenant=None, client_id=None, thumbprint=None, cert_content=None, protocol='http', path=None):
|
|
24
|
-
self.requests_timeout = 45
|
|
25
|
-
self.session_uuid = False
|
|
26
|
-
self.session_offset = -1
|
|
27
|
-
self.session_nb_error = 0
|
|
28
|
-
|
|
29
|
-
self.username = username
|
|
30
|
-
self.password = password
|
|
31
|
-
|
|
32
|
-
self.tenant = tenant
|
|
33
|
-
self.client_id = client_id
|
|
34
|
-
self.thumbprint = thumbprint
|
|
35
|
-
self.cert_content = cert_content
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.path = path or ''
|
|
39
|
-
|
|
40
|
-
self.url = '{0}://{1}'.format(protocol, host)
|
|
41
|
-
|
|
42
|
-
self.login()
|
|
43
|
-
|
|
44
|
-
def login(self):
|
|
45
|
-
baseurl = urljoin(self.url, self.path)
|
|
46
|
-
self.request = ClientContext(baseurl)
|
|
47
|
-
|
|
48
|
-
if not self.tenant:
|
|
49
|
-
self.request.with_user_credentials(self.username, self.password)
|
|
50
|
-
if not isinstance(self.request.authentication_context._provider, SamlTokenProvider) or \
|
|
51
|
-
not self.request.authentication_context._provider.get_authentication_cookie():
|
|
52
|
-
raise requests.exceptions.RequestException(self.request.get_last_error())
|
|
53
|
-
|
|
54
|
-
# get the server_site url
|
|
55
|
-
if not self.path.startswith('/'):
|
|
56
|
-
self.path = '/%s' % self.path
|
|
57
|
-
options = RequestOptions(self.url)
|
|
58
|
-
self.request._ensure_form_digest(options)
|
|
59
|
-
baseurl = self.request._contextWebInformation.WebFullUrl
|
|
60
|
-
else:
|
|
61
|
-
self.ctx = self.request.with_client_certificate(
|
|
62
|
-
tenant=self.tenant,
|
|
63
|
-
client_id=self.client_id,
|
|
64
|
-
#cert_path=self.cert_path,
|
|
65
|
-
private_key=self.cert_content,
|
|
66
|
-
thumbprint=self.thumbprint,
|
|
67
|
-
)
|
|
68
|
-
self.ctx.web.get().execute_query()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if not baseurl:
|
|
72
|
-
raise requests.exceptions.RequestException("Full Url not found %s" % self.path)
|
|
73
|
-
if not baseurl.endswith('/'):
|
|
74
|
-
baseurl = '%s/' % baseurl
|
|
75
|
-
parsed_base = urlparse(baseurl).path
|
|
76
|
-
self.baseurl = '%s%s' % (self.url, parsed_base)
|
|
77
|
-
if not self.path.startswith('/'):
|
|
78
|
-
self.path = '/%s' % self.path
|
|
79
|
-
if not self.path.endswith('/') and len(self.path) > 1:
|
|
80
|
-
self.path = '%s/' % (self.path, )
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def format_request(self, url, method='POST', data="", session=False):
|
|
84
|
-
assert(method in ['POST', 'DELETE'])
|
|
85
|
-
r_meth = {
|
|
86
|
-
'POST': HttpMethod.Post,
|
|
87
|
-
'DELETE': HttpMethod.Delete,
|
|
88
|
-
}
|
|
89
|
-
options = RequestOptions(url)
|
|
90
|
-
options.method = r_meth[method]
|
|
91
|
-
options.set_header("X-HTTP-Method", method)
|
|
92
|
-
options.set_header('Accept', 'application/json')
|
|
93
|
-
options.set_header('Content-Type', 'application/json')
|
|
94
|
-
self.request._authenticate_request(options)
|
|
95
|
-
self.request._ensure_form_digest(options)
|
|
96
|
-
|
|
97
|
-
if session:
|
|
98
|
-
return session.post(url, data=data, headers=options.headers, auth=options.auth, timeout=self.requests_timeout)
|
|
99
|
-
|
|
100
|
-
return requests.post(url, data=data, headers=options.headers, auth=options.auth, timeout=self.requests_timeout)
|
|
101
|
-
|
|
102
|
-
def parse_error(self, result):
|
|
103
|
-
try:
|
|
104
|
-
if 'application/json' in result.headers.get('Content-Type'):
|
|
105
|
-
resp_content = result.json()
|
|
106
|
-
msg = resp_content['odata.error']['message']
|
|
107
|
-
error = []
|
|
108
|
-
if isinstance(msg, dict):
|
|
109
|
-
error = [msg['value']]
|
|
110
|
-
else:
|
|
111
|
-
error = [msg]
|
|
112
|
-
if resp_content['odata.error'].get('code'):
|
|
113
|
-
error.append('Code: %s' % resp_content['odata.error']['code'])
|
|
114
|
-
return ' '.join(error)
|
|
115
|
-
except:
|
|
116
|
-
pass
|
|
117
|
-
return result.content
|
|
118
|
-
|
|
119
|
-
def create_folder(self, remote_path):
|
|
120
|
-
webUri = '%s%s' % (self.path, remote_path)
|
|
121
|
-
request_url = "%s_api/web/GetFolderByServerRelativeUrl('%s')" % (self.baseurl, webUri)
|
|
122
|
-
result = self.format_request(request_url, 'POST')
|
|
123
|
-
if result.status_code not in (200, 201):
|
|
124
|
-
result = self.format_request("%s_api/Web/Folders/add('%s')" % (self.baseurl, webUri), 'POST')
|
|
125
|
-
if result.status_code not in (200, 201):
|
|
126
|
-
raise Exception(self.parse_error(result))
|
|
127
|
-
return True
|
|
128
|
-
|
|
129
|
-
def delete(self, remote_path):
|
|
130
|
-
webUri = '%s%s' % (self.path, remote_path)
|
|
131
|
-
request_url = "%s_api/web/getfilebyserverrelativeurl('%s')" % (self.baseurl, webUri)
|
|
132
|
-
result = self.format_request(request_url, 'DELETE')
|
|
133
|
-
if result.status_code not in (200, 201):
|
|
134
|
-
raise Exception(self.parse_error(result))
|
|
135
|
-
return True
|
|
136
|
-
|
|
137
|
-
def move(self, remote_path, dest, retry=True):
|
|
138
|
-
webUri = '%s%s' % (self.path, remote_path)
|
|
139
|
-
destUri = '%s%s' % (self.path, dest)
|
|
140
|
-
# falgs=1 to overwrite existing file
|
|
141
|
-
request_url = "%s_api/web/getfilebyserverrelativeurl('%s')/moveto(newurl='%s',flags=1)" % (self.baseurl, webUri, destUri)
|
|
142
|
-
result = self.format_request(request_url, 'POST')
|
|
143
|
-
if result.status_code not in (200, 201):
|
|
144
|
-
error = self.parse_error(result)
|
|
145
|
-
if retry and ('timed out' in error or '2130575252' in error):
|
|
146
|
-
logging.getLogger('cloud.backup').info('OneDrive move: session time out')
|
|
147
|
-
self.login()
|
|
148
|
-
return self.move(remote_path, dest, retry=False)
|
|
149
|
-
raise Exception(self.parse_error(result))
|
|
150
|
-
return True
|
|
151
|
-
|
|
152
|
-
def upload(self, fileobj, remote_path, buffer_size=None, log=False, progress_obj=False, continuation=False):
|
|
153
|
-
if not self.session_uuid:
|
|
154
|
-
self.session_uuid = uuid.uuid1()
|
|
155
|
-
|
|
156
|
-
if progress_obj:
|
|
157
|
-
log = True
|
|
158
|
-
|
|
159
|
-
if log:
|
|
160
|
-
logger = logging.getLogger('cloud.backup')
|
|
161
|
-
try:
|
|
162
|
-
size = os.path.getsize(fileobj.name)
|
|
163
|
-
except:
|
|
164
|
-
size = None
|
|
165
|
-
|
|
166
|
-
if not continuation:
|
|
167
|
-
self.session_offset = -1
|
|
168
|
-
|
|
169
|
-
if self.session_offset != -1:
|
|
170
|
-
fileobj.seek(self.session_offset)
|
|
171
|
-
|
|
172
|
-
if not buffer_size:
|
|
173
|
-
buffer_size = 10* 1024 * 1024
|
|
174
|
-
|
|
175
|
-
x = ""
|
|
176
|
-
split_name = remote_path.split('/')
|
|
177
|
-
new_file = split_name.pop()
|
|
178
|
-
split_name.insert(0, self.path)
|
|
179
|
-
path = '/'.join(split_name)
|
|
180
|
-
if path[-1] != '/':
|
|
181
|
-
path += '/'
|
|
182
|
-
webUri = '%s%s' % (path, new_file)
|
|
183
|
-
s = requests.Session()
|
|
184
|
-
|
|
185
|
-
while True:
|
|
186
|
-
if self.session_offset == -1:
|
|
187
|
-
# first loop create an empty file
|
|
188
|
-
request_url = "%s_api/web/GetFolderByServerRelativeUrl('%s')/Files/add(url='%s',overwrite=true)" % (self.baseurl, path, new_file)
|
|
189
|
-
self.session_offset = 0
|
|
190
|
-
else:
|
|
191
|
-
x = fileobj.read(buffer_size)
|
|
192
|
-
if not x:
|
|
193
|
-
break
|
|
194
|
-
if not self.session_offset:
|
|
195
|
-
# 2nd loop
|
|
196
|
-
if len(x) == buffer_size:
|
|
197
|
-
# split needed
|
|
198
|
-
request_url="%s_api/web/getfilebyserverrelativeurl('%s')/startupload(uploadId=guid'%s')" % (self.baseurl, webUri, self.session_uuid)
|
|
199
|
-
else:
|
|
200
|
-
# file size < buffer: no need to split
|
|
201
|
-
request_url = "%s_api/web/GetFolderByServerRelativeUrl('%s')/Files/add(url='%s',overwrite=true)" % (self.baseurl, path, new_file)
|
|
202
|
-
elif len(x) == buffer_size:
|
|
203
|
-
request_url = "%s_api/web/getfilebyserverrelativeurl('%s')/continueupload(uploadId=guid'%s',fileOffset=%s)" % (self.baseurl, webUri, self.session_uuid, self.session_offset)
|
|
204
|
-
else:
|
|
205
|
-
request_url = "%s_api/web/getfilebyserverrelativeurl('%s')/finishupload(uploadId=guid'%s',fileOffset=%s)" % (self.baseurl, webUri, self.session_uuid, self.session_offset)
|
|
206
|
-
|
|
207
|
-
result = self.format_request(request_url, method='POST', data=x, session=s)
|
|
208
|
-
if result.status_code not in (200, 201):
|
|
209
|
-
return (False, self.parse_error(result))
|
|
210
|
-
self.session_nb_error = 0
|
|
211
|
-
self.session_offset += len(x)
|
|
212
|
-
|
|
213
|
-
if log and self.session_offset and self.session_offset % (buffer_size*5) == 0:
|
|
214
|
-
percent_txt = ''
|
|
215
|
-
if size:
|
|
216
|
-
percent = round(self.session_offset*100/size)
|
|
217
|
-
percent_txt = '%d%%' % percent
|
|
218
|
-
if progress_obj:
|
|
219
|
-
progress_obj.write({'name': percent})
|
|
220
|
-
|
|
221
|
-
logger.info('OneDrive: %d bytes sent on %s bytes %s' % (self.session_offset, size or 'unknown', percent_txt))
|
|
222
|
-
self.session_offset = -1
|
|
223
|
-
return (True, '')
|
|
224
|
-
|
|
225
|
-
def list(self, remote_path):
|
|
226
|
-
if not remote_path.startswith(self.path):
|
|
227
|
-
remote_path = posixpath.join(self.path, remote_path)
|
|
228
|
-
request_url = "%s_api/web/getfolderbyserverrelativeurl('%s')/files" % (self.baseurl, remote_path)
|
|
229
|
-
options = RequestOptions(request_url)
|
|
230
|
-
options.method = HttpMethod.Get
|
|
231
|
-
options.set_header("X-HTTP-Method", "GET")
|
|
232
|
-
options.set_header('accept', 'application/json;odata=verbose')
|
|
233
|
-
self.request._authenticate_request(options)
|
|
234
|
-
self.request._ensure_form_digest(options)
|
|
235
|
-
result = requests.get(url=request_url, headers=options.headers, auth=options.auth)
|
|
236
|
-
if result.status_code not in (200, 201):
|
|
237
|
-
raise requests.exceptions.RequestException(self.parse_error(result))
|
|
238
|
-
|
|
239
|
-
result = result.json()
|
|
240
|
-
files=[]
|
|
241
|
-
for i in range(len(result['d']['results'])):
|
|
242
|
-
item = result['d']['results'][i]
|
|
243
|
-
files.append(item)
|
|
244
|
-
return files
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def download(self, remote_path, filename):
|
|
248
|
-
if not remote_path.startswith(self.path):
|
|
249
|
-
remote_path = posixpath.join(self.path, remote_path)
|
|
250
|
-
request_url = "%s_api/web/getfilebyserverrelativeurl('%s')/$value" % (self.baseurl, remote_path)
|
|
251
|
-
options = RequestOptions(request_url)
|
|
252
|
-
options.method = HttpMethod.Get
|
|
253
|
-
options.set_header("X-HTTP-Method", "GET")
|
|
254
|
-
options.set_header('accept', 'application/json;odata=verbose')
|
|
255
|
-
retry = 5
|
|
256
|
-
while retry:
|
|
257
|
-
try:
|
|
258
|
-
self.request._authenticate_request(options)
|
|
259
|
-
self.request._ensure_form_digest(options)
|
|
260
|
-
with requests.get(url=request_url, headers=options.headers, auth=options.auth, stream=True, timeout=120) as r:
|
|
261
|
-
if r.status_code not in (200, 201):
|
|
262
|
-
error = self.parse_error(r)
|
|
263
|
-
raise requests.exceptions.RequestException(error)
|
|
264
|
-
|
|
265
|
-
with open(filename, 'wb') as file:
|
|
266
|
-
for chunk in r.iter_content(chunk_size=8192):
|
|
267
|
-
if chunk:
|
|
268
|
-
file.write(chunk)
|
|
269
|
-
except requests.exceptions.RequestException:
|
|
270
|
-
time.sleep(3)
|
|
271
|
-
self.login()
|
|
272
|
-
retry -= 1
|
|
273
|
-
if not retry:
|
|
274
|
-
raise
|
|
275
|
-
continue
|
|
276
|
-
|
|
277
|
-
retry = 0
|
|
278
|
-
|
|
279
|
-
return filename
|
|
280
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|