rclone-api 1.5.15__py3-none-any.whl → 1.5.18__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/filesystem.py +52 -8
- rclone_api/http_server.py +38 -0
- rclone_api/rclone_impl.py +4 -0
- {rclone_api-1.5.15.dist-info → rclone_api-1.5.18.dist-info}/METADATA +2 -1
- {rclone_api-1.5.15.dist-info → rclone_api-1.5.18.dist-info}/RECORD +9 -9
- {rclone_api-1.5.15.dist-info → rclone_api-1.5.18.dist-info}/WHEEL +0 -0
- {rclone_api-1.5.15.dist-info → rclone_api-1.5.18.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.15.dist-info → rclone_api-1.5.18.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.15.dist-info → rclone_api-1.5.18.dist-info}/top_level.txt +0 -0
rclone_api/filesystem.py
CHANGED
@@ -2,6 +2,8 @@ import abc
|
|
2
2
|
import shutil
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
+
from rclone_api.config import Config
|
6
|
+
|
5
7
|
|
6
8
|
class FileSystem(abc.ABC):
|
7
9
|
def __init__(self) -> None:
|
@@ -27,6 +29,14 @@ class FileSystem(abc.ABC):
|
|
27
29
|
def mkdir(self, path: str, parents=True, exist_ok=True) -> None:
|
28
30
|
pass
|
29
31
|
|
32
|
+
@abc.abstractmethod
|
33
|
+
def ls(self, path: Path | str) -> list[str]:
|
34
|
+
pass
|
35
|
+
|
36
|
+
@abc.abstractmethod
|
37
|
+
def cwd(self) -> "FSPath":
|
38
|
+
pass
|
39
|
+
|
30
40
|
@abc.abstractmethod
|
31
41
|
def get_path(self, path: str) -> "FSPath":
|
32
42
|
pass
|
@@ -41,16 +51,22 @@ class FileSystem(abc.ABC):
|
|
41
51
|
self.write_binary(path, utf)
|
42
52
|
|
43
53
|
|
44
|
-
class
|
54
|
+
class RealFS(FileSystem):
|
45
55
|
|
46
56
|
@staticmethod
|
47
|
-
def
|
57
|
+
def from_path(path: Path | str) -> "FSPath":
|
48
58
|
path_str = Path(path).as_posix()
|
49
|
-
return FSPath(
|
59
|
+
return FSPath(RealFS(), path_str)
|
50
60
|
|
51
61
|
def __init__(self) -> None:
|
52
62
|
super().__init__()
|
53
63
|
|
64
|
+
def ls(self, path: Path | str) -> list[str]:
|
65
|
+
return [str(p) for p in Path(path).iterdir()]
|
66
|
+
|
67
|
+
def cwd(self) -> "FSPath":
|
68
|
+
return RealFS.from_path(Path.cwd())
|
69
|
+
|
54
70
|
def copy(self, src: Path | str, dest: Path | str) -> None:
|
55
71
|
shutil.copy(str(src), str(dest))
|
56
72
|
|
@@ -72,8 +88,8 @@ class RealFileSystem(FileSystem):
|
|
72
88
|
return FSPath(self, path)
|
73
89
|
|
74
90
|
|
75
|
-
class
|
76
|
-
def __init__(self, rclone_conf: Path, src: str) -> None:
|
91
|
+
class RemoteFS(FileSystem):
|
92
|
+
def __init__(self, rclone_conf: Path | Config, src: str) -> None:
|
77
93
|
from rclone_api import HttpServer, Rclone
|
78
94
|
|
79
95
|
super().__init__()
|
@@ -82,6 +98,12 @@ class RemoteFileSystem(FileSystem):
|
|
82
98
|
self.server: HttpServer = self.rclone.serve_http(src=src)
|
83
99
|
self.shutdown = False
|
84
100
|
|
101
|
+
def root(self) -> "FSPath":
|
102
|
+
return FSPath(self, "")
|
103
|
+
|
104
|
+
def cwd(self) -> "FSPath":
|
105
|
+
return self.root()
|
106
|
+
|
85
107
|
def _to_str(self, path: Path | str) -> str:
|
86
108
|
if isinstance(path, Path):
|
87
109
|
return path.as_posix()
|
@@ -108,7 +130,25 @@ class RemoteFileSystem(FileSystem):
|
|
108
130
|
return self.server.exists(path)
|
109
131
|
|
110
132
|
def mkdir(self, path: str, parents=True, exist_ok=True) -> None:
|
111
|
-
raise NotImplementedError("
|
133
|
+
raise NotImplementedError("RemoteFS does not support mkdir")
|
134
|
+
|
135
|
+
def is_dir(self, path: Path | str) -> bool:
|
136
|
+
path = self._to_str(path)
|
137
|
+
err = self.server.list(path)
|
138
|
+
return isinstance(err, list)
|
139
|
+
|
140
|
+
def is_file(self, path: Path | str) -> bool:
|
141
|
+
path = self._to_str(path)
|
142
|
+
err = self.server.list(path)
|
143
|
+
# Make faster.
|
144
|
+
return isinstance(err, Exception) and self.exists(path)
|
145
|
+
|
146
|
+
def ls(self, path: Path | str) -> list[str]:
|
147
|
+
path = self._to_str(path)
|
148
|
+
err = self.server.list(path)
|
149
|
+
if isinstance(err, Exception):
|
150
|
+
raise FileNotFoundError(f"File not found: {path}, because of {err}")
|
151
|
+
return err
|
112
152
|
|
113
153
|
def get_path(self, path: str) -> "FSPath":
|
114
154
|
return FSPath(self, path)
|
@@ -151,10 +191,14 @@ class FSPath:
|
|
151
191
|
|
152
192
|
def rmtree(self, ignore_errors=False) -> None:
|
153
193
|
assert self.exists(), f"Path does not exist: {self.path}"
|
154
|
-
# check fs is
|
155
|
-
assert isinstance(self.fs,
|
194
|
+
# check fs is RealFS
|
195
|
+
assert isinstance(self.fs, RealFS)
|
156
196
|
shutil.rmtree(self.path, ignore_errors=ignore_errors)
|
157
197
|
|
198
|
+
def ls(self) -> "list[FSPath]":
|
199
|
+
names: list[str] = self.fs.ls(self.path)
|
200
|
+
return [self / name for name in names]
|
201
|
+
|
158
202
|
@property
|
159
203
|
def name(self) -> str:
|
160
204
|
return Path(self.path).name
|
rclone_api/http_server.py
CHANGED
@@ -11,6 +11,7 @@ from threading import Semaphore
|
|
11
11
|
from typing import Any
|
12
12
|
|
13
13
|
import httpx
|
14
|
+
from bs4 import BeautifulSoup
|
14
15
|
|
15
16
|
from rclone_api.file_part import FilePart
|
16
17
|
from rclone_api.process import Process
|
@@ -23,6 +24,25 @@ _PUT_WARNED = False
|
|
23
24
|
_range = range
|
24
25
|
|
25
26
|
|
27
|
+
def _parse_files(html: str) -> list[str]:
|
28
|
+
soup = BeautifulSoup(html, "html.parser")
|
29
|
+
files = []
|
30
|
+
# Find each table row with class "file"
|
31
|
+
for tr in soup.find_all("tr", class_="file"):
|
32
|
+
name_span = tr.find("span", class_="name") # type: ignore
|
33
|
+
if not name_span:
|
34
|
+
continue
|
35
|
+
a_tag = name_span.find("a") # type: ignore
|
36
|
+
if not a_tag:
|
37
|
+
continue
|
38
|
+
# Get the text from the <a> tag
|
39
|
+
file_name = a_tag.get_text(strip=True) # type: ignore
|
40
|
+
# Skip directories (they end with a slash)
|
41
|
+
if not file_name.endswith("/"):
|
42
|
+
files.append(file_name)
|
43
|
+
return files
|
44
|
+
|
45
|
+
|
26
46
|
class HttpServer:
|
27
47
|
"""HTTP server configuration."""
|
28
48
|
|
@@ -103,6 +123,24 @@ class HttpServer:
|
|
103
123
|
warnings.warn(f"Failed to remove {path} from {self.url}: {e}")
|
104
124
|
return e
|
105
125
|
|
126
|
+
# curl "http://localhost:5572/?list"
|
127
|
+
|
128
|
+
def list(self, path: str) -> list[str] | Exception:
|
129
|
+
"""List files on the server."""
|
130
|
+
|
131
|
+
try:
|
132
|
+
assert self.process is not None
|
133
|
+
url = self.url
|
134
|
+
if path:
|
135
|
+
url += f"/{path}"
|
136
|
+
url += "/?list"
|
137
|
+
response = httpx.get(url)
|
138
|
+
response.raise_for_status()
|
139
|
+
return _parse_files(response.content.decode())
|
140
|
+
except Exception as e:
|
141
|
+
warnings.warn(f"Failed to list files on {self.url}: {e}")
|
142
|
+
return e
|
143
|
+
|
106
144
|
def download(
|
107
145
|
self, path: str, dst: Path, range: Range | None = None
|
108
146
|
) -> Path | Exception:
|
rclone_api/rclone_impl.py
CHANGED
@@ -26,6 +26,7 @@ from rclone_api.dir_listing import DirListing
|
|
26
26
|
from rclone_api.exec import RcloneExec
|
27
27
|
from rclone_api.file import File
|
28
28
|
from rclone_api.file_stream import FilesStream
|
29
|
+
from rclone_api.filesystem import RemoteFS
|
29
30
|
from rclone_api.group_files import group_files
|
30
31
|
from rclone_api.http_server import HttpServer
|
31
32
|
from rclone_api.mount import Mount
|
@@ -103,6 +104,9 @@ class RcloneImpl:
|
|
103
104
|
cmd += other_args
|
104
105
|
return self._launch_process(cmd, capture=False)
|
105
106
|
|
107
|
+
def filesystem(self, src: str) -> RemoteFS:
|
108
|
+
return RemoteFS(self.config, src)
|
109
|
+
|
106
110
|
def launch_server(
|
107
111
|
self,
|
108
112
|
addr: str,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: rclone_api
|
3
|
-
Version: 1.5.
|
3
|
+
Version: 1.5.18
|
4
4
|
Summary: rclone api in python
|
5
5
|
Home-page: https://github.com/zackees/rclone-api
|
6
6
|
License: BSD 3-Clause License
|
@@ -19,6 +19,7 @@ Requires-Dist: psycopg2-binary>=2.9.10
|
|
19
19
|
Requires-Dist: httpx>=0.28.1
|
20
20
|
Requires-Dist: download>=0.3.5
|
21
21
|
Requires-Dist: appdirs>=1.4.4
|
22
|
+
Requires-Dist: beautifulsoup4>=4.13.3
|
22
23
|
Dynamic: home-page
|
23
24
|
Dynamic: license-file
|
24
25
|
|
@@ -13,15 +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=
|
16
|
+
rclone_api/filesystem.py,sha256=htF2dF6v-tkiqyTj4BnDm3Ctn5seXZ2tiUFjSrMLOq8,6272
|
17
17
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
18
|
-
rclone_api/http_server.py,sha256=
|
18
|
+
rclone_api/http_server.py,sha256=ZdL-rGaq0zIjcaIiRIbPBQ4OIWZ7dCu71aq0nRlKtY4,11686
|
19
19
|
rclone_api/install.py,sha256=Xb1BRn8rQcSpSd4dzmvIOELP2zM2DytUeIZ6jzv738A,2893
|
20
20
|
rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
|
21
21
|
rclone_api/mount.py,sha256=LZqEhuKZunbWVqmsOIqkkCotaxWJpdFRS1InXveoU5E,1428
|
22
22
|
rclone_api/mount_util.py,sha256=jqhJEVTHV6c6lOOzUYb4FLMbqDMHdz7-QRcdH-IobFc,10154
|
23
23
|
rclone_api/process.py,sha256=MeWiN-TrrN0HmtWexBPxGwf84z6f-_E5yaXE-YtLYpY,5879
|
24
|
-
rclone_api/rclone_impl.py,sha256=
|
24
|
+
rclone_api/rclone_impl.py,sha256=qIvd9qcKsdD7l9_d3Thy5nJUD9xX4MAlOMekWb1NPhg,46665
|
25
25
|
rclone_api/remote.py,sha256=mTgMTQTwxUmbLjTpr-AGTId2ycXKI9mLX5L7PPpDIoc,520
|
26
26
|
rclone_api/rpath.py,sha256=Y1JjQWcie39EgQrq-UtbfDz5yDLCwwfu27W7AQXllSE,2860
|
27
27
|
rclone_api/scan_missing_folders.py,sha256=-8NCwpCaHeHrX-IepCoAEsX1rl8S-GOCxcIhTr_w3gA,4747
|
@@ -54,9 +54,9 @@ rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9
|
|
54
54
|
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=6-nlMclS8jyVvMvFbQDcZOX9MY1WbCcKA_s9bwuYxnk,9793
|
55
55
|
rclone_api/s3/multipart/upload_parts_server_side_merge.py,sha256=Fp2pdrs5dONQI9LkfNolgAGj1-Z2V1SsRd0r0sreuXI,18040
|
56
56
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
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.
|
62
|
-
rclone_api-1.5.
|
57
|
+
rclone_api-1.5.18.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
58
|
+
rclone_api-1.5.18.dist-info/METADATA,sha256=u0TSArQPF3xtUyFdUbZ84vfgNoaZxv3Bx-RklZlvi9g,32696
|
59
|
+
rclone_api-1.5.18.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
60
|
+
rclone_api-1.5.18.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
|
61
|
+
rclone_api-1.5.18.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
62
|
+
rclone_api-1.5.18.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|