rclone-api 1.0.96__tar.gz → 1.0.99__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.0.96 → rclone_api-1.0.99}/PKG-INFO +1 -1
- {rclone_api-1.0.96 → rclone_api-1.0.99}/pyproject.toml +1 -1
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/cmd/copy_large_s3.py +4 -1
- rclone_api-1.0.99/src/rclone_api/experimental/flags.py +121 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/rclone.py +26 -11
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/chunk_uploader.py +16 -1
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/PKG-INFO +1 -1
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/SOURCES.txt +1 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.aiderignore +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/lint.yml +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/push_macos.yml +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/push_ubuntu.yml +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/push_win.yml +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.gitignore +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.pylintrc +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.vscode/launch.json +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.vscode/settings.json +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/.vscode/tasks.json +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/LICENSE +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/MANIFEST.in +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/README.md +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/clean +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/install +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/lint +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/requirements.testing.txt +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/setup.cfg +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/setup.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/__init__.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/cmd/list_files.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/completed_process.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/config.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/convert.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/deprecated.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/diff.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/dir.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/dir_listing.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/exec.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/file.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/filelist.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/group_files.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/process.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/remote.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/rpath.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/api.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/basic_ops.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/create.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/types.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/scan_missing_folders.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/types.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/util.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/walk.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/entry_points.txt +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/requires.txt +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/test +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/archive/test_paramiko.py.disabled +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_cmd_list_files.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_copy.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_copy_files.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_diff.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_group_files.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_is_synced.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_ls.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mount.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mount_s3.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mount_webdav.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mounted_ranged_download.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_obscure.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_rclone_config.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_remote_control.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_remotes.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_s3.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_scan_missing_folders.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_size_files.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_walk.py +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/tox.ini +0 -0
- {rclone_api-1.0.96 → rclone_api-1.0.99}/upload_package.sh +0 -0
|
@@ -16,6 +16,7 @@ class Args:
|
|
|
16
16
|
read_concurrent_chunks: int
|
|
17
17
|
retries: int
|
|
18
18
|
save_state_json: Path
|
|
19
|
+
verbose: bool
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def list_files(rclone: Rclone, path: str):
|
|
@@ -29,6 +30,7 @@ def _parse_args() -> Args:
|
|
|
29
30
|
parser = argparse.ArgumentParser(description="List files in a remote path.")
|
|
30
31
|
parser.add_argument("src", help="File to copy")
|
|
31
32
|
parser.add_argument("dst", help="Destination file")
|
|
33
|
+
parser.add_argument("-v", "--verbose", help="Verbose output", action="store_true")
|
|
32
34
|
parser.add_argument(
|
|
33
35
|
"--config", help="Path to rclone config file", type=Path, required=True
|
|
34
36
|
)
|
|
@@ -58,6 +60,7 @@ def _parse_args() -> Args:
|
|
|
58
60
|
read_concurrent_chunks=args.read_concurrent_chunks,
|
|
59
61
|
retries=args.retries,
|
|
60
62
|
save_state_json=args.resumable_json,
|
|
63
|
+
verbose=args.verbose,
|
|
61
64
|
)
|
|
62
65
|
return out
|
|
63
66
|
|
|
@@ -73,7 +76,7 @@ def main() -> int:
|
|
|
73
76
|
concurrent_chunks=args.read_concurrent_chunks,
|
|
74
77
|
retries=args.retries,
|
|
75
78
|
save_state_json=args.save_state_json,
|
|
76
|
-
verbose=
|
|
79
|
+
verbose=args.verbose,
|
|
77
80
|
)
|
|
78
81
|
print(rslt)
|
|
79
82
|
return 0
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
2
|
+
from typing import Type, TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _merge(cls: Type[T], dataclass_a: T, dataclass_b: T) -> T:
|
|
8
|
+
if not is_dataclass(dataclass_a) or not is_dataclass(dataclass_b):
|
|
9
|
+
raise ValueError("Both inputs must be dataclass instances")
|
|
10
|
+
if type(dataclass_a) is not type(dataclass_b):
|
|
11
|
+
raise ValueError("Dataclass instances must be of the same type")
|
|
12
|
+
|
|
13
|
+
merged_kwargs = {}
|
|
14
|
+
for field in fields(dataclass_a):
|
|
15
|
+
a_value = getattr(dataclass_a, field.name)
|
|
16
|
+
b_value = getattr(dataclass_b, field.name)
|
|
17
|
+
|
|
18
|
+
if is_dataclass(a_value) and is_dataclass(b_value):
|
|
19
|
+
merged_kwargs[field.name] = _merge(type(a_value), a_value, b_value)
|
|
20
|
+
else:
|
|
21
|
+
merged_kwargs[field.name] = b_value if b_value is not None else a_value
|
|
22
|
+
|
|
23
|
+
return cls(**merged_kwargs)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _field_name_to_flag(field_name: str) -> str:
|
|
27
|
+
return f"--{field_name.replace('_', '-')}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class BaseFlags:
|
|
32
|
+
def to_args(self) -> list[str]:
|
|
33
|
+
args = []
|
|
34
|
+
for field in fields(self):
|
|
35
|
+
value = getattr(self, field.name)
|
|
36
|
+
if value is None:
|
|
37
|
+
continue
|
|
38
|
+
# If the field value is a nested dataclass that supports to_args, use it.
|
|
39
|
+
if is_dataclass(value) and hasattr(value, "to_args"):
|
|
40
|
+
to_args = getattr(value, "to_args")
|
|
41
|
+
args.extend(to_args())
|
|
42
|
+
elif isinstance(value, bool):
|
|
43
|
+
# Only include the flag if the boolean is True.
|
|
44
|
+
if value:
|
|
45
|
+
args.append(_field_name_to_flag(field.name))
|
|
46
|
+
else:
|
|
47
|
+
args.append(_field_name_to_flag(field.name))
|
|
48
|
+
if isinstance(value, list):
|
|
49
|
+
# Join list values with a comma.
|
|
50
|
+
args.append(",".join(map(str, value)))
|
|
51
|
+
else:
|
|
52
|
+
args.append(str(value))
|
|
53
|
+
return args
|
|
54
|
+
|
|
55
|
+
def merge(self, other: "BaseFlags") -> "BaseFlags":
|
|
56
|
+
# Use the type of self, so merging CopyFlags returns a CopyFlags instance.
|
|
57
|
+
return _merge(type(self), self, other)
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
return str(self.to_args())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(repr=False)
|
|
64
|
+
class CopyFlags(BaseFlags):
|
|
65
|
+
check_first: bool | None = None
|
|
66
|
+
checksum: bool | None = False
|
|
67
|
+
compare_dest: list[str] | None = None
|
|
68
|
+
copy_dest: list[str] | None = None
|
|
69
|
+
cutoff_mode: str | None = None
|
|
70
|
+
ignore_case_sync: bool | None = None
|
|
71
|
+
ignore_checksum: bool | None = None
|
|
72
|
+
ignore_existing: bool | None = None
|
|
73
|
+
ignore_size: bool | None = None
|
|
74
|
+
ignore_times: bool | None = None
|
|
75
|
+
immutable: bool | None = None
|
|
76
|
+
inplace: bool | None = None
|
|
77
|
+
links: bool | None = None
|
|
78
|
+
max_backlog: int | None = None
|
|
79
|
+
max_duration: str | None = None
|
|
80
|
+
max_transfer: str | None = None
|
|
81
|
+
metadata: bool | None = None
|
|
82
|
+
modify_window: str | None = None
|
|
83
|
+
multi_thread_chunk_size: str | None = None
|
|
84
|
+
multi_thread_cutoff: str | None = None
|
|
85
|
+
multi_thread_streams: int | None = None
|
|
86
|
+
multi_thread_write_buffer_size: str | None = None
|
|
87
|
+
no_check_dest: bool | None = None
|
|
88
|
+
no_traverse: bool | None = None
|
|
89
|
+
no_update_dir_modtime: bool | None = None
|
|
90
|
+
no_update_modtime: bool | None = None
|
|
91
|
+
order_by: str | None = None
|
|
92
|
+
partial_suffix: str | None = None
|
|
93
|
+
refresh_times: bool | None = None
|
|
94
|
+
server_side_across_configs: bool | None = None
|
|
95
|
+
size_only: bool | None = None
|
|
96
|
+
streaming_upload_cutoff: str | None = None
|
|
97
|
+
update: bool | None = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(repr=False)
|
|
101
|
+
class Flags(BaseFlags):
|
|
102
|
+
copy: CopyFlags | None = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def unit_test() -> None:
|
|
106
|
+
copy_flags_a = CopyFlags(compare_dest=["a", "b"])
|
|
107
|
+
copy_flags_b = CopyFlags(checksum=False)
|
|
108
|
+
flags_a = copy_flags_a.merge(copy_flags_b)
|
|
109
|
+
print("A:", flags_a)
|
|
110
|
+
|
|
111
|
+
copy_flags_c = CopyFlags(checksum=True)
|
|
112
|
+
copy_flags_d = CopyFlags(checksum=False)
|
|
113
|
+
|
|
114
|
+
merged_c_d = copy_flags_c.merge(copy_flags_d)
|
|
115
|
+
print("B:", merged_c_d)
|
|
116
|
+
merged_d_c = copy_flags_d.merge(copy_flags_c)
|
|
117
|
+
print("C:", merged_d_c)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
unit_test()
|
|
@@ -893,6 +893,7 @@ class Rclone:
|
|
|
893
893
|
) -> Generator[Process, None, None]:
|
|
894
894
|
"""Like mount, but can be used in a context manager."""
|
|
895
895
|
error_happened = False
|
|
896
|
+
verbose = get_verbose(verbose)
|
|
896
897
|
proc = self.mount(
|
|
897
898
|
src,
|
|
898
899
|
outdir,
|
|
@@ -918,19 +919,33 @@ class Rclone:
|
|
|
918
919
|
if outdir.exists():
|
|
919
920
|
print(f"{outdir} mount still exists, attempting to remove")
|
|
920
921
|
if not _IS_WINDOWS:
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
922
|
+
|
|
923
|
+
def exec(cmd: str) -> int:
|
|
924
|
+
if verbose:
|
|
925
|
+
print(f"Executing: {cmd}")
|
|
926
|
+
rtn = os.system(cmd)
|
|
927
|
+
if rtn != 0 and verbose:
|
|
928
|
+
print(f"Failed to execute: {cmd}")
|
|
929
|
+
return rtn
|
|
930
|
+
|
|
931
|
+
exec(f"fusermount -u {outdir}")
|
|
932
|
+
exec(f"umount {outdir}")
|
|
924
933
|
time.sleep(2)
|
|
925
934
|
if outdir.exists():
|
|
926
|
-
is_empty =
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
935
|
+
is_empty = True
|
|
936
|
+
try:
|
|
937
|
+
is_empty = not list(outdir.iterdir())
|
|
938
|
+
if not is_empty:
|
|
939
|
+
warnings.warn(f"Failed to unmount {outdir}")
|
|
940
|
+
else:
|
|
941
|
+
try:
|
|
942
|
+
outdir.rmdir()
|
|
943
|
+
except Exception as e:
|
|
944
|
+
warnings.warn(f"Failed to remove {outdir}: {e}")
|
|
945
|
+
except Exception as e:
|
|
946
|
+
warnings.warn(
|
|
947
|
+
f"Failed during mount cleanup of {outdir}: because {e}"
|
|
948
|
+
)
|
|
934
949
|
|
|
935
950
|
@deprecated("mount")
|
|
936
951
|
def mount_webdav(
|
|
@@ -260,6 +260,20 @@ def _get_chunk_tmpdir() -> Path:
|
|
|
260
260
|
return out
|
|
261
261
|
|
|
262
262
|
|
|
263
|
+
def _get_file_size(file_path: Path, timeout: int = 60) -> int:
|
|
264
|
+
sleep_time = timeout / 60 if timeout > 0 else 1
|
|
265
|
+
start = time.time()
|
|
266
|
+
while True:
|
|
267
|
+
try:
|
|
268
|
+
if file_path.exists():
|
|
269
|
+
return file_path.stat().st_size
|
|
270
|
+
except FileNotFoundError:
|
|
271
|
+
pass
|
|
272
|
+
if time.time() - start > timeout:
|
|
273
|
+
raise TimeoutError(f"File {file_path} not found after {timeout} seconds")
|
|
274
|
+
time.sleep(sleep_time)
|
|
275
|
+
|
|
276
|
+
|
|
263
277
|
def file_chunker(
|
|
264
278
|
upload_state: UploadState, max_chunks: int | None, output: Queue[FileChunk | None]
|
|
265
279
|
) -> None:
|
|
@@ -279,7 +293,8 @@ def file_chunker(
|
|
|
279
293
|
file_path = upload_info.src_file_path
|
|
280
294
|
chunk_size = upload_info.chunk_size
|
|
281
295
|
src = Path(file_path)
|
|
282
|
-
|
|
296
|
+
# Mounted files may take a while to appear, so keep retrying.
|
|
297
|
+
file_size = _get_file_size(src, timeout=60)
|
|
283
298
|
part_number = 1
|
|
284
299
|
done_part_numbers: set[int] = {
|
|
285
300
|
p.part_number for p in upload_state.parts if p is not None
|
|
@@ -50,6 +50,7 @@ src/rclone_api.egg-info/top_level.txt
|
|
|
50
50
|
src/rclone_api/assets/example.txt
|
|
51
51
|
src/rclone_api/cmd/copy_large_s3.py
|
|
52
52
|
src/rclone_api/cmd/list_files.py
|
|
53
|
+
src/rclone_api/experimental/flags.py
|
|
53
54
|
src/rclone_api/s3/api.py
|
|
54
55
|
src/rclone_api/s3/basic_ops.py
|
|
55
56
|
src/rclone_api/s3/chunk_uploader.py
|
|
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
|