rclone-api 1.2.1__py2.py3-none-any.whl → 1.2.3__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 +1 -1
- rclone_api/mount.py +15 -5
- rclone_api/rclone.py +113 -95
- rclone_api/s3/api.py +7 -1
- rclone_api/s3/chunk_types.py +10 -2
- rclone_api/s3/types.py +1 -0
- rclone_api/s3/upload_file_multipart.py +11 -8
- {rclone_api-1.2.1.dist-info → rclone_api-1.2.3.dist-info}/METADATA +1 -1
- {rclone_api-1.2.1.dist-info → rclone_api-1.2.3.dist-info}/RECORD +13 -13
- {rclone_api-1.2.1.dist-info → rclone_api-1.2.3.dist-info}/LICENSE +0 -0
- {rclone_api-1.2.1.dist-info → rclone_api-1.2.3.dist-info}/WHEEL +0 -0
- {rclone_api-1.2.1.dist-info → rclone_api-1.2.3.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.2.1.dist-info → rclone_api-1.2.3.dist-info}/top_level.txt +0 -0
rclone_api/cmd/copy_large_s3.py
CHANGED
rclone_api/mount.py
CHANGED
|
@@ -115,7 +115,7 @@ def prepare_mount(outdir: Path, verbose: bool) -> None:
|
|
|
115
115
|
def wait_for_mount(
|
|
116
116
|
path: Path,
|
|
117
117
|
mount_process: Any,
|
|
118
|
-
timeout: int =
|
|
118
|
+
timeout: int = 20,
|
|
119
119
|
post_mount_delay: int = 5,
|
|
120
120
|
poll_interval: float = 1.0,
|
|
121
121
|
check_mount_flag: bool = False,
|
|
@@ -292,6 +292,7 @@ class MultiMountFileChunker:
|
|
|
292
292
|
def __init__(
|
|
293
293
|
self,
|
|
294
294
|
filename: str,
|
|
295
|
+
filesize: int,
|
|
295
296
|
chunk_size: SizeSuffix,
|
|
296
297
|
mounts: list[Mount],
|
|
297
298
|
executor: ThreadPoolExecutor,
|
|
@@ -300,6 +301,7 @@ class MultiMountFileChunker:
|
|
|
300
301
|
from rclone_api.util import get_verbose
|
|
301
302
|
|
|
302
303
|
self.filename = filename
|
|
304
|
+
self.filesize = filesize
|
|
303
305
|
self.chunk_size = chunk_size
|
|
304
306
|
self.executor = executor
|
|
305
307
|
self.mounts_processing: list[Mount] = []
|
|
@@ -309,7 +311,7 @@ class MultiMountFileChunker:
|
|
|
309
311
|
self.verbose = get_verbose(verbose)
|
|
310
312
|
|
|
311
313
|
def close(self) -> None:
|
|
312
|
-
self.executor.shutdown(wait=
|
|
314
|
+
self.executor.shutdown(wait=True, cancel_futures=True)
|
|
313
315
|
with ThreadPoolExecutor() as executor:
|
|
314
316
|
for mount in self.mounts_processing:
|
|
315
317
|
executor.submit(lambda: mount.close())
|
|
@@ -319,9 +321,11 @@ class MultiMountFileChunker:
|
|
|
319
321
|
print(f"Fetching data range: offset={offset}, size={size}")
|
|
320
322
|
try:
|
|
321
323
|
try:
|
|
324
|
+
if offset + size > self.filesize:
|
|
325
|
+
size = self.filesize - offset
|
|
322
326
|
assert (
|
|
323
|
-
offset
|
|
324
|
-
), f"Invalid offset: {offset}"
|
|
327
|
+
offset + size <= self.filesize
|
|
328
|
+
), f"Invalid offset + size: {offset + size}, it is beyond the end of the file."
|
|
325
329
|
assert size > 0, f"Invalid size: {size}"
|
|
326
330
|
assert offset >= 0, f"Invalid offset: {offset}"
|
|
327
331
|
except AssertionError as e:
|
|
@@ -356,6 +360,12 @@ class MultiMountFileChunker:
|
|
|
356
360
|
f"Fetching chunk: offset={offset}, size={size}, path={path}"
|
|
357
361
|
)
|
|
358
362
|
try:
|
|
363
|
+
# make sure we don't overflow the file size
|
|
364
|
+
# assert (
|
|
365
|
+
# offset + size <= self.chunk_size.as_int()
|
|
366
|
+
# ), f"Invalid offset + size: {offset + size}, it is beyond the end of the file."
|
|
367
|
+
if offset + size > self.filesize:
|
|
368
|
+
size = self.filesize - offset
|
|
359
369
|
with path.open("rb") as f:
|
|
360
370
|
f.seek(offset)
|
|
361
371
|
return f.read(size)
|
|
@@ -369,7 +379,7 @@ class MultiMountFileChunker:
|
|
|
369
379
|
except Exception as e:
|
|
370
380
|
stack_trace = traceback.format_exc()
|
|
371
381
|
warnings.warn(
|
|
372
|
-
f"Error fetching file chunk at offset{offset} + {size}: {e}\n{stack_trace}"
|
|
382
|
+
f"Error fetching file chunk at offset {offset} + {size}: {e}\n{stack_trace}"
|
|
373
383
|
)
|
|
374
384
|
return e
|
|
375
385
|
finally:
|
rclone_api/rclone.py
CHANGED
|
@@ -353,13 +353,11 @@ class Rclone:
|
|
|
353
353
|
check: bool | None = None,
|
|
354
354
|
verbose: bool | None = None,
|
|
355
355
|
other_args: list[str] | None = None,
|
|
356
|
-
) ->
|
|
357
|
-
"""Copy
|
|
356
|
+
) -> CompletedProcess:
|
|
357
|
+
"""Copy one file from source to destination.
|
|
358
358
|
|
|
359
359
|
Warning - slow.
|
|
360
360
|
|
|
361
|
-
Args:
|
|
362
|
-
payload: Dictionary of source and destination file paths
|
|
363
361
|
"""
|
|
364
362
|
check = get_check(check)
|
|
365
363
|
verbose = get_verbose(verbose)
|
|
@@ -368,7 +366,8 @@ class Rclone:
|
|
|
368
366
|
cmd_list: list[str] = ["copyto", src, dst]
|
|
369
367
|
if other_args is not None:
|
|
370
368
|
cmd_list += other_args
|
|
371
|
-
self._run(cmd_list, check=check)
|
|
369
|
+
cp = self._run(cmd_list, check=check)
|
|
370
|
+
return CompletedProcess.from_subprocess(cp)
|
|
372
371
|
|
|
373
372
|
def copy_files(
|
|
374
373
|
self,
|
|
@@ -680,18 +679,17 @@ class Rclone:
|
|
|
680
679
|
dst: str,
|
|
681
680
|
save_state_json: Path,
|
|
682
681
|
chunk_size: SizeSuffix | None = None,
|
|
683
|
-
read_threads: int =
|
|
682
|
+
read_threads: int = 8,
|
|
684
683
|
write_threads: int = 16,
|
|
685
684
|
retries: int = 3,
|
|
686
685
|
verbose: bool | None = None,
|
|
687
686
|
max_chunks_before_suspension: int | None = None,
|
|
688
|
-
mount_path: Path | None = None,
|
|
689
687
|
mount_log: Path | None = None,
|
|
690
688
|
) -> MultiUploadResult:
|
|
691
689
|
"""For massive files that rclone can't handle in one go, this function will copy the file in chunks to an S3 store"""
|
|
692
690
|
from rclone_api.s3.api import S3Client
|
|
693
691
|
from rclone_api.s3.create import S3Credentials
|
|
694
|
-
from rclone_api.util import S3PathInfo,
|
|
692
|
+
from rclone_api.util import S3PathInfo, split_s3_path
|
|
695
693
|
|
|
696
694
|
other_args: list[str] = ["--no-modtime", "--vfs-read-wait", "1s"]
|
|
697
695
|
chunk_size = chunk_size or SizeSuffix("64M")
|
|
@@ -717,105 +715,117 @@ class Rclone:
|
|
|
717
715
|
other_args += ["--direct-io"]
|
|
718
716
|
# --vfs-cache-max-size
|
|
719
717
|
other_args += ["--vfs-cache-max-size", vfs_disk_space_total_size.as_str()]
|
|
720
|
-
mount_path =
|
|
718
|
+
mount_path = Path("tmp_mnts") / "RCLONE_API_DYNAMIC_MOUNT"
|
|
721
719
|
src_path = Path(src)
|
|
722
720
|
name = src_path.name
|
|
723
|
-
parent_path = str(src_path.parent.as_posix())
|
|
724
|
-
with self.scoped_mount(
|
|
725
|
-
parent_path,
|
|
726
|
-
mount_path,
|
|
727
|
-
use_links=True,
|
|
728
|
-
vfs_cache_mode="minimal",
|
|
729
|
-
verbose=False,
|
|
730
|
-
log=mount_log,
|
|
731
|
-
other_args=other_args,
|
|
732
|
-
):
|
|
733
|
-
path_info: S3PathInfo = split_s3_path(dst)
|
|
734
|
-
remote = path_info.remote
|
|
735
|
-
bucket_name = path_info.bucket
|
|
736
|
-
s3_key = path_info.key
|
|
737
|
-
parsed: Parsed = self.config.parse()
|
|
738
|
-
sections: dict[str, Section] = parsed.sections
|
|
739
|
-
if remote not in sections:
|
|
740
|
-
raise ValueError(
|
|
741
|
-
f"Remote {remote} not found in rclone config, remotes are: {sections.keys()}"
|
|
742
|
-
)
|
|
743
721
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if type == "b2":
|
|
757
|
-
return S3Provider.BACKBLAZE.value
|
|
758
|
-
if type != "s3":
|
|
759
|
-
raise ValueError(f"Remote {remote} is not an S3 remote")
|
|
760
|
-
return S3Provider.S3.value
|
|
761
|
-
|
|
762
|
-
provider: str
|
|
763
|
-
if provided_provider_str := get_provider_str():
|
|
764
|
-
if verbose:
|
|
765
|
-
print(f"Using provided provider: {provided_provider_str}")
|
|
766
|
-
provider = provided_provider_str
|
|
767
|
-
else:
|
|
768
|
-
if verbose:
|
|
769
|
-
print(f"Using default provider: {S3Provider.S3.value}")
|
|
770
|
-
provider = S3Provider.S3.value
|
|
771
|
-
provider_enum = S3Provider.from_str(provider)
|
|
772
|
-
|
|
773
|
-
s3_creds: S3Credentials = S3Credentials(
|
|
774
|
-
provider=provider_enum,
|
|
775
|
-
access_key_id=section.access_key_id(),
|
|
776
|
-
secret_access_key=section.secret_access_key(),
|
|
777
|
-
endpoint_url=section.endpoint(),
|
|
722
|
+
src_parent_path = Path(src).parent.as_posix()
|
|
723
|
+
size_result: SizeResult = self.size_files(src_parent_path, [name])
|
|
724
|
+
|
|
725
|
+
target_size = SizeSuffix(size_result.total_size)
|
|
726
|
+
if target_size < SizeSuffix("5M"):
|
|
727
|
+
# fallback to normal copy
|
|
728
|
+
completed_proc = self.copy_to(src, dst, check=True)
|
|
729
|
+
if completed_proc.ok:
|
|
730
|
+
return MultiUploadResult.UPLOADED_FRESH
|
|
731
|
+
if size_result.total_size <= 0:
|
|
732
|
+
raise ValueError(
|
|
733
|
+
f"File {src} has size {size_result.total_size}, is this a directory?"
|
|
778
734
|
)
|
|
779
735
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
736
|
+
path_info: S3PathInfo = split_s3_path(dst)
|
|
737
|
+
remote = path_info.remote
|
|
738
|
+
bucket_name = path_info.bucket
|
|
739
|
+
s3_key = path_info.key
|
|
740
|
+
parsed: Parsed = self.config.parse()
|
|
741
|
+
sections: dict[str, Section] = parsed.sections
|
|
742
|
+
if remote not in sections:
|
|
743
|
+
raise ValueError(
|
|
744
|
+
f"Remote {remote} not found in rclone config, remotes are: {sections.keys()}"
|
|
786
745
|
)
|
|
787
746
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
retries=retries,
|
|
794
|
-
resume_path_json=save_state_json,
|
|
795
|
-
max_chunks_before_suspension=max_chunks_before_suspension,
|
|
747
|
+
section: Section = sections[remote]
|
|
748
|
+
dst_type = section.type()
|
|
749
|
+
if dst_type != "s3" and dst_type != "b2":
|
|
750
|
+
raise ValueError(
|
|
751
|
+
f"Remote {remote} is not an S3 remote, it is of type {dst_type}"
|
|
796
752
|
)
|
|
797
753
|
|
|
798
|
-
|
|
754
|
+
def get_provider_str(section=section) -> str | None:
|
|
755
|
+
type: str = section.type()
|
|
756
|
+
provider: str | None = section.provider()
|
|
757
|
+
if provider is not None:
|
|
758
|
+
return provider
|
|
759
|
+
if type == "b2":
|
|
760
|
+
return S3Provider.BACKBLAZE.value
|
|
761
|
+
if type != "s3":
|
|
762
|
+
raise ValueError(f"Remote {remote} is not an S3 remote")
|
|
763
|
+
return S3Provider.S3.value
|
|
764
|
+
|
|
765
|
+
provider: str
|
|
766
|
+
if provided_provider_str := get_provider_str():
|
|
767
|
+
if verbose:
|
|
768
|
+
print(f"Using provided provider: {provided_provider_str}")
|
|
769
|
+
provider = provided_provider_str
|
|
770
|
+
else:
|
|
771
|
+
if verbose:
|
|
772
|
+
print(f"Using default provider: {S3Provider.S3.value}")
|
|
773
|
+
provider = S3Provider.S3.value
|
|
774
|
+
provider_enum = S3Provider.from_str(provider)
|
|
775
|
+
|
|
776
|
+
s3_creds: S3Credentials = S3Credentials(
|
|
777
|
+
provider=provider_enum,
|
|
778
|
+
access_key_id=section.access_key_id(),
|
|
779
|
+
secret_access_key=section.secret_access_key(),
|
|
780
|
+
endpoint_url=section.endpoint(),
|
|
781
|
+
)
|
|
799
782
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
783
|
+
chunk_fetcher: MultiMountFileChunker = self.get_multi_mount_file_chunker(
|
|
784
|
+
src=src_path.as_posix(),
|
|
785
|
+
chunk_size=chunk_size,
|
|
786
|
+
threads=read_threads,
|
|
787
|
+
mount_log=mount_log,
|
|
788
|
+
direct_io=True,
|
|
789
|
+
)
|
|
804
790
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
791
|
+
client = S3Client(s3_creds)
|
|
792
|
+
upload_config: S3MutliPartUploadConfig = S3MutliPartUploadConfig(
|
|
793
|
+
chunk_size=chunk_size.as_int(),
|
|
794
|
+
chunk_fetcher=chunk_fetcher.fetch,
|
|
795
|
+
max_write_threads=write_threads,
|
|
796
|
+
retries=retries,
|
|
797
|
+
resume_path_json=save_state_json,
|
|
798
|
+
max_chunks_before_suspension=max_chunks_before_suspension,
|
|
799
|
+
)
|
|
810
800
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
801
|
+
src_file = mount_path / name
|
|
802
|
+
|
|
803
|
+
print(f"Uploading {name} to {s3_key} in bucket {bucket_name}")
|
|
804
|
+
print(f"Source: {src_path}")
|
|
805
|
+
print(f"bucket_name: {bucket_name}")
|
|
806
|
+
print(f"upload_config: {upload_config}")
|
|
807
|
+
|
|
808
|
+
# get the file size
|
|
809
|
+
|
|
810
|
+
upload_target = S3UploadTarget(
|
|
811
|
+
src_file=src_file,
|
|
812
|
+
src_file_size=size_result.total_size,
|
|
813
|
+
bucket_name=bucket_name,
|
|
814
|
+
s3_key=s3_key,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
try:
|
|
818
|
+
out: MultiUploadResult = client.upload_file_multipart(
|
|
819
|
+
upload_target=upload_target,
|
|
820
|
+
upload_config=upload_config,
|
|
821
|
+
)
|
|
822
|
+
return out
|
|
823
|
+
except Exception as e:
|
|
824
|
+
print(f"Error uploading file: {e}")
|
|
825
|
+
traceback.print_exc()
|
|
826
|
+
raise
|
|
827
|
+
finally:
|
|
828
|
+
chunk_fetcher.close()
|
|
819
829
|
|
|
820
830
|
def _copy_bytes(
|
|
821
831
|
self,
|
|
@@ -963,9 +973,17 @@ class Rclone:
|
|
|
963
973
|
for mount in mounts:
|
|
964
974
|
mount.close()
|
|
965
975
|
raise
|
|
976
|
+
|
|
977
|
+
src_path: Path = Path(src)
|
|
978
|
+
src_parent_path = src_path.parent.as_posix()
|
|
979
|
+
name = src_path.name
|
|
980
|
+
size_result: SizeResult = self.size_files(src_parent_path, [name])
|
|
981
|
+
filesize = size_result.total_size
|
|
982
|
+
|
|
966
983
|
executor = ThreadPoolExecutor(max_workers=threads)
|
|
967
984
|
filechunker: MultiMountFileChunker = MultiMountFileChunker(
|
|
968
985
|
filename=filename,
|
|
986
|
+
filesize=filesize,
|
|
969
987
|
chunk_size=chunk_size,
|
|
970
988
|
mounts=mounts,
|
|
971
989
|
executor=executor,
|
rclone_api/s3/api.py
CHANGED
|
@@ -58,7 +58,12 @@ class S3Client:
|
|
|
58
58
|
bucket_name = upload_target.bucket_name
|
|
59
59
|
|
|
60
60
|
try:
|
|
61
|
-
|
|
61
|
+
|
|
62
|
+
if upload_target.src_file_size is None:
|
|
63
|
+
filesize = upload_target.src_file.stat().st_size
|
|
64
|
+
else:
|
|
65
|
+
filesize = upload_target.src_file_size
|
|
66
|
+
|
|
62
67
|
if filesize < _MIN_THRESHOLD_FOR_CHUNKING:
|
|
63
68
|
warnings.warn(
|
|
64
69
|
f"File size {filesize} is less than the minimum threshold for chunking ({_MIN_THRESHOLD_FOR_CHUNKING}), switching to single threaded upload."
|
|
@@ -73,6 +78,7 @@ class S3Client:
|
|
|
73
78
|
chunk_fetcher=upload_config.chunk_fetcher,
|
|
74
79
|
bucket_name=bucket_name,
|
|
75
80
|
file_path=upload_target.src_file,
|
|
81
|
+
file_size=filesize,
|
|
76
82
|
object_name=upload_target.s3_key,
|
|
77
83
|
resumable_info_path=resume_path_json,
|
|
78
84
|
chunk_size=chunk_size,
|
rclone_api/s3/chunk_types.py
CHANGED
|
@@ -74,6 +74,10 @@ class FileChunk:
|
|
|
74
74
|
return b""
|
|
75
75
|
|
|
76
76
|
def close(self):
|
|
77
|
+
import traceback
|
|
78
|
+
|
|
79
|
+
stacktrace = traceback.format_stack()
|
|
80
|
+
locked_print(f"Closing file chunk: {self.filepart}\n{stacktrace}")
|
|
77
81
|
if self.filepart.exists():
|
|
78
82
|
self.filepart.unlink()
|
|
79
83
|
|
|
@@ -172,8 +176,12 @@ class UploadState:
|
|
|
172
176
|
lock: Lock = Lock()
|
|
173
177
|
parts: list[FinishedPiece | None] = field(default_factory=list)
|
|
174
178
|
|
|
175
|
-
def update_source_file(self, src_file: Path) -> None:
|
|
176
|
-
new_file_size =
|
|
179
|
+
def update_source_file(self, src_file: Path, known_file_size: int | None) -> None:
|
|
180
|
+
new_file_size = (
|
|
181
|
+
known_file_size
|
|
182
|
+
if known_file_size is not None
|
|
183
|
+
else os.path.getsize(src_file)
|
|
184
|
+
)
|
|
177
185
|
if new_file_size != self.upload_info.file_size:
|
|
178
186
|
raise ValueError("File size changed, cannot resume")
|
|
179
187
|
self.upload_info.src_file_path = src_file
|
rclone_api/s3/types.py
CHANGED
|
@@ -76,6 +76,7 @@ def prepare_upload_file_multipart(
|
|
|
76
76
|
s3_client: BaseClient,
|
|
77
77
|
bucket_name: str,
|
|
78
78
|
file_path: Path,
|
|
79
|
+
file_size: int | None,
|
|
79
80
|
object_name: str,
|
|
80
81
|
chunk_size: int,
|
|
81
82
|
retries: int,
|
|
@@ -89,7 +90,7 @@ def prepare_upload_file_multipart(
|
|
|
89
90
|
mpu = s3_client.create_multipart_upload(Bucket=bucket_name, Key=object_name)
|
|
90
91
|
upload_id = mpu["UploadId"]
|
|
91
92
|
|
|
92
|
-
file_size = os.path.getsize(file_path)
|
|
93
|
+
file_size = file_size if file_size is not None else os.path.getsize(file_path)
|
|
93
94
|
|
|
94
95
|
upload_info: UploadInfo = UploadInfo(
|
|
95
96
|
s3_client=s3_client,
|
|
@@ -121,6 +122,7 @@ def upload_file_multipart(
|
|
|
121
122
|
chunk_fetcher: Callable[[int, int], Future[bytes | Exception]],
|
|
122
123
|
bucket_name: str,
|
|
123
124
|
file_path: Path,
|
|
125
|
+
file_size: int | None,
|
|
124
126
|
object_name: str,
|
|
125
127
|
resumable_info_path: Path | None,
|
|
126
128
|
chunk_size: int = 16 * 1024 * 1024, # Default chunk size is 16MB; can be overridden
|
|
@@ -130,12 +132,12 @@ def upload_file_multipart(
|
|
|
130
132
|
abort_transfer_on_failure: bool = False,
|
|
131
133
|
) -> MultiUploadResult:
|
|
132
134
|
"""Upload a file to the bucket using multipart upload with customizable chunk size."""
|
|
133
|
-
file_size = os.path.getsize(str(file_path))
|
|
134
|
-
if chunk_size > file_size:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
file_size = file_size if file_size is not None else os.path.getsize(str(file_path))
|
|
136
|
+
# if chunk_size > file_size:
|
|
137
|
+
# warnings.warn(
|
|
138
|
+
# f"Chunk size {chunk_size} is greater than file size {file_size}, using file size"
|
|
139
|
+
# )
|
|
140
|
+
# chunk_size = file_size
|
|
139
141
|
|
|
140
142
|
if chunk_size < _MIN_UPLOAD_CHUNK_SIZE:
|
|
141
143
|
raise ValueError(
|
|
@@ -160,6 +162,7 @@ def upload_file_multipart(
|
|
|
160
162
|
s3_client=s3_client,
|
|
161
163
|
bucket_name=bucket_name,
|
|
162
164
|
file_path=file_path,
|
|
165
|
+
file_size=file_size,
|
|
163
166
|
object_name=object_name,
|
|
164
167
|
chunk_size=chunk_size,
|
|
165
168
|
retries=retries,
|
|
@@ -194,7 +197,7 @@ def upload_file_multipart(
|
|
|
194
197
|
upload_state = loaded_state
|
|
195
198
|
|
|
196
199
|
try:
|
|
197
|
-
upload_state.update_source_file(file_path)
|
|
200
|
+
upload_state.update_source_file(file_path, file_size)
|
|
198
201
|
except ValueError as e:
|
|
199
202
|
locked_print(f"Cannot resume upload: {e}, size changed, starting over")
|
|
200
203
|
_abort_previous_upload(upload_state)
|
|
@@ -11,9 +11,9 @@ rclone_api/exec.py,sha256=Pd7pUBd8ib5MzqvMybG2DQISPRbDRu20VjVRL2mLAVY,1076
|
|
|
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=yklGrFzqADpxE8oK6PQCt78ZryK4JmU8lqv4S8cgroA,15234
|
|
15
15
|
rclone_api/process.py,sha256=rBj_S86jC6nqCYop-jq8r9eMSteKeObxUrJMgH8LZvI,5084
|
|
16
|
-
rclone_api/rclone.py,sha256=
|
|
16
|
+
rclone_api/rclone.py,sha256=V4oeepsleic2llCA7JekMw1iOwN7uYE3ktJ-gEA7wcw,49277
|
|
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
|
|
@@ -21,21 +21,21 @@ rclone_api/types.py,sha256=Gchc24Ze0QFUoUZpF_H1pwtP37OT8MKLUpMk3RwXSTs,6151
|
|
|
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=fYHyHq2YZT_dfMbS7SCpEeLCaWD-BU-jcpKP9eKf1jk,3388
|
|
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/profile/mount_copy_bytes.py,sha256=_bd9oergTuCdj1P6AVh_pC6GmtCpLFQYwiTdhwXeYZE,8404
|
|
29
|
-
rclone_api/s3/api.py,sha256=
|
|
29
|
+
rclone_api/s3/api.py,sha256=PafsIEyWDpLWAXsZAjFm9CY14vJpsDr9lOsn0kGRLZ0,4009
|
|
30
30
|
rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
|
|
31
31
|
rclone_api/s3/chunk_file.py,sha256=dCF5PcI3ygw4PXuaLluBU6euW4QYYpHjsLR_jjhZxQc,4505
|
|
32
|
-
rclone_api/s3/chunk_types.py,sha256=
|
|
32
|
+
rclone_api/s3/chunk_types.py,sha256=2n9U1BZ_5mcpoLLbSqkhvzgKj814jtSMnih9CWKcChU,10592
|
|
33
33
|
rclone_api/s3/create.py,sha256=wgfkapv_j904CfKuWyiBIWJVxfAx_ftemFSUV14aT68,3149
|
|
34
|
-
rclone_api/s3/types.py,sha256=
|
|
35
|
-
rclone_api/s3/upload_file_multipart.py,sha256=
|
|
36
|
-
rclone_api-1.2.
|
|
37
|
-
rclone_api-1.2.
|
|
38
|
-
rclone_api-1.2.
|
|
39
|
-
rclone_api-1.2.
|
|
40
|
-
rclone_api-1.2.
|
|
41
|
-
rclone_api-1.2.
|
|
34
|
+
rclone_api/s3/types.py,sha256=ZUw9s164wljCEMTS4CoHXNzFIhYJEgKD6NDAu-RXyr8,1551
|
|
35
|
+
rclone_api/s3/upload_file_multipart.py,sha256=aApXXnGt7MP1KQcYKIZHJvb6pGRaVxGJMq397bDxFgo,10792
|
|
36
|
+
rclone_api-1.2.3.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
|
37
|
+
rclone_api-1.2.3.dist-info/METADATA,sha256=t_v6jdm2t9UmtBzhZDGNC6kcJ_I3w4yjRNeyi1Cc8As,4536
|
|
38
|
+
rclone_api-1.2.3.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
|
39
|
+
rclone_api-1.2.3.dist-info/entry_points.txt,sha256=TV8kwP3FRzYwUEr0RLC7aJh0W03SAefIJNXTJ-FdMIQ,200
|
|
40
|
+
rclone_api-1.2.3.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
|
41
|
+
rclone_api-1.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|