rclone-api 1.5.6__py2.py3-none-any.whl → 1.5.7__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/mount.py +12 -242
- rclone_api/mount_util.py +245 -0
- rclone_api/rclone_impl.py +2 -1
- {rclone_api-1.5.6.dist-info → rclone_api-1.5.7.dist-info}/METADATA +1 -1
- {rclone_api-1.5.6.dist-info → rclone_api-1.5.7.dist-info}/RECORD +9 -8
- {rclone_api-1.5.6.dist-info → rclone_api-1.5.7.dist-info}/LICENSE +0 -0
- {rclone_api-1.5.6.dist-info → rclone_api-1.5.7.dist-info}/WHEEL +0 -0
- {rclone_api-1.5.6.dist-info → rclone_api-1.5.7.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.6.dist-info → rclone_api-1.5.7.dist-info}/top_level.txt +0 -0
rclone_api/mount.py
CHANGED
@@ -1,49 +1,8 @@
|
|
1
|
-
import atexit
|
2
|
-
import os
|
3
|
-
import platform
|
4
|
-
import shutil
|
5
|
-
import subprocess
|
6
|
-
import time
|
7
|
-
import warnings
|
8
|
-
import weakref
|
9
|
-
from concurrent.futures import ThreadPoolExecutor
|
10
1
|
from dataclasses import dataclass
|
11
2
|
from pathlib import Path
|
12
|
-
from typing import Any
|
13
3
|
|
14
4
|
from rclone_api.process import Process
|
15
5
|
|
16
|
-
_SYSTEM = platform.system() # "Linux", "Darwin", "Windows", etc.
|
17
|
-
|
18
|
-
_MOUNTS_FOR_GC: weakref.WeakSet = weakref.WeakSet()
|
19
|
-
|
20
|
-
|
21
|
-
def _add_mount_for_gc(mount: "Mount") -> None:
|
22
|
-
# weak reference to avoid circular references
|
23
|
-
_MOUNTS_FOR_GC.add(mount)
|
24
|
-
|
25
|
-
|
26
|
-
def _remove_mount_for_gc(mount: "Mount") -> None:
|
27
|
-
_MOUNTS_FOR_GC.discard(mount)
|
28
|
-
|
29
|
-
|
30
|
-
def _cleanup_mounts() -> None:
|
31
|
-
with ThreadPoolExecutor() as executor:
|
32
|
-
mount: Mount
|
33
|
-
for mount in _MOUNTS_FOR_GC:
|
34
|
-
executor.submit(mount.close)
|
35
|
-
|
36
|
-
|
37
|
-
def _cache_dir_delete_on_exit(cache_dir: Path) -> None:
|
38
|
-
if cache_dir.exists():
|
39
|
-
try:
|
40
|
-
shutil.rmtree(cache_dir)
|
41
|
-
except Exception as e:
|
42
|
-
warnings.warn(f"Error removing cache directory {cache_dir}: {e}")
|
43
|
-
|
44
|
-
|
45
|
-
atexit.register(_cleanup_mounts)
|
46
|
-
|
47
6
|
|
48
7
|
@dataclass
|
49
8
|
class Mount:
|
@@ -58,21 +17,29 @@ class Mount:
|
|
58
17
|
_closed: bool = False
|
59
18
|
|
60
19
|
def __post_init__(self):
|
20
|
+
from rclone_api.mount_util import add_mount_for_gc, wait_for_mount
|
21
|
+
|
61
22
|
assert isinstance(self.mount_path, Path)
|
62
23
|
assert self.process is not None
|
63
|
-
wait_for_mount(self
|
64
|
-
|
24
|
+
wait_for_mount(self)
|
25
|
+
add_mount_for_gc(self)
|
65
26
|
|
66
27
|
def close(self, wait=True) -> None:
|
67
28
|
"""Clean up the mount."""
|
29
|
+
from rclone_api.mount_util import (
|
30
|
+
cache_dir_delete_on_exit,
|
31
|
+
clean_mount,
|
32
|
+
remove_mount_for_gc,
|
33
|
+
)
|
34
|
+
|
68
35
|
if self._closed:
|
69
36
|
return
|
70
37
|
self._closed = True
|
71
38
|
self.process.terminate()
|
72
39
|
clean_mount(self, verbose=False, wait=wait)
|
73
40
|
if self.cache_dir and self.cache_dir_delete_on_exit:
|
74
|
-
|
75
|
-
|
41
|
+
cache_dir_delete_on_exit(self.cache_dir)
|
42
|
+
remove_mount_for_gc(self)
|
76
43
|
|
77
44
|
def __enter__(self) -> "Mount":
|
78
45
|
return self
|
@@ -86,200 +53,3 @@ class Mount:
|
|
86
53
|
# make this a hashable object
|
87
54
|
def __hash__(self):
|
88
55
|
return hash(self.mount_path)
|
89
|
-
|
90
|
-
|
91
|
-
def run_command(cmd: str, verbose: bool) -> int:
|
92
|
-
"""Run a shell command and print its output if verbose is True."""
|
93
|
-
if verbose:
|
94
|
-
print(f"Executing: {cmd}")
|
95
|
-
try:
|
96
|
-
result = subprocess.run(
|
97
|
-
cmd, shell=True, capture_output=True, text=True, check=False
|
98
|
-
)
|
99
|
-
if result.returncode != 0 and verbose:
|
100
|
-
print(f"Command failed: {cmd}\nStdErr: {result.stderr.strip()}")
|
101
|
-
return result.returncode
|
102
|
-
except Exception as e:
|
103
|
-
warnings.warn(f"Error running command '{cmd}': {e}")
|
104
|
-
return -1
|
105
|
-
|
106
|
-
|
107
|
-
def prepare_mount(outdir: Path, verbose: bool) -> None:
|
108
|
-
if _SYSTEM == "Windows":
|
109
|
-
# Windows -> Must create parent directories only if they don't exist
|
110
|
-
if verbose:
|
111
|
-
print(f"Creating parent directories for {outdir}")
|
112
|
-
outdir.parent.mkdir(parents=True, exist_ok=True)
|
113
|
-
else:
|
114
|
-
# Linux -> Must create parent directories and the directory itself
|
115
|
-
if verbose:
|
116
|
-
print(f"Creating directories for {outdir}")
|
117
|
-
outdir.mkdir(parents=True, exist_ok=True)
|
118
|
-
|
119
|
-
|
120
|
-
def wait_for_mount(
|
121
|
-
path: Path,
|
122
|
-
mount_process: Any,
|
123
|
-
timeout: int = 20,
|
124
|
-
post_mount_delay: int = 5,
|
125
|
-
poll_interval: float = 1.0,
|
126
|
-
check_mount_flag: bool = False,
|
127
|
-
) -> None:
|
128
|
-
"""
|
129
|
-
Wait for a mount point to become available by checking if the directory exists,
|
130
|
-
optionally verifying that it is a mount point, and confirming that it contains files.
|
131
|
-
This function periodically polls for the mount status, ensures the mount process
|
132
|
-
is still running, and applies an extra delay after detecting content for stabilization.
|
133
|
-
|
134
|
-
Args:
|
135
|
-
path (Path): The mount point directory to check.
|
136
|
-
mount_process (Any): A Process instance handling the mount (must be an instance of Process).
|
137
|
-
timeout (int): Maximum time in seconds to wait for the mount to become available.
|
138
|
-
post_mount_delay (int): Additional seconds to wait after detecting files.
|
139
|
-
poll_interval (float): Seconds between each poll iteration.
|
140
|
-
check_mount_flag (bool): If True, verifies that the path is recognized as a mount point.
|
141
|
-
|
142
|
-
Raises:
|
143
|
-
subprocess.CalledProcessError: If the mount_process exits unexpectedly.
|
144
|
-
TimeoutError: If the mount is not available within the timeout period.
|
145
|
-
TypeError: If mount_process is not an instance of Process.
|
146
|
-
"""
|
147
|
-
|
148
|
-
if not isinstance(mount_process, Process):
|
149
|
-
raise TypeError("mount_process must be an instance of Process")
|
150
|
-
|
151
|
-
expire_time = time.time() + timeout
|
152
|
-
last_error = None
|
153
|
-
|
154
|
-
while time.time() < expire_time:
|
155
|
-
# Check if the mount process has terminated unexpectedly.
|
156
|
-
rtn = mount_process.poll()
|
157
|
-
if rtn is not None:
|
158
|
-
cmd_str = subprocess.list2cmdline(mount_process.cmd)
|
159
|
-
print(f"Mount process terminated unexpectedly: {cmd_str}")
|
160
|
-
raise subprocess.CalledProcessError(rtn, cmd_str)
|
161
|
-
|
162
|
-
# Check if the mount path exists.
|
163
|
-
if path.exists():
|
164
|
-
# Optionally check if path is a mount point.
|
165
|
-
if check_mount_flag:
|
166
|
-
try:
|
167
|
-
if not os.path.ismount(str(path)):
|
168
|
-
print(
|
169
|
-
f"{path} exists but is not recognized as a mount point yet."
|
170
|
-
)
|
171
|
-
time.sleep(poll_interval)
|
172
|
-
continue
|
173
|
-
except Exception as e:
|
174
|
-
print(f"Could not verify mount point status for {path}: {e}")
|
175
|
-
|
176
|
-
try:
|
177
|
-
# Check for at least one entry in the directory.
|
178
|
-
if any(path.iterdir()):
|
179
|
-
print(
|
180
|
-
f"Mount point {path} appears available with files. Waiting {post_mount_delay} seconds for stabilization."
|
181
|
-
)
|
182
|
-
time.sleep(post_mount_delay)
|
183
|
-
return
|
184
|
-
else:
|
185
|
-
print(f"Mount point {path} is empty. Waiting for files to appear.")
|
186
|
-
except Exception as e:
|
187
|
-
last_error = e
|
188
|
-
print(f"Error accessing {path}: {e}")
|
189
|
-
else:
|
190
|
-
print(f"Mount point {path} does not exist yet.")
|
191
|
-
|
192
|
-
time.sleep(poll_interval)
|
193
|
-
|
194
|
-
raise TimeoutError(
|
195
|
-
f"Mount point {path} did not become available within {timeout} seconds. Last error: {last_error}"
|
196
|
-
)
|
197
|
-
|
198
|
-
|
199
|
-
def clean_mount(mount: Mount | Path, verbose: bool = False, wait=True) -> None:
|
200
|
-
"""
|
201
|
-
Clean up a mount path across Linux, macOS, and Windows.
|
202
|
-
|
203
|
-
The function attempts to unmount the mount at mount_path, then, if the
|
204
|
-
directory is empty, removes it. On Linux it uses 'fusermount -u' (for FUSE mounts)
|
205
|
-
and 'umount'. On macOS it uses 'umount' (and optionally 'diskutil unmount'),
|
206
|
-
while on Windows it attempts to remove the mount point via 'mountvol /D'.
|
207
|
-
"""
|
208
|
-
proc = mount.process if isinstance(mount, Mount) else None
|
209
|
-
if proc is not None and proc.poll() is None:
|
210
|
-
if verbose:
|
211
|
-
print(f"Terminating mount process {proc.pid}")
|
212
|
-
proc.kill()
|
213
|
-
|
214
|
-
# Check if the mount path exists; if an OSError occurs, assume it exists.
|
215
|
-
mount_path = mount.mount_path if isinstance(mount, Mount) else mount
|
216
|
-
try:
|
217
|
-
mount_exists = mount_path.exists()
|
218
|
-
except OSError:
|
219
|
-
# warnings.warn(f"Error checking {mount_path}: {e}")
|
220
|
-
mount_exists = True
|
221
|
-
|
222
|
-
# Give the system a moment (if unmount is in progress, etc.)
|
223
|
-
if wait:
|
224
|
-
time.sleep(2)
|
225
|
-
|
226
|
-
if not mount_exists:
|
227
|
-
if verbose:
|
228
|
-
print(f"{mount_path} does not exist; nothing to clean up.")
|
229
|
-
return
|
230
|
-
|
231
|
-
if verbose:
|
232
|
-
print(f"{mount_path} still exists, attempting to unmount and remove.")
|
233
|
-
|
234
|
-
# Platform-specific unmount procedures
|
235
|
-
if _SYSTEM == "Linux":
|
236
|
-
# Try FUSE unmount first (if applicable), then the regular umount.
|
237
|
-
run_command(f"fusermount -u {mount_path}", verbose)
|
238
|
-
run_command(f"umount {mount_path}", verbose)
|
239
|
-
elif _SYSTEM == "Darwin":
|
240
|
-
# On macOS, use umount; optionally try diskutil for stubborn mounts.
|
241
|
-
run_command(f"umount {mount_path}", verbose)
|
242
|
-
# Optionally: uncomment the next line if diskutil unmount is preferred.
|
243
|
-
# run_command(f"diskutil unmount {mount_path}", verbose)
|
244
|
-
elif _SYSTEM == "Windows":
|
245
|
-
# On Windows, remove the mount point using mountvol.
|
246
|
-
run_command(f"mountvol {mount_path} /D", verbose)
|
247
|
-
# If that does not work, try to remove the directory directly.
|
248
|
-
try:
|
249
|
-
mount_path.rmdir()
|
250
|
-
if verbose:
|
251
|
-
print(f"Successfully removed mount directory {mount_path}")
|
252
|
-
except Exception:
|
253
|
-
# warnings.warn(f"Failed to remove mount {mount_path}: {e}")
|
254
|
-
pass
|
255
|
-
else:
|
256
|
-
warnings.warn(f"Unsupported platform: {_SYSTEM}")
|
257
|
-
|
258
|
-
# Allow some time for the unmount commands to take effect.
|
259
|
-
if wait:
|
260
|
-
time.sleep(2)
|
261
|
-
|
262
|
-
# Re-check if the mount path still exists.
|
263
|
-
try:
|
264
|
-
still_exists = mount_path.exists()
|
265
|
-
except OSError as e:
|
266
|
-
warnings.warn(f"Error re-checking {mount_path}: {e}")
|
267
|
-
still_exists = True
|
268
|
-
|
269
|
-
if still_exists:
|
270
|
-
if verbose:
|
271
|
-
print(f"{mount_path} still exists after unmount attempt.")
|
272
|
-
# Attempt to remove the directory if it is empty.
|
273
|
-
try:
|
274
|
-
# Only remove if the directory is empty.
|
275
|
-
if not any(mount_path.iterdir()):
|
276
|
-
mount_path.rmdir()
|
277
|
-
if verbose:
|
278
|
-
print(f"Removed empty mount directory {mount_path}")
|
279
|
-
else:
|
280
|
-
warnings.warn(f"{mount_path} is not empty; cannot remove.")
|
281
|
-
except Exception as e:
|
282
|
-
warnings.warn(f"Failed during cleanup of {mount_path}: {e}")
|
283
|
-
else:
|
284
|
-
if verbose:
|
285
|
-
print(f"{mount_path} successfully cleaned up.")
|
rclone_api/mount_util.py
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
import atexit
|
2
|
+
import os
|
3
|
+
import platform
|
4
|
+
import shutil
|
5
|
+
import subprocess
|
6
|
+
import time
|
7
|
+
import warnings
|
8
|
+
import weakref
|
9
|
+
from concurrent.futures import ThreadPoolExecutor
|
10
|
+
from pathlib import Path
|
11
|
+
|
12
|
+
from rclone_api.mount import Mount
|
13
|
+
from rclone_api.process import Process
|
14
|
+
|
15
|
+
_SYSTEM = platform.system() # "Linux", "Darwin", "Windows", etc.
|
16
|
+
|
17
|
+
_MOUNTS_FOR_GC: weakref.WeakSet = weakref.WeakSet()
|
18
|
+
|
19
|
+
|
20
|
+
def _cleanup_mounts() -> None:
|
21
|
+
with ThreadPoolExecutor() as executor:
|
22
|
+
mount: Mount
|
23
|
+
for mount in _MOUNTS_FOR_GC:
|
24
|
+
executor.submit(mount.close)
|
25
|
+
|
26
|
+
|
27
|
+
def _run_command(cmd: str, verbose: bool) -> int:
|
28
|
+
"""Run a shell command and print its output if verbose is True."""
|
29
|
+
if verbose:
|
30
|
+
print(f"Executing: {cmd}")
|
31
|
+
try:
|
32
|
+
result = subprocess.run(
|
33
|
+
cmd, shell=True, capture_output=True, text=True, check=False
|
34
|
+
)
|
35
|
+
if result.returncode != 0 and verbose:
|
36
|
+
print(f"Command failed: {cmd}\nStdErr: {result.stderr.strip()}")
|
37
|
+
return result.returncode
|
38
|
+
except Exception as e:
|
39
|
+
warnings.warn(f"Error running command '{cmd}': {e}")
|
40
|
+
return -1
|
41
|
+
|
42
|
+
|
43
|
+
atexit.register(_cleanup_mounts)
|
44
|
+
|
45
|
+
|
46
|
+
def cache_dir_delete_on_exit(cache_dir: Path) -> None:
|
47
|
+
if cache_dir.exists():
|
48
|
+
try:
|
49
|
+
shutil.rmtree(cache_dir)
|
50
|
+
except Exception as e:
|
51
|
+
warnings.warn(f"Error removing cache directory {cache_dir}: {e}")
|
52
|
+
|
53
|
+
|
54
|
+
def add_mount_for_gc(mount: Mount) -> None:
|
55
|
+
# weak reference to avoid circular references
|
56
|
+
_MOUNTS_FOR_GC.add(mount)
|
57
|
+
|
58
|
+
|
59
|
+
def remove_mount_for_gc(mount: Mount) -> None:
|
60
|
+
_MOUNTS_FOR_GC.discard(mount)
|
61
|
+
|
62
|
+
|
63
|
+
def prepare_mount(outdir: Path, verbose: bool) -> None:
|
64
|
+
if _SYSTEM == "Windows":
|
65
|
+
# Windows -> Must create parent directories only if they don't exist
|
66
|
+
if verbose:
|
67
|
+
print(f"Creating parent directories for {outdir}")
|
68
|
+
outdir.parent.mkdir(parents=True, exist_ok=True)
|
69
|
+
else:
|
70
|
+
# Linux -> Must create parent directories and the directory itself
|
71
|
+
if verbose:
|
72
|
+
print(f"Creating directories for {outdir}")
|
73
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
74
|
+
|
75
|
+
|
76
|
+
def wait_for_mount(
|
77
|
+
mount: Mount,
|
78
|
+
timeout: int = 20,
|
79
|
+
post_mount_delay: int = 5,
|
80
|
+
poll_interval: float = 1.0,
|
81
|
+
check_mount_flag: bool = False,
|
82
|
+
) -> None:
|
83
|
+
"""
|
84
|
+
Wait for a mount point to become available by checking if the directory exists,
|
85
|
+
optionally verifying that it is a mount point, and confirming that it contains files.
|
86
|
+
This function periodically polls for the mount status, ensures the mount process
|
87
|
+
is still running, and applies an extra delay after detecting content for stabilization.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
src (Path): The mount point directory to check.
|
91
|
+
mount_process (Any): A Process instance handling the mount (must be an instance of Process).
|
92
|
+
timeout (int): Maximum time in seconds to wait for the mount to become available.
|
93
|
+
post_mount_delay (int): Additional seconds to wait after detecting files.
|
94
|
+
poll_interval (float): Seconds between each poll iteration.
|
95
|
+
check_mount_flag (bool): If True, verifies that the path is recognized as a mount point.
|
96
|
+
|
97
|
+
Raises:
|
98
|
+
subprocess.CalledProcessError: If the mount_process exits unexpectedly.
|
99
|
+
TimeoutError: If the mount is not available within the timeout period.
|
100
|
+
TypeError: If mount_process is not an instance of Process.
|
101
|
+
"""
|
102
|
+
|
103
|
+
mount_process = mount.process
|
104
|
+
src = mount.mount_path
|
105
|
+
|
106
|
+
if not isinstance(mount_process, Process):
|
107
|
+
raise TypeError("mount_process must be an instance of Process")
|
108
|
+
|
109
|
+
expire_time = time.time() + timeout
|
110
|
+
last_error = None
|
111
|
+
|
112
|
+
while time.time() < expire_time:
|
113
|
+
# Check if the mount process has terminated unexpectedly.
|
114
|
+
rtn = mount_process.poll()
|
115
|
+
if rtn is not None:
|
116
|
+
cmd_str = subprocess.list2cmdline(mount_process.cmd)
|
117
|
+
print(f"Mount process terminated unexpectedly: {cmd_str}")
|
118
|
+
raise subprocess.CalledProcessError(rtn, cmd_str)
|
119
|
+
|
120
|
+
# Check if the mount path exists.
|
121
|
+
if src.exists():
|
122
|
+
# Optionally check if path is a mount point.
|
123
|
+
if check_mount_flag:
|
124
|
+
try:
|
125
|
+
if not os.path.ismount(str(src)):
|
126
|
+
print(
|
127
|
+
f"{src} exists but is not recognized as a mount point yet."
|
128
|
+
)
|
129
|
+
time.sleep(poll_interval)
|
130
|
+
continue
|
131
|
+
except Exception as e:
|
132
|
+
print(f"Could not verify mount point status for {src}: {e}")
|
133
|
+
|
134
|
+
try:
|
135
|
+
# Check for at least one entry in the directory.
|
136
|
+
if any(src.iterdir()):
|
137
|
+
print(
|
138
|
+
f"Mount point {src} appears available with files. Waiting {post_mount_delay} seconds for stabilization."
|
139
|
+
)
|
140
|
+
time.sleep(post_mount_delay)
|
141
|
+
return
|
142
|
+
else:
|
143
|
+
print(f"Mount point {src} is empty. Waiting for files to appear.")
|
144
|
+
except Exception as e:
|
145
|
+
last_error = e
|
146
|
+
print(f"Error accessing {src}: {e}")
|
147
|
+
else:
|
148
|
+
print(f"Mount point {src} does not exist yet.")
|
149
|
+
|
150
|
+
time.sleep(poll_interval)
|
151
|
+
|
152
|
+
# raise TimeoutError(
|
153
|
+
# f"Mount point {src} did not become available within {timeout} seconds. Last error: {last_error}"
|
154
|
+
# )
|
155
|
+
if last_error is not None:
|
156
|
+
raise last_error
|
157
|
+
|
158
|
+
|
159
|
+
def clean_mount(mount: Mount | Path, verbose: bool = False, wait=True) -> None:
|
160
|
+
"""
|
161
|
+
Clean up a mount path across Linux, macOS, and Windows.
|
162
|
+
|
163
|
+
The function attempts to unmount the mount at mount_path, then, if the
|
164
|
+
directory is empty, removes it. On Linux it uses 'fusermount -u' (for FUSE mounts)
|
165
|
+
and 'umount'. On macOS it uses 'umount' (and optionally 'diskutil unmount'),
|
166
|
+
while on Windows it attempts to remove the mount point via 'mountvol /D'.
|
167
|
+
"""
|
168
|
+
|
169
|
+
def verbose_print(msg: str):
|
170
|
+
if verbose:
|
171
|
+
print(msg)
|
172
|
+
|
173
|
+
proc = mount.process if isinstance(mount, Mount) else None
|
174
|
+
if proc is not None and proc.poll() is None:
|
175
|
+
verbose_print(f"Terminating mount process {proc.pid}")
|
176
|
+
proc.kill()
|
177
|
+
|
178
|
+
# Check if the mount path exists; if an OSError occurs, assume it exists.
|
179
|
+
mount_path = mount.mount_path if isinstance(mount, Mount) else mount
|
180
|
+
try:
|
181
|
+
mount_exists = mount_path.exists()
|
182
|
+
except OSError:
|
183
|
+
# warnings.warn(f"Error checking {mount_path}: {e}")
|
184
|
+
mount_exists = True
|
185
|
+
|
186
|
+
# Give the system a moment (if unmount is in progress, etc.)
|
187
|
+
if wait:
|
188
|
+
time.sleep(2)
|
189
|
+
|
190
|
+
if not mount_exists:
|
191
|
+
verbose_print(f"{mount_path} does not exist; nothing to clean up.")
|
192
|
+
return
|
193
|
+
|
194
|
+
verbose_print(f"{mount_path} still exists, attempting to unmount and remove.")
|
195
|
+
|
196
|
+
# Platform-specific unmount procedures
|
197
|
+
if _SYSTEM == "Linux":
|
198
|
+
# Try FUSE unmount first (if applicable), then the regular umount.
|
199
|
+
_run_command(f"fusermount -u {mount_path}", verbose)
|
200
|
+
_run_command(f"umount {mount_path}", verbose)
|
201
|
+
elif _SYSTEM == "Darwin":
|
202
|
+
# On macOS, use umount; optionally try diskutil for stubborn mounts.
|
203
|
+
_run_command(f"umount {mount_path}", verbose)
|
204
|
+
# Optionally: uncomment the next line if diskutil unmount is preferred.
|
205
|
+
# _run_command(f"diskutil unmount {mount_path}", verbose)
|
206
|
+
elif _SYSTEM == "Windows":
|
207
|
+
# On Windows, remove the mount point using mountvol.
|
208
|
+
_run_command(f"mountvol {mount_path} /D", verbose)
|
209
|
+
# If that does not work, try to remove the directory directly.
|
210
|
+
try:
|
211
|
+
mount_path.rmdir()
|
212
|
+
if verbose:
|
213
|
+
print(f"Successfully removed mount directory {mount_path}")
|
214
|
+
except Exception:
|
215
|
+
# warnings.warn(f"Failed to remove mount {mount_path}: {e}")
|
216
|
+
pass
|
217
|
+
else:
|
218
|
+
warnings.warn(f"Unsupported platform: {_SYSTEM}")
|
219
|
+
|
220
|
+
# Allow some time for the unmount commands to take effect.
|
221
|
+
if wait:
|
222
|
+
time.sleep(2)
|
223
|
+
|
224
|
+
# Re-check if the mount path still exists.
|
225
|
+
try:
|
226
|
+
still_exists = mount_path.exists()
|
227
|
+
except OSError as e:
|
228
|
+
warnings.warn(f"Error re-checking {mount_path}: {e}")
|
229
|
+
still_exists = True
|
230
|
+
|
231
|
+
if still_exists:
|
232
|
+
verbose_print(f"{mount_path} still exists after unmount attempt.")
|
233
|
+
# Attempt to remove the directory if it is empty.
|
234
|
+
try:
|
235
|
+
# Only remove if the directory is empty.
|
236
|
+
if not any(mount_path.iterdir()):
|
237
|
+
mount_path.rmdir()
|
238
|
+
if verbose:
|
239
|
+
verbose_print(f"Removed empty mount directory {mount_path}")
|
240
|
+
else:
|
241
|
+
warnings.warn(f"{mount_path} is not empty; cannot remove.")
|
242
|
+
except Exception as e:
|
243
|
+
warnings.warn(f"Failed during cleanup of {mount_path}: {e}")
|
244
|
+
else:
|
245
|
+
verbose_print(f"{mount_path} successfully cleaned up.")
|
rclone_api/rclone_impl.py
CHANGED
@@ -27,7 +27,7 @@ from rclone_api.file import File
|
|
27
27
|
from rclone_api.file_stream import FilesStream
|
28
28
|
from rclone_api.group_files import group_files
|
29
29
|
from rclone_api.http_server import HttpServer
|
30
|
-
from rclone_api.mount import Mount
|
30
|
+
from rclone_api.mount import Mount
|
31
31
|
from rclone_api.process import Process
|
32
32
|
from rclone_api.remote import Remote
|
33
33
|
from rclone_api.rpath import RPath
|
@@ -1017,6 +1017,7 @@ class RcloneImpl:
|
|
1017
1017
|
Raises:
|
1018
1018
|
subprocess.CalledProcessError: If the mount operation fails
|
1019
1019
|
"""
|
1020
|
+
from rclone_api.mount_util import clean_mount, prepare_mount
|
1020
1021
|
|
1021
1022
|
allow_writes = allow_writes or False
|
1022
1023
|
use_links = use_links or True
|
@@ -17,9 +17,10 @@ rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,803
|
|
17
17
|
rclone_api/http_server.py,sha256=LhovQu2AI-Z7zQIWflWelCiCDLnWzisL32Rs5350kxE,8850
|
18
18
|
rclone_api/install.py,sha256=Xb1BRn8rQcSpSd4dzmvIOELP2zM2DytUeIZ6jzv738A,2893
|
19
19
|
rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
|
20
|
-
rclone_api/mount.py,sha256=
|
20
|
+
rclone_api/mount.py,sha256=LZqEhuKZunbWVqmsOIqkkCotaxWJpdFRS1InXveoU5E,1428
|
21
|
+
rclone_api/mount_util.py,sha256=jCoObKMNTKO7xG2udAmEXtXdF182rCS5tn56Oc2CEUk,8993
|
21
22
|
rclone_api/process.py,sha256=tGooS5NLdPuqHh7hCH8SfK44A6LGftPQCPQUNgSo0a0,5714
|
22
|
-
rclone_api/rclone_impl.py,sha256=
|
23
|
+
rclone_api/rclone_impl.py,sha256=kr0gvOSuiwxU15rvATYZmSzpWYDiAzjZ2WNe1wqI6NM,46345
|
23
24
|
rclone_api/remote.py,sha256=mTgMTQTwxUmbLjTpr-AGTId2ycXKI9mLX5L7PPpDIoc,520
|
24
25
|
rclone_api/rpath.py,sha256=Y1JjQWcie39EgQrq-UtbfDz5yDLCwwfu27W7AQXllSE,2860
|
25
26
|
rclone_api/scan_missing_folders.py,sha256=-8NCwpCaHeHrX-IepCoAEsX1rl8S-GOCxcIhTr_w3gA,4747
|
@@ -52,9 +53,9 @@ rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9
|
|
52
53
|
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=diJoUpVYow6No_dNgOZIYVsv43k4evb6zixqpzWJaUk,9771
|
53
54
|
rclone_api/s3/multipart/upload_parts_server_side_merge.py,sha256=Fp2pdrs5dONQI9LkfNolgAGj1-Z2V1SsRd0r0sreuXI,18040
|
54
55
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
55
|
-
rclone_api-1.5.
|
56
|
-
rclone_api-1.5.
|
57
|
-
rclone_api-1.5.
|
58
|
-
rclone_api-1.5.
|
59
|
-
rclone_api-1.5.
|
60
|
-
rclone_api-1.5.
|
56
|
+
rclone_api-1.5.7.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
57
|
+
rclone_api-1.5.7.dist-info/METADATA,sha256=f2c9hq_CZYIpVr-U9jV44E9FwPg1Tn59XTJf_zhme14,32633
|
58
|
+
rclone_api-1.5.7.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
59
|
+
rclone_api-1.5.7.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
|
60
|
+
rclone_api-1.5.7.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
61
|
+
rclone_api-1.5.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|