rclone-api 1.0.67__py2.py3-none-any.whl → 1.0.69__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,143 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from queue import Queue
3
+ from threading import Thread
4
+ from typing import Generator
5
+
6
+ from rclone_api import Dir
7
+ from rclone_api.dir_listing import DirListing
8
+ from rclone_api.types import ListingOption
9
+ from rclone_api.walk import walk_runner_depth_first
10
+
11
+ _MAX_OUT_QUEUE_SIZE = 50
12
+
13
+
14
+ # ONLY Works from src -> dst diffing.
15
+ def _async_diff_dir_walk_task(
16
+ src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], reverse=False
17
+ ) -> None:
18
+
19
+ try:
20
+ stack = [(src, dst)]
21
+ while stack:
22
+ curr_src, curr_dst = stack.pop()
23
+ curr_src = curr_src
24
+ curr_dst = curr_dst
25
+
26
+ with ThreadPoolExecutor(max_workers=2) as executor:
27
+ # src_dir_listing = src.ls(listing_option=ListingOption.DIRS_ONLY)
28
+ # dst_dir_listing = dst.ls(listing_option=ListingOption.DIRS_ONLY)
29
+ t1 = executor.submit(
30
+ src.ls, listing_option=ListingOption.DIRS_ONLY, reverse=reverse
31
+ )
32
+ t2 = executor.submit(
33
+ dst.ls, listing_option=ListingOption.DIRS_ONLY, reverse=reverse
34
+ )
35
+ src_dir_listing: DirListing = t1.result()
36
+ dst_dir_listing: DirListing = t2.result()
37
+
38
+ # dirlisting = current_dir.ls()
39
+ # if reverse:
40
+ # dirlisting.dirs.reverse()
41
+ # if depth != 0:
42
+ # for subdir in dirlisting.dirs: # Process deeper directories first
43
+ # # stack.append((child, depth - 1 if depth > 0 else depth))
44
+ # next_depth = depth - 1 if depth > 0 else depth
45
+ # _walk_runner_depth_first(
46
+ # subdir, next_depth, out_queue, reverse=reverse
47
+ # )
48
+ # out_queue.put(dirlisting)
49
+
50
+ # for subdir in dst_dir_listing.dirs:
51
+ # subdir.to_string(include_remote=False)
52
+ # walk_runner_depth_first()
53
+
54
+ # find elements missing on dst
55
+ # missing_on_dst: set[Dir] = set(src_dir_listing.dirs) - set(
56
+ # dst_dir_listing.dirs
57
+ # )
58
+ # exists_on_dst: set[Dir] = set(src_dir_listing.dirs) - missing_on_dst
59
+
60
+ dst_files: list[str] = [d.name for d in dst_dir_listing.dirs]
61
+ src_files: list[str] = [d.name for d in src_dir_listing.dirs]
62
+
63
+ dst_files_set: set[str] = set(dst_files)
64
+ # src_files_set: set[str] = set(src_files)
65
+
66
+ # print(f"src_files: {src_files}")
67
+ # print(f"dst_files: {dst_files}")
68
+
69
+ matching_dirs: list[str] = []
70
+
71
+ for file in src_files:
72
+ if file not in dst_files_set:
73
+ # print(f"missing dir on src: {file}")
74
+ queue_dir_listing: Queue[DirListing | None] = Queue()
75
+ walk_runner_depth_first(
76
+ dir=curr_src,
77
+ max_depth=max_depth,
78
+ out_queue=queue_dir_listing,
79
+ reverse=reverse,
80
+ )
81
+ while dirlisting := queue_dir_listing.get():
82
+ if dirlisting is None:
83
+ break
84
+ # print(f"dirlisting: {dirlisting}")
85
+ for d in dirlisting.dirs:
86
+ out_queue.put(d)
87
+ else:
88
+ matching_dirs.append(file)
89
+
90
+ for matching_dir in matching_dirs:
91
+ # print(f"matching dir: {matching_dir}")
92
+ _async_diff_dir_walk_task(
93
+ src=curr_src / matching_dir,
94
+ dst=curr_dst / matching_dir,
95
+ max_depth=max_depth,
96
+ out_queue=out_queue,
97
+ reverse=reverse,
98
+ )
99
+
100
+ out_queue.put(None)
101
+ except KeyboardInterrupt:
102
+ import _thread
103
+
104
+ out_queue.put(None)
105
+ _thread.interrupt_main()
106
+
107
+
108
+ def diff_walk(
109
+ src: Dir,
110
+ dst: Dir,
111
+ max_depth: int = -1,
112
+ reverse: bool = False,
113
+ ) -> Generator[Dir, None, None]:
114
+ """Walk through the given directory recursively.
115
+
116
+ Args:
117
+ dir: Directory or Remote to walk through
118
+ max_depth: Maximum depth to traverse (-1 for unlimited)
119
+
120
+ Yields:
121
+ DirListing: Directory listing for each directory encountered
122
+ """
123
+
124
+ try:
125
+ out_queue: Queue[Dir | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
126
+
127
+ def task() -> None:
128
+ _async_diff_dir_walk_task(src, dst, max_depth, out_queue, reverse=reverse)
129
+
130
+ worker = Thread(
131
+ target=task,
132
+ daemon=True,
133
+ )
134
+ worker.start()
135
+
136
+ while dirlisting := out_queue.get():
137
+ if dirlisting is None:
138
+ break
139
+ yield dirlisting
140
+
141
+ worker.join()
142
+ except KeyboardInterrupt:
143
+ pass
rclone_api/dir.py CHANGED
@@ -1,9 +1,11 @@
1
1
  import json
2
+ from pathlib import Path
2
3
  from typing import Generator
3
4
 
4
5
  from rclone_api.dir_listing import DirListing
5
6
  from rclone_api.remote import Remote
6
7
  from rclone_api.rpath import RPath
8
+ from rclone_api.types import ListingOption
7
9
 
8
10
 
9
11
  class Dir:
@@ -41,11 +43,30 @@ class Dir:
41
43
  # self.path.set_rclone(self.path.remote.rclone)
42
44
  assert self.path.rclone is not None
43
45
 
44
- def ls(self, max_depth: int = 0, reverse: bool = False) -> DirListing:
46
+ def ls(
47
+ self,
48
+ max_depth: int | None = None,
49
+ glob: str | None = None,
50
+ reverse: bool = False,
51
+ listing_option: ListingOption = ListingOption.ALL,
52
+ ) -> DirListing:
45
53
  """List files and directories in the given path."""
46
54
  assert self.path.rclone is not None
47
55
  dir = Dir(self.path)
48
- return self.path.rclone.ls(dir, max_depth=max_depth, reverse=reverse)
56
+ return self.path.rclone.ls(
57
+ dir,
58
+ max_depth=max_depth,
59
+ glob=glob,
60
+ reverse=reverse,
61
+ listing_option=listing_option,
62
+ )
63
+
64
+ def relative_to(self, other: "Dir") -> str:
65
+ """Return the relative path to the other directory."""
66
+ self_path = Path(self.path.path)
67
+ other_path = Path(other.path.path)
68
+ rel_path = self_path.relative_to(other_path)
69
+ return str(rel_path)
49
70
 
50
71
  def walk(
51
72
  self, breadth_first: bool, max_depth: int = -1
@@ -67,3 +88,26 @@ class Dir:
67
88
  data = self.path.to_json()
68
89
  data_str = json.dumps(data)
69
90
  return data_str
91
+
92
+ def to_string(self, include_remote: bool = True) -> str:
93
+ """Convert the File to a string."""
94
+ out = str(self.path)
95
+ if not include_remote:
96
+ _, out = out.split(":", 1)
97
+ return out
98
+
99
+ # / operator
100
+ def __truediv__(self, other: str) -> "Dir":
101
+ """Join the current path with another path."""
102
+ path = Path(self.path.path) / other
103
+ rpath = RPath(
104
+ self.path.remote,
105
+ str(path),
106
+ name=other,
107
+ size=0,
108
+ mime_type="inode/directory",
109
+ mod_time="",
110
+ is_dir=True,
111
+ )
112
+ rpath.set_rclone(self.path.rclone)
113
+ return Dir(rpath)
rclone_api/rclone.py CHANGED
@@ -7,7 +7,6 @@ import subprocess
7
7
  import time
8
8
  import warnings
9
9
  from concurrent.futures import Future, ThreadPoolExecutor
10
- from enum import Enum
11
10
  from fnmatch import fnmatch
12
11
  from pathlib import Path
13
12
  from tempfile import TemporaryDirectory
@@ -26,6 +25,7 @@ from rclone_api.group_files import group_files
26
25
  from rclone_api.process import Process
27
26
  from rclone_api.remote import Remote
28
27
  from rclone_api.rpath import RPath
28
+ from rclone_api.types import ListingOption, ModTimeStrategy
29
29
  from rclone_api.util import (
30
30
  get_check,
31
31
  get_rclone_exe,
@@ -42,17 +42,6 @@ def rclone_verbose(verbose: bool | None) -> bool:
42
42
  return bool(int(os.getenv("RCLONE_API_VERBOSE", "0")))
43
43
 
44
44
 
45
- class ModTimeStrategy(Enum):
46
- USE_SERVER_MODTIME = "use-server-modtime"
47
- NO_MODTIME = "no-modtime"
48
-
49
-
50
- class ListingOption(Enum):
51
- DIRS_ONLY = "dirs-only"
52
- FILES_ONLY = "files-only"
53
- ALL = "all"
54
-
55
-
56
45
  class Rclone:
57
46
  def __init__(
58
47
  self, rclone_conf: Path | Config, rclone_exe: Path | None = None
@@ -203,17 +192,20 @@ class Rclone:
203
192
  diff_option: DiffOption = DiffOption.COMBINED,
204
193
  fast_list: bool = True,
205
194
  size_only: bool | None = None,
195
+ checkers: int | None = None,
206
196
  other_args: list[str] | None = None,
207
197
  ) -> Generator[DiffItem, None, None]:
208
198
  """Be extra careful with the src and dst values. If you are off by one
209
199
  parent directory, you will get a huge amount of false diffs."""
210
200
  other_args = other_args or []
201
+ if checkers is None or checkers < 1:
202
+ checkers = 1000
211
203
  cmd = [
212
204
  "check",
213
205
  src,
214
206
  dst,
215
207
  "--checkers",
216
- "1000",
208
+ str(checkers),
217
209
  "--log-level",
218
210
  "INFO",
219
211
  f"--{diff_option.value}",
@@ -232,6 +224,8 @@ class Rclone:
232
224
  cmd += ["--min-size", min_size]
233
225
  if max_size:
234
226
  cmd += ["--max-size", max_size]
227
+ if diff_option == DiffOption.MISSING_ON_DST:
228
+ cmd += ["--one-way"]
235
229
  if other_args:
236
230
  cmd += other_args
237
231
  proc = self._launch_process(cmd, capture=True)
@@ -286,6 +280,33 @@ class Rclone:
286
280
  dir_obj, max_depth=max_depth, breadth_first=breadth_first, reverse=reverse
287
281
  )
288
282
 
283
+ def diff_walk(
284
+ self,
285
+ src: Dir | Remote | str,
286
+ dst: Dir | Remote | str,
287
+ max_depth: int = -1,
288
+ reverse: bool = False,
289
+ ) -> Generator[Dir, None, None]:
290
+ """Walk through the given path recursively.
291
+
292
+ WORK IN PROGRESS!!
293
+
294
+ Args:
295
+ src: Source directory or Remote to walk through
296
+ dst: Destination directory or Remote to walk through
297
+ max_depth: Maximum depth to traverse (-1 for unlimited)
298
+
299
+ Yields:
300
+ DirListing: Directory listing for each directory encountered
301
+ """
302
+ from rclone_api.diff_walk import diff_walk
303
+
304
+ src_dir = Dir(to_path(src, self))
305
+ dst_dir = Dir(to_path(dst, self))
306
+ yield from diff_walk(
307
+ src=src_dir, dst=dst_dir, max_depth=max_depth, reverse=reverse
308
+ )
309
+
289
310
  def cleanup(
290
311
  self, path: str, other_args: list[str] | None = None
291
312
  ) -> CompletedProcess:
rclone_api/types.py ADDED
@@ -0,0 +1,12 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ModTimeStrategy(Enum):
5
+ USE_SERVER_MODTIME = "use-server-modtime"
6
+ NO_MODTIME = "no-modtime"
7
+
8
+
9
+ class ListingOption(Enum):
10
+ DIRS_ONLY = "dirs-only"
11
+ FILES_ONLY = "files-only"
12
+ ALL = "all"
rclone_api/walk.py CHANGED
@@ -9,7 +9,7 @@ from rclone_api.remote import Remote
9
9
  _MAX_OUT_QUEUE_SIZE = 50
10
10
 
11
11
 
12
- def _walk_runner_breadth_first(
12
+ def walk_runner_breadth_first(
13
13
  dir: Dir,
14
14
  max_depth: int,
15
15
  out_queue: Queue[DirListing | None],
@@ -40,7 +40,7 @@ def _walk_runner_breadth_first(
40
40
  _thread.interrupt_main()
41
41
 
42
42
 
43
- def _walk_runner_depth_first(
43
+ def walk_runner_depth_first(
44
44
  dir: Dir, max_depth: int, out_queue: Queue[DirListing | None], reverse=False
45
45
  ) -> None:
46
46
  try:
@@ -54,7 +54,7 @@ def _walk_runner_depth_first(
54
54
  for subdir in dirlisting.dirs: # Process deeper directories first
55
55
  # stack.append((child, depth - 1 if depth > 0 else depth))
56
56
  next_depth = depth - 1 if depth > 0 else depth
57
- _walk_runner_depth_first(
57
+ walk_runner_depth_first(
58
58
  subdir, next_depth, out_queue, reverse=reverse
59
59
  )
60
60
  out_queue.put(dirlisting)
@@ -85,16 +85,17 @@ def walk(
85
85
  # Convert Remote to Dir if needed
86
86
  if isinstance(dir, Remote):
87
87
  dir = Dir(dir)
88
- out_queue: Queue[DirListing] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
88
+ out_queue: Queue[DirListing | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
89
89
 
90
- strategy = (
91
- _walk_runner_breadth_first if breadth_first else _walk_runner_depth_first
92
- )
90
+ def _task() -> None:
91
+ if breadth_first:
92
+ walk_runner_breadth_first(dir, max_depth, out_queue, reverse)
93
+ else:
94
+ walk_runner_depth_first(dir, max_depth, out_queue, reverse)
93
95
 
94
96
  # Start worker thread
95
97
  worker = Thread(
96
- target=strategy,
97
- args=(dir, max_depth, out_queue, reverse),
98
+ target=_task,
98
99
  daemon=True,
99
100
  )
100
101
  worker.start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.67
3
+ Version: 1.0.69
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -5,23 +5,25 @@ 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
7
7
  rclone_api/diff.py,sha256=ggdDLUZxa13jMcPzKBcwAElmPCNWMOSR89D4yhpO74M,5264
8
- rclone_api/dir.py,sha256=2-PFaDpjEs28z82DQ-TyaCgrm_OgpwlkwfTnx1-Wwpk,2194
8
+ rclone_api/diff_walk.py,sha256=V8U6qwCzG7_Z6-xFpB4zP6kCq_jN30OMYo7tT92OTd0,5061
9
+ rclone_api/dir.py,sha256=8EwaGonkCeHhe-rsVl98PkEKvyf-3pFUJnXqkN6Fx24,3509
9
10
  rclone_api/dir_listing.py,sha256=9Qqf2SUswrOEkyqmaH23V51I18X6ePiXb9B1vUwRF5o,1571
10
11
  rclone_api/exec.py,sha256=1ovvaMXDEfLiT7BrYZyE85u_yFhEUwUNW3jPOzqknR8,1023
11
12
  rclone_api/file.py,sha256=YtR5Y6c0YfXTS-sReOy2UgiSnafcAeO6b2hnbojBQD4,1423
12
13
  rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
13
14
  rclone_api/group_files.py,sha256=kOHh6ysFDkxjldSwvW6KqmiADUC1yFCdrZRY57TvbGY,5328
14
15
  rclone_api/process.py,sha256=RrMfTe0bndmJ6gBK67ioqNvCstJ8aTC8RlGX1XBLlcw,4191
15
- rclone_api/rclone.py,sha256=CLjyQXRt-YaWwEOKZ2G1LACxUprtG8eLGAcNkVywxh8,27985
16
+ rclone_api/rclone.py,sha256=nN1bqUmiopCKP6VNG4XpSfBfWx4EuAIyxB9NevYp-OA,28854
16
17
  rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
17
18
  rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
19
+ rclone_api/types.py,sha256=VXA-56va2S41onAWM_UmhHlG4qeyMUEF5dsVFyfLlH8,232
18
20
  rclone_api/util.py,sha256=_cvmHcJPRl2yXw4zgZiop3z-riA_8Ek6S5NDPw8cqSY,4198
19
- rclone_api/walk.py,sha256=UaNOE3ICd8k5ouSFZvkVEH4r2GnnrD9TxfwkFcQnayo,3170
21
+ rclone_api/walk.py,sha256=ndWV7WBVQLbpZu3HuJrAe1cXcmQVjT9_YPsbat158bQ,3231
20
22
  rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
21
23
  rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
22
- rclone_api-1.0.67.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
23
- rclone_api-1.0.67.dist-info/METADATA,sha256=H866NatSXqCR0niXYtEebKY14_byytSoF1jZ1HHab1s,4489
24
- rclone_api-1.0.67.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
25
- rclone_api-1.0.67.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
26
- rclone_api-1.0.67.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
27
- rclone_api-1.0.67.dist-info/RECORD,,
24
+ rclone_api-1.0.69.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
25
+ rclone_api-1.0.69.dist-info/METADATA,sha256=bl94_jCcD0a0buUsLVFIQ-MPQ5O2fy-hFrvK1EvU7Ls,4489
26
+ rclone_api-1.0.69.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
27
+ rclone_api-1.0.69.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
28
+ rclone_api-1.0.69.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
29
+ rclone_api-1.0.69.dist-info/RECORD,,