rclone-api 1.0.41__py2.py3-none-any.whl → 1.0.42__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rclone-api might be problematic. Click here for more details.

@@ -0,0 +1,145 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class FilePathParts:
6
+ """File path dataclass."""
7
+
8
+ remote: str
9
+ parents: list[str]
10
+ name: str
11
+
12
+
13
+ def parse_file(file_path: str) -> FilePathParts:
14
+ """Parse file path into parts."""
15
+ assert not file_path.endswith("/"), "This looks like a directory path"
16
+ parts = file_path.split(":")
17
+ remote = parts[0]
18
+ path = parts[1]
19
+ if path.startswith("/"):
20
+ path = path[1:]
21
+ parents = path.split("/")
22
+ if len(parents) == 1:
23
+ return FilePathParts(remote=remote, parents=[], name=parents[0])
24
+ name = parents.pop()
25
+ return FilePathParts(remote=remote, parents=parents, name=name)
26
+
27
+
28
+ class TreeNode:
29
+ def __init__(
30
+ self,
31
+ name: str,
32
+ child_nodes: dict[str, "TreeNode"] | None = None,
33
+ files: list[str] | None = None,
34
+ parent: "TreeNode | None" = None,
35
+ ):
36
+ self.name = name
37
+ self.child_nodes = child_nodes or {}
38
+ self.files = files or []
39
+ self.count = 0
40
+ self.parent = parent
41
+
42
+ def add_count(self):
43
+ self.count += 1
44
+ if self.parent:
45
+ self.parent.add_count()
46
+
47
+ def get_path(self) -> str:
48
+ paths_reversed: list[str] = [self.name]
49
+ node: TreeNode | None = self
50
+ assert node is not None
51
+ while node := node.parent:
52
+ paths_reversed.append(node.name)
53
+ return "/".join(reversed(paths_reversed))
54
+
55
+ def get_child_subpaths(self, parent_path: str | None = None) -> list[str]:
56
+ paths: list[str] = []
57
+ for child in self.child_nodes.values():
58
+ child_paths = child.get_child_subpaths(parent_path=child.name)
59
+ paths.extend(child_paths)
60
+ for file in self.files:
61
+ if parent_path:
62
+ file = f"{parent_path}/{file}"
63
+ paths.append(file)
64
+ return paths
65
+
66
+ def __repr__(self, indent: int = 0) -> str:
67
+ # return f"{self.name}: {self.count}, {len(self.children)}"
68
+ leftpad = " " * indent
69
+ msg = f"{leftpad}{self.name}: {self.count}"
70
+ if self.child_nodes:
71
+ # msg += f"\n {len(self.children)} children"
72
+ msg += "\n"
73
+ for child in self.child_nodes.values():
74
+ if isinstance(child, TreeNode):
75
+ msg += child.__repr__(indent + 2)
76
+ else:
77
+ msg += f"{leftpad} {child}\n"
78
+ return msg
79
+
80
+
81
+ def _merge(node: TreeNode, parent_path: str, out: dict[str, list[str]]) -> None:
82
+ parent_path = parent_path + "/" + node.name
83
+ this_count = node.count
84
+ child_count = 0
85
+ children_has_files = False
86
+ if not node.child_nodes and not node.files:
87
+ return # done
88
+
89
+ if node.files:
90
+ children_has_files = True
91
+ filelist = out.setdefault(parent_path, [])
92
+ # for file in node.files:
93
+ # filelist.append(file)
94
+ # out[parent_path] = filelist
95
+ paths = node.get_child_subpaths()
96
+ for path in paths:
97
+ filelist.append(path)
98
+ out[parent_path] = filelist
99
+ return
100
+
101
+ for child in node.child_nodes.values():
102
+ child_count += child.count
103
+ child_count += len(node.files)
104
+ for file in node.files:
105
+ child_count += 1
106
+
107
+ if child_count != this_count or children_has_files:
108
+ # print(
109
+ # f"Cannot merge {node.name} because different counts or has children with files"
110
+ # )
111
+ filelist = out.setdefault(parent_path, [])
112
+ for child in node.child_nodes.values():
113
+ subpaths = child.get_child_subpaths()
114
+ filelist.extend(subpaths)
115
+ out[parent_path] = filelist
116
+ else:
117
+ for child in node.child_nodes.values():
118
+ _merge(child, parent_path, out)
119
+
120
+
121
+ def group_files(files: list[str]) -> dict[str, list[str]]:
122
+ """split between filename and parent directory path"""
123
+ tree: dict[str, TreeNode] = {}
124
+ for file in files:
125
+ parts = parse_file(file)
126
+ remote = parts.remote
127
+ node: TreeNode = tree.setdefault(remote, TreeNode(remote))
128
+ for parent in parts.parents:
129
+ is_last = parent == parts.parents[-1]
130
+ node = node.child_nodes.setdefault(parent, TreeNode(parent, parent=node))
131
+ if is_last:
132
+ node.files.append(parts.name)
133
+ node.add_count()
134
+ outpaths: dict[str, list[str]] = {}
135
+ for _, node in tree.items():
136
+ _merge(node, "", outpaths)
137
+ out: dict[str, list[str]] = {}
138
+ for path, files in outpaths.items():
139
+ # fixup path
140
+ assert path.startswith("/"), "Path should start with /"
141
+ path = path[1:]
142
+ # replace the first / with :
143
+ path = path.replace("/", ":", 1)
144
+ out[path] = files
145
+ return out
rclone_api/rclone.py CHANGED
@@ -21,13 +21,13 @@ from rclone_api.diff import DiffItem, diff_stream_from_running_process
21
21
  from rclone_api.dir_listing import DirListing
