rclone-api 1.1.8__py2.py3-none-any.whl → 1.1.11__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/cmd/copy_large_s3.py +2 -2
- rclone_api/mount.py +16 -3
- rclone_api/rclone.py +6 -4
- rclone_api/s3/chunk_types.py +14 -0
- rclone_api/s3/chunk_uploader.py +19 -1
- rclone_api/types.py +19 -19
- {rclone_api-1.1.8.dist-info → rclone_api-1.1.11.dist-info}/METADATA +1 -1
- {rclone_api-1.1.8.dist-info → rclone_api-1.1.11.dist-info}/RECORD +12 -12
- {rclone_api-1.1.8.dist-info → rclone_api-1.1.11.dist-info}/LICENSE +0 -0
- {rclone_api-1.1.8.dist-info → rclone_api-1.1.11.dist-info}/WHEEL +0 -0
- {rclone_api-1.1.8.dist-info → rclone_api-1.1.11.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.1.8.dist-info → rclone_api-1.1.11.dist-info}/top_level.txt +0 -0
rclone_api/cmd/copy_large_s3.py
CHANGED
|
@@ -38,13 +38,13 @@ def _parse_args() -> Args:
|
|
|
38
38
|
"--chunk-size",
|
|
39
39
|
help="Chunk size that will be read and uploaded in in SizeSuffix (i.e. 128M = 128 megabytes) form",
|
|
40
40
|
type=str,
|
|
41
|
-
default="
|
|
41
|
+
default="1G",
|
|
42
42
|
)
|
|
43
43
|
parser.add_argument(
|
|
44
44
|
"--threads",
|
|
45
45
|
help="Number of threads to use per chunk",
|
|
46
46
|
type=int,
|
|
47
|
-
default=
|
|
47
|
+
default=64,
|
|
48
48
|
)
|
|
49
49
|
parser.add_argument("--retries", help="Number of retries", type=int, default=3)
|
|
50
50
|
parser.add_argument(
|
rclone_api/mount.py
CHANGED
|
@@ -18,12 +18,23 @@ class Mount:
|
|
|
18
18
|
|
|
19
19
|
mount_path: Path
|
|
20
20
|
process: Process
|
|
21
|
+
_closed: bool = False
|
|
21
22
|
|
|
22
23
|
def __post_init__(self):
|
|
23
24
|
assert isinstance(self.mount_path, Path)
|
|
24
25
|
assert self.process is not None
|
|
25
26
|
wait_for_mount(self.mount_path, self.process)
|
|
26
27
|
|
|
28
|
+
def close(self, wait=True) -> None:
|
|
29
|
+
"""Clean up the mount."""
|
|
30
|
+
if self._closed:
|
|
31
|
+
return
|
|
32
|
+
self._closed = True
|
|
33
|
+
clean_mount(self, verbose=False)
|
|
34
|
+
|
|
35
|
+
def __del__(self):
|
|
36
|
+
self.close(wait=False)
|
|
37
|
+
|
|
27
38
|
|
|
28
39
|
def run_command(cmd: str, verbose: bool) -> int:
|
|
29
40
|
"""Run a shell command and print its output if verbose is True."""
|
|
@@ -74,7 +85,7 @@ def wait_for_mount(path: Path, mount_process: Any, timeout: int = 10) -> None:
|
|
|
74
85
|
time.sleep(1)
|
|
75
86
|
|
|
76
87
|
|
|
77
|
-
def clean_mount(mount: Mount | Path, verbose: bool = False) -> None:
|
|
88
|
+
def clean_mount(mount: Mount | Path, verbose: bool = False, wait=True) -> None:
|
|
78
89
|
"""
|
|
79
90
|
Clean up a mount path across Linux, macOS, and Windows.
|
|
80
91
|
|
|
@@ -98,7 +109,8 @@ def clean_mount(mount: Mount | Path, verbose: bool = False) -> None:
|
|
|
98
109
|
mount_exists = True
|
|
99
110
|
|
|
100
111
|
# Give the system a moment (if unmount is in progress, etc.)
|
|
101
|
-
|
|
112
|
+
if wait:
|
|
113
|
+
time.sleep(2)
|
|
102
114
|
|
|
103
115
|
if not mount_exists:
|
|
104
116
|
if verbose:
|
|
@@ -132,7 +144,8 @@ def clean_mount(mount: Mount | Path, verbose: bool = False) -> None:
|
|
|
132
144
|
warnings.warn(f"Unsupported platform: {_SYSTEM}")
|
|
133
145
|
|
|
134
146
|
# Allow some time for the unmount commands to take effect.
|
|
135
|
-
|
|
147
|
+
if wait:
|
|
148
|
+
time.sleep(2)
|
|
136
149
|
|
|
137
150
|
# Re-check if the mount path still exists.
|
|
138
151
|
try:
|
rclone_api/rclone.py
CHANGED
|
@@ -695,6 +695,7 @@ class Rclone:
|
|
|
695
695
|
vfs_read_chunk_size = unit_chunk_size
|
|
696
696
|
vfs_read_chunk_size_limit = chunk_size
|
|
697
697
|
vfs_read_chunk_streams = threads
|
|
698
|
+
vfs_disk_space_total_size = chunk_size
|
|
698
699
|
assert (
|
|
699
700
|
chunk_size.as_int() % vfs_read_chunk_size.as_int() == 0
|
|
700
701
|
), f"chunk_size {chunk_size} must be a multiple of vfs_read_chunk_size {vfs_read_chunk_size}"
|
|
@@ -704,6 +705,10 @@ class Rclone:
|
|
|
704
705
|
vfs_read_chunk_size_limit.as_str(),
|
|
705
706
|
]
|
|
706
707
|
other_args += ["--vfs-read-chunk-streams", str(vfs_read_chunk_streams)]
|
|
708
|
+
other_args += [
|
|
709
|
+
"--vfs-disk-space-total-size",
|
|
710
|
+
vfs_disk_space_total_size.as_str(),
|
|
711
|
+
]
|
|
707
712
|
mount_path = mount_path or Path("tmp_mnts") / random_str(12)
|
|
708
713
|
src_path = Path(src)
|
|
709
714
|
name = src_path.name
|
|
@@ -903,11 +908,8 @@ class Rclone:
|
|
|
903
908
|
warnings.warn(f"Error in scoped_mount: {e}\n\nStack Trace:\n{stack_trace}")
|
|
904
909
|
raise
|
|
905
910
|
finally:
|
|
906
|
-
|
|
907
911
|
if not error_happened:
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
clean_mount(mount, verbose=verbose)
|
|
912
|
+
mount.close()
|
|
911
913
|
|
|
912
914
|
# Settings optimized for s3.
|
|
913
915
|
def mount_s3(
|
rclone_api/s3/chunk_types.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import hashlib
|
|
1
2
|
import json
|
|
2
3
|
import os
|
|
3
4
|
import time
|
|
@@ -103,6 +104,19 @@ class UploadInfo:
|
|
|
103
104
|
return
|
|
104
105
|
self._total_chunks = self.total_chunks()
|
|
105
106
|
|
|
107
|
+
def fingerprint(self) -> str:
|
|
108
|
+
# hash the attributes that are used to identify the upload
|
|
109
|
+
hasher = hashlib.sha256()
|
|
110
|
+
# first is file size
|
|
111
|
+
hasher.update(str(self.file_size).encode("utf-8"))
|
|
112
|
+
# second is the file path
|
|
113
|
+
hasher.update(str(self.src_file_path).encode("utf-8"))
|
|
114
|
+
# next is chunk size
|
|
115
|
+
hasher.update(str(self.chunk_size).encode("utf-8"))
|
|
116
|
+
# next is the number of parts
|
|
117
|
+
hasher.update(str(self._total_chunks).encode("utf-8"))
|
|
118
|
+
return hasher.hexdigest()
|
|
119
|
+
|
|
106
120
|
def to_json(self) -> dict:
|
|
107
121
|
json_dict = {}
|
|
108
122
|
for f in fields(self):
|
rclone_api/s3/chunk_uploader.py
CHANGED
|
@@ -162,7 +162,25 @@ def upload_file_multipart(
|
|
|
162
162
|
return upload_state
|
|
163
163
|
|
|
164
164
|
filechunks: Queue[FileChunk | None] = Queue(10)
|
|
165
|
-
|
|
165
|
+
new_state = make_new_state()
|
|
166
|
+
loaded_state = get_upload_state()
|
|
167
|
+
|
|
168
|
+
if loaded_state is None:
|
|
169
|
+
upload_state = new_state
|
|
170
|
+
else:
|
|
171
|
+
# if the file size has changed, we cannot resume
|
|
172
|
+
if (
|
|
173
|
+
loaded_state.upload_info.fingerprint()
|
|
174
|
+
!= new_state.upload_info.fingerprint()
|
|
175
|
+
):
|
|
176
|
+
locked_print(
|
|
177
|
+
f"Cannot resume upload: file size changed, starting over for {file_path}"
|
|
178
|
+
)
|
|
179
|
+
_abort_previous_upload(loaded_state)
|
|
180
|
+
upload_state = new_state
|
|
181
|
+
else:
|
|
182
|
+
upload_state = loaded_state
|
|
183
|
+
|
|
166
184
|
try:
|
|
167
185
|
upload_state.update_source_file(file_path)
|
|
168
186
|
except ValueError as e:
|
rclone_api/types.py
CHANGED
|
@@ -52,33 +52,33 @@ def _to_size_suffix(size: int) -> str:
|
|
|
52
52
|
raise ValueError(f"Invalid size: {size}")
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
# Update regex to allow decimals (e.g., 16.5MB)
|
|
56
|
+
_PATTERN_SIZE_SUFFIX = re.compile(r"^(\d+(?:\.\d+)?)([A-Za-z]+)$")
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def _from_size_suffix(size: str) -> int:
|
|
59
|
-
# 16MiB
|
|
60
|
-
# parse out number and suffix
|
|
61
60
|
if size == "0":
|
|
62
61
|
return 0
|
|
63
62
|
match = _PATTERN_SIZE_SUFFIX.match(size)
|
|
64
63
|
if match is None:
|
|
65
64
|
raise ValueError(f"Invalid size suffix: {size}")
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
num_str, suffix = match.group(1), match.group(2)
|
|
66
|
+
n = float(num_str)
|
|
67
|
+
# Determine the unit from the first letter (e.g., "M" from "MB")
|
|
68
|
+
unit = suffix[0].upper()
|
|
69
|
+
if unit == "B":
|
|
70
|
+
return int(n)
|
|
71
|
+
if unit == "K":
|
|
72
|
+
return int(n * 1024)
|
|
73
|
+
if unit == "M":
|
|
74
|
+
return int(n * 1024 * 1024)
|
|
75
|
+
if unit == "G":
|
|
76
|
+
return int(n * 1024 * 1024 * 1024)
|
|
77
|
+
if unit == "T":
|
|
78
|
+
return int(n * 1024**4)
|
|
79
|
+
if unit == "P":
|
|
80
|
+
return int(n * 1024**5)
|
|
81
|
+
raise ValueError(f"Invalid size suffix: {suffix}")
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
class SizeSuffix:
|
|
@@ -11,30 +11,30 @@ rclone_api/exec.py,sha256=1ovvaMXDEfLiT7BrYZyE85u_yFhEUwUNW3jPOzqknR8,1023
|
|
|
11
11
|
rclone_api/file.py,sha256=EP5yT2dZ0H2p7CY5n0y5k5pHhIliV25pm8KOwBklUTk,1863
|
|
12
12
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
|
13
13
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
|
14
|
-
rclone_api/mount.py,sha256=
|
|
14
|
+
rclone_api/mount.py,sha256=Xj3BMVSDEwUbtMi8ycn5mUom-fAf-F9lJOjR_BzVllw,6073
|
|
15
15
|
rclone_api/process.py,sha256=xYUgU17txkZfZdr4vtRfvD8YjvSfdrbjM7PYW1npAMI,4264
|
|
16
|
-
rclone_api/rclone.py,sha256
|
|
16
|
+
rclone_api/rclone.py,sha256=BA3DJvCraLIrglDGSi1mF-wCH8rsqpBzc-5O-IWQpq8,39809
|
|
17
17
|
rclone_api/remote.py,sha256=O9WDUFQy9f6oT1HdUbTixK2eg0xtBBm8k4Xl6aa6K00,431
|
|
18
18
|
rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
|
|
19
19
|
rclone_api/scan_missing_folders.py,sha256=Kulca2Q6WZodt00ATFHkmqqInuoPvBkhTcS9703y6po,4740
|
|
20
|
-
rclone_api/types.py,sha256=
|
|
20
|
+
rclone_api/types.py,sha256=zfTb0iM6mhfqgaYS6j6T0NIOA4e9GymNOXLPhVELe4A,3853
|
|
21
21
|
rclone_api/util.py,sha256=_Z-GUMVXnHYOGdo2dy2ie2P5fGgyg8KdGjHKicx68Ko,4573
|
|
22
22
|
rclone_api/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
|
|
23
23
|
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
|
24
|
-
rclone_api/cmd/copy_large_s3.py,sha256=
|
|
24
|
+
rclone_api/cmd/copy_large_s3.py,sha256=UR5qkW9vLtGLC951Om8UWFcQKApZUF-SzUhlIo3YIGU,3069
|
|
25
25
|
rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
|
|
26
26
|
rclone_api/experimental/flags.py,sha256=qCVD--fSTmzlk9hloRLr0q9elzAOFzPsvVpKM3aB1Mk,2739
|
|
27
27
|
rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
|
|
28
28
|
rclone_api/s3/api.py,sha256=VstlaEnBjO2JDQuCRLdTfUGvQLbfshlXXhAzimFv4Vc,3763
|
|
29
29
|
rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
|
|
30
30
|
rclone_api/s3/chunk_file.py,sha256=XPoDl7DJMJIGBMRoPO2wqwqCMT7ZrIsEkDqlbMH8jzs,3506
|
|
31
|
-
rclone_api/s3/chunk_types.py,sha256=
|
|
32
|
-
rclone_api/s3/chunk_uploader.py,sha256=
|
|
31
|
+
rclone_api/s3/chunk_types.py,sha256=pZUKc3L418XTwET1rLm4eRo_2swKQckSqlc4XjYmhDo,9147
|
|
32
|
+
rclone_api/s3/chunk_uploader.py,sha256=Y4I208YoXTJQQke1qRsXARHxBLjJAz9nt_r1mKkplUQ,9670
|
|
33
33
|
rclone_api/s3/create.py,sha256=SK3IGHZwsSkoG4Zb4NCphcVg9_f7VifDKng-tExMS2s,3088
|
|
34
34
|
rclone_api/s3/types.py,sha256=81_3jwg6MGIxC-GxL-6zANzKO6au9C0BWvAqRyODxOM,1361
|
|
35
|
-
rclone_api-1.1.
|
|
36
|
-
rclone_api-1.1.
|
|
37
|
-
rclone_api-1.1.
|
|
38
|
-
rclone_api-1.1.
|
|
39
|
-
rclone_api-1.1.
|
|
40
|
-
rclone_api-1.1.
|
|
35
|
+
rclone_api-1.1.11.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
|
36
|
+
rclone_api-1.1.11.dist-info/METADATA,sha256=LnxU7g0Hzoe_gvGmz1F0MKU67cRtsJEPk1Gkxe6SQVQ,4479
|
|
37
|
+
rclone_api-1.1.11.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
|
38
|
+
rclone_api-1.1.11.dist-info/entry_points.txt,sha256=6eNqTRXKhVf8CpWNjXiOa_0Du9tHiW_HD2iQSXRsUg8,132
|
|
39
|
+
rclone_api-1.1.11.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
|
40
|
+
rclone_api-1.1.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|