rclone-api 1.5.38__py3-none-any.whl → 1.5.39__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 +1003 -1003
- rclone_api/config.py +186 -153
- rclone_api/db/__init__.py +3 -3
- rclone_api/detail/walk.py +116 -116
- rclone_api/dir.py +113 -113
- rclone_api/log.py +44 -44
- rclone_api/rclone_impl.py +1360 -1360
- rclone_api/s3/multipart/upload_parts_server_side_merge.py +546 -546
- rclone_api/scan_missing_folders.py +153 -153
- {rclone_api-1.5.38.dist-info → rclone_api-1.5.39.dist-info}/METADATA +1100 -1100
- {rclone_api-1.5.38.dist-info → rclone_api-1.5.39.dist-info}/RECORD +15 -15
- {rclone_api-1.5.38.dist-info → rclone_api-1.5.39.dist-info}/WHEEL +0 -0
- {rclone_api-1.5.38.dist-info → rclone_api-1.5.39.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.38.dist-info → rclone_api-1.5.39.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.38.dist-info → rclone_api-1.5.39.dist-info}/top_level.txt +0 -0
@@ -1,153 +1,153 @@
|
|
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.detail.walk import walk_runner_depth_first
|
10
|
-
from rclone_api.dir_listing import DirListing
|
11
|
-
from rclone_api.types import ListingOption, Order
|
12
|
-
|
13
|
-
_MAX_OUT_QUEUE_SIZE = 50
|
14
|
-
|
15
|
-
|
16
|
-
def _reorder_inplace(data: list, order: Order) -> None:
|
17
|
-
if order == Order.NORMAL:
|
18
|
-
return
|
19
|
-
elif order == Order.REVERSE:
|
20
|
-
data.reverse()
|
21
|
-
return
|
22
|
-
elif order == Order.RANDOM:
|
23
|
-
random.shuffle(data)
|
24
|
-
return
|
25
|
-
else:
|
26
|
-
raise ValueError(f"Invalid order: {order}")
|
27
|
-
|
28
|
-
|
29
|
-
# ONLY Works from src -> dst diffing.
|
30
|
-
def _async_diff_dir_walk_task(
|
31
|
-
src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], order: Order
|
32
|
-
) -> None:
|
33
|
-
can_scan_two_deep = max_depth > 1 or max_depth == -1
|
34
|
-
ls_depth = 2 if can_scan_two_deep else 1
|
35
|
-
with ThreadPoolExecutor(max_workers=2) as executor:
|
36
|
-
t1 = executor.submit(
|
37
|
-
src.ls,
|
38
|
-
listing_option=ListingOption.DIRS_ONLY,
|
39
|
-
order=order,
|
40
|
-
max_depth=ls_depth,
|
41
|
-
)
|
42
|
-
t2 = executor.submit(
|
43
|
-
dst.ls,
|
44
|
-
listing_option=ListingOption.DIRS_ONLY,
|
45
|
-
order=order,
|
46
|
-
max_depth=ls_depth,
|
47
|
-
)
|
48
|
-
src_dir_listing: DirListing = t1.result()
|
49
|
-
dst_dir_listing: DirListing = t2.result()
|
50
|
-
next_depth = max_depth - ls_depth if max_depth > 0 else max_depth
|
51
|
-
dst_dirs: list[str] = [d.relative_to(src) for d in dst_dir_listing.dirs]
|
52
|
-
src_dirs: list[str] = [d.relative_to(dst) for d in src_dir_listing.dirs]
|
53
|
-
dst_files_set: set[str] = set(dst_dirs)
|
54
|
-
matching_dirs: list[str] = []
|
55
|
-
_reorder_inplace(src_dirs, order)
|
56
|
-
_reorder_inplace(dst_dirs, order)
|
57
|
-
for i, src_dir in enumerate(src_dirs):
|
58
|
-
src_dir_dir = src / src_dir
|
59
|
-
if src_dir not in dst_files_set:
|
60
|
-
queue_dir_listing: Queue[DirListing | None] = Queue()
|
61
|
-
if next_depth > 0 or next_depth == -1:
|
62
|
-
walk_runner_depth_first(
|
63
|
-
dir=src_dir_dir,
|
64
|
-
out_queue=queue_dir_listing,
|
65
|
-
order=order,
|
66
|
-
max_depth=next_depth,
|
67
|
-
)
|
68
|
-
out_queue.put(src)
|
69
|
-
while dirlisting := queue_dir_listing.get():
|
70
|
-
if dirlisting is None:
|
71
|
-
break
|
72
|
-
# print(f"dirlisting: {dirlisting}")
|
73
|
-
for d in dirlisting.dirs:
|
74
|
-
out_queue.put(d)
|
75
|
-
else:
|
76
|
-
matching_dirs.append(src_dir)
|
77
|
-
|
78
|
-
for matching_dir in matching_dirs:
|
79
|
-
# print(f"matching dir: {matching_dir}")
|
80
|
-
if next_depth > 0 or next_depth == -1:
|
81
|
-
src_next = src / matching_dir
|
82
|
-
dst_next = dst / matching_dir
|
83
|
-
_async_diff_dir_walk_task(
|
84
|
-
src=src_next,
|
85
|
-
dst=dst_next,
|
86
|
-
max_depth=next_depth,
|
87
|
-
out_queue=out_queue,
|
88
|
-
order=order,
|
89
|
-
)
|
90
|
-
|
91
|
-
|
92
|
-
def async_diff_dir_walk_task(
|
93
|
-
src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], order: Order
|
94
|
-
) -> None:
|
95
|
-
try:
|
96
|
-
_async_diff_dir_walk_task(
|
97
|
-
src=src, dst=dst, max_depth=max_depth, out_queue=out_queue, order=order
|
98
|
-
)
|
99
|
-
except Exception:
|
100
|
-
import _thread
|
101
|
-
|
102
|
-
_thread.interrupt_main()
|
103
|
-
raise
|
104
|
-
finally:
|
105
|
-
out_queue.put(None)
|
106
|
-
|
107
|
-
|
108
|
-
def scan_missing_folders(
|
109
|
-
src: Dir,
|
110
|
-
dst: Dir,
|
111
|
-
max_depth: int = -1,
|
112
|
-
order: Order = Order.NORMAL,
|
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(
|
129
|
-
src=src,
|
130
|
-
dst=dst,
|
131
|
-
max_depth=max_depth,
|
132
|
-
out_queue=out_queue,
|
133
|
-
order=order,
|
134
|
-
)
|
135
|
-
|
136
|
-
worker = Thread(
|
137
|
-
target=task,
|
138
|
-
daemon=True,
|
139
|
-
)
|
140
|
-
worker.start()
|
141
|
-
|
142
|
-
while True:
|
143
|
-
try:
|
144
|
-
dir = out_queue.get_nowait()
|
145
|
-
if dir is None:
|
146
|
-
break
|
147
|
-
yield dir
|
148
|
-
except Empty:
|
149
|
-
time.sleep(0.1)
|
150
|
-
|
151
|
-
worker.join()
|
152
|
-
except KeyboardInterrupt:
|
153
|
-
pass
|
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.detail.walk import walk_runner_depth_first
|
10
|
+
from rclone_api.dir_listing import DirListing
|
11
|
+
from rclone_api.types import ListingOption, Order
|
12
|
+
|
13
|
+
_MAX_OUT_QUEUE_SIZE = 50
|
14
|
+
|
15
|
+
|
16
|
+
def _reorder_inplace(data: list, order: Order) -> None:
|
17
|
+
if order == Order.NORMAL:
|
18
|
+
return
|
19
|
+
elif order == Order.REVERSE:
|
20
|
+
data.reverse()
|
21
|
+
return
|
22
|
+
elif order == Order.RANDOM:
|
23
|
+
random.shuffle(data)
|
24
|
+
return
|
25
|
+
else:
|
26
|
+
raise ValueError(f"Invalid order: {order}")
|
27
|
+
|
28
|
+
|
29
|
+
# ONLY Works from src -> dst diffing.
|
30
|
+
def _async_diff_dir_walk_task(
|
31
|
+
src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], order: Order
|
32
|
+
) -> None:
|
33
|
+
can_scan_two_deep = max_depth > 1 or max_depth == -1
|
34
|
+
ls_depth = 2 if can_scan_two_deep else 1
|
35
|
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
36
|
+
t1 = executor.submit(
|
37
|
+
src.ls,
|
38
|
+
listing_option=ListingOption.DIRS_ONLY,
|
39
|
+
order=order,
|
40
|
+
max_depth=ls_depth,
|
41
|
+
)
|
42
|
+
t2 = executor.submit(
|
43
|
+
dst.ls,
|
44
|
+
listing_option=ListingOption.DIRS_ONLY,
|
45
|
+
order=order,
|
46
|
+
max_depth=ls_depth,
|
47
|
+
)
|
48
|
+
src_dir_listing: DirListing = t1.result()
|
49
|
+
dst_dir_listing: DirListing = t2.result()
|
50
|
+
next_depth = max_depth - ls_depth if max_depth > 0 else max_depth
|
51
|
+
dst_dirs: list[str] = [d.relative_to(src) for d in dst_dir_listing.dirs]
|
52
|
+
src_dirs: list[str] = [d.relative_to(dst) for d in src_dir_listing.dirs]
|
53
|
+
dst_files_set: set[str] = set(dst_dirs)
|
54
|
+
matching_dirs: list[str] = []
|
55
|
+
_reorder_inplace(src_dirs, order)
|
56
|
+
_reorder_inplace(dst_dirs, order)
|
57
|
+
for i, src_dir in enumerate(src_dirs):
|
58
|
+
src_dir_dir = src / src_dir
|
59
|
+
if src_dir not in dst_files_set:
|
60
|
+
queue_dir_listing: Queue[DirListing | None] = Queue()
|
61
|
+
if next_depth > 0 or next_depth == -1:
|
62
|
+
walk_runner_depth_first(
|
63
|
+
dir=src_dir_dir,
|
64
|
+
out_queue=queue_dir_listing,
|
65
|
+
order=order,
|
66
|
+
max_depth=next_depth,
|
67
|
+
)
|
68
|
+
out_queue.put(src)
|
69
|
+
while dirlisting := queue_dir_listing.get():
|
70
|
+
if dirlisting is None:
|
71
|
+
break
|
72
|
+
# print(f"dirlisting: {dirlisting}")
|
73
|
+
for d in dirlisting.dirs:
|
74
|
+
out_queue.put(d)
|
75
|
+
else:
|
76
|
+
matching_dirs.append(src_dir)
|
77
|
+
|
78
|
+
for matching_dir in matching_dirs:
|
79
|
+
# print(f"matching dir: {matching_dir}")
|
80
|
+
if next_depth > 0 or next_depth == -1:
|
81
|
+
src_next = src / matching_dir
|
82
|
+
dst_next = dst / matching_dir
|
83
|
+
_async_diff_dir_walk_task(
|
84
|
+
src=src_next,
|
85
|
+
dst=dst_next,
|
86
|
+
max_depth=next_depth,
|
87
|
+
out_queue=out_queue,
|
88
|
+
order=order,
|
89
|
+
)
|
90
|
+
|
91
|
+
|
92
|
+
def async_diff_dir_walk_task(
|
93
|
+
src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], order: Order
|
94
|
+
) -> None:
|
95
|
+
try:
|
96
|
+
_async_diff_dir_walk_task(
|
97
|
+
src=src, dst=dst, max_depth=max_depth, out_queue=out_queue, order=order
|
98
|
+
)
|
99
|
+
except Exception:
|
100
|
+
import _thread
|
101
|
+
|
102
|
+
_thread.interrupt_main()
|
103
|
+
raise
|
104
|
+
finally:
|
105
|
+
out_queue.put(None)
|
106
|
+
|
107
|
+
|
108
|
+
def scan_missing_folders(
|
109
|
+
src: Dir,
|
110
|
+
dst: Dir,
|
111
|
+
max_depth: int = -1,
|
112
|
+
order: Order = Order.NORMAL,
|
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(
|
129
|
+
src=src,
|
130
|
+
dst=dst,
|
131
|
+
max_depth=max_depth,
|
132
|
+
out_queue=out_queue,
|
133
|
+
order=order,
|
134
|
+
)
|
135
|
+
|
136
|
+
worker = Thread(
|
137
|
+
target=task,
|
138
|
+
daemon=True,
|
139
|
+
)
|
140
|
+
worker.start()
|
141
|
+
|
142
|
+
while True:
|
143
|
+
try:
|
144
|
+
dir = out_queue.get_nowait()
|
145
|
+
if dir is None:
|
146
|
+
break
|
147
|
+
yield dir
|
148
|
+
except Empty:
|
149
|
+
time.sleep(0.1)
|
150
|
+
|
151
|
+
worker.join()
|
152
|
+
except KeyboardInterrupt:
|
153
|
+
pass
|