rclone-api 1.0.17__py2.py3-none-any.whl → 1.0.19__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/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
|