rclone-api 1.0.69__py2.py3-none-any.whl → 1.0.72__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.
- rclone_api/__init__.py +5 -2
- rclone_api/dir.py +4 -4
- rclone_api/rclone.py +13 -14
- rclone_api/scan_missing_folders.py +135 -0
- rclone_api/types.py +6 -0
- rclone_api/walk.py +16 -10
- {rclone_api-1.0.69.dist-info → rclone_api-1.0.72.dist-info}/METADATA +1 -1
- {rclone_api-1.0.69.dist-info → rclone_api-1.0.72.dist-info}/RECORD +12 -12
- rclone_api/diff_walk.py +0 -143
- {rclone_api-1.0.69.dist-info → rclone_api-1.0.72.dist-info}/LICENSE +0 -0
- {rclone_api-1.0.69.dist-info → rclone_api-1.0.72.dist-info}/WHEEL +0 -0
- {rclone_api-1.0.69.dist-info → rclone_api-1.0.72.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.0.69.dist-info → rclone_api-1.0.72.dist-info}/top_level.txt +0 -0
rclone_api/__init__.py
CHANGED
@@ -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
|
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
|
]
|
rclone_api/dir.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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",
|
rclone_api/rclone.py
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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,
|
283
|
+
dir_obj, max_depth=max_depth, breadth_first=breadth_first, order=order
|
281
284
|
)
|
282
285
|
|
283
|
-
def
|
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
|
-
|
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.
|
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
|
307
|
-
src=src_dir, dst=dst_dir, max_depth=max_depth,
|
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
|
rclone_api/types.py
CHANGED
rclone_api/walk.py
CHANGED
@@ -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
|
-
|
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,
|
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,
|
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
|
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
|
-
|
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,
|
98
|
+
walk_runner_breadth_first(dir, max_depth, out_queue, order)
|
93
99
|
else:
|
94
|
-
walk_runner_depth_first(dir, max_depth, out_queue,
|
100
|
+
walk_runner_depth_first(dir, max_depth, out_queue, order)
|
95
101
|
|
96
102
|
# Start worker thread
|
97
103
|
worker = Thread(
|
@@ -1,29 +1,29 @@
|
|
1
|
-
rclone_api/__init__.py,sha256=
|
1
|
+
rclone_api/__init__.py,sha256=kJUk9KAxQ2AEms9lM5yqw-dqlabarhU69b2FAxJVBlY,692
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
3
|
rclone_api/completed_process.py,sha256=Pp-hXnLgej0IGO5ee9Fmx64dGzIofbQFEUyXdFCvO54,1371
|
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
|
7
7
|
rclone_api/diff.py,sha256=ggdDLUZxa13jMcPzKBcwAElmPCNWMOSR89D4yhpO74M,5264
|
8
|
-
rclone_api/
|
9
|
-
rclone_api/dir.py,sha256=8EwaGonkCeHhe-rsVl98PkEKvyf-3pFUJnXqkN6Fx24,3509
|
8
|
+
rclone_api/dir.py,sha256=hL0i4zrNUbpvFWI3TvKyOyanJ2okcb1lz4r2kxlvSi4,3529
|
10
9
|
rclone_api/dir_listing.py,sha256=9Qqf2SUswrOEkyqmaH23V51I18X6ePiXb9B1vUwRF5o,1571
|
11
10
|
rclone_api/exec.py,sha256=1ovvaMXDEfLiT7BrYZyE85u_yFhEUwUNW3jPOzqknR8,1023
|
12
11
|
rclone_api/file.py,sha256=YtR5Y6c0YfXTS-sReOy2UgiSnafcAeO6b2hnbojBQD4,1423
|
13
12
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
14
13
|
rclone_api/group_files.py,sha256=kOHh6ysFDkxjldSwvW6KqmiADUC1yFCdrZRY57TvbGY,5328
|
15
14
|
rclone_api/process.py,sha256=RrMfTe0bndmJ6gBK67ioqNvCstJ8aTC8RlGX1XBLlcw,4191
|
16
|
-
rclone_api/rclone.py,sha256=
|
15
|
+
rclone_api/rclone.py,sha256=kIamoje3fUaWboMdF_d_a4WVaSa8BfK6zDFY8U6pNbs,28820
|
17
16
|
rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
|
18
17
|
rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
|
19
|
-
rclone_api/
|
18
|
+
rclone_api/scan_missing_folders.py,sha256=ke2AVIdX-MiuJUi7poJBzP8GxjzzMhCsr1Mxbg0EIu4,4217
|
19
|
+
rclone_api/types.py,sha256=DcbNw1R6j2f_1zHrhqEJcpCR-8kJfFJawMY0AmPsCnM,321
|
20
20
|
rclone_api/util.py,sha256=_cvmHcJPRl2yXw4zgZiop3z-riA_8Ek6S5NDPw8cqSY,4198
|
21
|
-
rclone_api/walk.py,sha256
|
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.
|
25
|
-
rclone_api-1.0.
|
26
|
-
rclone_api-1.0.
|
27
|
-
rclone_api-1.0.
|
28
|
-
rclone_api-1.0.
|
29
|
-
rclone_api-1.0.
|
24
|
+
rclone_api-1.0.72.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
25
|
+
rclone_api-1.0.72.dist-info/METADATA,sha256=xzlW0jfYuqOPGSCI9k8MxNvp4BpI0Duojacxq_l-C-M,4489
|
26
|
+
rclone_api-1.0.72.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
|
27
|
+
rclone_api-1.0.72.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
|
28
|
+
rclone_api-1.0.72.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
29
|
+
rclone_api-1.0.72.dist-info/RECORD,,
|
rclone_api/diff_walk.py
DELETED
@@ -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
|