22
22
  from rclone_api.exec import RcloneExec
23
23
  from rclone_api.file import File
24
+ from rclone_api.group_files import group_files
24
25
  from rclone_api.process import Process
25
26
  from rclone_api.remote import Remote
26
27
  from rclone_api.rpath import RPath
27
28
  from rclone_api.util import (
28
29
  get_rclone_exe,
29
30
  get_verbose,
30
- partition_files,
31
31
  to_path,
32
32
  wait_for_mount,
33
33
  )
@@ -218,7 +218,7 @@ class Rclone:
218
218
  if len(payload) == 0:
219
219
  return
220
220
 
221
- datalists: dict[str, list[str]] = partition_files(payload)
221
+ datalists: dict[str, list[str]] = group_files(payload)
222
222
  out: subprocess.CompletedProcess | None = None
223
223
 
224
224
  futures: list[Future] = []
@@ -297,7 +297,7 @@ class Rclone:
297
297
  )
298
298
  return CompletedProcess.from_subprocess(cp)
299
299
 
300
- datalists: dict[str, list[str]] = partition_files(payload)
300
+ datalists: dict[str, list[str]] = group_files(payload)
301
301
  completed_processes: list[subprocess.CompletedProcess] = []
302
302
  verbose = get_verbose(verbose)
303
303
 
rclone_api/util.py CHANGED
@@ -129,15 +129,3 @@ def wait_for_mount(path: Path, mount_process: Any, timeout: int = 60) -> None:
129
129
  if path.exists():
130
130
  return
131
131
  raise TimeoutError(f"Path {path} did not exist after {timeout} seconds")
132
-
133
-
134
- def partition_files(files: list[str]) -> dict[str, list[str]]:
135
- """split between filename and parent directory path"""
136
- datalists: dict[str, list[str]] = {}
137
- for f in files:
138
- base = os.path.basename(f)
139
- parent_path = os.path.dirname(f)
140
- if parent_path not in datalists:
141
- datalists[parent_path] = []
142
- datalists[parent_path].append(base)
143
- return datalists
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.41
3
+ Version: 1.0.42
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -10,17 +10,18 @@ rclone_api/dir_listing.py,sha256=9Qqf2SUswrOEkyqmaH23V51I18X6ePiXb9B1vUwRF5o,157
10
10
  rclone_api/exec.py,sha256=HWmnU2Jwb-3EttSbAJSaLloYA7YI2mHTzRJ5VEri9aM,941
11
11
  rclone_api/file.py,sha256=D02iHJW1LhfOiM_R_yPHP8_ApnDiYrkuraVcrV8-qkw,1246
12
12
  rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
13
+ rclone_api/group_files.py,sha256=xHE3_gvvMWgvLk33TyTNAa0GRFvIRo2qkwsEKA-yujU,4730
13
14
  rclone_api/process.py,sha256=RrMfTe0bndmJ6gBK67ioqNvCstJ8aTC8RlGX1XBLlcw,4191
14
- rclone_api/rclone.py,sha256=hPR1dV4s-ekruVBbLHpojNvNLreJVdm3tWVOHsSDh2c,20986
15
+ rclone_api/rclone.py,sha256=zKLlHykEs43kt-FB3_YFvwPmRcHY2a3-fShMfGEjYH0,21004
15
16
  rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
16
17
  rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
17
- rclone_api/util.py,sha256=yUN7afH4PuuUNcrXwdtyDTIluvkNOw-c5v5VPKDXoBM,4325
18
+ rclone_api/util.py,sha256=sUjH5NmsawmNbPMY7V6hD8vFJXCwbl44XM1kuij3tA0,3918
18
19
  rclone_api/walk.py,sha256=kca0t1GAnF6FLclN01G8NG__Qe-ggodLtAbQSHyVPng,2968
19
20
  rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
20
21
  rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
21
- rclone_api-1.0.41.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
22
- rclone_api-1.0.41.dist-info/METADATA,sha256=GnbL4D29d2oXbfexuymEQT9P1-PVEQJtoxxAd8YCa3A,4489
23
- rclone_api-1.0.41.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
24
- rclone_api-1.0.41.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
25
- rclone_api-1.0.41.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
26
- rclone_api-1.0.41.dist-info/RECORD,,
22
+ rclone_api-1.0.42.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
23
+ rclone_api-1.0.42.dist-info/METADATA,sha256=f5ePfkFNusMVvM9SSIdDsLXuZOUFKYF_ak8yAKnuzc4,4489
24
+ rclone_api-1.0.42.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
25
+ rclone_api-1.0.42.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
26
+ rclone_api-1.0.42.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
27
+ rclone_api-1.0.42.dist-info/RECORD,,