lfss 0.7.3__tar.gz → 0.7.4__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.
- {lfss-0.7.3 → lfss-0.7.4}/PKG-INFO +1 -1
- {lfss-0.7.3 → lfss-0.7.4}/docs/Permission.md +7 -0
- lfss-0.7.4/lfss/cli/cli.py +95 -0
- lfss-0.7.4/lfss/client/__init__.py +155 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/client/api.py +2 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/config.py +0 -3
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/database.py +2 -2
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/server.py +24 -5
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/utils.py +4 -0
- {lfss-0.7.3 → lfss-0.7.4}/pyproject.toml +1 -1
- lfss-0.7.3/lfss/cli/cli.py +0 -52
- lfss-0.7.3/lfss/client/__init__.py +0 -60
- {lfss-0.7.3 → lfss-0.7.4}/Readme.md +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/docs/Known_issues.md +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/api.js +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/index.html +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/popup.css +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/popup.js +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/scripts.js +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/styles.css +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/frontend/utils.js +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/cli/balance.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/cli/panel.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/cli/serve.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/cli/user.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/sql/init.sql +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/sql/pragma.sql +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/__init__.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/connection_pool.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/datatype.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/error.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/log.py +0 -0
- {lfss-0.7.3 → lfss-0.7.4}/lfss/src/stat.py +0 -0
@@ -18,6 +18,13 @@ Non-admin users can access files based on:
|
|
18
18
|
- If the file is `unset`, then the file's permission is inherited from the owner's permission.
|
19
19
|
- If both the owner and the file have `unset` permission, then the file is `public`.
|
20
20
|
|
21
|
+
### Meta-data access
|
22
|
+
- Non-login users can't access any file-meta.
|
23
|
+
- All users can access the file-meta of files under their own path.
|
24
|
+
- For files under other users' path, the file-meta is determined in a way same as file access.
|
25
|
+
- Admins can access the path-meta of all users.
|
26
|
+
- All users can access the path-meta of their own path.
|
27
|
+
|
21
28
|
### Path-listing
|
22
29
|
- Non-login users cannot list any files.
|
23
30
|
- All users can list the files under their own path
|
@@ -0,0 +1,95 @@
|
|
1
|
+
from lfss.client import Connector, upload_directory, upload_file, download_file, download_directory
|
2
|
+
from lfss.src.datatype import FileReadPermission
|
3
|
+
from pathlib import Path
|
4
|
+
import argparse
|
5
|
+
|
6
|
+
def parse_arguments():
|
7
|
+
parser = argparse.ArgumentParser(description="Command line interface, please set LFSS_ENDPOINT and LFSS_TOKEN environment variables.")
|
8
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
9
|
+
|
10
|
+
sp = parser.add_subparsers(dest="command", required=True)
|
11
|
+
|
12
|
+
# upload
|
13
|
+
sp_upload = sp.add_parser("upload", help="Upload files")
|
14
|
+
sp_upload.add_argument("src", help="Source file or directory", type=str)
|
15
|
+
sp_upload.add_argument("dst", help="Destination url path", type=str)
|
16
|
+
sp_upload.add_argument("-j", "--jobs", type=int, default=1, help="Number of concurrent uploads")
|
17
|
+
sp_upload.add_argument("--interval", type=float, default=0, help="Interval between files, only works with directory upload")
|
18
|
+
sp_upload.add_argument("--conflict", choices=["overwrite", "abort", "skip", "skip-ahead"], default="abort", help="Conflict resolution")
|
19
|
+
sp_upload.add_argument("--permission", type=FileReadPermission, default=FileReadPermission.UNSET, help="File permission")
|
20
|
+
sp_upload.add_argument("--retries", type=int, default=0, help="Number of retries")
|
21
|
+
|
22
|
+
# download
|
23
|
+
sp_download = sp.add_parser("download", help="Download files")
|
24
|
+
sp_download.add_argument("src", help="Source url path", type=str)
|
25
|
+
sp_download.add_argument("dst", help="Destination file or directory", type=str)
|
26
|
+
sp_download.add_argument("-j", "--jobs", type=int, default=1, help="Number of concurrent downloads")
|
27
|
+
sp_download.add_argument("--interval", type=float, default=0, help="Interval between files, only works with directory download")
|
28
|
+
sp_download.add_argument("--overwrite", action="store_true", help="Overwrite existing files")
|
29
|
+
sp_download.add_argument("--retries", type=int, default=0, help="Number of retries")
|
30
|
+
|
31
|
+
return parser.parse_args()
|
32
|
+
|
33
|
+
def main():
|
34
|
+
args = parse_arguments()
|
35
|
+
connector = Connector()
|
36
|
+
if args.command == "upload":
|
37
|
+
src_path = Path(args.src)
|
38
|
+
if src_path.is_dir():
|
39
|
+
failed_upload = upload_directory(
|
40
|
+
connector, args.src, args.dst,
|
41
|
+
verbose=args.verbose,
|
42
|
+
n_concurrent=args.jobs,
|
43
|
+
n_retries=args.retries,
|
44
|
+
interval=args.interval,
|
45
|
+
conflict=args.conflict,
|
46
|
+
permission=args.permission
|
47
|
+
)
|
48
|
+
if failed_upload:
|
49
|
+
print("Failed to upload:")
|
50
|
+
for path in failed_upload:
|
51
|
+
print(f" {path}")
|
52
|
+
else:
|
53
|
+
success = upload_file(
|
54
|
+
connector,
|
55
|
+
file_path = args.src,
|
56
|
+
dst_url = args.dst,
|
57
|
+
verbose=args.verbose,
|
58
|
+
n_retries=args.retries,
|
59
|
+
interval=args.interval,
|
60
|
+
conflict=args.conflict,
|
61
|
+
permission=args.permission
|
62
|
+
)
|
63
|
+
if not success:
|
64
|
+
print("Failed to upload.")
|
65
|
+
|
66
|
+
elif args.command == "download":
|
67
|
+
is_dir = args.src.endswith("/")
|
68
|
+
if is_dir:
|
69
|
+
failed_download = download_directory(
|
70
|
+
connector, args.src, args.dst,
|
71
|
+
verbose=args.verbose,
|
72
|
+
n_concurrent=args.jobs,
|
73
|
+
n_retries=args.retries,
|
74
|
+
interval=args.interval,
|
75
|
+
overwrite=args.overwrite
|
76
|
+
)
|
77
|
+
if failed_download:
|
78
|
+
print("Failed to download:")
|
79
|
+
for path in failed_download:
|
80
|
+
print(f" {path}")
|
81
|
+
else:
|
82
|
+
success = download_file(
|
83
|
+
connector,
|
84
|
+
src_url = args.src,
|
85
|
+
file_path = args.dst,
|
86
|
+
verbose=args.verbose,
|
87
|
+
n_retries=args.retries,
|
88
|
+
interval=args.interval,
|
89
|
+
overwrite=args.overwrite
|
90
|
+
)
|
91
|
+
if not success:
|
92
|
+
print("Failed to download.")
|
93
|
+
else:
|
94
|
+
raise NotImplementedError(f"Command {args.command} not implemented.")
|
95
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
import os, time, pathlib
|
2
|
+
from threading import Lock
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
4
|
+
from .api import Connector
|
5
|
+
|
6
|
+
def upload_file(
|
7
|
+
connector: Connector,
|
8
|
+
file_path: str,
|
9
|
+
dst_url: str,
|
10
|
+
n_retries: int = 0,
|
11
|
+
interval: float = 0,
|
12
|
+
verbose: bool = False,
|
13
|
+
**put_kwargs
|
14
|
+
):
|
15
|
+
this_try = 0
|
16
|
+
while this_try <= n_retries:
|
17
|
+
try:
|
18
|
+
with open(file_path, 'rb') as f:
|
19
|
+
blob = f.read()
|
20
|
+
connector.put(dst_url, blob, **put_kwargs)
|
21
|
+
break
|
22
|
+
except Exception as e:
|
23
|
+
if isinstance(e, KeyboardInterrupt):
|
24
|
+
raise e
|
25
|
+
if verbose:
|
26
|
+
print(f"Error uploading {file_path}: {e}, retrying...")
|
27
|
+
this_try += 1
|
28
|
+
finally:
|
29
|
+
time.sleep(interval)
|
30
|
+
|
31
|
+
if this_try > n_retries:
|
32
|
+
if verbose:
|
33
|
+
print(f"Failed to upload {file_path} after {n_retries} retries.")
|
34
|
+
return False
|
35
|
+
return True
|
36
|
+
|
37
|
+
def upload_directory(
|
38
|
+
connector: Connector,
|
39
|
+
directory: str,
|
40
|
+
path: str,
|
41
|
+
n_concurrent: int = 1,
|
42
|
+
n_retries: int = 0,
|
43
|
+
interval: float = 0,
|
44
|
+
verbose: bool = False,
|
45
|
+
**put_kwargs
|
46
|
+
) -> list[str]:
|
47
|
+
assert path.endswith('/'), "Path must end with a slash."
|
48
|
+
if path.startswith('/'):
|
49
|
+
path = path[1:]
|
50
|
+
directory = str(directory)
|
51
|
+
|
52
|
+
_counter = 0
|
53
|
+
_counter_lock = Lock()
|
54
|
+
|
55
|
+
faild_files = []
|
56
|
+
def put_file(file_path):
|
57
|
+
with _counter_lock:
|
58
|
+
nonlocal _counter
|
59
|
+
_counter += 1
|
60
|
+
this_count = _counter
|
61
|
+
dst_path = f"{path}{os.path.relpath(file_path, directory)}"
|
62
|
+
if verbose:
|
63
|
+
print(f"[{this_count}] Uploading {file_path} to {dst_path}")
|
64
|
+
|
65
|
+
if not upload_file(
|
66
|
+
connector, file_path, dst_path,
|
67
|
+
n_retries=n_retries, interval=interval, verbose=verbose, **put_kwargs
|
68
|
+
):
|
69
|
+
faild_files.append(file_path)
|
70
|
+
|
71
|
+
with ThreadPoolExecutor(n_concurrent) as executor:
|
72
|
+
for root, dirs, files in os.walk(directory):
|
73
|
+
for file in files:
|
74
|
+
executor.submit(put_file, os.path.join(root, file))
|
75
|
+
|
76
|
+
return faild_files
|
77
|
+
|
78
|
+
def download_file(
|
79
|
+
connector: Connector,
|
80
|
+
src_url: str,
|
81
|
+
file_path: str,
|
82
|
+
n_retries: int = 0,
|
83
|
+
interval: float = 0,
|
84
|
+
verbose: bool = False,
|
85
|
+
overwrite: bool = False
|
86
|
+
):
|
87
|
+
this_try = 0
|
88
|
+
while this_try <= n_retries:
|
89
|
+
if not overwrite and os.path.exists(file_path):
|
90
|
+
if verbose:
|
91
|
+
print(f"File {file_path} already exists, skipping download.")
|
92
|
+
return True
|
93
|
+
try:
|
94
|
+
blob = connector.get(src_url)
|
95
|
+
if not blob:
|
96
|
+
return False
|
97
|
+
pathlib.Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
98
|
+
with open(file_path, 'wb') as f:
|
99
|
+
f.write(blob)
|
100
|
+
break
|
101
|
+
except Exception as e:
|
102
|
+
if isinstance(e, KeyboardInterrupt):
|
103
|
+
raise e
|
104
|
+
if verbose:
|
105
|
+
print(f"Error downloading {src_url}: {e}, retrying...")
|
106
|
+
this_try += 1
|
107
|
+
finally:
|
108
|
+
time.sleep(interval)
|
109
|
+
|
110
|
+
if this_try > n_retries:
|
111
|
+
if verbose:
|
112
|
+
print(f"Failed to download {src_url} after {n_retries} retries.")
|
113
|
+
return False
|
114
|
+
return True
|
115
|
+
|
116
|
+
def download_directory(
|
117
|
+
connector: Connector,
|
118
|
+
src_path: str,
|
119
|
+
directory: str,
|
120
|
+
n_concurrent: int = 1,
|
121
|
+
n_retries: int = 0,
|
122
|
+
interval: float = 0,
|
123
|
+
verbose: bool = False,
|
124
|
+
overwrite: bool = False
|
125
|
+
) -> list[str]:
|
126
|
+
|
127
|
+
directory = str(directory)
|
128
|
+
|
129
|
+
if not src_path.endswith('/'):
|
130
|
+
src_path += '/'
|
131
|
+
if not directory.endswith(os.sep):
|
132
|
+
directory += os.sep
|
133
|
+
|
134
|
+
_counter = 0
|
135
|
+
_counter_lock = Lock()
|
136
|
+
failed_files = []
|
137
|
+
def get_file(src_url):
|
138
|
+
nonlocal _counter, failed_files
|
139
|
+
with _counter_lock:
|
140
|
+
_counter += 1
|
141
|
+
this_count = _counter
|
142
|
+
dst_path = f"{directory}{os.path.relpath(src_url, src_path)}"
|
143
|
+
if verbose:
|
144
|
+
print(f"[{this_count}] Downloading {src_url} to {dst_path}")
|
145
|
+
|
146
|
+
if not download_file(
|
147
|
+
connector, src_url, dst_path,
|
148
|
+
n_retries=n_retries, interval=interval, verbose=verbose, overwrite=overwrite
|
149
|
+
):
|
150
|
+
failed_files.append(src_url)
|
151
|
+
|
152
|
+
with ThreadPoolExecutor(n_concurrent) as executor:
|
153
|
+
for file in connector.list_path(src_path).files:
|
154
|
+
executor.submit(get_file, file.url)
|
155
|
+
return failed_files
|
@@ -5,6 +5,7 @@ import urllib.parse
|
|
5
5
|
from lfss.src.datatype import (
|
6
6
|
FileReadPermission, FileRecord, DirectoryRecord, UserRecord, PathContents
|
7
7
|
)
|
8
|
+
from lfss.src.utils import ensure_uri_compnents
|
8
9
|
|
9
10
|
_default_endpoint = os.environ.get('LFSS_ENDPOINT', 'http://localhost:8000')
|
10
11
|
_default_token = os.environ.get('LFSS_TOKEN', '')
|
@@ -23,6 +24,7 @@ class Connector:
|
|
23
24
|
):
|
24
25
|
if path.startswith('/'):
|
25
26
|
path = path[1:]
|
27
|
+
path = ensure_uri_compnents(path)
|
26
28
|
def f(**kwargs):
|
27
29
|
url = f"{self.config['endpoint']}/{path}" + "?" + urllib.parse.urlencode(search_params)
|
28
30
|
headers: dict = kwargs.pop('headers', {})
|
@@ -15,6 +15,3 @@ LARGE_BLOB_DIR.mkdir(exist_ok=True)
|
|
15
15
|
LARGE_FILE_BYTES = 8 * 1024 * 1024 # 8MB
|
16
16
|
MAX_FILE_BYTES = 512 * 1024 * 1024 # 512MB
|
17
17
|
MAX_BUNDLE_BYTES = 512 * 1024 * 1024 # 512MB
|
18
|
-
|
19
|
-
def hash_credential(username, password):
|
20
|
-
return hashlib.sha256((username + password).encode()).hexdigest()
|
@@ -11,9 +11,9 @@ import aiofiles.os
|
|
11
11
|
|
12
12
|
from .connection_pool import execute_sql, unique_cursor, transaction
|
13
13
|
from .datatype import UserRecord, FileReadPermission, FileRecord, DirectoryRecord, PathContents
|
14
|
-
from .config import LARGE_BLOB_DIR
|
14
|
+
from .config import LARGE_BLOB_DIR
|
15
15
|
from .log import get_logger
|
16
|
-
from .utils import decode_uri_compnents
|
16
|
+
from .utils import decode_uri_compnents, hash_credential
|
17
17
|
from .error import *
|
18
18
|
|
19
19
|
class DBObjectBase(ABC):
|
@@ -339,15 +339,34 @@ async def bundle_files(path: str, user: UserRecord = Depends(registered_user)):
|
|
339
339
|
@router_api.get("/meta")
|
340
340
|
@handle_exception
|
341
341
|
async def get_file_meta(path: str, user: UserRecord = Depends(registered_user)):
|
342
|
+
"""
|
343
|
+
Permission:
|
344
|
+
for file:
|
345
|
+
if file is under user's path, return the meta,
|
346
|
+
else, determine by the permission same as get_file
|
347
|
+
for path:
|
348
|
+
if path is under user's path, return the meta, else return 403
|
349
|
+
"""
|
342
350
|
logger.info(f"GET meta({path}), user: {user.username}")
|
343
351
|
path = ensure_uri_compnents(path)
|
352
|
+
is_file = not path.endswith("/")
|
344
353
|
async with unique_cursor() as conn:
|
345
354
|
fconn = FileConn(conn)
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
355
|
+
if is_file:
|
356
|
+
record = await fconn.get_file_record(path)
|
357
|
+
if not record:
|
358
|
+
raise HTTPException(status_code=404, detail="File not found")
|
359
|
+
if not path.startswith(f"{user.username}/") and not user.is_admin:
|
360
|
+
uconn = UserConn(conn)
|
361
|
+
owner = await uconn.get_user_by_id(record.owner_id)
|
362
|
+
assert owner is not None, "Owner not found"
|
363
|
+
is_allowed, reason = check_user_permission(user, owner, record)
|
364
|
+
if not is_allowed:
|
365
|
+
raise HTTPException(status_code=403, detail=reason)
|
366
|
+
else:
|
367
|
+
record = await fconn.get_path_record(path)
|
368
|
+
if not path.startswith(f"{user.username}/") and not user.is_admin:
|
369
|
+
raise HTTPException(status_code=403, detail="Permission denied")
|
351
370
|
return record
|
352
371
|
|
353
372
|
@router_api.post("/meta")
|
lfss-0.7.3/lfss/cli/cli.py
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
from lfss.client import Connector, upload_directory
|
2
|
-
from lfss.src.datatype import FileReadPermission
|
3
|
-
from pathlib import Path
|
4
|
-
import argparse
|
5
|
-
|
6
|
-
def parse_arguments():
|
7
|
-
parser = argparse.ArgumentParser(description="Command line interface, please set LFSS_ENDPOINT and LFSS_TOKEN environment variables.")
|
8
|
-
|
9
|
-
sp = parser.add_subparsers(dest="command", required=True)
|
10
|
-
|
11
|
-
# upload
|
12
|
-
sp_upload = sp.add_parser("upload", help="Upload files")
|
13
|
-
sp_upload.add_argument("src", help="Source file or directory", type=str)
|
14
|
-
sp_upload.add_argument("dst", help="Destination path", type=str)
|
15
|
-
sp_upload.add_argument("-j", "--jobs", type=int, default=1, help="Number of concurrent uploads")
|
16
|
-
sp_upload.add_argument("--interval", type=float, default=0, help="Interval between files, only works with directory upload")
|
17
|
-
sp_upload.add_argument("--conflict", choices=["overwrite", "abort", "skip", "skip-ahead"], default="abort", help="Conflict resolution")
|
18
|
-
sp_upload.add_argument("--permission", type=FileReadPermission, default=FileReadPermission.UNSET, help="File permission")
|
19
|
-
sp_upload.add_argument("--retries", type=int, default=0, help="Number of retries, only works with directory upload")
|
20
|
-
|
21
|
-
return parser.parse_args()
|
22
|
-
|
23
|
-
def main():
|
24
|
-
args = parse_arguments()
|
25
|
-
connector = Connector()
|
26
|
-
if args.command == "upload":
|
27
|
-
src_path = Path(args.src)
|
28
|
-
if src_path.is_dir():
|
29
|
-
failed_upload = upload_directory(
|
30
|
-
connector, args.src, args.dst,
|
31
|
-
verbose=True,
|
32
|
-
n_concurrent=args.jobs,
|
33
|
-
n_reties=args.retries,
|
34
|
-
interval=args.interval,
|
35
|
-
conflict=args.conflict,
|
36
|
-
permission=args.permission
|
37
|
-
)
|
38
|
-
if failed_upload:
|
39
|
-
print("Failed to upload:")
|
40
|
-
for path in failed_upload:
|
41
|
-
print(f" {path}")
|
42
|
-
else:
|
43
|
-
with open(args.src, 'rb') as f:
|
44
|
-
connector.put(
|
45
|
-
args.dst,
|
46
|
-
f.read(),
|
47
|
-
conflict=args.conflict,
|
48
|
-
permission=args.permission
|
49
|
-
)
|
50
|
-
else:
|
51
|
-
raise NotImplementedError(f"Command {args.command} not implemented.")
|
52
|
-
|
@@ -1,60 +0,0 @@
|
|
1
|
-
import os, time
|
2
|
-
from threading import Lock
|
3
|
-
from concurrent.futures import ThreadPoolExecutor
|
4
|
-
from .api import Connector
|
5
|
-
|
6
|
-
def upload_directory(
|
7
|
-
connector: Connector,
|
8
|
-
directory: str,
|
9
|
-
path: str,
|
10
|
-
n_concurrent: int = 1,
|
11
|
-
n_reties: int = 0,
|
12
|
-
interval: float = 0,
|
13
|
-
verbose: bool = False,
|
14
|
-
**put_kwargs
|
15
|
-
) -> list[str]:
|
16
|
-
assert path.endswith('/'), "Path must end with a slash."
|
17
|
-
if path.startswith('/'):
|
18
|
-
path = path[1:]
|
19
|
-
|
20
|
-
_counter = 0
|
21
|
-
_counter_lock = Lock()
|
22
|
-
|
23
|
-
faild_files = []
|
24
|
-
def put_file(file_path):
|
25
|
-
with _counter_lock:
|
26
|
-
nonlocal _counter
|
27
|
-
_counter += 1
|
28
|
-
this_count = _counter
|
29
|
-
dst_path = f"{path}{os.path.relpath(file_path, directory)}"
|
30
|
-
if verbose:
|
31
|
-
print(f"[{this_count}] Uploading {file_path} to {dst_path}")
|
32
|
-
|
33
|
-
this_try = 0
|
34
|
-
with open(file_path, 'rb') as f:
|
35
|
-
blob = f.read()
|
36
|
-
|
37
|
-
while this_try <= n_reties:
|
38
|
-
try:
|
39
|
-
connector.put(dst_path, blob, **put_kwargs)
|
40
|
-
break
|
41
|
-
except Exception as e:
|
42
|
-
if isinstance(e, KeyboardInterrupt):
|
43
|
-
raise e
|
44
|
-
if verbose:
|
45
|
-
print(f"[{this_count}] Error uploading {file_path}: {e}, retrying...")
|
46
|
-
this_try += 1
|
47
|
-
finally:
|
48
|
-
time.sleep(interval)
|
49
|
-
|
50
|
-
if this_try > n_reties:
|
51
|
-
faild_files.append(file_path)
|
52
|
-
if verbose:
|
53
|
-
print(f"[{this_count}] Failed to upload {file_path} after {n_reties} retries.")
|
54
|
-
|
55
|
-
with ThreadPoolExecutor(n_concurrent) as executor:
|
56
|
-
for root, dirs, files in os.walk(directory):
|
57
|
-
for file in files:
|
58
|
-
executor.submit(put_file, os.path.join(root, file))
|
59
|
-
|
60
|
-
return faild_files
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|