rclone-api 1.1.5__tar.gz → 1.1.6__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.1.5 → rclone_api-1.1.6}/PKG-INFO +1 -1
- {rclone_api-1.1.5 → rclone_api-1.1.6}/pyproject.toml +1 -1
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/__init__.py +2 -1
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/cmd/copy_large_s3.py +10 -7
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/mount.py +13 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/rclone.py +29 -24
- rclone_api-1.1.6/src/rclone_api/types.py +125 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api.egg-info/PKG-INFO +1 -1
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api.egg-info/SOURCES.txt +1 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_mounted_ranged_download.py +2 -2
- rclone_api-1.1.6/tests/test_size_suffix.py +21 -0
- rclone_api-1.1.5/src/rclone_api/types.py +0 -35
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.aiderignore +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.github/workflows/lint.yml +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.github/workflows/push_macos.yml +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.github/workflows/push_ubuntu.yml +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.github/workflows/push_win.yml +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.gitignore +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.pylintrc +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.vscode/launch.json +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.vscode/settings.json +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/.vscode/tasks.json +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/LICENSE +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/MANIFEST.in +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/README.md +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/clean +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/install +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/lint +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/requirements.testing.txt +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/setup.cfg +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/setup.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/cmd/list_files.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/completed_process.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/config.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/convert.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/deprecated.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/diff.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/dir.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/dir_listing.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/exec.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/experimental/flags.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/experimental/flags_base.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/file.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/filelist.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/group_files.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/process.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/remote.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/rpath.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/api.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/basic_ops.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/chunk_file.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/chunk_types.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/chunk_uploader.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/create.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/s3/types.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/scan_missing_folders.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/util.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api/walk.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api.egg-info/entry_points.txt +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api.egg-info/requires.txt +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/test +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/archive/test_paramiko.py.disabled +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_cmd_list_files.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_copy.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_copy_files.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_diff.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_group_files.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_is_synced.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_ls.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_mount.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_mount_s3.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_mount_webdav.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_obscure.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_rclone_config.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_remote_control.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_remotes.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_s3.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_scan_missing_folders.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_size_files.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tests/test_walk.py +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/tox.ini +0 -0
- {rclone_api-1.1.5 → rclone_api-1.1.6}/upload_package.sh +0 -0
|
@@ -10,7 +10,7 @@ from .rclone import Rclone, rclone_verbose
|
|
|
10
10
|
from .remote import Remote
|
|
11
11
|
from .rpath import RPath
|
|
12
12
|
from .s3.types import MultiUploadResult
|
|
13
|
-
from .types import ListingOption, Order, SizeResult
|
|
13
|
+
from .types import ListingOption, Order, SizeResult, SizeSuffix
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
16
|
"Rclone",
|
|
@@ -34,4 +34,5 @@ __all__ = [
|
|
|
34
34
|
"Parsed",
|
|
35
35
|
"Section",
|
|
36
36
|
"MultiUploadResult",
|
|
37
|
+
"SizeSuffix",
|
|
37
38
|
]
|
|
@@ -2,7 +2,7 @@ import argparse
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from rclone_api import MultiUploadResult, Rclone
|
|
5
|
+
from rclone_api import MultiUploadResult, Rclone, SizeSuffix
|
|
6
6
|
|
|
7
7
|
_1MB = 1024 * 1024
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ class Args:
|
|
|
12
12
|
config_path: Path
|
|
13
13
|
src: str
|
|
14
14
|
dst: str
|
|
15
|
-
chunk_size_mb:
|
|
15
|
+
chunk_size_mb: SizeSuffix
|
|
16
16
|
read_concurrent_chunks: int
|
|
17
17
|
retries: int
|
|
18
18
|
save_state_json: Path
|
|
@@ -35,17 +35,20 @@ def _parse_args() -> Args:
|
|
|
35
35
|
"--config", help="Path to rclone config file", type=Path, required=True
|
|
36
36
|
)
|
|
37
37
|
parser.add_argument(
|
|
38
|
-
"--chunk-size
|
|
38
|
+
"--chunk-size",
|
|
39
|
+
help="Chunk size that will be read and uploaded in in SizeSuffix (i.e. 128M = 128 megabytes) form",
|
|
40
|
+
type=str,
|
|
41
|
+
default="128M",
|
|
39
42
|
)
|
|
40
43
|
parser.add_argument(
|
|
41
44
|
"--read-concurrent-chunks",
|
|
42
|
-
help="Maximum number of chunks to read",
|
|
45
|
+
help="Maximum number of chunks to read in a look ahead cache",
|
|
43
46
|
type=int,
|
|
44
|
-
default=
|
|
47
|
+
default=1,
|
|
45
48
|
)
|
|
46
49
|
parser.add_argument("--retries", help="Number of retries", type=int, default=3)
|
|
47
50
|
parser.add_argument(
|
|
48
|
-
"--
|
|
51
|
+
"--resume-json",
|
|
49
52
|
help="Path to resumable JSON file",
|
|
50
53
|
type=Path,
|
|
51
54
|
default="resume.json",
|
|
@@ -56,7 +59,7 @@ def _parse_args() -> Args:
|
|
|
56
59
|
config_path=Path(args.config),
|
|
57
60
|
src=args.src,
|
|
58
61
|
dst=args.dst,
|
|
59
|
-
chunk_size_mb=args.
|
|
62
|
+
chunk_size_mb=SizeSuffix(args.chunk_size),
|
|
60
63
|
read_concurrent_chunks=args.read_concurrent_chunks,
|
|
61
64
|
retries=args.retries,
|
|
62
65
|
save_state_json=args.resumable_json,
|
|
@@ -23,6 +23,19 @@ def run_command(cmd: str, verbose: bool) -> int:
|
|
|
23
23
|
return -1
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def prepare_mount(outdir: Path, verbose: bool) -> None:
|
|
27
|
+
if _SYSTEM == "Windows":
|
|
28
|
+
# Windows -> Must create parent directories only if they don't exist
|
|
29
|
+
if verbose:
|
|
30
|
+
print(f"Creating parent directories for {outdir}")
|
|
31
|
+
outdir.parent.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
else:
|
|
33
|
+
# Linux -> Must create parent directories and the directory itself
|
|
34
|
+
if verbose:
|
|
35
|
+
print(f"Creating directories for {outdir}")
|
|
36
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
|
|
26
39
|
def clean_mount(mount_path: Path, verbose: bool = False) -> None:
|
|
27
40
|
"""
|
|
28
41
|
Clean up a mount path across Linux, macOS, and Windows.
|
|
@@ -34,7 +34,13 @@ from rclone_api.s3.types import (
|
|
|
34
34
|
S3Provider,
|
|
35
35
|
S3UploadTarget,
|
|
36
36
|
)
|
|
37
|
-
from rclone_api.types import
|
|
37
|
+
from rclone_api.types import (
|
|
38
|
+
ListingOption,
|
|
39
|
+
ModTimeStrategy,
|
|
40
|
+
Order,
|
|
41
|
+
SizeResult,
|
|
42
|
+
SizeSuffix,
|
|
43
|
+
)
|
|
38
44
|
from rclone_api.util import (
|
|
39
45
|
get_check,
|
|
40
46
|
get_rclone_exe,
|
|
@@ -673,10 +679,13 @@ class Rclone:
|
|
|
673
679
|
src: str,
|
|
674
680
|
dst: str,
|
|
675
681
|
save_state_json: Path,
|
|
676
|
-
chunk_size:
|
|
677
|
-
|
|
678
|
-
* 1024
|
|
679
|
-
|
|
682
|
+
chunk_size: SizeSuffix | None = None,
|
|
683
|
+
# 16
|
|
684
|
+
# * 1024
|
|
685
|
+
# * 1024, # This setting will scale the performance of the upload
|
|
686
|
+
concurrent_chunks: (
|
|
687
|
+
int | None
|
|
688
|
+
) = None, # This setting will scale the performance of the upload
|
|
680
689
|
retries: int = 3,
|
|
681
690
|
verbose: bool | None = None,
|
|
682
691
|
max_chunks_before_suspension: int | None = None,
|
|
@@ -687,16 +696,22 @@ class Rclone:
|
|
|
687
696
|
from rclone_api.s3.create import S3Credentials
|
|
688
697
|
from rclone_api.util import S3PathInfo, random_str, split_s3_path
|
|
689
698
|
|
|
699
|
+
_tmp: SizeSuffix | str = chunk_size or "16MiB"
|
|
700
|
+
chunk_size = SizeSuffix(_tmp)
|
|
701
|
+
assert chunk_size is not None
|
|
702
|
+
concurrent_chunks = concurrent_chunks or 4
|
|
703
|
+
size_limit = SizeSuffix(chunk_size * concurrent_chunks)
|
|
704
|
+
|
|
690
705
|
other_args: list[str] = [
|
|
691
706
|
"--no-modtime",
|
|
692
707
|
"--vfs-read-wait",
|
|
693
708
|
"1s",
|
|
694
709
|
"--vfs-disk-space-total-size",
|
|
695
|
-
|
|
710
|
+
size_limit.as_str(), # purge quickly.
|
|
696
711
|
"--vfs-read-chunk-size",
|
|
697
|
-
|
|
712
|
+
chunk_size.as_str(),
|
|
698
713
|
"--vfs-read-chunk-size-limit",
|
|
699
|
-
|
|
714
|
+
size_limit.as_str(),
|
|
700
715
|
"--vfs-read-chunk-streams",
|
|
701
716
|
str(concurrent_chunks),
|
|
702
717
|
"--vfs-fast-fingerprint",
|
|
@@ -765,7 +780,7 @@ class Rclone:
|
|
|
765
780
|
|
|
766
781
|
client = S3Client(s3_creds)
|
|
767
782
|
config: S3MutliPartUploadConfig = S3MutliPartUploadConfig(
|
|
768
|
-
chunk_size=chunk_size,
|
|
783
|
+
chunk_size=chunk_size.as_int(),
|
|
769
784
|
retries=retries,
|
|
770
785
|
resume_path_json=save_state_json,
|
|
771
786
|
max_chunks_before_suspension=max_chunks_before_suspension,
|
|
@@ -788,7 +803,7 @@ class Rclone:
|
|
|
788
803
|
)
|
|
789
804
|
|
|
790
805
|
upload_config = S3MutliPartUploadConfig(
|
|
791
|
-
chunk_size=chunk_size,
|
|
806
|
+
chunk_size=chunk_size.as_int(),
|
|
792
807
|
retries=retries,
|
|
793
808
|
resume_path_json=save_state_json,
|
|
794
809
|
max_chunks_before_suspension=max_chunks_before_suspension,
|
|
@@ -845,24 +860,14 @@ class Rclone:
|
|
|
845
860
|
Raises:
|
|
846
861
|
subprocess.CalledProcessError: If the mount operation fails
|
|
847
862
|
"""
|
|
863
|
+
from rclone_api.mount import clean_mount, prepare_mount
|
|
864
|
+
|
|
848
865
|
allow_writes = allow_writes or False
|
|
849
866
|
use_links = use_links or True
|
|
850
867
|
verbose = get_verbose(verbose)
|
|
851
868
|
vfs_cache_mode = vfs_cache_mode or "full"
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if not is_empty:
|
|
855
|
-
raise ValueError(
|
|
856
|
-
f"Mount directory already exists and is not empty: {outdir}"
|
|
857
|
-
)
|
|
858
|
-
outdir.rmdir()
|
|
859
|
-
|
|
860
|
-
if _IS_WINDOWS:
|
|
861
|
-
# Windows -> Must create parent directories only if they don't exist
|
|
862
|
-
outdir.parent.mkdir(parents=True, exist_ok=True)
|
|
863
|
-
else:
|
|
864
|
-
# Linux -> Must create parent directories and the directory itself
|
|
865
|
-
outdir.mkdir(parents=True, exist_ok=True)
|
|
869
|
+
clean_mount(outdir, verbose=verbose)
|
|
870
|
+
prepare_mount(outdir, verbose=verbose)
|
|
866
871
|
src_str = convert_to_str(src)
|
|
867
872
|
cmd_list: list[str] = ["mount", src_str, str(outdir)]
|
|
868
873
|
if not allow_writes:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ModTimeStrategy(Enum):
|
|
7
|
+
USE_SERVER_MODTIME = "use-server-modtime"
|
|
8
|
+
NO_MODTIME = "no-modtime"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ListingOption(Enum):
|
|
12
|
+
DIRS_ONLY = "dirs-only"
|
|
13
|
+
FILES_ONLY = "files-only"
|
|
14
|
+
ALL = "all"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Order(Enum):
|
|
18
|
+
NORMAL = "normal"
|
|
19
|
+
REVERSE = "reverse"
|
|
20
|
+
RANDOM = "random"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class S3PathInfo:
|
|
25
|
+
remote: str
|
|
26
|
+
bucket: str
|
|
27
|
+
key: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class SizeResult:
|
|
32
|
+
"""Size result dataclass."""
|
|
33
|
+
|
|
34
|
+
prefix: str
|
|
35
|
+
total_size: int
|
|
36
|
+
file_sizes: dict[str, int]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _to_size_suffix(size: int) -> str:
|
|
40
|
+
if size < 1024:
|
|
41
|
+
return f"{size}B"
|
|
42
|
+
if size < 1024 * 1024:
|
|
43
|
+
return f"{size // 1024}K"
|
|
44
|
+
if size < 1024 * 1024 * 1024:
|
|
45
|
+
return f"{size // (1024 * 1024)}M"
|
|
46
|
+
if size < 1024 * 1024 * 1024 * 1024:
|
|
47
|
+
return f"{size // (1024 * 1024 * 1024)}G"
|
|
48
|
+
if size < 1024 * 1024 * 1024 * 1024 * 1024:
|
|
49
|
+
return f"{size // (1024 * 1024 * 1024 * 1024)}T"
|
|
50
|
+
if size < 1024 * 1024 * 1024 * 1024 * 1024 * 1024:
|
|
51
|
+
return f"{size // (1024 * 1024 * 1024 * 1024 * 1024)}P"
|
|
52
|
+
raise ValueError(f"Invalid size: {size}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_PATTERN_SIZE_SUFFIX = re.compile(r"^(\d+)([A-Za-z]+)$")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _from_size_suffix(size: str) -> int:
|
|
59
|
+
# 16MiB
|
|
60
|
+
# parse out number and suffix
|
|
61
|
+
match = _PATTERN_SIZE_SUFFIX.match(size)
|
|
62
|
+
if match is None:
|
|
63
|
+
raise ValueError(f"Invalid size suffix: {size}")
|
|
64
|
+
size = match.group(1)
|
|
65
|
+
suffix = match.group(2)[0:1].upper()
|
|
66
|
+
n = int(size)
|
|
67
|
+
if suffix == "B":
|
|
68
|
+
return n
|
|
69
|
+
if suffix == "K":
|
|
70
|
+
return n * 1024
|
|
71
|
+
if suffix == "M":
|
|
72
|
+
return n * 1024 * 1024
|
|
73
|
+
if suffix == "G":
|
|
74
|
+
return n * 1024 * 1024 * 1024
|
|
75
|
+
if suffix == "T":
|
|
76
|
+
return n * 1024 * 1024 * 1024 * 1024
|
|
77
|
+
if suffix == "P":
|
|
78
|
+
return n * 1024 * 1024 * 1024 * 1024 * 1024
|
|
79
|
+
raise ValueError(f"Invalid size suffix: {size}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SizeSuffix:
|
|
83
|
+
def __init__(self, size: "int | str | SizeSuffix"):
|
|
84
|
+
self._size: int
|
|
85
|
+
if isinstance(size, SizeSuffix):
|
|
86
|
+
self._size = size._size
|
|
87
|
+
elif isinstance(size, int):
|
|
88
|
+
self._size = size
|
|
89
|
+
elif isinstance(size, str):
|
|
90
|
+
self._size = _from_size_suffix(size)
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError(f"Invalid type for size: {type(size)}")
|
|
93
|
+
|
|
94
|
+
def as_int(self) -> int:
|
|
95
|
+
return self._size
|
|
96
|
+
|
|
97
|
+
def as_str(self) -> str:
|
|
98
|
+
return _to_size_suffix(self._size)
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return self.as_str()
|
|
102
|
+
|
|
103
|
+
def __str__(self) -> str:
|
|
104
|
+
return self.as_str()
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _to_size(size: "int | SizeSuffix") -> int:
|
|
108
|
+
if isinstance(size, int):
|
|
109
|
+
return size
|
|
110
|
+
elif isinstance(size, SizeSuffix):
|
|
111
|
+
return size._size
|
|
112
|
+
else:
|
|
113
|
+
raise ValueError(f"Invalid type for size: {type(size)}")
|
|
114
|
+
|
|
115
|
+
def __mul__(self, other: "int | SizeSuffix") -> "SizeSuffix":
|
|
116
|
+
other_int = SizeSuffix(other)
|
|
117
|
+
return SizeSuffix(self._size * other_int._size)
|
|
118
|
+
|
|
119
|
+
def __add__(self, other: "int | SizeSuffix") -> "SizeSuffix":
|
|
120
|
+
other_int = SizeSuffix(other)
|
|
121
|
+
return SizeSuffix(self._size + other_int._size)
|
|
122
|
+
|
|
123
|
+
def __sub__(self, other: "int | SizeSuffix") -> "SizeSuffix":
|
|
124
|
+
other_int = SizeSuffix(other)
|
|
125
|
+
return SizeSuffix(self._size - other_int._size)
|
|
@@ -37,7 +37,7 @@ from pathlib import Path
|
|
|
37
37
|
|
|
38
38
|
from dotenv import load_dotenv
|
|
39
39
|
|
|
40
|
-
from rclone_api import Rclone
|
|
40
|
+
from rclone_api import Rclone, SizeSuffix
|
|
41
41
|
|
|
42
42
|
_HERE = Path(__file__).parent
|
|
43
43
|
_PROJECT_ROOT = _HERE.parent
|
|
@@ -139,7 +139,7 @@ class RcloneCopyResumableFileToS3(unittest.TestCase):
|
|
|
139
139
|
rclone.copy_file_resumable_s3(
|
|
140
140
|
src="src:aa_misc_data/aa_misc_data/world_lending_library_2024_11.tar.zst.torrent",
|
|
141
141
|
dst="dst:rclone-api-unit-test/test_data/test.torrent.testwrite",
|
|
142
|
-
chunk_size=
|
|
142
|
+
chunk_size=SizeSuffix("16MB"),
|
|
143
143
|
retries=0,
|
|
144
144
|
save_state_json=save_state_json,
|
|
145
145
|
max_chunks_before_suspension=1,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit test file.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from rclone_api import SizeSuffix
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RcloneSuffixSize(unittest.TestCase):
|
|
11
|
+
"""Test rclone functionality."""
|
|
12
|
+
|
|
13
|
+
def test_list_remotes(self) -> None:
|
|
14
|
+
size_suffix = SizeSuffix(1024)
|
|
15
|
+
size_suffix = SizeSuffix("16MB")
|
|
16
|
+
size_int = size_suffix.as_int()
|
|
17
|
+
self.assertEqual(size_int, 16 * 1024 * 1024)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
unittest.main()
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from enum import Enum
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ModTimeStrategy(Enum):
|
|
6
|
-
USE_SERVER_MODTIME = "use-server-modtime"
|
|
7
|
-
NO_MODTIME = "no-modtime"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ListingOption(Enum):
|
|
11
|
-
DIRS_ONLY = "dirs-only"
|
|
12
|
-
FILES_ONLY = "files-only"
|
|
13
|
-
ALL = "all"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Order(Enum):
|
|
17
|
-
NORMAL = "normal"
|
|
18
|
-
REVERSE = "reverse"
|
|
19
|
-
RANDOM = "random"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass
|
|
23
|
-
class S3PathInfo:
|
|
24
|
-
remote: str
|
|
25
|
-
bucket: str
|
|
26
|
-
key: str
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@dataclass
|
|
30
|
-
class SizeResult:
|
|
31
|
-
"""Size result dataclass."""
|
|
32
|
-
|
|
33
|
-
prefix: str
|
|
34
|
-
total_size: int
|
|
35
|
-
file_sizes: dict[str, int]
|
|
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
|