rclone-api 1.0.69__tar.gz → 1.0.72__tar.gz

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.

Files changed (70) hide show
  1. {rclone_api-1.0.69 → rclone_api-1.0.72}/PKG-INFO +1 -1
  2. {rclone_api-1.0.69 → rclone_api-1.0.72}/pyproject.toml +1 -1
  3. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/__init__.py +5 -2
  4. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/dir.py +4 -4
  5. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/rclone.py +13 -14
  6. rclone_api-1.0.72/src/rclone_api/scan_missing_folders.py +135 -0
  7. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/types.py +6 -0
  8. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/walk.py +16 -10
  9. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api.egg-info/PKG-INFO +1 -1
  10. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api.egg-info/SOURCES.txt +2 -2
  11. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_diff.py +2 -2
  12. rclone_api-1.0.69/tests/test_diff_walk.py → rclone_api-1.0.72/tests/test_scan_missing_folders.py +8 -4
  13. rclone_api-1.0.69/src/rclone_api/diff_walk.py +0 -143
  14. {rclone_api-1.0.69 → rclone_api-1.0.72}/.aiderignore +0 -0
  15. {rclone_api-1.0.69 → rclone_api-1.0.72}/.github/workflows/lint.yml +0 -0
  16. {rclone_api-1.0.69 → rclone_api-1.0.72}/.github/workflows/push_macos.yml +0 -0
  17. {rclone_api-1.0.69 → rclone_api-1.0.72}/.github/workflows/push_ubuntu.yml +0 -0
  18. {rclone_api-1.0.69 → rclone_api-1.0.72}/.github/workflows/push_win.yml +0 -0
  19. {rclone_api-1.0.69 → rclone_api-1.0.72}/.gitignore +0 -0
  20. {rclone_api-1.0.69 → rclone_api-1.0.72}/.pylintrc +0 -0
  21. {rclone_api-1.0.69 → rclone_api-1.0.72}/.vscode/launch.json +0 -0
  22. {rclone_api-1.0.69 → rclone_api-1.0.72}/.vscode/settings.json +0 -0
  23. {rclone_api-1.0.69 → rclone_api-1.0.72}/.vscode/tasks.json +0 -0
  24. {rclone_api-1.0.69 → rclone_api-1.0.72}/LICENSE +0 -0
  25. {rclone_api-1.0.69 → rclone_api-1.0.72}/MANIFEST.in +0 -0
  26. {rclone_api-1.0.69 → rclone_api-1.0.72}/README.md +0 -0
  27. {rclone_api-1.0.69 → rclone_api-1.0.72}/clean +0 -0
  28. {rclone_api-1.0.69 → rclone_api-1.0.72}/install +0 -0
  29. {rclone_api-1.0.69 → rclone_api-1.0.72}/lint +0 -0
  30. {rclone_api-1.0.69 → rclone_api-1.0.72}/requirements.testing.txt +0 -0
  31. {rclone_api-1.0.69 → rclone_api-1.0.72}/setup.cfg +0 -0
  32. {rclone_api-1.0.69 → rclone_api-1.0.72}/setup.py +0 -0
  33. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/assets/example.txt +0 -0
  34. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/cli.py +0 -0
  35. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/cmd/list_files.py +0 -0
  36. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/completed_process.py +0 -0
  37. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/config.py +0 -0
  38. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/convert.py +0 -0
  39. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/deprecated.py +0 -0
  40. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/diff.py +0 -0
  41. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/dir_listing.py +0 -0
  42. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/exec.py +0 -0
  43. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/file.py +0 -0
  44. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/filelist.py +0 -0
  45. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/group_files.py +0 -0
  46. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/process.py +0 -0
  47. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/remote.py +0 -0
  48. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/rpath.py +0 -0
  49. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api/util.py +0 -0
  50. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  51. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api.egg-info/entry_points.txt +0 -0
  52. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api.egg-info/requires.txt +0 -0
  53. {rclone_api-1.0.69 → rclone_api-1.0.72}/src/rclone_api.egg-info/top_level.txt +0 -0
  54. {rclone_api-1.0.69 → rclone_api-1.0.72}/test +0 -0
  55. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_cmd_list_files.py +0 -0
  56. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_copy.py +0 -0
  57. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_copy_files.py +0 -0
  58. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_group_files.py +0 -0
  59. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_is_synced.py +0 -0
  60. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_ls.py +0 -0
  61. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_mount.py +0 -0
  62. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_mount_s3.py +0 -0
  63. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_mount_webdav.py +0 -0
  64. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_obscure.py +0 -0
  65. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_remote_control.py +0 -0
  66. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_remotes.py +0 -0
  67. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_serve_webdav.py +0 -0
  68. {rclone_api-1.0.69 → rclone_api-1.0.72}/tests/test_walk.py +0 -0
  69. {rclone_api-1.0.69 → rclone_api-1.0.72}/tox.ini +0 -0
  70. {rclone_api-1.0.69 → rclone_api-1.0.72}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.69
