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 CHANGED
@@ -841,7 +841,7 @@ class Rclone:
841
841
  def serve_http(
842
842
  self,
843
843
  src: str,
844
- addr: str = "localhost:8080",
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(src=src, addr=addr, other_args=other_args)
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,
@@ -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
- response = httpx.put(url, content=data, timeout=_TIMEOUT)
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.terminate()
227
- if self.process.stdout:
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 __exit__(self, exc_type, exc_val, exc_tb) -> None:
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rclone_api
3
- Version: 1.5.12
3
+ Version: 1.5.14
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -1,4 +1,4 @@
1
- rclone_api/__init__.py,sha256=uhoHM66jDRlk0Hq4PTrk7QZE6VcJqiu5Jeo9nj34edk,32380
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=Rc7wHn4XeyJCwNRe6pr88vHtbJxFGdYNcgRVYNuUG3E,9784
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=tGooS5NLdPuqHh7hCH8SfK44A6LGftPQCPQUNgSo0a0,5714
23
- rclone_api/rclone_impl.py,sha256=kr0gvOSuiwxU15rvATYZmSzpWYDiAzjZ2WNe1wqI6NM,46345
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=diJoUpVYow6No_dNgOZIYVsv43k4evb6zixqpzWJaUk,9771
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.12.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
57
- rclone_api-1.5.12.dist-info/METADATA,sha256=7MG4gBbQ1cKx9EHM4hyR4LhpOuZVyE55YKfjQYNEox4,32657
58
- rclone_api-1.5.12.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
59
- rclone_api-1.5.12.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
60
- rclone_api-1.5.12.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
61
- rclone_api-1.5.12.dist-info/RECORD,,
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,,