rclone-api 1.1.48__py2.py3-none-any.whl → 1.1.50__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 +101 -11
- {rclone_api-1.1.48.dist-info → rclone_api-1.1.50.dist-info}/METADATA +1 -1
- {rclone_api-1.1.48.dist-info → rclone_api-1.1.50.dist-info}/RECORD +7 -7
- {rclone_api-1.1.48.dist-info → rclone_api-1.1.50.dist-info}/LICENSE +0 -0
- {rclone_api-1.1.48.dist-info → rclone_api-1.1.50.dist-info}/WHEEL +0 -0
- {rclone_api-1.1.48.dist-info → rclone_api-1.1.50.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.1.48.dist-info → rclone_api-1.1.50.dist-info}/top_level.txt +0 -0
rclone_api/mount.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import os
|
|
1
3
|
import platform
|
|
2
4
|
import shutil
|
|
3
5
|
import subprocess
|
|
4
6
|
import time
|
|
5
7
|
import warnings
|
|
8
|
+
import weakref
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
10
|
from dataclasses import dataclass
|
|
7
11
|
from pathlib import Path
|
|
8
12
|
from typing import Any
|
|
@@ -11,6 +15,27 @@ from rclone_api.process import Process
|
|
|
11
15
|
|
|
12
16
|
_SYSTEM = platform.system() # "Linux", "Darwin", "Windows", etc.
|
|
13
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, wait=False)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
atexit.register(_cleanup_mounts)
|
|
38
|
+
|
|
14
39
|
|
|
15
40
|
@dataclass
|
|
16
41
|
class Mount:
|
|
@@ -27,15 +52,18 @@ class Mount:
|
|
|
27
52
|
assert isinstance(self.mount_path, Path)
|
|
28
53
|
assert self.process is not None
|
|
29
54
|
wait_for_mount(self.mount_path, self.process)
|
|
55
|
+
_add_mount_for_gc(self)
|
|
30
56
|
|
|
31
57
|
def close(self, wait=True) -> None:
|
|
32
58
|
"""Clean up the mount."""
|
|
33
59
|
if self._closed:
|
|
34
60
|
return
|
|
35
61
|
self._closed = True
|
|
62
|
+
self.process.terminate()
|
|
36
63
|
clean_mount(self, verbose=False, wait=wait)
|
|
37
64
|
if self.cache_dir and self.cache_dir_delete_on_exit:
|
|
38
65
|
_cache_dir_delete_on_exit(self.cache_dir)
|
|
66
|
+
_remove_mount_for_gc(self)
|
|
39
67
|
|
|
40
68
|
def __enter__(self) -> "Mount":
|
|
41
69
|
return self
|
|
@@ -46,6 +74,10 @@ class Mount:
|
|
|
46
74
|
def __del__(self):
|
|
47
75
|
self.close(wait=False)
|
|
48
76
|
|
|
77
|
+
# make this a hashable object
|
|
78
|
+
def __hash__(self):
|
|
79
|
+
return hash(self.mount_path)
|
|
80
|
+
|
|
49
81
|
|
|
50
82
|
def run_command(cmd: str, verbose: bool) -> int:
|
|
51
83
|
"""Run a shell command and print its output if verbose is True."""
|
|
@@ -76,25 +108,83 @@ def prepare_mount(outdir: Path, verbose: bool) -> None:
|
|
|
76
108
|
outdir.mkdir(parents=True, exist_ok=True)
|
|
77
109
|
|
|
78
110
|
|
|
79
|
-
def wait_for_mount(
|
|
80
|
-
|
|
111
|
+
def wait_for_mount(
|
|
112
|
+
path: Path,
|
|
113
|
+
mount_process: Any,
|
|
114
|
+
timeout: int = 10,
|
|
115
|
+
post_mount_delay: int = 5,
|
|
116
|
+
poll_interval: float = 1.0,
|
|
117
|
+
check_mount_flag: bool = False,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Wait for a mount point to become available by checking if the directory exists,
|
|
121
|
+
optionally verifying that it is a mount point, and confirming that it contains files.
|
|
122
|
+
This function periodically polls for the mount status, ensures the mount process
|
|
123
|
+
is still running, and applies an extra delay after detecting content for stabilization.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
path (Path): The mount point directory to check.
|
|
127
|
+
mount_process (Any): A Process instance handling the mount (must be an instance of Process).
|
|
128
|
+
timeout (int): Maximum time in seconds to wait for the mount to become available.
|
|
129
|
+
post_mount_delay (int): Additional seconds to wait after detecting files.
|
|
130
|
+
poll_interval (float): Seconds between each poll iteration.
|
|
131
|
+
check_mount_flag (bool): If True, verifies that the path is recognized as a mount point.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
subprocess.CalledProcessError: If the mount_process exits unexpectedly.
|
|
135
|
+
TimeoutError: If the mount is not available within the timeout period.
|
|
136
|
+
TypeError: If mount_process is not an instance of Process.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
if not isinstance(mount_process, Process):
|
|
140
|
+
raise TypeError("mount_process must be an instance of Process")
|
|
81
141
|
|
|
82
|
-
assert isinstance(mount_process, Process)
|
|
83
142
|
expire_time = time.time() + timeout
|
|
143
|
+
last_error = None
|
|
144
|
+
|
|
84
145
|
while time.time() < expire_time:
|
|
146
|
+
# Check if the mount process has terminated unexpectedly.
|
|
85
147
|
rtn = mount_process.poll()
|
|
86
148
|
if rtn is not None:
|
|
87
149
|
cmd_str = subprocess.list2cmdline(mount_process.cmd)
|
|
150
|
+
print(f"Mount process terminated unexpectedly: {cmd_str}")
|
|
88
151
|
raise subprocess.CalledProcessError(rtn, cmd_str)
|
|
152
|
+
|
|
153
|
+
# Check if the mount path exists.
|
|
89
154
|
if path.exists():
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
155
|
+
# Optionally check if path is a mount point.
|
|
156
|
+
if check_mount_flag:
|
|
157
|
+
try:
|
|
158
|
+
if not os.path.ismount(str(path)):
|
|
159
|
+
print(
|
|
160
|
+
f"{path} exists but is not recognized as a mount point yet."
|
|
161
|
+
)
|
|
162
|
+
time.sleep(poll_interval)
|
|
163
|
+
continue
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f"Could not verify mount point status for {path}: {e}")
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
# Check for at least one entry in the directory.
|
|
169
|
+
if any(path.iterdir()):
|
|
170
|
+
print(
|
|
171
|
+
f"Mount point {path} appears available with files. Waiting {post_mount_delay} seconds for stabilization."
|
|
172
|
+
)
|
|
173
|
+
time.sleep(post_mount_delay)
|
|
174
|
+
return
|
|
175
|
+
else:
|
|
176
|
+
print(f"Mount point {path} is empty. Waiting for files to appear.")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
last_error = e
|
|
179
|
+
print(f"Error accessing {path}: {e}")
|
|
180
|
+
else:
|
|
181
|
+
print(f"Mount point {path} does not exist yet.")
|
|
182
|
+
|
|
183
|
+
time.sleep(poll_interval)
|
|
184
|
+
|
|
185
|
+
raise TimeoutError(
|
|
186
|
+
f"Mount point {path} did not become available within {timeout} seconds. Last error: {last_error}"
|
|
187
|
+
)
|
|
98
188
|
|
|
99
189
|
|
|
100
190
|
def clean_mount(mount: Mount | Path, verbose: bool = False, wait=True) -> None:
|
|
@@ -11,7 +11,7 @@ rclone_api/exec.py,sha256=Pd7pUBd8ib5MzqvMybG2DQISPRbDRu20VjVRL2mLAVY,1076
|
|
|
11
11
|
rclone_api/file.py,sha256=EP5yT2dZ0H2p7CY5n0y5k5pHhIliV25pm8KOwBklUTk,1863
|
|
12
12
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
|
13
13
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
|
14
|
-
rclone_api/mount.py,sha256=
|
|
14
|
+
rclone_api/mount.py,sha256=lVKvOt10xD3w2Bk6ZjnxnviV8IlRkIofH79Uei3ciTs,10044
|
|
15
15
|
rclone_api/process.py,sha256=rBj_S86jC6nqCYop-jq8r9eMSteKeObxUrJMgH8LZvI,5084
|
|
16
16
|
rclone_api/rclone.py,sha256=ODHPq1OKKzOUgi7QPDc1UZHZLSoG9SkxnT1MXpoggx4,43823
|
|
17
17
|
rclone_api/remote.py,sha256=O9WDUFQy9f6oT1HdUbTixK2eg0xtBBm8k4Xl6aa6K00,431
|
|
@@ -33,9 +33,9 @@ rclone_api/s3/chunk_types.py,sha256=LbXayXY1KgVU1LkdbASD_BQ7TpVpwVnzMjtz--8LBaE,
|
|
|
33
33
|
rclone_api/s3/create.py,sha256=wgfkapv_j904CfKuWyiBIWJVxfAx_ftemFSUV14aT68,3149
|
|
34
34
|
rclone_api/s3/types.py,sha256=yBnJ38Tjk6RlydJ-sqZ7DSfyFloy8KDYJ0mv3vlOzLE,1388
|
|
35
35
|
rclone_api/s3/upload_file_multipart.py,sha256=y9azNAU8QH5Ovwz33V2HZwNmJdlFjJg-jrXLZ1gtMds,10364
|
|
36
|
-
rclone_api-1.1.
|
|
37
|
-
rclone_api-1.1.
|
|
38
|
-
rclone_api-1.1.
|
|
39
|
-
rclone_api-1.1.
|
|
40
|
-
rclone_api-1.1.
|
|
41
|
-
rclone_api-1.1.
|
|
36
|
+
rclone_api-1.1.50.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
|
37
|
+
rclone_api-1.1.50.dist-info/METADATA,sha256=keOMsvXUUQwz3T07ALlyBoA9HSawE1EHbcj2EfCEM00,4537
|
|
38
|
+
rclone_api-1.1.50.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
|
39
|
+
rclone_api-1.1.50.dist-info/entry_points.txt,sha256=TV8kwP3FRzYwUEr0RLC7aJh0W03SAefIJNXTJ-FdMIQ,200
|
|
40
|
+
rclone_api-1.1.50.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
|
41
|
+
rclone_api-1.1.50.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|