3
+ Version: 1.0.72
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -15,7 +15,7 @@ dependencies = [
15
15
  "python-dotenv>=1.0.0",
16
16
  ]
17
17
  # Change this with the version number bump.
18
- version = "1.0.69"
18
+ version = "1.0.72"
19
19
 
20
20
  [tool.setuptools]
21
21
  package-dir = {"" = "src"}
@@ -1,14 +1,15 @@
1
1
  from .completed_process import CompletedProcess
2
2
  from .config import Config
3
- from .diff import DiffItem, DiffType
3
+ from .diff import DiffItem, DiffOption, DiffType
4
4
  from .dir import Dir
5
5
  from .dir_listing import DirListing
6
6
  from .file import File
7
7
  from .filelist import FileList
8
8
  from .process import Process
9
- from .rclone import DiffOption, ListingOption, Rclone, rclone_verbose
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
13
 
13
14
  __all__ = [
14
15
  "Rclone",
@@ -26,4 +27,6 @@ __all__ = [
26
27
  "CompletedProcess",
27
28
  "DiffOption",
28
29
  "ListingOption",
30
+ "Order",
31
+ "ListingOption",
29
32
  ]
@@ -5,7 +5,7 @@ from typing import Generator
5
5
  from rclone_api.dir_listing import DirListing
6
6
  from rclone_api.remote import Remote
7
7
  from rclone_api.rpath import RPath
8
- from rclone_api.types import ListingOption
8
+ from rclone_api.types import ListingOption, Order
9
9
 
10
10
 
11
11
  class Dir:
@@ -47,7 +47,7 @@ class Dir:
47
47
  self,
48
48
  max_depth: int | None = None,
49
49
  glob: str | None = None,
50
- reverse: bool = False,
50
+ order: Order = Order.NORMAL,
51
51
  listing_option: ListingOption = ListingOption.ALL,
52
52
  ) -> DirListing:
53
53
  """List files and directories in the given path."""
@@ -57,7 +57,7 @@ class Dir:
57
57
  dir,
58
58
  max_depth=max_depth,
59
59
  glob=glob,
60
- reverse=reverse,
60
+ order=order,
61
61
  listing_option=listing_option,
62
62
  )
63
63
 
@@ -102,7 +102,7 @@ class Dir:
102
102
  path = Path(self.path.path) / other
