rclone-api 1.0.76__py2.py3-none-any.whl → 1.0.77__py2.py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- rclone_api/__init__.py +3 -1
- rclone_api/completed_process.py +11 -0
- rclone_api/file.py +11 -4
- rclone_api/group_files.py +45 -1
- rclone_api/rclone.py +77 -2
- rclone_api/remote.py +3 -0
- rclone_api/types.py +14 -0
- rclone_api/util.py +1 -1
- {rclone_api-1.0.76.dist-info → rclone_api-1.0.77.dist-info}/METADATA +1 -1
- {rclone_api-1.0.76.dist-info → rclone_api-1.0.77.dist-info}/RECORD +14 -14
- {rclone_api-1.0.76.dist-info → rclone_api-1.0.77.dist-info}/LICENSE +0 -0
- {rclone_api-1.0.76.dist-info → rclone_api-1.0.77.dist-info}/WHEEL +0 -0
- {rclone_api-1.0.76.dist-info → rclone_api-1.0.77.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.0.76.dist-info → rclone_api-1.0.77.dist-info}/top_level.txt +0 -0
rclone_api/__init__.py
CHANGED
@@ -9,7 +9,7 @@ from .process import Process
|
|
9
9
|
from .rclone import Rclone, rclone_verbose
|
10
10
|
from .remote import Remote
|
11
11
|
from .rpath import RPath
|
12
|
-
from .types import ListingOption, Order
|
12
|
+
from .types import GroupingOption, ListingOption, Order, SizeResult
|
13
13
|
|
14
14
|
__all__ = [
|
15
15
|
"Rclone",
|
@@ -29,4 +29,6 @@ __all__ = [
|
|
29
29
|
"ListingOption",
|
30
30
|
"Order",
|
31
31
|
"ListingOption",
|
32
|
+
"GroupingOption",
|
33
|
+
"SizeResult",
|
32
34
|
]
|
rclone_api/completed_process.py
CHANGED
@@ -47,3 +47,14 @@ class CompletedProcess:
|
|
47
47
|
if rtn != 0:
|
48
48
|
return rtn
|
49
49
|
return 0
|
50
|
+
|
51
|
+
def __str__(self) -> str:
|
52
|
+
|
53
|
+
cmd_strs: list[str] = []
|
54
|
+
rtn_cods: list[int] = []
|
55
|
+
for cp in self.completed:
|
56
|
+
cmd_strs.append(subprocess.list2cmdline(cp.args))
|
57
|
+
rtn_cods.append(cp.returncode)
|
58
|
+
msg = f"CompletedProcess: {len(cmd_strs)} commands\n"
|
59
|
+
msg += "\n".join([f"{cmd} -> {rtn}" for cmd, rtn in zip(cmd_strs, rtn_cods)])
|
60
|
+
return msg
|
rclone_api/file.py
CHANGED
@@ -40,10 +40,17 @@ class File:
|
|
40
40
|
|
41
41
|
def to_string(self, include_remote: bool = True) -> str:
|
42
42
|
"""Convert the File to a string."""
|
43
|
-
out = str(self.path)
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
# out = str(self.path)
|
44
|
+
remote = self.path.remote
|
45
|
+
rest = self.path.path
|
46
|
+
if include_remote:
|
47
|
+
return f"{remote.name}:{rest}"
|
48
|
+
return rest
|
49
|
+
|
50
|
+
@property
|
51
|
+
def size(self) -> int:
|
52
|
+
"""Get the size of the file."""
|
53
|
+
return self.path.size
|
47
54
|
|
48
55
|
def __str__(self) -> str:
|
49
56
|
return str(self.path)
|
rclone_api/group_files.py
CHANGED
@@ -9,6 +9,19 @@ class FilePathParts:
|
|
9
9
|
parents: list[str]
|
10
10
|
name: str
|
11
11
|
|
12
|
+
def to_string(self, include_remote: bool, include_bucket: bool) -> str:
|
13
|
+
"""Convert to string, may throw for not include_bucket=False."""
|
14
|
+
parents = list(self.parents)
|
15
|
+
if not include_bucket:
|
16
|
+
parents.pop(0)
|
17
|
+
path = "/".join(parents)
|
18
|
+
if path:
|
19
|
+
path += "/"
|
20
|
+
path += self.name
|
21
|
+
if include_remote:
|
22
|
+
return f"{self.remote}{path}"
|
23
|
+
return path
|
24
|
+
|
12
25
|
|
13
26
|
def parse_file(file_path: str) -> FilePathParts:
|
14
27
|
"""Parse file path into parts."""
|
@@ -165,4 +178,35 @@ def group_files(files: list[str], fully_qualified: bool = True) -> dict[str, lis
|
|
165
178
|
return out
|
166
179
|
|
167
180
|
|
168
|
-
|
181
|
+
def group_under_remote(
|
182
|
+
files: list[str], fully_qualified: bool = True
|
183
|
+
) -> dict[str, list[str]]:
|
184
|
+
"""split between filename and remote"""
|
185
|
+
assert fully_qualified is True, "Not implemented for fully_qualified=False"
|
186
|
+
out: dict[str, list[str]] = {}
|
187
|
+
for file in files:
|
188
|
+
parsed = parse_file(file)
|
189
|
+
remote = f"{parsed.remote}:"
|
190
|
+
file_list = out.setdefault(remote, [])
|
191
|
+
file_list.append(parsed.to_string(include_remote=False, include_bucket=True))
|
192
|
+
return out
|
193
|
+
|
194
|
+
|
195
|
+
def group_under_remote_bucket(
|
196
|
+
files: list[str], fully_qualified: bool = True
|
197
|
+
) -> dict[str, list[str]]:
|
198
|
+
"""split between filename and bucket"""
|
199
|
+
assert fully_qualified is True, "Not implemented for fully_qualified=False"
|
200
|
+
out: dict[str, list[str]] = {}
|
201
|
+
for file in files:
|
202
|
+
parsed = parse_file(file)
|
203
|
+
remote = f"{parsed.remote}:"
|
204
|
+
parts = parsed.parents
|
205
|
+
bucket = parts[0]
|
206
|
+
remote_bucket = f"{remote}{bucket}"
|
207
|
+
file_list = out.setdefault(remote_bucket, [])
|
208
|
+
file_list.append(parsed.to_string(include_remote=False, include_bucket=False))
|
209
|
+
return out
|
210
|
+
|
211
|
+
|
212
|
+
__all__ = ["group_files", "group_under_remote", "group_under_remote_bucket"]
|
rclone_api/rclone.py
CHANGED
@@ -22,11 +22,21 @@ from rclone_api.diff import DiffItem, DiffOption, diff_stream_from_running_proce
|
|
22
22
|
from rclone_api.dir_listing import DirListing
|
23
23
|
from rclone_api.exec import RcloneExec
|
24
24
|
from rclone_api.file import File
|
25
|
-
from rclone_api.group_files import
|
25
|
+
from rclone_api.group_files import (
|
26
|
+
group_files,
|
27
|
+
group_under_remote,
|
28
|
+
group_under_remote_bucket,
|
29
|
+
)
|
26
30
|
from rclone_api.process import Process
|
27
31
|
from rclone_api.remote import Remote
|
28
32
|
from rclone_api.rpath import RPath
|
29
|
-
from rclone_api.types import
|
33
|
+
from rclone_api.types import (
|
34
|
+
GroupingOption,
|
35
|
+
ListingOption,
|
36
|
+
ModTimeStrategy,
|
37
|
+
Order,
|
38
|
+
SizeResult,
|
39
|
+
)
|
30
40
|
from rclone_api.util import (
|
31
41
|
get_check,
|
32
42
|
get_rclone_exe,
|
@@ -827,3 +837,68 @@ class Rclone:
|
|
827
837
|
if proc.poll() is not None:
|
828
838
|
raise ValueError("NFS serve process failed to start")
|
829
839
|
return proc
|
840
|
+
|
841
|
+
def size_files(
|
842
|
+
self,
|
843
|
+
files: list[str],
|
844
|
+
fast_list: bool = True,
|
845
|
+
other_args: list[str] | None = None,
|
846
|
+
grouping: GroupingOption = GroupingOption.BUCKET,
|
847
|
+
check: bool | None = False,
|
848
|
+
verbose: bool | None = None,
|
849
|
+
) -> SizeResult:
|
850
|
+
"""Get the size of a list of files. Example of files items: "remote:bucket/to/file"."""
|
851
|
+
verbose = get_verbose(verbose)
|
852
|
+
check = get_check(check)
|
853
|
+
file_list: dict[str, list[str]]
|
854
|
+
if grouping == GroupingOption.BUCKET:
|
855
|
+
file_list = group_under_remote_bucket(files)
|
856
|
+
elif grouping == GroupingOption.REMOTE:
|
857
|
+
file_list = group_under_remote(files)
|
858
|
+
all_files: list[File] = []
|
859
|
+
for src_path, files in file_list.items():
|
860
|
+
cmd = ["lsjson", src_path, "--files-only", "-R"]
|
861
|
+
with TemporaryDirectory() as tmpdir:
|
862
|
+
# print("files: " + ",".join(files))
|
863
|
+
include_files_txt = Path(tmpdir) / "include_files.txt"
|
864
|
+
include_files_txt.write_text("\n".join(files), encoding="utf-8")
|
865
|
+
cmd += ["--files-from", str(include_files_txt)]
|
866
|
+
if fast_list:
|
867
|
+
cmd.append("--fast-list")
|
868
|
+
if other_args:
|
869
|
+
cmd += other_args
|
870
|
+
cp = self._run(cmd, check=check)
|
871
|
+
|
872
|
+
if cp.returncode != 0:
|
873
|
+
if check:
|
874
|
+
raise ValueError(f"Error getting file sizes: {cp.stderr}")
|
875
|
+
else:
|
876
|
+
warnings.warn(f"Error getting file sizes: {cp.stderr}")
|
877
|
+
stdout = cp.stdout
|
878
|
+
pieces = src_path.split(":", 1)
|
879
|
+
remote_name = pieces[0]
|
880
|
+
parent_path: str | None
|
881
|
+
if len(pieces) > 1:
|
882
|
+
parent_path = pieces[1]
|
883
|
+
else:
|
884
|
+
parent_path = None
|
885
|
+
remote = Remote(name=remote_name, rclone=self)
|
886
|
+
paths: list[RPath] = RPath.from_json_str(
|
887
|
+
stdout, remote, parent_path=parent_path
|
888
|
+
)
|
889
|
+
# print(paths)
|
890
|
+
all_files += [File(p) for p in paths]
|
891
|
+
file_sizes: dict[str, int] = {}
|
892
|
+
f: File
|
893
|
+
for f in all_files:
|
894
|
+
p = f.to_string(include_remote=True)
|
895
|
+
if p in file_sizes:
|
896
|
+
warnings.warn(f"Duplicate file found: {p}")
|
897
|
+
continue
|
898
|
+
size = f.size
|
899
|
+
if size == 0:
|
900
|
+
warnings.warn(f"File size is 0: {p}")
|
901
|
+
file_sizes[p] = f.size
|
902
|
+
total_size = sum(file_sizes.values())
|
903
|
+
out: SizeResult = SizeResult(total_size=total_size, file_sizes=file_sizes)
|
904
|
+
return out
|
rclone_api/remote.py
CHANGED
@@ -7,6 +7,9 @@ class Remote:
|
|
7
7
|
def __init__(self, name: str, rclone: Any) -> None:
|
8
8
|
from rclone_api.rclone import Rclone
|
9
9
|
|
10
|
+
if ":" in name:
|
11
|
+
raise ValueError("Remote name cannot contain ':'")
|
12
|
+
|
10
13
|
assert isinstance(rclone, Rclone)
|
11
14
|
self.name = name
|
12
15
|
self.rclone: Rclone = rclone
|
rclone_api/types.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
from dataclasses import dataclass
|
1
2
|
from enum import Enum
|
2
3
|
|
3
4
|
|
@@ -16,3 +17,16 @@ class Order(Enum):
|
|
16
17
|
NORMAL = "normal"
|
17
18
|
REVERSE = "reverse"
|
18
19
|
RANDOM = "random"
|
20
|
+
|
21
|
+
|
22
|
+
class GroupingOption(Enum):
|
23
|
+
BUCKET = "bucket"
|
24
|
+
REMOTE = "remote"
|
25
|
+
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class SizeResult:
|
29
|
+
"""Size result dataclass."""
|
30
|
+
|
31
|
+
total_size: int
|
32
|
+
file_sizes: dict[str, int]
|
rclone_api/util.py
CHANGED
@@ -104,7 +104,7 @@ def rclone_execute(
|
|
104
104
|
)
|
105
105
|
if verbose:
|
106
106
|
cmd_str = subprocess.list2cmdline(cmd)
|
107
|
-
print(f"
|
107
|
+
print(f"\nRunning: {cmd_str}")
|
108
108
|
cp = subprocess.run(
|
109
109
|
cmd, capture_output=capture, encoding="utf-8", check=False, shell=False
|
110
110
|
)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
rclone_api/__init__.py,sha256=
|
1
|
+
rclone_api/__init__.py,sha256=5KkqA2OnclssekOdx9ZdT1fFRlxzhpB9zBvXhpG44Ag,760
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
|
-
rclone_api/completed_process.py,sha256=
|
3
|
+
rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
|
4
4
|
rclone_api/config.py,sha256=tP6cU9DnCCEIRc_KP9HPur1jFLLg2QGFSxNwFm6_MVw,118
|
5
5
|
rclone_api/convert.py,sha256=Mx9Qo7zhkOedJd8LdhPvNGHp8znJzOk4f_2KWnoGc78,1012
|
6
6
|
rclone_api/deprecated.py,sha256=qWKpnZdYcBK7YQZKuVoWWXDwi-uqiAtbjgPcci_efow,590
|
@@ -8,22 +8,22 @@ rclone_api/diff.py,sha256=tMoJMAGmLSE6Q_7QhPf6PnCzb840djxMZtDmhc2GlGQ,5227
|
|
8
8
|
rclone_api/dir.py,sha256=i4h7LX5hB_WmVixxDRWL_l1nifvscrdWct_8Wx7wHZc,3540
|
9
9
|
rclone_api/dir_listing.py,sha256=9Qqf2SUswrOEkyqmaH23V51I18X6ePiXb9B1vUwRF5o,1571
|
10
10
|
rclone_api/exec.py,sha256=1ovvaMXDEfLiT7BrYZyE85u_yFhEUwUNW3jPOzqknR8,1023
|
11
|
-
rclone_api/file.py,sha256=
|
11
|
+
rclone_api/file.py,sha256=1rrZk_L2mdkgsvhr57dbs89wUaDb2SZBSwJg4e13xrk,1602
|
12
12
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
13
|
-
rclone_api/group_files.py,sha256=
|
13
|
+
rclone_api/group_files.py,sha256=pjZAjy01rh5zQ9fExXHDQ4mxmSClVVp_L_3uUWqrXsE,6939
|
14
14
|
rclone_api/process.py,sha256=RrMfTe0bndmJ6gBK67ioqNvCstJ8aTC8RlGX1XBLlcw,4191
|
15
|
-
rclone_api/rclone.py,sha256=
|
16
|
-
rclone_api/remote.py,sha256=
|
15
|
+
rclone_api/rclone.py,sha256=ptozXdIK9H3wEjcVBjZLLYh2sU9rTbrAc0SQfvKON-k,32598
|
16
|
+
rclone_api/remote.py,sha256=O9WDUFQy9f6oT1HdUbTixK2eg0xtBBm8k4Xl6aa6K00,431
|
17
17
|
rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
|
18
18
|
rclone_api/scan_missing_folders.py,sha256=Kulca2Q6WZodt00ATFHkmqqInuoPvBkhTcS9703y6po,4740
|
19
|
-
rclone_api/types.py,sha256=
|
20
|
-
rclone_api/util.py,sha256=
|
19
|
+
rclone_api/types.py,sha256=yysVEP4xxmYWl5DoLoRzBFaMgmJXFxRvQF34FqMtxGE,545
|
20
|
+
rclone_api/util.py,sha256=XMrA2m_di4h8JTM-qyx2iyrFZn-l-or_SJOa5tEsDsI,4200
|
21
21
|
rclone_api/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
|
22
22
|
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
23
23
|
rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
|
24
|
-
rclone_api-1.0.
|
25
|
-
rclone_api-1.0.
|
26
|
-
rclone_api-1.0.
|
27
|
-
rclone_api-1.0.
|
28
|
-
rclone_api-1.0.
|
29
|
-
rclone_api-1.0.
|
24
|
+
rclone_api-1.0.77.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
25
|
+
rclone_api-1.0.77.dist-info/METADATA,sha256=8SsedCvu64dTTGi_C-uaTKrDOnKZ5NMzUM9y1WvzoAE,4489
|
26
|
+
rclone_api-1.0.77.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
|
27
|
+
rclone_api-1.0.77.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
|
28
|
+
rclone_api-1.0.77.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
29
|
+
rclone_api-1.0.77.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|