rclone-api 1.0.68__py2.py3-none-any.whl → 1.0.69__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.
@@ -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}",
@@ -288,6 +280,33 @@ class Rclone:
288
280
  dir_obj, max_depth=max_depth, breadth_first=breadth_first, reverse=reverse
289
281
  )
290
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
+
291
310
  def cleanup(
292
311
  self, path: str, other_args: list[str] | None = None
293
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.68
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=Hnk74ZyCnxSkeNIGRfZLqtGdWHTzPRye2lzRah6vLeI,28071
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.68.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
23
- rclone_api-1.0.68.dist-info/METADATA,sha256=p3oHKpaFeHKBgQhxdAqBHrHVBwzm7k_VuL8W8oYmx9U,4489
24
- rclone_api-1.0.68.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
25
- rclone_api-1.0.68.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
26
- rclone_api-1.0.68.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
27
- rclone_api-1.0.68.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,,