103
103
  rpath = RPath(
104
104
  self.path.remote,
105
- str(path),
105
+ str(path.as_posix()),
106
106
  name=other,
107
107
  size=0,
108
108
  mime_type="inode/directory",
@@ -3,6 +3,7 @@ Unit test file.
3
3
  """
4
4
 
5
5
  import os
6
+ import random
6
7
  import subprocess
7
8
  import time
8
9
  import warnings
@@ -25,7 +26,7 @@ from rclone_api.group_files import group_files
25
26
  from rclone_api.process import Process
26
27
  from rclone_api.remote import Remote
27
28
  from rclone_api.rpath import RPath
28
- from rclone_api.types import ListingOption, ModTimeStrategy
29
+ from rclone_api.types import ListingOption, ModTimeStrategy, Order
29
30
  from rclone_api.util import (
30
31
  get_check,
31
32
  get_rclone_exe,
@@ -118,7 +119,7 @@ class Rclone:
118
119
  path: Dir | Remote | str,
119
120
  max_depth: int | None = None,
120
121
  glob: str | None = None,
121
- reverse: bool = False,
122
+ order: Order = Order.NORMAL,
122
123
  listing_option: ListingOption = ListingOption.ALL,
123
124
  ) -> DirListing:
124
125
  """List files in the given path.
@@ -164,8 +165,10 @@ class Rclone:
164
165
  if glob is not None:
165
166
  paths = [p for p in paths if fnmatch(p.path, glob)]
166
167
 
167
- if reverse:
168
+ if order == Order.REVERSE:
168
169
  paths.reverse()
170
+ elif order == Order.RANDOM:
171
+ random.shuffle(paths)
169
172
  return DirListing(paths)
170
173
 
171
174
  def listremotes(self) -> list[Remote]:
@@ -242,7 +245,7 @@ class Rclone:
242
245
  path: Dir | Remote | str,
243
246
  max_depth: int = -1,
244
247
  breadth_first: bool = True,
245
- reverse: bool = False,
248
+ order: Order = Order.NORMAL,
246
249
  ) -> Generator[DirListing, None, None]:
247
250
  """Walk through the given path recursively.
248
251
 
@@ -277,15 +280,15 @@ class Rclone:
277
280
  assert f"Invalid type for path: {type(path)}"
278
281
 
279
282
  yield from walk(
280
- dir_obj, max_depth=max_depth, breadth_first=breadth_first, reverse=reverse
283
+ dir_obj, max_depth=max_depth, breadth_first=breadth_first, order=order
281
284
  )
282
285
 
283
- def diff_walk(
286
+ def scan_missing_folders(
284
287
  self,
285
288
  src: Dir | Remote | str,
286
289
  dst: Dir | Remote | str,
287
290
  max_depth: int = -1,
288
- reverse: bool = False,
291
+ order: Order = Order.NORMAL,
289
292
  ) -> Generator[Dir, None, None]:
290
293
  """Walk through the given path recursively.
291
294
 
@@ -299,12 +302,12 @@ class Rclone:
299
302
  Yields:
300
303
  DirListing: Directory listing for each directory encountered
