rclone-api 1.4.8__tar.gz → 1.4.10__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.
- {rclone_api-1.4.8 → rclone_api-1.4.10}/PKG-INFO +1 -1
- {rclone_api-1.4.8 → rclone_api-1.4.10}/pyproject.toml +1 -1
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/cmd/copy_large_s3_finish.py +24 -13
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/detail/copy_file_parts.py +11 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/create.py +1 -1
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/s3_multipart_uploader_by_copy.py +40 -16
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api.egg-info/PKG-INFO +1 -1
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.aiderignore +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.github/workflows/lint.yml +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.github/workflows/push_macos.yml +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.github/workflows/push_ubuntu.yml +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.github/workflows/push_win.yml +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.gitignore +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.pylintrc +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.vscode/launch.json +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.vscode/settings.json +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/.vscode/tasks.json +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/LICENSE +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/MANIFEST.in +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/README.md +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/clean +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/install +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/lint +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/requirements.testing.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/setup.cfg +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/setup.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/__init__.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/cmd/analyze.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/cmd/copy_large_s3.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/cmd/list_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/cmd/save_to_db.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/completed_process.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/config.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/convert.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/db/__init__.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/db/db.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/db/models.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/deprecated.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/detail/walk.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/diff.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/dir.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/dir_listing.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/exec.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/experimental/flags.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/experimental/flags_base.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/file.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/file_item.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/file_part.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/file_stream.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/filelist.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/group_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/http_server.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/log.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/mount.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/process.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/rclone_impl.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/remote.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/rpath.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/api.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/basic_ops.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/chunk_task.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/multipart/file_info.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/multipart/finished_piece.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/multipart/upload_info.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/multipart/upload_state.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/types.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/s3/upload_file_multipart.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/scan_missing_folders.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/types.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api/util.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api.egg-info/SOURCES.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api.egg-info/entry_points.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api.egg-info/requires.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/test +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/archive/test_paramiko.py.disabled +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_cmd_list_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_copy.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_copy_bytes.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_copy_file_resumable_s3.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_copy_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_db.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_diff.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_file_item.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_group_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_is_synced.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_ls.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_ls_stream_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_mount.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_mount_s3.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_obscure.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_rclone_config.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_read_write_text.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_remote_control.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_remotes.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_s3.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_scan_missing_folders.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_serve_http.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_size_files.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_size_suffix.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tests/test_walk.py +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/tox.ini +0 -0
- {rclone_api-1.4.8 → rclone_api-1.4.10}/upload_package.sh +0 -0
@@ -116,13 +116,12 @@ def do_finish_part(rclone: Rclone, info: InfoJson, dst: str) -> None:
|
|
116
116
|
if parts_dir.endswith("/"):
|
117
117
|
parts_dir = parts_dir[:-1]
|
118
118
|
source_keys = info.fetch_all_finished()
|
119
|
+
# print(parts_dir)
|
120
|
+
# print(source_keys)
|
119
121
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
parent_path = parts_dir.split(s3_bucket)[1]
|
124
|
-
if parent_path.startswith("/"):
|
125
|
-
parent_path = parent_path[1:]
|
122
|
+
parts_path = parts_dir.split(s3_bucket)[1]
|
123
|
+
if parts_path.startswith("/"):
|
124
|
+
parts_path = parts_path[1:]
|
126
125
|
|
127
126
|
first_part: int | None = info.first_part
|
128
127
|
last_part: int | None = info.last_part
|
@@ -132,16 +131,20 @@ def do_finish_part(rclone: Rclone, info: InfoJson, dst: str) -> None:
|
|
132
131
|
assert last_part is not None
|
133
132
|
assert size is not None
|
134
133
|
|
135
|
-
def _to_s3_key(name: str) -> str:
|
136
|
-
|
134
|
+
def _to_s3_key(name: str | None) -> str:
|
135
|
+
if name:
|
136
|
+
out = f"{parts_path}/{name}"
|
137
|
+
return out
|
138
|
+
out = f"{parts_path}"
|
137
139
|
return out
|
138
140
|
|
139
141
|
# s3_keys: list[str] = [_to_s3_key(name=p) for p in source_keys]
|
140
142
|
parts: list[tuple[int, str]] = []
|
141
|
-
|
142
|
-
|
143
|
-
s3_key = _to_s3_key(name=
|
144
|
-
parts.append((
|
143
|
+
part_num = 1
|
144
|
+
for part_key in source_keys:
|
145
|
+
s3_key = _to_s3_key(name=part_key)
|
146
|
+
parts.append((part_num, s3_key))
|
147
|
+
part_num += 1
|
145
148
|
|
146
149
|
# for key in parts:
|
147
150
|
# print(key)
|
@@ -149,14 +152,22 @@ def do_finish_part(rclone: Rclone, info: InfoJson, dst: str) -> None:
|
|
149
152
|
chunksize = info.chunksize
|
150
153
|
assert chunksize is not None
|
151
154
|
|
155
|
+
import os
|
156
|
+
|
157
|
+
dst_name = info.dst_name
|
158
|
+
dst_dir = os.path.dirname(parts_path)
|
159
|
+
# dst_key =
|
160
|
+
dst_key = f"{dst_dir}/{dst_name}"
|
161
|
+
|
152
162
|
finish_multipart_upload_from_keys(
|
153
163
|
s3_client=s3_client,
|
154
164
|
source_bucket=s3_creds.bucket_name,
|
155
165
|
parts=parts,
|
156
166
|
destination_bucket=s3_creds.bucket_name,
|
157
|
-
destination_key=
|
167
|
+
destination_key=dst_key,
|
158
168
|
chunk_size=chunksize.as_int(),
|
159
169
|
final_size=size.as_int(),
|
170
|
+
max_workers=100,
|
160
171
|
retries=3,
|
161
172
|
)
|
162
173
|
|
@@ -211,6 +211,17 @@ class InfoJson:
|
|
211
211
|
def parts_dir(self) -> str:
|
212
212
|
return os.path.dirname(self.src_info)
|
213
213
|
|
214
|
+
@property
|
215
|
+
def dst(self) -> str:
|
216
|
+
parts_dir = self.parts_dir
|
217
|
+
assert parts_dir.endswith("-parts")
|
218
|
+
out = parts_dir[:-6]
|
219
|
+
return out
|
220
|
+
|
221
|
+
@property
|
222
|
+
def dst_name(self) -> str:
|
223
|
+
return os.path.basename(self.dst)
|
224
|
+
|
214
225
|
def compute_all_parts(self) -> list[PartInfo] | Exception:
|
215
226
|
# full_part_infos: list[PartInfo] | Exception = PartInfo.split_parts(
|
216
227
|
# src_size, SizeSuffix("96MB")
|
@@ -25,7 +25,7 @@ def _create_backblaze_s3_client(creds: S3Credentials, verbose: bool) -> BaseClie
|
|
25
25
|
aws_access_key_id=access_key,
|
26
26
|
aws_secret_access_key=secret_key,
|
27
27
|
endpoint_url=endpoint_url,
|
28
|
-
verify=False, # Disables SSL certificate verification
|
28
|
+
# verify=False, # Disables SSL certificate verification
|
29
29
|
config=Config(
|
30
30
|
signature_version="s3v4",
|
31
31
|
region_name=region_name,
|
@@ -9,6 +9,7 @@ from existing S3 objects using upload_part_copy.
|
|
9
9
|
from concurrent.futures import Future, ThreadPoolExecutor
|
10
10
|
from dataclasses import dataclass
|
11
11
|
from pathlib import Path
|
12
|
+
from threading import Semaphore
|
12
13
|
from typing import Optional
|
13
14
|
|
14
15
|
from botocore.client import BaseClient
|
@@ -183,7 +184,7 @@ def upload_part_copy_task(
|
|
183
184
|
source_key: str,
|
184
185
|
part_number: int,
|
185
186
|
retries: int = 3,
|
186
|
-
) -> FinishedPiece:
|
187
|
+
) -> FinishedPiece | Exception:
|
187
188
|
"""
|
188
189
|
Upload a part by copying from an existing S3 object.
|
189
190
|
|
@@ -200,8 +201,11 @@ def upload_part_copy_task(
|
|
200
201
|
"""
|
201
202
|
copy_source = {"Bucket": source_bucket, "Key": source_key}
|
202
203
|
|
204
|
+
# from botocore.exceptions import NoSuchKey
|
205
|
+
|
203
206
|
retries = retries + 1 # Add one for the initial attempt
|
204
207
|
for retry in range(retries):
|
208
|
+
params: dict = {}
|
205
209
|
try:
|
206
210
|
if retry > 0:
|
207
211
|
locked_print(f"Retrying part copy {part_number} for {info.object_name}")
|
@@ -226,16 +230,23 @@ def upload_part_copy_task(
|
|
226
230
|
etag = part["CopyPartResult"]["ETag"]
|
227
231
|
|
228
232
|
return FinishedPiece(etag=etag, part_number=part_number)
|
233
|
+
# except NoSuchKey as e:
|
234
|
+
# locked_print(f"Error copying part {part_number}: {e}")
|
235
|
+
# return e
|
229
236
|
|
230
237
|
except Exception as e:
|
238
|
+
msg = f"Error copying {copy_source} -> {info.object_name}: {e}, params={params}"
|
239
|
+
if "NoSuchKey" in str(e):
|
240
|
+
locked_print(msg)
|
241
|
+
return e
|
231
242
|
if retry == retries - 1:
|
232
|
-
locked_print(
|
233
|
-
|
243
|
+
locked_print(msg)
|
244
|
+
return e
|
234
245
|
else:
|
235
|
-
locked_print(f"
|
246
|
+
locked_print(f"{msg}, retrying")
|
236
247
|
continue
|
237
248
|
|
238
|
-
|
249
|
+
return Exception("Should not reach here")
|
239
250
|
|
240
251
|
|
241
252
|
def complete_multipart_upload_from_parts(
|
@@ -303,9 +314,14 @@ def finish_multipart_upload_from_keys(
|
|
303
314
|
locked_print(
|
304
315
|
f"Creating multipart upload for {destination_bucket}/{destination_key} from {len(parts)} source objects"
|
305
316
|
)
|
306
|
-
|
307
|
-
|
308
|
-
|
317
|
+
|
318
|
+
create_params: dict[str, str] = {
|
319
|
+
"Bucket": destination_bucket,
|
320
|
+
"Key": destination_key,
|
321
|
+
}
|
322
|
+
print(f"Creating multipart upload with {create_params}")
|
323
|
+
mpu = s3_client.create_multipart_upload(**create_params)
|
324
|
+
print(f"Created multipart upload: {mpu}")
|
309
325
|
upload_id = mpu["UploadId"]
|
310
326
|
|
311
327
|
# Create upload info
|
@@ -319,9 +335,12 @@ def finish_multipart_upload_from_keys(
|
|
319
335
|
file_size=final_size,
|
320
336
|
)
|
321
337
|
|
322
|
-
futures: list[Future[FinishedPiece]] = []
|
338
|
+
futures: list[Future[FinishedPiece | Exception]] = []
|
323
339
|
|
324
340
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
341
|
+
# semaphore
|
342
|
+
|
343
|
+
semaphore = Semaphore(max_workers * 2)
|
325
344
|
for part_number, source_key in parts:
|
326
345
|
|
327
346
|
def task(
|
@@ -340,17 +359,22 @@ def finish_multipart_upload_from_keys(
|
|
340
359
|
)
|
341
360
|
|
342
361
|
fut = executor.submit(task)
|
362
|
+
fut.add_done_callback(lambda x: semaphore.release())
|
343
363
|
futures.append(fut)
|
364
|
+
semaphore.acquire()
|
344
365
|
|
345
|
-
|
346
|
-
|
366
|
+
# Upload parts by copying from source objects
|
367
|
+
finished_parts: list[FinishedPiece] = []
|
347
368
|
|
348
|
-
|
349
|
-
|
350
|
-
|
369
|
+
for fut in futures:
|
370
|
+
finished_part = fut.result()
|
371
|
+
if isinstance(finished_part, Exception):
|
372
|
+
executor.shutdown(wait=True, cancel_futures=True)
|
373
|
+
raise finished_part
|
374
|
+
finished_parts.append(finished_part)
|
351
375
|
|
352
|
-
|
353
|
-
|
376
|
+
# Complete the multipart upload
|
377
|
+
return complete_multipart_upload_from_parts(upload_info, finished_parts)
|
354
378
|
|
355
379
|
|
356
380
|
class S3MultiPartUploader:
|
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
|
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
|
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
|
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
|
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
|