rclone-api 1.1.60__py2.py3-none-any.whl → 1.1.62__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/mount.py +83 -1
- rclone_api/profile/mount_copy_bytes.py +15 -10
- rclone_api/rclone.py +122 -6
- rclone_api/types.py +8 -0
- {rclone_api-1.1.60.dist-info → rclone_api-1.1.62.dist-info}/METADATA +1 -1
- {rclone_api-1.1.60.dist-info → rclone_api-1.1.62.dist-info}/RECORD +10 -10
- {rclone_api-1.1.60.dist-info → rclone_api-1.1.62.dist-info}/LICENSE +0 -0
- {rclone_api-1.1.60.dist-info → rclone_api-1.1.62.dist-info}/WHEEL +0 -0
- {rclone_api-1.1.60.dist-info → rclone_api-1.1.62.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.1.60.dist-info → rclone_api-1.1.62.dist-info}/top_level.txt +0 -0
rclone_api/mount.py
CHANGED
|
@@ -6,12 +6,14 @@ import subprocess
|
|
|
6
6
|
import time
|
|
7
7
|
import warnings
|
|
8
8
|
import weakref
|
|
9
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from threading import Lock, Semaphore
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
14
15
|
from rclone_api.process import Process
|
|
16
|
+
from rclone_api.types import SizeSuffix
|
|
15
17
|
|
|
16
18
|
_SYSTEM = platform.system() # "Linux", "Darwin", "Windows", etc.
|
|
17
19
|
|
|
@@ -283,3 +285,83 @@ def _cache_dir_delete_on_exit(cache_dir: Path) -> None:
|
|
|
283
285
|
shutil.rmtree(cache_dir)
|
|
284
286
|
except Exception as e:
|
|
285
287
|
warnings.warn(f"Error removing cache directory {cache_dir}: {e}")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class MultiMountFileChunker:
|
|
291
|
+
def __init__(
|
|
292
|
+
self,
|
|
293
|
+
filename: str,
|
|
294
|
+
chunk_size: SizeSuffix,
|
|
295
|
+
mounts: list[Mount],
|
|
296
|
+
executor: ThreadPoolExecutor,
|
|
297
|
+
) -> None:
|
|
298
|
+
self.filename = filename
|
|
299
|
+
self.chunk_size = chunk_size
|
|
300
|
+
self.executor = executor
|
|
301
|
+
self.mounts_processing: list[Mount] = []
|
|
302
|
+
self.mounts_availabe: list[Mount] = mounts
|
|
303
|
+
self.semaphore = Semaphore(len(mounts))
|
|
304
|
+
self.lock = Lock()
|
|
305
|
+
|
|
306
|
+
def close(self) -> None:
|
|
307
|
+
self.executor.shutdown(wait=False, cancel_futures=True)
|
|
308
|
+
with ThreadPoolExecutor() as executor:
|
|
309
|
+
for mount in self.mounts_processing:
|
|
310
|
+
executor.submit(lambda: mount.close())
|
|
311
|
+
|
|
312
|
+
def fetch(self, offset: int, size: int) -> bytes | Exception:
|
|
313
|
+
try:
|
|
314
|
+
assert offset % self.chunk_size.as_int() == 0
|
|
315
|
+
assert size <= self.chunk_size.as_int()
|
|
316
|
+
assert size > 0
|
|
317
|
+
assert offset >= 0
|
|
318
|
+
|
|
319
|
+
chunks: list[tuple[int, int]] = []
|
|
320
|
+
start = offset
|
|
321
|
+
end = offset + size
|
|
322
|
+
while start < end:
|
|
323
|
+
chunk_size = min(self.chunk_size.as_int(), end - start)
|
|
324
|
+
chunks.append((start, chunk_size))
|
|
325
|
+
start += chunk_size
|
|
326
|
+
|
|
327
|
+
futures: list[Future[bytes | Exception]] = []
|
|
328
|
+
for start, chunk_size in chunks:
|
|
329
|
+
self.semaphore.acquire()
|
|
330
|
+
with self.lock:
|
|
331
|
+
mount = self.mounts_availabe.pop()
|
|
332
|
+
self.mounts_processing.append(mount)
|
|
333
|
+
|
|
334
|
+
path = mount.mount_path / self.filename
|
|
335
|
+
|
|
336
|
+
def task() -> bytes | Exception:
|
|
337
|
+
try:
|
|
338
|
+
with path.open("rb") as f:
|
|
339
|
+
f.seek(offset)
|
|
340
|
+
return f.read(size)
|
|
341
|
+
except KeyboardInterrupt as e:
|
|
342
|
+
import _thread
|
|
343
|
+
|
|
344
|
+
_thread.interrupt_main()
|
|
345
|
+
return Exception(e)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
return e
|
|
348
|
+
finally:
|
|
349
|
+
with self.lock:
|
|
350
|
+
self.mounts_processing.remove(mount)
|
|
351
|
+
self.mounts_availabe.append(mount)
|
|
352
|
+
self.semaphore.release()
|
|
353
|
+
|
|
354
|
+
fut = self.executor.submit(task)
|
|
355
|
+
futures.append(fut)
|
|
356
|
+
|
|
357
|
+
out: bytes = b""
|
|
358
|
+
for fut in futures:
|
|
359
|
+
chunk: bytes | Exception = fut.result()
|
|
360
|
+
if isinstance(chunk, Exception):
|
|
361
|
+
return chunk
|
|
362
|
+
else:
|
|
363
|
+
out += chunk
|
|
364
|
+
del chunk
|
|
365
|
+
return out
|
|
366
|
+
except Exception as e:
|
|
367
|
+
return e
|
|
@@ -6,7 +6,6 @@ import argparse
|
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
8
|
import time
|
|
9
|
-
from concurrent.futures import Future, ThreadPoolExecutor
|
|
10
9
|
from dataclasses import dataclass
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
@@ -119,12 +118,14 @@ def _run_profile(
|
|
|
119
118
|
print("#" * 80)
|
|
120
119
|
net_io_start = psutil.net_io_counters()
|
|
121
120
|
start = time.time()
|
|
121
|
+
chunk_size = size // transfers
|
|
122
122
|
bytes_or_err: bytes | Exception = rclone.copy_bytes(
|
|
123
123
|
src=src_file,
|
|
124
124
|
offset=offset.as_int(),
|
|
125
125
|
length=size.as_int(),
|
|
126
|
+
chunk_size=chunk_size,
|
|
126
127
|
direct_io=direct_io,
|
|
127
|
-
|
|
128
|
+
max_threads=transfers,
|
|
128
129
|
mount_log=mount_log,
|
|
129
130
|
)
|
|
130
131
|
diff = time.time() - start
|
|
@@ -138,6 +139,8 @@ def _run_profile(
|
|
|
138
139
|
assert len(bytes_or_err) == size.as_int(), f"Length: {len(bytes_or_err)} != {size}"
|
|
139
140
|
|
|
140
141
|
# print io stats
|
|
142
|
+
# disabling num for now
|
|
143
|
+
num = 1
|
|
141
144
|
bytes_sent = (net_io_end.bytes_sent - net_io_start.bytes_sent) // num
|
|
142
145
|
bytes_recv = (net_io_end.bytes_recv - net_io_start.bytes_recv) // num
|
|
143
146
|
packets_sent = (net_io_end.packets_sent - net_io_start.packets_sent) // num
|
|
@@ -230,8 +233,8 @@ def main() -> None:
|
|
|
230
233
|
shutil.rmtree(mount_root_path)
|
|
231
234
|
|
|
232
235
|
args = _parse_args()
|
|
233
|
-
transfer_list = [
|
|
234
|
-
parallel_workers = args.num
|
|
236
|
+
transfer_list = [args.num]
|
|
237
|
+
# parallel_workers = args.num
|
|
235
238
|
|
|
236
239
|
def task(
|
|
237
240
|
offset: SizeSuffix,
|
|
@@ -250,12 +253,14 @@ def main() -> None:
|
|
|
250
253
|
num=args.num,
|
|
251
254
|
)
|
|
252
255
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
task(SizeSuffix(0))
|
|
257
|
+
|
|
258
|
+
# with ThreadPoolExecutor(max_workers=parallel_workers) as _:
|
|
259
|
+
# tasks: list[Future] = []
|
|
260
|
+
# for i in range(parallel_workers):
|
|
261
|
+
# offset = SizeSuffix(i * 1024 * 1024 * 256)
|
|
262
|
+
# future = ThreadPoolExecutor().submit(lambda: task(offset=offset))
|
|
263
|
+
# tasks.append(future)
|
|
259
264
|
|
|
260
265
|
|
|
261
266
|
if __name__ == "__main__":
|
rclone_api/rclone.py
CHANGED
|
@@ -25,7 +25,7 @@ from rclone_api.dir_listing import DirListing
|
|
|
25
25
|
from rclone_api.exec import RcloneExec
|
|
26
26
|
from rclone_api.file import File
|
|
27
27
|
from rclone_api.group_files import group_files
|
|
28
|
-
from rclone_api.mount import Mount, clean_mount, prepare_mount
|
|
28
|
+
from rclone_api.mount import Mount, MultiMountFileChunker, clean_mount, prepare_mount
|
|
29
29
|
from rclone_api.process import Process
|
|
30
30
|
from rclone_api.remote import Remote
|
|
31
31
|
from rclone_api.rpath import RPath
|
|
@@ -815,7 +815,7 @@ class Rclone:
|
|
|
815
815
|
)
|
|
816
816
|
return out
|
|
817
817
|
|
|
818
|
-
def
|
|
818
|
+
def _copy_bytes(
|
|
819
819
|
self,
|
|
820
820
|
src: str,
|
|
821
821
|
offset: int,
|
|
@@ -829,7 +829,7 @@ class Rclone:
|
|
|
829
829
|
"""Copy bytes from a file to another file."""
|
|
830
830
|
from rclone_api.util import random_str
|
|
831
831
|
|
|
832
|
-
|
|
832
|
+
tmp_mnts = Path("tmp_mnts") / random_str(12)
|
|
833
833
|
src_parent_path = Path(src).parent.as_posix()
|
|
834
834
|
src_file = Path(src).name
|
|
835
835
|
other_args: list[str] = [
|
|
@@ -844,7 +844,7 @@ class Rclone:
|
|
|
844
844
|
max_read_ahead = SizeSuffix(vfs_read_chunk_size.as_int())
|
|
845
845
|
|
|
846
846
|
# other_args += ["--vfs-read-chunk-size", str(vfs_read_chunk_size)]
|
|
847
|
-
other_args += ["--vfs-read-chunk-size", str(
|
|
847
|
+
other_args += ["--vfs-read-chunk-size", str(vfs_read_chunk_size)]
|
|
848
848
|
other_args += ["--vfs-read-chunk-size-limit", str(vfs_read_chunk_size_limit)]
|
|
849
849
|
other_args += ["--vfs-read-chunk-streams", str(vfs_read_chunk_streams)]
|
|
850
850
|
other_args += ["--vfs-disk-space-total-size", str(vfs_disk_space_total_size)]
|
|
@@ -860,7 +860,7 @@ class Rclone:
|
|
|
860
860
|
# use scoped mount to do the read, then write the bytes to the destination
|
|
861
861
|
with self.scoped_mount(
|
|
862
862
|
src_parent_path,
|
|
863
|
-
|
|
863
|
+
tmp_mnts,
|
|
864
864
|
use_links=True,
|
|
865
865
|
verbose=mount_log is not None,
|
|
866
866
|
vfs_cache_mode="minimal",
|
|
@@ -869,7 +869,7 @@ class Rclone:
|
|
|
869
869
|
cache_dir=cache_dir,
|
|
870
870
|
cache_dir_delete_on_exit=True,
|
|
871
871
|
):
|
|
872
|
-
src_file_mnt =
|
|
872
|
+
src_file_mnt = tmp_mnts / src_file
|
|
873
873
|
with open(src_file_mnt, "rb") as f:
|
|
874
874
|
f.seek(offset)
|
|
875
875
|
data = f.read(length)
|
|
@@ -883,6 +883,122 @@ class Rclone:
|
|
|
883
883
|
except Exception as e:
|
|
884
884
|
return e
|
|
885
885
|
|
|
886
|
+
def get_multi_mount_file_chunker(
|
|
887
|
+
self,
|
|
888
|
+
src: str,
|
|
889
|
+
chunk_size: SizeSuffix,
|
|
890
|
+
threads: int,
|
|
891
|
+
mount_log: Path | None,
|
|
892
|
+
direct_io: bool,
|
|
893
|
+
) -> MultiMountFileChunker:
|
|
894
|
+
from rclone_api.util import random_str
|
|
895
|
+
|
|
896
|
+
mounts: list[Mount] = []
|
|
897
|
+
vfs_read_chunk_size = chunk_size
|
|
898
|
+
vfs_read_chunk_size_limit = chunk_size
|
|
899
|
+
vfs_read_chunk_streams = 0
|
|
900
|
+
vfs_disk_space_total_size = chunk_size
|
|
901
|
+
other_args: list[str] = []
|
|
902
|
+
other_args += ["--no-modtime"]
|
|
903
|
+
other_args += ["--vfs-read-chunk-size", vfs_read_chunk_size.as_str()]
|
|
904
|
+
other_args += [
|
|
905
|
+
"--vfs-read-chunk-size-limit",
|
|
906
|
+
vfs_read_chunk_size_limit.as_str(),
|
|
907
|
+
]
|
|
908
|
+
other_args += ["--vfs-read-chunk-streams", str(vfs_read_chunk_streams)]
|
|
909
|
+
other_args += [
|
|
910
|
+
"--vfs-disk-space-total-size",
|
|
911
|
+
vfs_disk_space_total_size.as_str(),
|
|
912
|
+
]
|
|
913
|
+
other_args += ["--read-only"]
|
|
914
|
+
if direct_io:
|
|
915
|
+
other_args += ["--direct-io"]
|
|
916
|
+
|
|
917
|
+
filename = Path(src).name
|
|
918
|
+
with ThreadPoolExecutor(max_workers=threads) as executor:
|
|
919
|
+
futures: list[Future] = []
|
|
920
|
+
try:
|
|
921
|
+
for i in range(threads):
|
|
922
|
+
tmp_mnts = Path("tmp_mnts") / random_str(12)
|
|
923
|
+
verbose = mount_log is not None
|
|
924
|
+
clean_mount(tmp_mnts, verbose=verbose)
|
|
925
|
+
prepare_mount(tmp_mnts, verbose=verbose)
|
|
926
|
+
src_parent_path = Path(src).parent.as_posix()
|
|
927
|
+
|
|
928
|
+
def task(src_parent_path=src_parent_path, tmp_mnts=tmp_mnts):
|
|
929
|
+
return self.mount(
|
|
930
|
+
src=src_parent_path,
|
|
931
|
+
outdir=tmp_mnts,
|
|
932
|
+
allow_writes=False,
|
|
933
|
+
use_links=True,
|
|
934
|
+
vfs_cache_mode="minimal",
|
|
935
|
+
verbose=False,
|
|
936
|
+
cache_dir=Path("cache") / random_str(12),
|
|
937
|
+
cache_dir_delete_on_exit=True,
|
|
938
|
+
log=mount_log,
|
|
939
|
+
other_args=other_args,
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
futures.append(executor.submit(task))
|
|
943
|
+
mount_errors: list[Exception] = []
|
|
944
|
+
for fut in futures:
|
|
945
|
+
try:
|
|
946
|
+
mount = fut.result()
|
|
947
|
+
mounts.append(mount)
|
|
948
|
+
except Exception as er:
|
|
949
|
+
mount_errors.append(er)
|
|
950
|
+
if mount_errors:
|
|
951
|
+
raise Exception(mount_errors)
|
|
952
|
+
except Exception:
|
|
953
|
+
for mount in mounts:
|
|
954
|
+
mount.close()
|
|
955
|
+
raise
|
|
956
|
+
executor = ThreadPoolExecutor(max_workers=threads)
|
|
957
|
+
filechunker: MultiMountFileChunker = MultiMountFileChunker(
|
|
958
|
+
filename=filename,
|
|
959
|
+
chunk_size=chunk_size,
|
|
960
|
+
mounts=mounts,
|
|
961
|
+
executor=executor,
|
|
962
|
+
)
|
|
963
|
+
return filechunker
|
|
964
|
+
|
|
965
|
+
def copy_bytes(
|
|
966
|
+
self,
|
|
967
|
+
src: str,
|
|
968
|
+
offset: int,
|
|
969
|
+
length: int,
|
|
970
|
+
chunk_size: SizeSuffix,
|
|
971
|
+
max_threads: int = 1,
|
|
972
|
+
# If outfile is supplied then bytes are written to this file and success returns bytes(0)
|
|
973
|
+
outfile: Path | None = None,
|
|
974
|
+
mount_log: Path | None = None,
|
|
975
|
+
direct_io: bool = True,
|
|
976
|
+
) -> bytes | Exception:
|
|
977
|
+
"""Copy bytes from a file to another file."""
|
|
978
|
+
# determine number of threads from chunk size
|
|
979
|
+
threads = max(1, min(max_threads, length // chunk_size.as_int()))
|
|
980
|
+
filechunker = self.get_multi_mount_file_chunker(
|
|
981
|
+
src=src,
|
|
982
|
+
chunk_size=chunk_size,
|
|
983
|
+
threads=threads,
|
|
984
|
+
mount_log=mount_log,
|
|
985
|
+
direct_io=direct_io,
|
|
986
|
+
)
|
|
987
|
+
try:
|
|
988
|
+
data = filechunker.fetch(offset, length)
|
|
989
|
+
if isinstance(data, Exception):
|
|
990
|
+
raise data
|
|
991
|
+
if outfile is None:
|
|
992
|
+
return data
|
|
993
|
+
with open(outfile, "wb") as out:
|
|
994
|
+
out.write(data)
|
|
995
|
+
del data
|
|
996
|
+
return bytes(0)
|
|
997
|
+
except Exception as e:
|
|
998
|
+
return e
|
|
999
|
+
finally:
|
|
1000
|
+
filechunker.close()
|
|
1001
|
+
|
|
886
1002
|
def copy_dir(
|
|
887
1003
|
self, src: str | Dir, dst: str | Dir, args: list[str] | None = None
|
|
888
1004
|
) -> CompletedProcess:
|
rclone_api/types.py
CHANGED
|
@@ -170,3 +170,11 @@ class SizeSuffix:
|
|
|
170
170
|
raise ZeroDivisionError("Division by zero is undefined")
|
|
171
171
|
# Use floor division to maintain integer arithmetic.
|
|
172
172
|
return SizeSuffix(self._size // other_int._size)
|
|
173
|
+
|
|
174
|
+
# support / division
|
|
175
|
+
def __floordiv__(self, other: "int | SizeSuffix") -> "SizeSuffix":
|
|
176
|
+
other_int = SizeSuffix(other)
|
|
177
|
+
if other_int._size == 0:
|
|
178
|
+
raise ZeroDivisionError("Division by zero is undefined")
|
|
179
|
+
# Use floor division to maintain integer arithmetic.
|
|
180
|
+
return SizeSuffix(self._size // other_int._size)
|
|
@@ -11,13 +11,13 @@ 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=hd6_zmJ-wH337F0eh9JJMMlVzj92m32tLm2t5qZFxNc,12931
|
|
15
15
|
rclone_api/process.py,sha256=rBj_S86jC6nqCYop-jq8r9eMSteKeObxUrJMgH8LZvI,5084
|
|
16
|
-
rclone_api/rclone.py,sha256=
|
|
16
|
+
rclone_api/rclone.py,sha256=Sd4NrCoCwIe8wfOEpJ-eL6BRozIvCHI6fhnr6BI2ET8,48226
|
|
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=2x9APG861HFrMwt5AIpxYI_ahDY09vUjGMtTE5xHlEs,5085
|
|
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
|
|
@@ -25,7 +25,7 @@ rclone_api/cmd/copy_large_s3.py,sha256=-rfedi-ZzPUdCSP8ai9LRL0y1xVkvN-viQQlk8HVU
|
|
|
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
|
-
rclone_api/profile/mount_copy_bytes.py,sha256=
|
|
28
|
+
rclone_api/profile/mount_copy_bytes.py,sha256=KJhn2ufpRwT9c9bH_q2RWvklw5x63nvI-UMtv9O8CPg,7717
|
|
29
29
|
rclone_api/s3/api.py,sha256=qxtRDUpHYqJ7StJRtP8U_PbF_BvYRg705568SyvF-R0,3770
|
|
30
30
|
rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
|
|
31
31
|
rclone_api/s3/chunk_file.py,sha256=D6wM9Nuu73LNGC8JCCfevqjF3qdZ21mQxYQClFLZLMU,3726
|
|
@@ -33,9 +33,9 @@ rclone_api/s3/chunk_types.py,sha256=LbXayXY1KgVU1LkdbASD_BQ7TpVpwVnzMjtz--8LBaE,
|
|
|
33
33
|
rclone_api/s3/create.py,sha256=wgfkapv_j904CfKuWyiBIWJVxfAx_ftemFSUV14aT68,3149
|
|
34
34
|
rclone_api/s3/types.py,sha256=yBnJ38Tjk6RlydJ-sqZ7DSfyFloy8KDYJ0mv3vlOzLE,1388
|
|
35
35
|
rclone_api/s3/upload_file_multipart.py,sha256=y9azNAU8QH5Ovwz33V2HZwNmJdlFjJg-jrXLZ1gtMds,10364
|
|
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.
|
|
41
|
-
rclone_api-1.1.
|
|
36
|
+
rclone_api-1.1.62.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
|
37
|
+
rclone_api-1.1.62.dist-info/METADATA,sha256=d0_DUkLYpOf_vedOxoFXs7Qco4e_6A3RBGaPogBUkec,4537
|
|
38
|
+
rclone_api-1.1.62.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
|
39
|
+
rclone_api-1.1.62.dist-info/entry_points.txt,sha256=TV8kwP3FRzYwUEr0RLC7aJh0W03SAefIJNXTJ-FdMIQ,200
|
|
40
|
+
rclone_api-1.1.62.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
|
41
|
+
rclone_api-1.1.62.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|