rclone-api 1.0.76__py2.py3-none-any.whl → 1.0.77__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/__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
|