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 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
  ]
@@ -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
- if not include_remote:
45
- _, out = out.split(":", 1)
46
- return out
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
- __all__ = ["group_files"]
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 group_files
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 ListingOption, ModTimeStrategy, Order
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"Running: {cmd_str}")
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
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.76
3
+ Version: 1.0.77
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -1,6 +1,6 @@
1
- rclone_api/__init__.py,sha256=kJUk9KAxQ2AEms9lM5yqw-dqlabarhU69b2FAxJVBlY,692
1
+ rclone_api/__init__.py,sha256=5KkqA2OnclssekOdx9ZdT1fFRlxzhpB9zBvXhpG44Ag,760
2
2
  rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
3
- rclone_api/completed_process.py,sha256=Pp-hXnLgej0IGO5ee9Fmx64dGzIofbQFEUyXdFCvO54,1371
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=YtR5Y6c0YfXTS-sReOy2UgiSnafcAeO6b2hnbojBQD4,1423
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=kOHh6ysFDkxjldSwvW6KqmiADUC1yFCdrZRY57TvbGY,5328
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=JeeqJsJH8rBZW895rkT3HMCkaWeKllnG-6xj_PzLwXY,29738
16
- rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
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=DcbNw1R6j2f_1zHrhqEJcpCR-8kJfFJawMY0AmPsCnM,321
20
- rclone_api/util.py,sha256=_cvmHcJPRl2yXw4zgZiop3z-riA_8Ek6S5NDPw8cqSY,4198
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.76.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
25
- rclone_api-1.0.76.dist-info/METADATA,sha256=13_xQBLBt8rYCmVYCXjHahKRMdC8p1Ao0lKOY8xhlVU,4489
26
- rclone_api-1.0.76.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
27
- rclone_api-1.0.76.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
28
- rclone_api-1.0.76.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
29
- rclone_api-1.0.76.dist-info/RECORD,,
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,,