301
304
  """
302
- from rclone_api.diff_walk import diff_walk
305
+ from rclone_api.scan_missing_folders import scan_missing_folders
303
306
 
304
307
  src_dir = Dir(to_path(src, self))
305
308
  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
309
+ yield from scan_missing_folders(
310
+ src=src_dir, dst=dst_dir, max_depth=max_depth, order=order
308
311
  )
309
312
 
310
313
  def cleanup(
@@ -426,10 +429,6 @@ class Rclone:
426
429
  chunk = files_fqdn[i : i + chunk_size]
427
430
  files_str = "\n".join(chunk)
428
431
  print(f"{files_str}")
429
- # files_str = "\n".join(files_fqdn)
430
- # print(f"Copying {nfiles} files: \n{files_str}")
431
-
432
- # print(include_files_txt)
433
432
  cmd_list: list[str] = [
434
433
  "copy",
435
434
  src_path,
@@ -0,0 +1,135 @@
1
+ import random
2
+ import time
3
+ from concurrent.futures import ThreadPoolExecutor
4
+ from queue import Empty, Queue
5
+ from threading import Thread
6
+ from typing import Generator
7
+
8
+ from rclone_api import Dir
9
+ from rclone_api.dir_listing import DirListing
10
+ from rclone_api.types import ListingOption, Order
11
+ from rclone_api.walk import walk_runner_depth_first
12
+
13
+ _MAX_OUT_QUEUE_SIZE = 50
14
+
15
+
16
+ # ONLY Works from src -> dst diffing.
17
+ def _async_diff_dir_walk_task(
18
+ src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], order: Order
19
+ ) -> None:
20
+ curr_src, curr_dst = src, dst
21
+ with ThreadPoolExecutor(max_workers=2) as executor:
22
+ t1 = executor.submit(
23
+ src.ls, listing_option=ListingOption.DIRS_ONLY, order=order
24
+ )
25
+ t2 = executor.submit(
26
+ dst.ls, listing_option=ListingOption.DIRS_ONLY, order=order
27
+ )
28
+ src_dir_listing: DirListing = t1.result()
29
+ dst_dir_listing: DirListing = t2.result()
30
+ next_depth = max_depth - 1 if max_depth > 0 else max_depth
31
+ dst_dirs: list[str] = [d.name for d in dst_dir_listing.dirs]
32
+ src_dirs: list[str] = [d.name for d in src_dir_listing.dirs]
33
+ dst_files_set: set[str] = set(dst_dirs)
34
+ matching_dirs: list[str] = []
35
+ if order == Order.REVERSE:
36
+ src_dirs.reverse()
37
+ dst_dirs.reverse()
38
+ elif order == Order.RANDOM:
39
+ random.shuffle(src_dirs)
40
+ random.shuffle(dst_dirs)
41
+ for file in src_dirs:
42
+ if file not in dst_files_set:
43
+ queue_dir_listing: Queue[DirListing | None] = Queue()
44
+ if next_depth > 0 or next_depth == -1:
45
+ walk_runner_depth_first(
46
+ dir=curr_src,
47
+ out_queue=queue_dir_listing,
48
+ order=order,
49
+ max_depth=next_depth,
50
+ )
51
+ while dirlisting := queue_dir_listing.get():
52
+ if dirlisting is None:
53
+ break
54
+ # print(f"dirlisting: {dirlisting}")
55
+ for d in dirlisting.dirs:
56
+ out_queue.put(d)
57
+ else:
58
+ matching_dirs.append(file)
59
+
60
+ for matching_dir in matching_dirs:
61
+ # print(f"matching dir: {matching_dir}")
62
+ if next_depth > 0 or next_depth == -1:
63
+ src_next = curr_src / matching_dir
64
+ dst_next = curr_dst / matching_dir
65
+ _async_diff_dir_walk_task(
66
+ src=src_next,
67
+ dst=dst_next,
68
+ max_depth=next_depth,
69
+ out_queue=out_queue,
70
+ order=order,
71
+ )
72
+
73
+
74
+ def async_diff_dir_walk_task(
75
+ src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], order: Order
76
+ ) -> None:
77
+ try:
78
+ _async_diff_dir_walk_task(
79
+ src=src, dst=dst, max_depth=max_depth, out_queue=out_queue, order=order
80
+ )
81
+ except Exception:
82
+ import _thread
83
+
84
+ _thread.interrupt_main()
85
+ raise
86
+ finally:
87
+ out_queue.put(None)
88
+
89
+
90
+ def scan_missing_folders(
91
+ src: Dir,
92
+ dst: Dir,
93
+ max_depth: int = -1,
94
+ order: Order = Order.NORMAL,
95
+ ) -> Generator[Dir, None, None]:
96
+ """Walk through the given directory recursively.
97
+
98
+ Args:
99
+ dir: Directory or Remote to walk through
100
+ max_depth: Maximum depth to traverse (-1 for unlimited)
101
+
102
+ Yields:
103
+ DirListing: Directory listing for each directory encountered
104
+ """
105
+
106
+ try:
107
+ out_queue: Queue[Dir | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
108
+
109
+ def task() -> None:
110
+ async_diff_dir_walk_task(
111
+ src=src,
112
+ dst=dst,
113
+ max_depth=max_depth,
114
+ out_queue=out_queue,
115
+ order=order,
116
+ )
117
+
118
+ worker = Thread(
119
+ target=task,
120
+ daemon=True,
121
+ )
122
+ worker.start()
123
+
124
+ while True:
125
+ try:
126
+ dir = out_queue.get_nowait()
127
+ if dir is None:
128
+ break
129
+ yield dir
130
+ except Empty:
131
+ time.sleep(0.1)
132
+
133
+ worker.join()
134
+ except KeyboardInterrupt:
135
+ pass
@@ -10,3 +10,9 @@ class ListingOption(Enum):
10
10
  DIRS_ONLY = "dirs-only"
11
11
  FILES_ONLY = "files-only"
12
12
  ALL = "all"
13
+
14
+
15
+ class Order(Enum):
16
+ NORMAL = "normal"
17
+ REVERSE = "reverse"
18
+ RANDOM = "random"
@@ -1,3 +1,4 @@
1
+ import random
1
2
  from queue import Queue
2
3
  from threading import Thread
3
4
  from typing import Generator
@@ -5,6 +6,7 @@ from typing import Generator
5
6
  from rclone_api import Dir
6
7
  from rclone_api.dir_listing import DirListing
7
8
  from rclone_api.remote import Remote
9
+ from rclone_api.types import Order
8
10
 
9
11
  _MAX_OUT_QUEUE_SIZE = 50
10
12
 
@@ -13,14 +15,14 @@ def walk_runner_breadth_first(
13
15
  dir: Dir,
14
16
  max_depth: int,
15
17
  out_queue: Queue[DirListing | None],
16
- reverse: bool = False,
18
+ order: Order = Order.NORMAL,
17
19
  ) -> None:
18
20
  queue: Queue[Dir] = Queue()
19
21
  queue.put(dir)
20
22
  try:
21
23
  while not queue.empty():
22
24
  current_dir = queue.get()
23
- dirlisting = current_dir.ls(max_depth=0, reverse=reverse)
25
+ dirlisting = current_dir.ls(max_depth=0, order=order)
24
26
  out_queue.put(dirlisting)
25
27
  dirs = dirlisting.dirs
26
28
 
@@ -41,22 +43,26 @@ def walk_runner_breadth_first(
41
43
 
42
44
 
43
45
  def walk_runner_depth_first(
44
- dir: Dir, max_depth: int, out_queue: Queue[DirListing | None], reverse=False
46
+ dir: Dir,
47
+ max_depth: int,
48
+ out_queue: Queue[DirListing | None],
49
+ order: Order = Order.NORMAL,
45
50
  ) -> None:
46
51
  try:
47
52
  stack = [(dir, max_depth)]
48
53
  while stack:
49
54
  current_dir, depth = stack.pop()
50
55
  dirlisting = current_dir.ls()
51
- if reverse:
56
+ if order == Order.REVERSE:
52
57
  dirlisting.dirs.reverse()
58
+ if order == Order.RANDOM:
59
+
60
+ random.shuffle(dirlisting.dirs)
53
61
  if depth != 0:
54
62
  for subdir in dirlisting.dirs: # Process deeper directories first
55
63
  # stack.append((child, depth - 1 if depth > 0 else depth))
56
64
  next_depth = depth - 1 if depth > 0 else depth
57
- walk_runner_depth_first(
58
- subdir, next_depth, out_queue, reverse=reverse
59
- )
65
+ walk_runner_depth_first(subdir, next_depth, out_queue, order=order)
60
66
  out_queue.put(dirlisting)
61
67
  out_queue.put(None)
62
68
  except KeyboardInterrupt:
@@ -70,7 +76,7 @@ def walk(
70
76
  dir: Dir | Remote,
71
77
  breadth_first: bool,
72
78
  max_depth: int = -1,
73
- reverse: bool = False,
79
+ order: Order = Order.NORMAL,
74
80
  ) -> Generator[DirListing, None, None]:
75
81
  """Walk through the given directory recursively.
