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 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(self, max_depth: int = -1) -> Generator[DirListing, None, None]:
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
- cmd.append("--recursive")
65
- if max_depth > -1:
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, src: Remote | Dir | str, outdir: Path, allow_writes=False, use_links=True
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 _walk_runner(
13
- queue: Queue[Dir], max_depth: int, out_queue: Queue[DirListing | None]
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 walk(dir: Dir | Remote, max_depth: int = -1) -> Generator[DirListing, None, None]:
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
- in_queue.put(dir)
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=_walk_runner, args=(in_queue, max_depth, out_queue), daemon=True
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.17
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=xUV4i9_E7QHrqHld2IPBlon1CGaAp8qQYMKfJTyVvoQ,2088
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=QLI-65R4Jvov9oOJSVY5UpDOS2xj6m-9YCg8upR4-0M,3560
11
- rclone_api/rclone.py,sha256=hvzo5zZmP5IQAtuwLcsTeeazDY0slqz76DdulV2mn3s,9640
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=J78-bY2AhNpt2ICsI5LqmXRE7oC6wVDoKoicIoU6XMg,1953
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.17.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
18
- rclone_api-1.0.17.dist-info/METADATA,sha256=9_ahew4G4YXDjXQljGkoA8s1KZa76mPrv3TZ99c2Xo4,4421
19
- rclone_api-1.0.17.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
20
- rclone_api-1.0.17.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
21
- rclone_api-1.0.17.dist-info/RECORD,,
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,,