rclone-api 1.0.17__py2.py3-none-any.whl → 1.0.19__py2.py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- rclone_api/dir.py +4 -2
- rclone_api/process.py +3 -1
- rclone_api/rclone.py +97 -5
- rclone_api/walk.py +40 -7
- {rclone_api-1.0.17.dist-info → rclone_api-1.0.19.dist-info}/METADATA +2 -1
- {rclone_api-1.0.17.dist-info → rclone_api-1.0.19.dist-info}/RECORD +9 -9
- {rclone_api-1.0.17.dist-info → rclone_api-1.0.19.dist-info}/LICENSE +0 -0
- {rclone_api-1.0.17.dist-info → rclone_api-1.0.19.dist-info}/WHEEL +0 -0
- {rclone_api-1.0.17.dist-info → rclone_api-1.0.19.dist-info}/top_level.txt +0 -0
rclone_api/dir.py
CHANGED
@@ -47,12 +47,14 @@ class Dir:
|
|
47
47
|
dir = Dir(self.path)
|
48
48
|
return self.path.rclone.ls(dir, max_depth=max_depth)
|
49
49
|
|
50
|
-
def walk(
|
50
|
+
def walk(
|
51
|
+
self, breadth_first: bool, max_depth: int = -1
|
52
|
+
) -> Generator[DirListing, None, None]:
|
51
53
|
"""List files and directories in the given path."""
|
52
54
|
from rclone_api.walk import walk
|
53
55
|
|
54
56
|
assert self.path.rclone is not None
|
55
|
-
return walk(self, max_depth=max_depth)
|
57
|
+
return walk(self, breadth_first=breadth_first, max_depth=max_depth)
|
56
58
|
|
57
59
|
def to_json(self) -> dict:
|
58
60
|
"""Convert the Dir to a JSON serializable dictionary."""
|
rclone_api/process.py
CHANGED
@@ -69,8 +69,10 @@ class Process:
|
|
69
69
|
tmpfile = Path(self.tempdir.name) / "rclone.conf"
|
70
70
|
tmpfile.write_text(args.rclone_conf.text, encoding="utf-8")
|
71
71
|
rclone_conf = tmpfile
|
72
|
+
self.needs_cleanup = True
|
72
73
|
else:
|
73
74
|
rclone_conf = args.rclone_conf
|
75
|
+
self.needs_cleanup = False
|
74
76
|
|
75
77
|
assert rclone_conf.exists()
|
76
78
|
|
@@ -85,7 +87,7 @@ class Process:
|
|
85
87
|
self.process = subprocess.Popen(self.cmd, shell=False)
|
86
88
|
|
87
89
|
def cleanup(self) -> None:
|
88
|
-
if self.tempdir:
|
90
|
+
if self.tempdir and self.needs_cleanup:
|
89
91
|
try:
|
90
92
|
self.tempdir.cleanup()
|
91
93
|
except Exception as e:
|
rclone_api/rclone.py
CHANGED
@@ -38,6 +38,12 @@ class Rclone:
|
|
38
38
|
def _launch_process(self, cmd: list[str]) -> Process:
|
39
39
|
return self._exec.launch_process(cmd)
|
40
40
|
|
41
|
+
def obscure(self, password: str) -> str:
|
42
|
+
"""Obscure a password for use in rclone config files."""
|
43
|
+
cmd_list: list[str] = ["obscure", password]
|
44
|
+
cp = self._run(cmd_list)
|
45
|
+
return cp.stdout.strip()
|
46
|
+
|
41
47
|
def ls(
|
42
48
|
self,
|
43
49
|
path: Dir | Remote | str,
|
@@ -61,8 +67,9 @@ class Rclone:
|
|
61
67
|
|
62
68
|
cmd = ["lsjson"]
|
63
69
|
if max_depth is not None:
|
64
|
-
|
65
|
-
|
70
|
+
if max_depth < 0:
|
71
|
+
cmd.append("--recursive")
|
72
|
+
if max_depth > 0:
|
66
73
|
cmd.append("--max-depth")
|
67
74
|
cmd.append(str(max_depth))
|
68
75
|
cmd.append(str(path))
|
@@ -96,7 +103,7 @@ class Rclone:
|
|
96
103
|
return out
|
97
104
|
|
98
105
|
def walk(
|
99
|
-
self, path: Dir | Remote | str, max_depth: int = -1
|
106
|
+
self, path: Dir | Remote | str, max_depth: int = -1, breadth_first: bool = True
|
100
107
|
) -> Generator[DirListing, None, None]:
|
101
108
|
"""Walk through the given path recursively.
|
102
109
|
|
@@ -107,6 +114,7 @@ class Rclone:
|
|
107
114
|
Yields:
|
108
115
|
DirListing: Directory listing for each directory encountered
|
109
116
|
"""
|
117
|
+
dir_obj: Dir
|
110
118
|
if isinstance(path, Dir):
|
111
119
|
# Create a Remote object for the path
|
112
120
|
remote = path.remote
|
@@ -126,9 +134,10 @@ class Rclone:
|
|
126
134
|
elif isinstance(path, Remote):
|
127
135
|
dir_obj = Dir(path)
|
128
136
|
else:
|
137
|
+
dir_obj = Dir(path) # shut up pyright
|
129
138
|
assert f"Invalid type for path: {type(path)}"
|
130
139
|
|
131
|
-
yield from walk(dir_obj, max_depth=max_depth)
|
140
|
+
yield from walk(dir_obj, max_depth=max_depth, breadth_first=breadth_first)
|
132
141
|
|
133
142
|
def copyfile(self, src: File | str, dst: File | str) -> None:
|
134
143
|
"""Copy a single file from source to destination.
|
@@ -239,7 +248,13 @@ class Rclone:
|
|
239
248
|
return self._run(cmd_list)
|
240
249
|
|
241
250
|
def mount(
|
242
|
-
self,
|
251
|
+
self,
|
252
|
+
src: Remote | Dir | str,
|
253
|
+
outdir: Path,
|
254
|
+
allow_writes=False,
|
255
|
+
use_links=True,
|
256
|
+
vfs_cache_mode="full",
|
257
|
+
other_cmds: list[str] | None = None,
|
243
258
|
) -> Process:
|
244
259
|
"""Mount a remote or directory to a local path.
|
245
260
|
|
@@ -266,8 +281,85 @@ class Rclone:
|
|
266
281
|
cmd_list.append("--read-only")
|
267
282
|
if use_links:
|
268
283
|
cmd_list.append("--links")
|
284
|
+
if vfs_cache_mode:
|
285
|
+
cmd_list.append("--vfs-cache-mode")
|
286
|
+
cmd_list.append(vfs_cache_mode)
|
287
|
+
if other_cmds:
|
288
|
+
cmd_list += other_cmds
|
269
289
|
proc = self._launch_process(cmd_list)
|
270
290
|
time.sleep(2) # give it a moment to mount
|
271
291
|
if proc.poll() is not None:
|
272
292
|
raise ValueError("Mount process failed to start")
|
273
293
|
return proc
|
294
|
+
|
295
|
+
def mount_webdav(
|
296
|
+
self,
|
297
|
+
url: str,
|
298
|
+
outdir: Path,
|
299
|
+
vfs_cache_mode="full",
|
300
|
+
other_cmds: list[str] | None = None,
|
301
|
+
) -> Process:
|
302
|
+
"""Mount a remote or directory to a local path.
|
303
|
+
|
304
|
+
Args:
|
305
|
+
src: Remote or directory to mount
|
306
|
+
outdir: Local path to mount to
|
307
|
+
|
308
|
+
Returns:
|
309
|
+
CompletedProcess from the mount command execution
|
310
|
+
|
311
|
+
Raises:
|
312
|
+
subprocess.CalledProcessError: If the mount operation fails
|
313
|
+
"""
|
314
|
+
if outdir.exists():
|
315
|
+
is_empty = not list(outdir.iterdir())
|
316
|
+
if not is_empty:
|
317
|
+
raise ValueError(
|
318
|
+
f"Mount directory already exists and is not empty: {outdir}"
|
319
|
+
)
|
320
|
+
outdir.rmdir()
|
321
|
+
|
322
|
+
src_str = url
|
323
|
+
cmd_list: list[str] = ["mount", src_str, str(outdir)]
|
324
|
+
cmd_list.append("--vfs-cache-mode")
|
325
|
+
cmd_list.append(vfs_cache_mode)
|
326
|
+
if other_cmds:
|
327
|
+
cmd_list += other_cmds
|
328
|
+
proc = self._launch_process(cmd_list)
|
329
|
+
# proc = rclone_exec.launch_process(cmd_list)
|
330
|
+
time.sleep(2)
|
331
|
+
if proc.poll() is not None:
|
332
|
+
raise ValueError("Mount process failed to start")
|
333
|
+
return proc
|
334
|
+
|
335
|
+
def serve_webdav(
|
336
|
+
self,
|
337
|
+
src: Remote | Dir | str,
|
338
|
+
user: str,
|
339
|
+
password: str,
|
340
|
+
addr: str = "localhost:2049",
|
341
|
+
allow_other: bool = False,
|
342
|
+
) -> Process:
|
343
|
+
"""Serve a remote or directory via NFS.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
src: Remote or directory to serve
|
347
|
+
addr: Network address and port to serve on (default: localhost:2049)
|
348
|
+
allow_other: Allow other users to access the share
|
349
|
+
|
350
|
+
Returns:
|
351
|
+
Process: The running NFS server process
|
352
|
+
|
353
|
+
Raises:
|
354
|
+
ValueError: If the NFS server fails to start
|
355
|
+
"""
|
356
|
+
src_str = convert_to_str(src)
|
357
|
+
cmd_list: list[str] = ["serve", "webdav", "--addr", addr, src_str]
|
358
|
+
cmd_list.extend(["--user", user, "--pass", password])
|
359
|
+
if allow_other:
|
360
|
+
cmd_list.append("--allow-other")
|
361
|
+
proc = self._launch_process(cmd_list)
|
362
|
+
time.sleep(2) # give it a moment to start
|
363
|
+
if proc.poll() is not None:
|
364
|
+
raise ValueError("NFS serve process failed to start")
|
365
|
+
return proc
|
rclone_api/walk.py
CHANGED
@@ -9,9 +9,11 @@ from rclone_api.remote import Remote
|
|
9
9
|
_MAX_OUT_QUEUE_SIZE = 50
|
10
10
|
|
11
11
|
|
12
|
-
def
|
13
|
-
|
12
|
+
def _walk_runner_breadth_first(
|
13
|
+
dir: Dir, max_depth: int, out_queue: Queue[DirListing | None]
|
14
14
|
) -> None:
|
15
|
+
queue: Queue[Dir] = Queue()
|
16
|
+
queue.put(dir)
|
15
17
|
try:
|
16
18
|
while not queue.empty():
|
17
19
|
current_dir = queue.get()
|
@@ -35,7 +37,35 @@ def _walk_runner(
|
|
35
37
|
_thread.interrupt_main()
|
36
38
|
|
37
39
|
|
38
|
-
def
|
40
|
+
def _walk_runner_depth_first(
|
41
|
+
dir: Dir, max_depth: int, out_queue: Queue[DirListing | None]
|
42
|
+
) -> None:
|
43
|
+
try:
|
44
|
+
stack = [(dir, max_depth)]
|
45
|
+
while stack:
|
46
|
+
current_dir, depth = stack.pop()
|
47
|
+
dirlisting = current_dir.ls()
|
48
|
+
if depth != 0:
|
49
|
+
for subdir in reversed(
|
50
|
+
dirlisting.dirs
|
51
|
+
): # Process deeper directories first
|
52
|
+
# stack.append((child, depth - 1 if depth > 0 else depth))
|
53
|
+
next_depth = depth - 1 if depth > 0 else depth
|
54
|
+
_walk_runner_depth_first(subdir, next_depth, out_queue)
|
55
|
+
out_queue.put(dirlisting)
|
56
|
+
out_queue.put(None)
|
57
|
+
except KeyboardInterrupt:
|
58
|
+
import _thread
|
59
|
+
|
60
|
+
out_queue.put(None)
|
61
|
+
_thread.interrupt_main()
|
62
|
+
|
63
|
+
|
64
|
+
def walk(
|
65
|
+
dir: Dir | Remote,
|
66
|
+
breadth_first: bool,
|
67
|
+
max_depth: int = -1,
|
68
|
+
) -> Generator[DirListing, None, None]:
|
39
69
|
"""Walk through the given directory recursively.
|
40
70
|
|
41
71
|
Args:
|
@@ -49,14 +79,17 @@ def walk(dir: Dir | Remote, max_depth: int = -1) -> Generator[DirListing, None,
|
|
49
79
|
# Convert Remote to Dir if needed
|
50
80
|
if isinstance(dir, Remote):
|
51
81
|
dir = Dir(dir)
|
52
|
-
|
53
|
-
in_queue: Queue[Dir] = Queue()
|
54
82
|
out_queue: Queue[DirListing] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
|
55
|
-
|
83
|
+
|
84
|
+
strategy = (
|
85
|
+
_walk_runner_breadth_first if breadth_first else _walk_runner_depth_first
|
86
|
+
)
|
56
87
|
|
57
88
|
# Start worker thread
|
58
89
|
worker = Thread(
|
59
|
-
target=
|
90
|
+
target=strategy,
|
91
|
+
args=(dir, max_depth, out_queue),
|
92
|
+
daemon=True,
|
60
93
|
)
|
61
94
|
worker.start()
|
62
95
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: rclone_api
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.19
|
4
4
|
Summary: rclone api in python
|
5
5
|
Home-page: https://github.com/zackees/rclone-api
|
6
6
|
Maintainer: Zachary Vorhies
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Requires-Python: >=3.10
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
+
Requires-Dist: pyright>=1.1.393
|
13
14
|
Requires-Dist: python-dotenv>=1.0.0
|
14
15
|
Dynamic: home-page
|
15
16
|
Dynamic: maintainer
|
@@ -2,20 +2,20 @@ rclone_api/__init__.py,sha256=UWQMbhE4WOQ3Skfb0LagFKW8PUKUGOm9_T3z--5FHiY,388
|
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
3
|
rclone_api/config.py,sha256=tP6cU9DnCCEIRc_KP9HPur1jFLLg2QGFSxNwFm6_MVw,118
|
4
4
|
rclone_api/convert.py,sha256=Mx9Qo7zhkOedJd8LdhPvNGHp8znJzOk4f_2KWnoGc78,1012
|
5
|
-
rclone_api/dir.py,sha256=
|
5
|
+
rclone_api/dir.py,sha256=vV-bcI2ESijmwF5rPID5WO2K7soAfZa35wv4KRh_GIo,2154
|
6
6
|
rclone_api/dir_listing.py,sha256=8t5Jx9ZVOJPqGKTJbWaES6bjgogUT2bnpbPVWwK1Fcs,1124
|
7
7
|
rclone_api/exec.py,sha256=9qSOpZo8YRYxv3hOvNr57ApnY2KbjxwT1QNr8OgcLM4,883
|
8
8
|
rclone_api/file.py,sha256=D02iHJW1LhfOiM_R_yPHP8_ApnDiYrkuraVcrV8-qkw,1246
|
9
9
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
10
|
-
rclone_api/process.py,sha256=
|
11
|
-
rclone_api/rclone.py,sha256=
|
10
|
+
rclone_api/process.py,sha256=C7vdGEvIKcI4fx-Z702wK1qomzSsFpdke2SQnBHxd-Y,3660
|
11
|
+
rclone_api/rclone.py,sha256=PTHimJPPqRe-iSqe08zv1FrFB_H2T1eD3Y0WgshEye8,12730
|
12
12
|
rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
|
13
13
|
rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
|
14
14
|
rclone_api/util.py,sha256=BDRJ2MIceDoKVTjUQBwyhjbA8UwPrZ-0ZSa9xyMJd0E,3343
|
15
|
-
rclone_api/walk.py,sha256=
|
15
|
+
rclone_api/walk.py,sha256=kca0t1GAnF6FLclN01G8NG__Qe-ggodLtAbQSHyVPng,2968
|
16
16
|
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
17
|
-
rclone_api-1.0.
|
18
|
-
rclone_api-1.0.
|
19
|
-
rclone_api-1.0.
|
20
|
-
rclone_api-1.0.
|
21
|
-
rclone_api-1.0.
|
17
|
+
rclone_api-1.0.19.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
18
|
+
rclone_api-1.0.19.dist-info/METADATA,sha256=MFSLZe61zLT-ZKKoPb9UiN-r0A-ZbFzQvmo8S-FUnuc,4454
|
19
|
+
rclone_api-1.0.19.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
|
20
|
+
rclone_api-1.0.19.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
21
|
+
rclone_api-1.0.19.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|