rclone-api 1.4.3__py2.py3-none-any.whl → 1.4.5__py2.py3-none-any.whl
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.
- rclone_api/detail/copy_file_parts.py +64 -48
- rclone_api/http_server.py +41 -27
- {rclone_api-1.4.3.dist-info → rclone_api-1.4.5.dist-info}/METADATA +1 -1
- {rclone_api-1.4.3.dist-info → rclone_api-1.4.5.dist-info}/RECORD +8 -8
- {rclone_api-1.4.3.dist-info → rclone_api-1.4.5.dist-info}/LICENSE +0 -0
- {rclone_api-1.4.3.dist-info → rclone_api-1.4.5.dist-info}/WHEEL +0 -0
- {rclone_api-1.4.3.dist-info → rclone_api-1.4.5.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.4.3.dist-info → rclone_api-1.4.5.dist-info}/top_level.txt +0 -0
@@ -8,8 +8,8 @@ from concurrent.futures import Future, ThreadPoolExecutor
|
|
8
8
|
from dataclasses import dataclass
|
9
9
|
from datetime import datetime
|
10
10
|
from pathlib import Path
|
11
|
-
from tempfile import TemporaryDirectory
|
12
11
|
|
12
|
+
from rclone_api import rclone_verbose
|
13
13
|
from rclone_api.dir_listing import DirListing
|
14
14
|
from rclone_api.http_server import HttpServer
|
15
15
|
from rclone_api.rclone_impl import RcloneImpl
|
@@ -19,6 +19,8 @@ from rclone_api.types import (
|
|
19
19
|
SizeSuffix,
|
20
20
|
)
|
21
21
|
|
22
|
+
rclone_verbose(True)
|
23
|
+
|
22
24
|
|
23
25
|
@dataclass
|
24
26
|
class UploadPart:
|
@@ -47,6 +49,11 @@ def upload_task(self: RcloneImpl, upload_part: UploadPart) -> UploadPart:
|
|
47
49
|
try:
|
48
50
|
if upload_part.exception is not None:
|
49
51
|
return upload_part
|
52
|
+
# print(f"Uploading {upload_part.chunk} to {upload_part.dst_part}")
|
53
|
+
msg = "\n#########################################\n"
|
54
|
+
msg += f"# Uploading {upload_part.chunk} to {upload_part.dst_part}\n"
|
55
|
+
msg += "#########################################\n"
|
56
|
+
print
|
50
57
|
self.copy_to(upload_part.chunk.as_posix(), upload_part.dst_part)
|
51
58
|
return upload_part
|
52
59
|
except Exception as e:
|
@@ -249,6 +256,8 @@ def copy_file_parts(
|
|
249
256
|
threads: int = 1,
|
250
257
|
) -> Exception | None:
|
251
258
|
"""Copy parts of a file from source to destination."""
|
259
|
+
from rclone_api.util import random_str
|
260
|
+
|
252
261
|
if dst_dir.endswith("/"):
|
253
262
|
dst_dir = dst_dir[:-1]
|
254
263
|
src_size = self.size_file(src)
|
@@ -311,65 +320,72 @@ def copy_file_parts(
|
|
311
320
|
print(info_json)
|
312
321
|
|
313
322
|
finished_tasks: list[UploadPart] = []
|
323
|
+
tmp_dir = str(Path("chunks") / random_str(12))
|
324
|
+
import atexit
|
325
|
+
import shutil
|
326
|
+
|
327
|
+
atexit.register(lambda: shutil.rmtree(tmp_dir, ignore_errors=True))
|
314
328
|
|
315
329
|
with self.serve_http(src_dir) as http_server:
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
with ThreadPoolExecutor(max_workers=threads) as
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
330
|
+
tmpdir: Path = Path(tmp_dir)
|
331
|
+
write_semaphore = threading.Semaphore(threads)
|
332
|
+
with ThreadPoolExecutor(max_workers=threads) as upload_executor:
|
333
|
+
with ThreadPoolExecutor(max_workers=threads) as read_executor:
|
334
|
+
for part_info in part_infos:
|
335
|
+
part_number: int = part_info.part_number
|
336
|
+
range: Range = part_info.range
|
337
|
+
offset: SizeSuffix = SizeSuffix(range.start)
|
338
|
+
length: SizeSuffix = SizeSuffix(range.end - range.start)
|
339
|
+
end = offset + length
|
340
|
+
suffix = _gen_name(part_number, offset, end)
|
341
|
+
part_dst = f"{dst_dir}/{suffix}"
|
342
|
+
|
343
|
+
def _read_task(
|
344
|
+
src_name=src_name,
|
345
|
+
http_server=http_server,
|
346
|
+
tmpdir=tmpdir,
|
347
|
+
offset=offset,
|
348
|
+
length=length,
|
349
|
+
part_dst=part_dst,
|
350
|
+
) -> UploadPart:
|
351
|
+
return read_task(
|
331
352
|
src_name=src_name,
|
332
353
|
http_server=http_server,
|
333
354
|
tmpdir=tmpdir,
|
334
355
|
offset=offset,
|
335
356
|
length=length,
|
336
357
|
part_dst=part_dst,
|
337
|
-
)
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
#
|
350
|
-
|
351
|
-
|
352
|
-
)
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
upload_fut.add_done_callback(
|
362
|
-
lambda fut: finished_tasks.append(fut.result())
|
363
|
-
)
|
364
|
-
|
365
|
-
read_fut.add_done_callback(queue_upload_task)
|
366
|
-
# SEMAPHORE ACQUIRE!!!
|
367
|
-
# If we are back filled on the writers, then we stall.
|
368
|
-
write_semaphore.acquire()
|
358
|
+
)
|
359
|
+
|
360
|
+
read_fut: Future[UploadPart] = read_executor.submit(_read_task)
|
361
|
+
|
362
|
+
# Releases the semaphore when the write task is done
|
363
|
+
def queue_upload_task(
|
364
|
+
read_fut=read_fut,
|
365
|
+
) -> None:
|
366
|
+
upload_part = read_fut.result()
|
367
|
+
upload_fut: Future[UploadPart] = upload_executor.submit(
|
368
|
+
upload_task, self, upload_part
|
369
|
+
)
|
370
|
+
# SEMAPHORE RELEASE!!!
|
371
|
+
upload_fut.add_done_callback(
|
372
|
+
lambda _: write_semaphore.release()
|
373
|
+
)
|
374
|
+
upload_fut.add_done_callback(
|
375
|
+
lambda fut: finished_tasks.append(fut.result())
|
376
|
+
)
|
377
|
+
|
378
|
+
read_fut.add_done_callback(queue_upload_task)
|
379
|
+
# SEMAPHORE ACQUIRE!!!
|
380
|
+
# If we are back filled on the writers, then we stall.
|
381
|
+
write_semaphore.acquire()
|
369
382
|
|
370
383
|
exceptions: list[Exception] = [
|
371
384
|
t.exception for t in finished_tasks if t.exception is not None
|
372
385
|
]
|
386
|
+
|
387
|
+
shutil.rmtree(tmp_dir, ignore_errors=True)
|
388
|
+
|
373
389
|
if len(exceptions) > 0:
|
374
390
|
return Exception(f"Failed to copy parts: {exceptions}", exceptions)
|
375
391
|
|
rclone_api/http_server.py
CHANGED
@@ -3,6 +3,7 @@ Unit test file for testing rclone mount functionality.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import tempfile
|
6
|
+
import time
|
6
7
|
import warnings
|
7
8
|
from concurrent.futures import Future, ThreadPoolExecutor
|
8
9
|
from pathlib import Path
|
@@ -63,33 +64,46 @@ class HttpServer:
|
|
63
64
|
self, path: str, dst: Path, range: Range | None = None
|
64
65
|
) -> Path | Exception:
|
65
66
|
"""Get bytes from the server."""
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
67
|
+
|
68
|
+
def task() -> Path | Exception:
|
69
|
+
|
70
|
+
if not dst.parent.exists():
|
71
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
72
|
+
headers: dict[str, str] = {}
|
73
|
+
if range:
|
74
|
+
headers.update(range.to_header())
|
75
|
+
url = self._get_file_url(path)
|
76
|
+
try:
|
77
|
+
with httpx.stream(
|
78
|
+
"GET", url, headers=headers, timeout=_TIMEOUT
|
79
|
+
) as response:
|
80
|
+
response.raise_for_status()
|
81
|
+
with open(dst, "wb") as file:
|
82
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
83
|
+
if chunk:
|
84
|
+
file.write(chunk)
|
85
|
+
else:
|
86
|
+
assert response.is_closed
|
87
|
+
# print(f"Downloaded bytes {start}-{end} to {dst}")
|
88
|
+
if range:
|
89
|
+
print(f"Downloaded bytes {range.start}-{range.end} to {dst}")
|
90
|
+
else:
|
91
|
+
size = dst.stat().st_size
|
92
|
+
print(f"Downloaded {size} bytes to {dst}")
|
93
|
+
return dst
|
94
|
+
except Exception as e:
|
95
|
+
warnings.warn(f"Failed to download {url} to {dst}: {e}")
|
96
|
+
return e
|
97
|
+
|
98
|
+
retries = 3
|
99
|
+
for i in _range(retries):
|
100
|
+
out = task()
|
101
|
+
if not isinstance(out, Exception):
|
102
|
+
return out
|
103
|
+
warnings.warn(f"Failed to download {path} to {dst}: {out}, retrying ({i})")
|
104
|
+
time.sleep(10)
|
105
|
+
else:
|
106
|
+
return Exception(f"Failed to download {path} to {dst}")
|
93
107
|
|
94
108
|
def download_multi_threaded(
|
95
109
|
self,
|
@@ -14,7 +14,7 @@ rclone_api/file_part.py,sha256=i6ByS5_sae8Eba-4imBVTxd-xKC8ExWy7NR8QGr0ors,6155
|
|
14
14
|
rclone_api/file_stream.py,sha256=_W3qnwCuigqA0hzXl2q5pAxSZDRaUSwet4BkT0lpnzs,1431
|
15
15
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
16
16
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
17
|
-
rclone_api/http_server.py,sha256=
|
17
|
+
rclone_api/http_server.py,sha256=3fPBV6l50erTe32DyeJBNmsDrn5KuujsbmEAbx13T-c,8720
|
18
18
|
rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
|
19
19
|
rclone_api/mount.py,sha256=TE_VIBMW7J1UkF_6HRCt8oi_jGdMov4S51bm2OgxFAM,10045
|
20
20
|
rclone_api/process.py,sha256=BGXJTZVT__jeaDyjN8_kRycliOhkBErMPdHO1hKRvJE,5271
|
@@ -32,7 +32,7 @@ rclone_api/cmd/save_to_db.py,sha256=ylvnhg_yzexM-m6Zr7XDiswvoDVSl56ELuFAdb9gqBY,
|
|
32
32
|
rclone_api/db/__init__.py,sha256=OSRUdnSWUlDTOHmjdjVmxYTUNpTbtaJ5Ll9sl-PfZg0,40
|
33
33
|
rclone_api/db/db.py,sha256=YRnYrCaXHwytQt07uEZ_mMpvPHo9-0IWcOb95fVOOfs,10086
|
34
34
|
rclone_api/db/models.py,sha256=v7qaXUehvsDvU51uk69JI23fSIs9JFGcOa-Tv1c_wVs,1600
|
35
|
-
rclone_api/detail/copy_file_parts.py,sha256=
|
35
|
+
rclone_api/detail/copy_file_parts.py,sha256=UQ9nZQftZtUVLrofbG6Y1-rBppI8F4qE5lKAlAJC570,12642
|
36
36
|
rclone_api/detail/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
|
37
37
|
rclone_api/experimental/flags.py,sha256=qCVD--fSTmzlk9hloRLr0q9elzAOFzPsvVpKM3aB1Mk,2739
|
38
38
|
rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
|
@@ -47,9 +47,9 @@ rclone_api/s3/multipart/file_info.py,sha256=8v_07_eADo0K-Nsv7F0Ac1wcv3lkIsrR3MaR
|
|
47
47
|
rclone_api/s3/multipart/finished_piece.py,sha256=TcwA58-qgKBiskfHrePoCWaSSep6Za9psZEpzrLUUhE,1199
|
48
48
|
rclone_api/s3/multipart/upload_info.py,sha256=d6_OfzFR_vtDzCEegFfzCfWi2kUBUV4aXZzqAEVp1c4,1874
|
49
49
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
50
|
-
rclone_api-1.4.
|
51
|
-
rclone_api-1.4.
|
52
|
-
rclone_api-1.4.
|
53
|
-
rclone_api-1.4.
|
54
|
-
rclone_api-1.4.
|
55
|
-
rclone_api-1.4.
|
50
|
+
rclone_api-1.4.5.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
51
|
+
rclone_api-1.4.5.dist-info/METADATA,sha256=6U0dTVTIP3w6wYGOeDTMJDjPWOUjEe-3kft24IlVjYc,4627
|
52
|
+
rclone_api-1.4.5.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
53
|
+
rclone_api-1.4.5.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
|
54
|
+
rclone_api-1.4.5.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
55
|
+
rclone_api-1.4.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|