76
82
 
@@ -89,9 +95,9 @@ def walk(
89
95
 
90
96
  def _task() -> None:
91
97
  if breadth_first:
92
- walk_runner_breadth_first(dir, max_depth, out_queue, reverse)
98
+ walk_runner_breadth_first(dir, max_depth, out_queue, order)
93
99
  else:
94
- walk_runner_depth_first(dir, max_depth, out_queue, reverse)
100
+ walk_runner_depth_first(dir, max_depth, out_queue, order)
95
101
 
96
102
  # Start worker thread
97
103
  worker = Thread(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.69
3
+ Version: 1.0.72
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -27,7 +27,6 @@ src/rclone_api/config.py
27
27
  src/rclone_api/convert.py
28
28
  src/rclone_api/deprecated.py
29
29
  src/rclone_api/diff.py
30
- src/rclone_api/diff_walk.py
31
30
  src/rclone_api/dir.py
32
31
  src/rclone_api/dir_listing.py
33
32
  src/rclone_api/exec.py
@@ -38,6 +37,7 @@ src/rclone_api/process.py
38
37
  src/rclone_api/rclone.py
39
38
  src/rclone_api/remote.py
40
39
  src/rclone_api/rpath.py
40
+ src/rclone_api/scan_missing_folders.py
41
41
  src/rclone_api/types.py
42
42
  src/rclone_api/util.py
43
43
  src/rclone_api/walk.py
@@ -53,7 +53,6 @@ tests/test_cmd_list_files.py
53
53
  tests/test_copy.py
54
54
  tests/test_copy_files.py
55
55
  tests/test_diff.py
56
- tests/test_diff_walk.py
57
56
  tests/test_group_files.py
58
57
  tests/test_is_synced.py
59
58
  tests/test_ls.py
@@ -63,5 +62,6 @@ tests/test_mount_webdav.py
63
62
  tests/test_obscure.py
64
63
  tests/test_remote_control.py
65
64
  tests/test_remotes.py
65
+ tests/test_scan_missing_folders.py
66
66
  tests/test_serve_webdav.py
67
67
  tests/test_walk.py
@@ -7,8 +7,8 @@ import unittest
7
7
 
8
8
  from dotenv import load_dotenv
9
9
 
10
- from rclone_api import Config, DiffOption, Rclone
11
- from rclone_api.diff import DiffItem, DiffType
10
+ from rclone_api import Config, Rclone
11
+ from rclone_api.diff import DiffItem, DiffOption, DiffType
12
12
 
13
13
  load_dotenv()
14
14
 
@@ -8,6 +8,7 @@ import unittest
8
8
  from dotenv import load_dotenv
9
9
 
10
10
  from rclone_api import Config, Dir, Rclone
11
+ from rclone_api.types import Order
11
12
 
12
13
  load_dotenv()
13
14
 
@@ -37,7 +38,7 @@ endpoint = {BUCKET_URL}
37
38
  return out
38
39
 
39
40
 
40
- class RcloneDiffTests(unittest.TestCase):
41
+ class RcloneScanMissingFoldersTests(unittest.TestCase):
41
42
  """Test rclone functionality."""
42
43
 
43
44
  def setUp(self) -> None:
@@ -55,13 +56,16 @@ class RcloneDiffTests(unittest.TestCase):
55
56
  )
56
57
  os.environ["RCLONE_API_VERBOSE"] = "1"
57
58
 
58
- def test_diff_walk(self) -> None:
59
+ def test_scan_missing_folders(self) -> None:
59
60
  """Test copying a single file to remote storage."""
60
61
  rclone = Rclone(_generate_rclone_config())
61
62
  item: Dir
62
63
  all: list[Dir] = []
63
- for item in rclone.diff_walk(
64
- "dst:rclone-api-unit-test", "dst:rclone-api-unit-test"
64
+ for item in rclone.scan_missing_folders(
65
+ src="dst:rclone-api-unit-test",
66
+ dst="dst:rclone-api-unit-test",
67
+ max_depth=-1,
68
+ order=Order.NORMAL,
65
69
  ):
66
70
  all.append(item)
67
71
  self.assertEqual(len(all), 0)
@@ -1,143 +0,0 @@
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes