rclone-api 1.5.63__py3-none-any.whl → 1.5.65__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/__init__.py +14 -0
- rclone_api/s3/multipart/upload_parts_resumable.py +65 -8
- {rclone_api-1.5.63.dist-info → rclone_api-1.5.65.dist-info}/METADATA +1 -1
- {rclone_api-1.5.63.dist-info → rclone_api-1.5.65.dist-info}/RECORD +8 -8
- {rclone_api-1.5.63.dist-info → rclone_api-1.5.65.dist-info}/WHEEL +1 -1
- {rclone_api-1.5.63.dist-info → rclone_api-1.5.65.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.63.dist-info → rclone_api-1.5.65.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.63.dist-info → rclone_api-1.5.65.dist-info}/top_level.txt +0 -0
rclone_api/__init__.py
CHANGED
@@ -7,6 +7,8 @@ The API wraps the rclone command-line tool, providing a Pythonic interface
|
|
7
7
|
for common operations like copying, listing, and managing remote storage.
|
8
8
|
"""
|
9
9
|
|
10
|
+
import os
|
11
|
+
|
10
12
|
# Import core components and utilities
|
11
13
|
from datetime import datetime
|
12
14
|
from pathlib import Path
|
@@ -64,6 +66,18 @@ def rclone_verbose(val: bool | None) -> bool:
|
|
64
66
|
return _rclone_verbose(val)
|
65
67
|
|
66
68
|
|
69
|
+
class Logging:
|
70
|
+
@staticmethod
|
71
|
+
def enable_upload_parts_logging(value: bool) -> None:
|
72
|
+
"""
|
73
|
+
Enable or disable logging of upload parts.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
value: If True, enables upload parts logging; otherwise disables it.
|
77
|
+
"""
|
78
|
+
os.environ["LOG_UPLOAD_S3_RESUMABLE"] = "1" if value else "0"
|
79
|
+
|
80
|
+
|
67
81
|
class Rclone:
|
68
82
|
"""
|
69
83
|
Main interface for interacting with Rclone.
|
@@ -17,11 +17,28 @@ from rclone_api.types import (
|
|
17
17
|
SizeSuffix,
|
18
18
|
)
|
19
19
|
|
20
|
+
_LOCK = threading.Lock()
|
21
|
+
|
22
|
+
|
23
|
+
def _maybe_log(msg: str) -> None:
|
24
|
+
print(msg)
|
25
|
+
if os.getenv("LOG_UPLOAD_S3_RESUMABLE") == "1":
|
26
|
+
log_path = Path("log") / "s3_resumable_upload.log"
|
27
|
+
with _LOCK:
|
28
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
29
|
+
# log_path.write_text(msg, append=True)
|
30
|
+
with open(log_path, mode="a", encoding="utf-8") as f:
|
31
|
+
f.write(msg)
|
32
|
+
f.write("\n")
|
33
|
+
|
20
34
|
|
21
35
|
@dataclass
|
22
36
|
class UploadPart:
|
23
37
|
chunk: Path
|
24
38
|
dst_part: str
|
39
|
+
part_num: int
|
40
|
+
total_parts: int
|
41
|
+
total_size: SizeSuffix
|
25
42
|
exception: Exception | None = None
|
26
43
|
finished: bool = False
|
27
44
|
|
@@ -46,10 +63,18 @@ def upload_task(self: RcloneImpl, upload_part: UploadPart) -> UploadPart:
|
|
46
63
|
if upload_part.exception is not None:
|
47
64
|
return upload_part
|
48
65
|
# print(f"Uploading {upload_part.chunk} to {upload_part.dst_part}")
|
49
|
-
|
66
|
+
num_parts = upload_part.total_parts
|
67
|
+
total_size = upload_part.total_size
|
68
|
+
part_num = upload_part.part_num
|
69
|
+
msg = "\n#############################################################\n"
|
50
70
|
msg += f"# Uploading {upload_part.chunk} to {upload_part.dst_part}\n"
|
51
|
-
msg += "
|
52
|
-
|
71
|
+
msg += f"# Part number: {part_num} / {num_parts}\n"
|
72
|
+
msg += f"# Total parts: {num_parts}\n"
|
73
|
+
msg += f"# Total size: {total_size.as_int()} bytes\n"
|
74
|
+
msg += f"# Chunk size: {upload_part.chunk.stat().st_size} bytes\n"
|
75
|
+
msg += f"# Range: {upload_part.chunk.name}\n"
|
76
|
+
msg += "##############################################################\n"
|
77
|
+
_maybe_log(msg)
|
53
78
|
self.copy_to(upload_part.chunk.as_posix(), upload_part.dst_part)
|
54
79
|
return upload_part
|
55
80
|
except Exception as e:
|
@@ -66,6 +91,9 @@ def read_task(
|
|
66
91
|
offset: SizeSuffix,
|
67
92
|
length: SizeSuffix,
|
68
93
|
part_dst: str,
|
94
|
+
part_number: int,
|
95
|
+
total_parts: int,
|
96
|
+
total_size: SizeSuffix,
|
69
97
|
) -> UploadPart:
|
70
98
|
outchunk: Path = tmpdir / f"{offset.as_int()}-{(offset + length).as_int()}.chunk"
|
71
99
|
range = Range(offset.as_int(), (offset + length).as_int())
|
@@ -77,10 +105,23 @@ def read_task(
|
|
77
105
|
dst=outchunk,
|
78
106
|
)
|
79
107
|
if isinstance(err, Exception):
|
80
|
-
out = UploadPart(
|
108
|
+
out = UploadPart(
|
109
|
+
chunk=outchunk,
|
110
|
+
dst_part="",
|
111
|
+
part_num=-1,
|
112
|
+
total_parts=total_parts,
|
113
|
+
total_size=SizeSuffix(0),
|
114
|
+
exception=err,
|
115
|
+
)
|
81
116
|
out.dispose()
|
82
117
|
return out
|
83
|
-
return UploadPart(
|
118
|
+
return UploadPart(
|
119
|
+
chunk=outchunk,
|
120
|
+
dst_part=part_dst,
|
121
|
+
part_num=part_number,
|
122
|
+
total_parts=total_parts,
|
123
|
+
total_size=total_size,
|
124
|
+
)
|
84
125
|
except KeyboardInterrupt as ke:
|
85
126
|
_thread.interrupt_main()
|
86
127
|
raise ke
|
@@ -88,7 +129,14 @@ def read_task(
|
|
88
129
|
_thread.interrupt_main()
|
89
130
|
raise se
|
90
131
|
except Exception as e:
|
91
|
-
return UploadPart(
|
132
|
+
return UploadPart(
|
133
|
+
chunk=outchunk,
|
134
|
+
dst_part=part_dst,
|
135
|
+
part_num=part_number,
|
136
|
+
total_parts=total_parts,
|
137
|
+
total_size=total_size,
|
138
|
+
exception=e,
|
139
|
+
)
|
92
140
|
|
93
141
|
|
94
142
|
def collapse_runs(numbers: list[int]) -> list[str]:
|
@@ -199,6 +247,7 @@ def upload_parts_resumable(
|
|
199
247
|
f"all_numbers_already_done: {collapse_runs(sorted(list(all_numbers_already_done)))}"
|
200
248
|
)
|
201
249
|
|
250
|
+
total_parts = len(part_infos)
|
202
251
|
filtered_part_infos: list[PartInfo] = []
|
203
252
|
for part_info in part_infos:
|
204
253
|
if part_info.part_number not in all_numbers_already_done:
|
@@ -261,6 +310,9 @@ def upload_parts_resumable(
|
|
261
310
|
offset=offset,
|
262
311
|
length=length,
|
263
312
|
part_dst=part_dst,
|
313
|
+
part_number=part_number,
|
314
|
+
total_parts=total_parts,
|
315
|
+
total_size=src_size,
|
264
316
|
)
|
265
317
|
|
266
318
|
read_fut: Future[UploadPart] = read_executor.submit(_read_task)
|
@@ -293,12 +345,17 @@ def upload_parts_resumable(
|
|
293
345
|
shutil.rmtree(tmp_dir, ignore_errors=True)
|
294
346
|
|
295
347
|
if len(exceptions) > 0:
|
296
|
-
|
348
|
+
msg = f"Failed to copy parts: {exceptions}"
|
349
|
+
_maybe_log(msg)
|
350
|
+
return Exception(msg, exceptions)
|
297
351
|
|
298
352
|
finished_parts: list[int] = info_json.fetch_all_finished_part_numbers()
|
299
353
|
print(f"finished_names: {finished_parts}")
|
300
354
|
|
301
355
|
diff_set = set(all_part_numbers).symmetric_difference(set(finished_parts))
|
302
356
|
all_part_numbers_done = len(diff_set) == 0
|
303
|
-
print(f"all_part_numbers_done: {all_part_numbers_done}")
|
357
|
+
# print(f"all_part_numbers_done: {all_part_numbers_done}")
|
358
|
+
msg = f"all_part_numbers_done: {all_part_numbers_done}"
|
359
|
+
_maybe_log(msg)
|
360
|
+
|
304
361
|
return None
|
@@ -1,4 +1,4 @@
|
|
1
|
-
rclone_api/__init__.py,sha256=
|
1
|
+
rclone_api/__init__.py,sha256=AH0_iCsWfPCphaXgN0eV7zeZ5WvinUYpyizwPIuUqmQ,35244
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
3
|
rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
|
4
4
|
rclone_api/config.py,sha256=URZwMME01f0EZymprCESuZ5dk4IuUSKbHhwIeTHrn7A,6131
|
@@ -55,12 +55,12 @@ rclone_api/s3/multipart/info_json.py,sha256=-e8UCwrqjAP64U8PmH-o2ciJ6TN48DwHktJf
|
|
55
55
|
rclone_api/s3/multipart/merge_state.py,sha256=ziTB9CYV-OWaky5C1fOT9hifSY2zgUrk5HmX1Xeu2UA,4978
|
56
56
|
rclone_api/s3/multipart/upload_info.py,sha256=d6_OfzFR_vtDzCEegFfzCfWi2kUBUV4aXZzqAEVp1c4,1874
|
57
57
|
rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9akiew3MFjGmufLo2w,12503
|
58
|
-
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=
|
58
|
+
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=Th6m3owHS6Z-ieNhhDtU0_A6nDgKMo9y_Qv6e7lRia8,11634
|
59
59
|
rclone_api/s3/multipart/upload_parts_server_side_merge.py,sha256=Fp2pdrs5dONQI9LkfNolgAGj1-Z2V1SsRd0r0sreuXI,18040
|
60
60
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
61
|
-
rclone_api-1.5.
|
62
|
-
rclone_api-1.5.
|
63
|
-
rclone_api-1.5.
|
64
|
-
rclone_api-1.5.
|
65
|
-
rclone_api-1.5.
|
66
|
-
rclone_api-1.5.
|
61
|
+
rclone_api-1.5.65.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
62
|
+
rclone_api-1.5.65.dist-info/METADATA,sha256=tx0O8RMD9s9oAQYXW79wpP9HupCOuh0fb13px0uy9b0,37305
|
63
|
+
rclone_api-1.5.65.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
64
|
+
rclone_api-1.5.65.dist-info/entry_points.txt,sha256=ognh2e11HTjn73_KL5MWI67pBKS2jekBi-QTiRXySXA,316
|
65
|
+
rclone_api-1.5.65.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
66
|
+
rclone_api-1.5.65.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|