rclone-api 1.5.12__py3-none-any.whl → 1.5.14__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/__init__.py +4 -2
- rclone_api/filesystem.py +170 -0
- rclone_api/http_server.py +22 -6
- rclone_api/process.py +8 -1
- rclone_api/rclone_impl.py +11 -2
- rclone_api/s3/multipart/upload_parts_resumable.py +1 -1
- {rclone_api-1.5.12.dist-info → rclone_api-1.5.14.dist-info}/METADATA +1 -1
- {rclone_api-1.5.12.dist-info → rclone_api-1.5.14.dist-info}/RECORD +12 -11
- {rclone_api-1.5.12.dist-info → rclone_api-1.5.14.dist-info}/WHEEL +0 -0
- {rclone_api-1.5.12.dist-info → rclone_api-1.5.14.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.12.dist-info → rclone_api-1.5.14.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.12.dist-info → rclone_api-1.5.14.dist-info}/top_level.txt +0 -0
rclone_api/__init__.py
CHANGED
@@ -841,7 +841,7 @@ class Rclone:
|
|
841
841
|
def serve_http(
|
842
842
|
self,
|
843
843
|
src: str,
|
844
|
-
addr: str =
|
844
|
+
addr: str | None = None,
|
845
845
|
other_args: list[str] | None = None,
|
846
846
|
) -> HttpServer:
|
847
847
|
"""
|
@@ -862,7 +862,9 @@ class Rclone:
|
|
862
862
|
Returns:
|
863
863
|
HttpServer object with methods for accessing the served content
|
864
864
|
"""
|
865
|
-
return self.impl.serve_http(
|
865
|
+
return self.impl.serve_http(
|
866
|
+
src=src, cache_mode="minimal", addr=addr, other_args=other_args
|
867
|
+
)
|
866
868
|
|
867
869
|
def size_files(
|
868
870
|
self,
|
rclone_api/filesystem.py
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
import abc
|
2
|
+
import shutil
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
class FileSystem(abc.ABC):
|
7
|
+
def __init__(self) -> None:
|
8
|
+
pass
|
9
|
+
|
10
|
+
@abc.abstractmethod
|
11
|
+
def copy(self, src: Path | str, dest: Path | str) -> None:
|
12
|
+
pass
|
13
|
+
|
14
|
+
@abc.abstractmethod
|
15
|
+
def read_binary(self, path: Path | str) -> bytes:
|
16
|
+
pass
|
17
|
+
|
18
|
+
@abc.abstractmethod
|
19
|
+
def exists(self, path: Path | str) -> bool:
|
20
|
+
pass
|
21
|
+
|
22
|
+
@abc.abstractmethod
|
23
|
+
def write_binary(self, path: Path | str, data: bytes) -> None:
|
24
|
+
pass
|
25
|
+
|
26
|
+
@abc.abstractmethod
|
27
|
+
def mkdir(self, path: str, parents=True, exist_ok=True) -> None:
|
28
|
+
pass
|
29
|
+
|
30
|
+
@abc.abstractmethod
|
31
|
+
def get_path(self, path: str) -> "FSPath":
|
32
|
+
pass
|
33
|
+
|
34
|
+
def read_text(self, path: Path | str) -> str:
|
35
|
+
utf = self.read_binary(path)
|
36
|
+
return utf.decode("utf-8")
|
37
|
+
|
38
|
+
def write_text(self, path: Path | str, data: str, encoding: str | None) -> None:
|
39
|
+
encoding = encoding or "utf-8"
|
40
|
+
utf = data.encode(encoding)
|
41
|
+
self.write_binary(path, utf)
|
42
|
+
|
43
|
+
|
44
|
+
class RealFileSystem(FileSystem):
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def get_real_path(path: Path | str) -> "FSPath":
|
48
|
+
path_str = Path(path).as_posix()
|
49
|
+
return FSPath(RealFileSystem(), path_str)
|
50
|
+
|
51
|
+
def __init__(self) -> None:
|
52
|
+
super().__init__()
|
53
|
+
|
54
|
+
def copy(self, src: Path | str, dest: Path | str) -> None:
|
55
|
+
shutil.copy(str(src), str(dest))
|
56
|
+
|
57
|
+
def read_binary(self, path: Path | str) -> bytes:
|
58
|
+
with open(path, "rb") as f:
|
59
|
+
return f.read()
|
60
|
+
|
61
|
+
def write_binary(self, path: Path | str, data: bytes) -> None:
|
62
|
+
with open(path, "wb") as f:
|
63
|
+
f.write(data)
|
64
|
+
|
65
|
+
def exists(self, path: Path | str) -> bool:
|
66
|
+
return Path(path).exists()
|
67
|
+
|
68
|
+
def mkdir(self, path: str, parents=True, exist_ok=True) -> None:
|
69
|
+
Path(path).mkdir(parents=parents, exist_ok=exist_ok)
|
70
|
+
|
71
|
+
def get_path(self, path: str) -> "FSPath":
|
72
|
+
return FSPath(self, path)
|
73
|
+
|
74
|
+
|
75
|
+
class RemoteFileSystem(FileSystem):
|
76
|
+
def __init__(self, rclone_conf: Path, src: str) -> None:
|
77
|
+
from rclone_api import HttpServer, Rclone
|
78
|
+
|
79
|
+
super().__init__()
|
80
|
+
self.rclone_conf = rclone_conf
|
81
|
+
self.rclone: Rclone = Rclone(rclone_conf)
|
82
|
+
self.server: HttpServer = self.rclone.serve_http(src=src)
|
83
|
+
self.shutdown = False
|
84
|
+
|
85
|
+
def _to_str(self, path: Path | str) -> str:
|
86
|
+
if isinstance(path, Path):
|
87
|
+
return path.as_posix()
|
88
|
+
return path
|
89
|
+
|
90
|
+
def copy(self, src: Path | str, dest: Path | str) -> None:
|
91
|
+
src = self._to_str(src)
|
92
|
+
dest = self._to_str(dest)
|
93
|
+
self.rclone.copy(src, dest)
|
94
|
+
|
95
|
+
def read_binary(self, path: Path | str) -> bytes:
|
96
|
+
path = self._to_str(path)
|
97
|
+
err = self.rclone.read_bytes(path)
|
98
|
+
if isinstance(err, Exception):
|
99
|
+
raise FileNotFoundError(f"File not found: {path}")
|
100
|
+
return err
|
101
|
+
|
102
|
+
def write_binary(self, path: Path | str, data: bytes) -> None:
|
103
|
+
path = self._to_str(path)
|
104
|
+
self.rclone.write_bytes(data, path)
|
105
|
+
|
106
|
+
def exists(self, path: Path | str) -> bool:
|
107
|
+
path = self._to_str(path)
|
108
|
+
return self.server.exists(path)
|
109
|
+
|
110
|
+
def mkdir(self, path: str, parents=True, exist_ok=True) -> None:
|
111
|
+
raise NotImplementedError("RemoteFileSystem does not support mkdir")
|
112
|
+
|
113
|
+
def get_path(self, path: str) -> "FSPath":
|
114
|
+
return FSPath(self, path)
|
115
|
+
|
116
|
+
def dispose(self) -> None:
|
117
|
+
if self.shutdown:
|
118
|
+
return
|
119
|
+
self.shutdown = True
|
120
|
+
self.server.shutdown()
|
121
|
+
|
122
|
+
def __del__(self) -> None:
|
123
|
+
self.dispose()
|
124
|
+
|
125
|
+
|
126
|
+
class FSPath:
|
127
|
+
def __init__(self, fs: FileSystem, path: str) -> None:
|
128
|
+
self.path = path
|
129
|
+
self.fs = fs
|
130
|
+
|
131
|
+
def read_text(self) -> str:
|
132
|
+
return self.fs.read_text(self.path)
|
133
|
+
|
134
|
+
def read_binary(self) -> bytes:
|
135
|
+
return self.fs.read_binary(self.path)
|
136
|
+
|
137
|
+
def exists(self) -> bool:
|
138
|
+
return self.fs.exists(self.path)
|
139
|
+
|
140
|
+
def __str__(self) -> str:
|
141
|
+
return self.path
|
142
|
+
|
143
|
+
def __repr__(self) -> str:
|
144
|
+
return f"FSPath({self.path})"
|
145
|
+
|
146
|
+
def mkdir(self, parents=True, exist_ok=True) -> None:
|
147
|
+
self.fs.mkdir(self.path, parents=parents, exist_ok=exist_ok)
|
148
|
+
|
149
|
+
def write_text(self, data: str, encoding: str | None = None) -> None:
|
150
|
+
self.fs.write_text(self.path, data, encoding=encoding)
|
151
|
+
|
152
|
+
def rmtree(self, ignore_errors=False) -> None:
|
153
|
+
assert self.exists(), f"Path does not exist: {self.path}"
|
154
|
+
# check fs is RealFileSystem
|
155
|
+
assert isinstance(self.fs, RealFileSystem)
|
156
|
+
shutil.rmtree(self.path, ignore_errors=ignore_errors)
|
157
|
+
|
158
|
+
@property
|
159
|
+
def name(self) -> str:
|
160
|
+
return Path(self.path).name
|
161
|
+
|
162
|
+
@property
|
163
|
+
def parent(self) -> "FSPath":
|
164
|
+
parent_path = Path(self.path).parent
|
165
|
+
parent_str = parent_path.as_posix()
|
166
|
+
return FSPath(self.fs, parent_str)
|
167
|
+
|
168
|
+
def __truediv__(self, other: str) -> "FSPath":
|
169
|
+
new_path = Path(self.path) / other
|
170
|
+
return FSPath(self.fs, new_path.as_posix())
|
rclone_api/http_server.py
CHANGED
@@ -17,6 +17,7 @@ from rclone_api.process import Process
|
|
17
17
|
from rclone_api.types import Range, SizeSuffix, get_chunk_tmpdir
|
18
18
|
|
19
19
|
_TIMEOUT = 10 * 60 # 10 minutes
|
20
|
+
_PUT_WARNED = False
|
20
21
|
|
21
22
|
|
22
23
|
_range = range
|
@@ -74,16 +75,34 @@ class HttpServer:
|
|
74
75
|
|
75
76
|
def put(self, path: str, data: bytes) -> Exception | None:
|
76
77
|
"""Put bytes to the server."""
|
78
|
+
global _PUT_WARNED
|
79
|
+
if not _PUT_WARNED:
|
80
|
+
_PUT_WARNED = True
|
81
|
+
warnings.warn("PUT method not implemented on the rclone binary as of 1.69")
|
77
82
|
try:
|
78
83
|
assert self.process is not None
|
79
84
|
url = self._get_file_url(path)
|
80
|
-
|
85
|
+
headers = {"Content-Type": "application/octet-stream"}
|
86
|
+
response = httpx.post(url, content=data, timeout=_TIMEOUT, headers=headers)
|
87
|
+
print("Allowed methods:", response.headers.get("Allow"))
|
81
88
|
response.raise_for_status()
|
82
89
|
return None
|
83
90
|
except Exception as e:
|
84
91
|
warnings.warn(f"Failed to put {path} to {self.url}: {e}")
|
85
92
|
return e
|
86
93
|
|
94
|
+
def delete(self, path: str) -> Exception | None:
|
95
|
+
"""Remove file from the server."""
|
96
|
+
try:
|
97
|
+
assert self.process is not None
|
98
|
+
url = self._get_file_url(path)
|
99
|
+
response = httpx.delete(url)
|
100
|
+
response.raise_for_status()
|
101
|
+
return None
|
102
|
+
except Exception as e:
|
103
|
+
warnings.warn(f"Failed to remove {path} from {self.url}: {e}")
|
104
|
+
return e
|
105
|
+
|
87
106
|
def download(
|
88
107
|
self, path: str, dst: Path, range: Range | None = None
|
89
108
|
) -> Path | Exception:
|
@@ -223,11 +242,8 @@ class HttpServer:
|
|
223
242
|
def shutdown(self) -> None:
|
224
243
|
"""Shutdown the server."""
|
225
244
|
if self.process:
|
226
|
-
self.process.
|
227
|
-
|
228
|
-
self.process.stdout.close()
|
229
|
-
if self.process.stderr:
|
230
|
-
self.process.stderr.close()
|
245
|
+
self.process.dispose()
|
246
|
+
self.process = None
|
231
247
|
|
232
248
|
|
233
249
|
class HttpFetcher:
|
rclone_api/process.py
CHANGED
@@ -30,6 +30,7 @@ class Process:
|
|
30
30
|
), f"rclone executable not found: {args.rclone_exe}"
|
31
31
|
self.args = args
|
32
32
|
self.log = args.log
|
33
|
+
self.cleaned_up = False
|
33
34
|
self.tempfile: Path | None = None
|
34
35
|
|
35
36
|
verbose = get_verbose(args.verbose)
|
@@ -75,11 +76,17 @@ class Process:
|
|
75
76
|
def __enter__(self) -> "Process":
|
76
77
|
return self
|
77
78
|
|
78
|
-
def
|
79
|
+
def dispose(self) -> None:
|
80
|
+
if self.cleaned_up:
|
81
|
+
return
|
82
|
+
self.cleaned_up = True
|
79
83
|
self.terminate()
|
80
84
|
self.wait()
|
81
85
|
self.cleanup()
|
82
86
|
|
87
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
88
|
+
self.dispose()
|
89
|
+
|
83
90
|
def cleanup(self) -> None:
|
84
91
|
if self.tempfile:
|
85
92
|
clear_temp_config_file(self.tempfile)
|
rclone_api/rclone_impl.py
CHANGED
@@ -6,6 +6,7 @@ import os
|
|
6
6
|
import random
|
7
7
|
import subprocess
|
8
8
|
import time
|
9
|
+
import tracemalloc
|
9
10
|
import warnings
|
10
11
|
from concurrent.futures import Future, ThreadPoolExecutor
|
11
12
|
from datetime import datetime
|
@@ -51,6 +52,9 @@ from rclone_api.util import (
|
|
51
52
|
to_path,
|
52
53
|
)
|
53
54
|
|
55
|
+
# Enable tracing memory usage always
|
56
|
+
tracemalloc.start()
|
57
|
+
|
54
58
|
|
55
59
|
def rclone_verbose(verbose: bool | None) -> bool:
|
56
60
|
if verbose is not None:
|
@@ -1164,6 +1168,7 @@ class RcloneImpl:
|
|
1164
1168
|
def serve_http(
|
1165
1169
|
self,
|
1166
1170
|
src: str,
|
1171
|
+
cache_mode: str | None,
|
1167
1172
|
addr: str | None = None,
|
1168
1173
|
serve_http_log: Path | None = None,
|
1169
1174
|
other_args: list[str] | None = None,
|
@@ -1186,9 +1191,13 @@ class RcloneImpl:
|
|
1186
1191
|
"0",
|
1187
1192
|
"--vfs-read-chunk-size-limit",
|
1188
1193
|
"512M",
|
1189
|
-
"--vfs-cache-mode",
|
1190
|
-
"off",
|
1191
1194
|
]
|
1195
|
+
|
1196
|
+
if cache_mode:
|
1197
|
+
cmd_list += [
|
1198
|
+
"--vfs-cache-mode",
|
1199
|
+
cache_mode,
|
1200
|
+
]
|
1192
1201
|
if serve_http_log:
|
1193
1202
|
cmd_list += ["--log-file", str(serve_http_log)]
|
1194
1203
|
cmd_list += ["-vvvv"]
|
@@ -232,7 +232,7 @@ def upload_parts_resumable(
|
|
232
232
|
|
233
233
|
atexit.register(lambda: shutil.rmtree(tmp_dir, ignore_errors=True))
|
234
234
|
|
235
|
-
with self.serve_http(src_dir) as http_server:
|
235
|
+
with self.serve_http(src_dir, cache_mode="minimal") as http_server:
|
236
236
|
tmpdir: Path = Path(tmp_dir)
|
237
237
|
write_semaphore = threading.Semaphore(threads)
|
238
238
|
with ThreadPoolExecutor(max_workers=threads) as upload_executor:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
rclone_api/__init__.py,sha256=
|
1
|
+
rclone_api/__init__.py,sha256=tzjjY0b-VfJ8BFYs15S3OApVwd7S1JEYbv0gUz-13aE,32421
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
3
|
rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
|
4
4
|
rclone_api/config.py,sha256=f6jEAxVorGFr31oHfcsu5AJTtOJj2wR5tTSsbGGZuIw,2558
|
@@ -13,14 +13,15 @@ rclone_api/file_item.py,sha256=cH-AQYsxedhNPp4c8NHY1ad4Z7St4yf_VGbmiGD59no,1770
|
|
13
13
|
rclone_api/file_part.py,sha256=i6ByS5_sae8Eba-4imBVTxd-xKC8ExWy7NR8QGr0ors,6155
|
14
14
|
rclone_api/file_stream.py,sha256=_W3qnwCuigqA0hzXl2q5pAxSZDRaUSwet4BkT0lpnzs,1431
|
15
15
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
16
|
+
rclone_api/filesystem.py,sha256=NlDlKWjX_WtaqjkwinMpqhaaU5QPkIrnTNKi7_k3tG0,4960
|
16
17
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
17
|
-
rclone_api/http_server.py,sha256=
|
18
|
+
rclone_api/http_server.py,sha256=p1_S9VAViVvGif6NA_rxrDgMqnOmPTxJaHQ7B43FK70,10431
|
18
19
|
rclone_api/install.py,sha256=Xb1BRn8rQcSpSd4dzmvIOELP2zM2DytUeIZ6jzv738A,2893
|
19
20
|
rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
|
20
21
|
rclone_api/mount.py,sha256=LZqEhuKZunbWVqmsOIqkkCotaxWJpdFRS1InXveoU5E,1428
|
21
22
|
rclone_api/mount_util.py,sha256=jqhJEVTHV6c6lOOzUYb4FLMbqDMHdz7-QRcdH-IobFc,10154
|
22
|
-
rclone_api/process.py,sha256=
|
23
|
-
rclone_api/rclone_impl.py,sha256=
|
23
|
+
rclone_api/process.py,sha256=MeWiN-TrrN0HmtWexBPxGwf84z6f-_E5yaXE-YtLYpY,5879
|
24
|
+
rclone_api/rclone_impl.py,sha256=STHQ_hT0chyp2yj3Cp-Vd0xXc1Pdpo3p9J3bTQEm0XE,46531
|
24
25
|
rclone_api/remote.py,sha256=mTgMTQTwxUmbLjTpr-AGTId2ycXKI9mLX5L7PPpDIoc,520
|
25
26
|
rclone_api/rpath.py,sha256=Y1JjQWcie39EgQrq-UtbfDz5yDLCwwfu27W7AQXllSE,2860
|
26
27
|
rclone_api/scan_missing_folders.py,sha256=-8NCwpCaHeHrX-IepCoAEsX1rl8S-GOCxcIhTr_w3gA,4747
|
@@ -50,12 +51,12 @@ rclone_api/s3/multipart/info_json.py,sha256=-e8UCwrqjAP64U8PmH-o2ciJ6TN48DwHktJf
|
|
50
51
|
rclone_api/s3/multipart/merge_state.py,sha256=ziTB9CYV-OWaky5C1fOT9hifSY2zgUrk5HmX1Xeu2UA,4978
|
51
52
|
rclone_api/s3/multipart/upload_info.py,sha256=d6_OfzFR_vtDzCEegFfzCfWi2kUBUV4aXZzqAEVp1c4,1874
|
52
53
|
rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9akiew3MFjGmufLo2w,12503
|
53
|
-
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=
|
54
|
+
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=6-nlMclS8jyVvMvFbQDcZOX9MY1WbCcKA_s9bwuYxnk,9793
|
54
55
|
rclone_api/s3/multipart/upload_parts_server_side_merge.py,sha256=Fp2pdrs5dONQI9LkfNolgAGj1-Z2V1SsRd0r0sreuXI,18040
|
55
56
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
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.
|
61
|
-
rclone_api-1.5.
|
57
|
+
rclone_api-1.5.14.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
58
|
+
rclone_api-1.5.14.dist-info/METADATA,sha256=NEyfLjNiEDEL1_rdIVPkqPPr1fksSmZFpxgDFmkCFJQ,32657
|
59
|
+
rclone_api-1.5.14.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
60
|
+
rclone_api-1.5.14.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
|
61
|
+
rclone_api-1.5.14.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
62
|
+
rclone_api-1.5.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|