rclone-api 1.0.18__tar.gz → 1.0.19__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {rclone_api-1.0.18 → rclone_api-1.0.19}/PKG-INFO +2 -1
- {rclone_api-1.0.18 → rclone_api-1.0.19}/lint +2 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/pyproject.toml +3 -2
- {rclone_api-1.0.18 → rclone_api-1.0.19}/requirements.testing.txt +2 -1
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/dir.py +4 -2
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/rclone.py +13 -4
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/walk.py +40 -7
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/PKG-INFO +2 -1
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/SOURCES.txt +2 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/requires.txt +1 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/test +1 -1
- rclone_api-1.0.18/tests/test_serve_webdav.py → rclone_api-1.0.19/tests/test_mount_webdav.py +11 -55
- rclone_api-1.0.18/tests/test_walk.py → rclone_api-1.0.19/tests/test_obscure.py +7 -8
- rclone_api-1.0.19/tests/test_serve_webdav.py +108 -0
- rclone_api-1.0.19/tests/test_walk.py +86 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.aiderignore +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/lint.yml +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/push_macos.yml +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/push_ubuntu.yml +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/push_win.yml +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.gitignore +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.pylintrc +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.vscode/launch.json +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.vscode/settings.json +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/.vscode/tasks.json +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/LICENSE +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/MANIFEST.in +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/README.md +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/clean +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/install +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/setup.cfg +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/setup.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/__init__.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/config.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/convert.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/dir_listing.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/exec.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/file.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/filelist.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/process.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/remote.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/rpath.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/util.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_copy.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_is_synced.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_ls.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_mount.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_remotes.py +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/tox.ini +0 -0
- {rclone_api-1.0.18 → rclone_api-1.0.19}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: rclone_api
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.19
|
4
4
|
Summary: rclone api in python
|
5
5
|
Home-page: https://github.com/zackees/rclone-api
|
6
6
|
Maintainer: Zachary Vorhies
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Requires-Python: >=3.10
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
+
Requires-Dist: pyright>=1.1.393
|
13
14
|
Requires-Dist: python-dotenv>=1.0.0
|
14
15
|
Dynamic: home-page
|
15
16
|
Dynamic: maintainer
|
@@ -11,10 +11,11 @@ keywords = ["template-python-cmd"]
|
|
11
11
|
license = { text = "BSD 3-Clause License" }
|
12
12
|
classifiers = ["Programming Language :: Python :: 3"]
|
13
13
|
dependencies = [
|
14
|
-
"
|
14
|
+
"pyright>=1.1.393",
|
15
|
+
"python-dotenv>=1.0.0",
|
15
16
|
]
|
16
17
|
# Change this with the version number bump.
|
17
|
-
version = "1.0.
|
18
|
+
version = "1.0.19"
|
18
19
|
|
19
20
|
[tool.setuptools]
|
20
21
|
package-dir = {"" = "src"}
|
@@ -47,12 +47,14 @@ class Dir:
|
|
47
47
|
dir = Dir(self.path)
|
48
48
|
return self.path.rclone.ls(dir, max_depth=max_depth)
|
49
49
|
|
50
|
-
def walk(
|
50
|
+
def walk(
|
51
|
+
self, breadth_first: bool, max_depth: int = -1
|
52
|
+
) -> Generator[DirListing, None, None]:
|
51
53
|
"""List files and directories in the given path."""
|
52
54
|
from rclone_api.walk import walk
|
53
55
|
|
54
56
|
assert self.path.rclone is not None
|
55
|
-
return walk(self, max_depth=max_depth)
|
57
|
+
return walk(self, breadth_first=breadth_first, max_depth=max_depth)
|
56
58
|
|
57
59
|
def to_json(self) -> dict:
|
58
60
|
"""Convert the Dir to a JSON serializable dictionary."""
|
@@ -38,6 +38,12 @@ class Rclone:
|
|
38
38
|
def _launch_process(self, cmd: list[str]) -> Process:
|
39
39
|
return self._exec.launch_process(cmd)
|
40
40
|
|
41
|
+
def obscure(self, password: str) -> str:
|
42
|
+
"""Obscure a password for use in rclone config files."""
|
43
|
+
cmd_list: list[str] = ["obscure", password]
|
44
|
+
cp = self._run(cmd_list)
|
45
|
+
return cp.stdout.strip()
|
46
|
+
|
41
47
|
def ls(
|
42
48
|
self,
|
43
49
|
path: Dir | Remote | str,
|
@@ -61,8 +67,9 @@ class Rclone:
|
|
61
67
|
|
62
68
|
cmd = ["lsjson"]
|
63
69
|
if max_depth is not None:
|
64
|
-
|
65
|
-
|
70
|
+
if max_depth < 0:
|
71
|
+
cmd.append("--recursive")
|
72
|
+
if max_depth > 0:
|
66
73
|
cmd.append("--max-depth")
|
67
74
|
cmd.append(str(max_depth))
|
68
75
|
cmd.append(str(path))
|
@@ -96,7 +103,7 @@ class Rclone:
|
|
96
103
|
return out
|
97
104
|
|
98
105
|
def walk(
|
99
|
-
self, path: Dir | Remote | str, max_depth: int = -1
|
106
|
+
self, path: Dir | Remote | str, max_depth: int = -1, breadth_first: bool = True
|
100
107
|
) -> Generator[DirListing, None, None]:
|
101
108
|
"""Walk through the given path recursively.
|
102
109
|
|
@@ -107,6 +114,7 @@ class Rclone:
|
|
107
114
|
Yields:
|
108
115
|
DirListing: Directory listing for each directory encountered
|
109
116
|
"""
|
117
|
+
dir_obj: Dir
|
110
118
|
if isinstance(path, Dir):
|
111
119
|
# Create a Remote object for the path
|
112
120
|
remote = path.remote
|
@@ -126,9 +134,10 @@ class Rclone:
|
|
126
134
|
elif isinstance(path, Remote):
|
127
135
|
dir_obj = Dir(path)
|
128
136
|
else:
|
137
|
+
dir_obj = Dir(path) # shut up pyright
|
129
138
|
assert f"Invalid type for path: {type(path)}"
|
130
139
|
|
131
|
-
yield from walk(dir_obj, max_depth=max_depth)
|
140
|
+
yield from walk(dir_obj, max_depth=max_depth, breadth_first=breadth_first)
|
132
141
|
|
133
142
|
def copyfile(self, src: File | str, dst: File | str) -> None:
|
134
143
|
"""Copy a single file from source to destination.
|
@@ -9,9 +9,11 @@ from rclone_api.remote import Remote
|
|
9
9
|
_MAX_OUT_QUEUE_SIZE = 50
|
10
10
|
|
11
11
|
|
12
|
-
def
|
13
|
-
|
12
|
+
def _walk_runner_breadth_first(
|
13
|
+
dir: Dir, max_depth: int, out_queue: Queue[DirListing | None]
|
14
14
|
) -> None:
|
15
|
+
queue: Queue[Dir] = Queue()
|
16
|
+
queue.put(dir)
|
15
17
|
try:
|
16
18
|
while not queue.empty():
|
17
19
|
current_dir = queue.get()
|
@@ -35,7 +37,35 @@ def _walk_runner(
|
|
35
37
|
_thread.interrupt_main()
|
36
38
|
|
37
39
|
|
38
|
-
def
|
40
|
+
def _walk_runner_depth_first(
|
41
|
+
dir: Dir, max_depth: int, out_queue: Queue[DirListing | None]
|
42
|
+
) -> None:
|
43
|
+
try:
|
44
|
+
stack = [(dir, max_depth)]
|
45
|
+
while stack:
|
46
|
+
current_dir, depth = stack.pop()
|
47
|
+
dirlisting = current_dir.ls()
|
48
|
+
if depth != 0:
|
49
|
+
for subdir in reversed(
|
50
|
+
dirlisting.dirs
|
51
|
+
): # Process deeper directories first
|
52
|
+
# stack.append((child, depth - 1 if depth > 0 else depth))
|
53
|
+
next_depth = depth - 1 if depth > 0 else depth
|
54
|
+
_walk_runner_depth_first(subdir, next_depth, out_queue)
|
55
|
+
out_queue.put(dirlisting)
|
56
|
+
out_queue.put(None)
|
57
|
+
except KeyboardInterrupt:
|
58
|
+
import _thread
|
59
|
+
|
60
|
+
out_queue.put(None)
|
61
|
+
_thread.interrupt_main()
|
62
|
+
|
63
|
+
|
64
|
+
def walk(
|
65
|
+
dir: Dir | Remote,
|
66
|
+
breadth_first: bool,
|
67
|
+
max_depth: int = -1,
|
68
|
+
) -> Generator[DirListing, None, None]:
|
39
69
|
"""Walk through the given directory recursively.
|
40
70
|
|
41
71
|
Args:
|
@@ -49,14 +79,17 @@ def walk(dir: Dir | Remote, max_depth: int = -1) -> Generator[DirListing, None,
|
|
49
79
|
# Convert Remote to Dir if needed
|
50
80
|
if isinstance(dir, Remote):
|
51
81
|
dir = Dir(dir)
|
52
|
-
|
53
|
-
in_queue: Queue[Dir] = Queue()
|
54
82
|
out_queue: Queue[DirListing] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
|
55
|
-
|
83
|
+
|
84
|
+
strategy = (
|
85
|
+
_walk_runner_breadth_first if breadth_first else _walk_runner_depth_first
|
86
|
+
)
|
56
87
|
|
57
88
|
# Start worker thread
|
58
89
|
worker = Thread(
|
59
|
-
target=
|
90
|
+
target=strategy,
|
91
|
+
args=(dir, max_depth, out_queue),
|
92
|
+
daemon=True,
|
60
93
|
)
|
61
94
|
worker.start()
|
62
95
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: rclone_api
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.19
|
4
4
|
Summary: rclone api in python
|
5
5
|
Home-page: https://github.com/zackees/rclone-api
|
6
6
|
Maintainer: Zachary Vorhies
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Requires-Python: >=3.10
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
+
Requires-Dist: pyright>=1.1.393
|
13
14
|
Requires-Dist: python-dotenv>=1.0.0
|
14
15
|
Dynamic: home-page
|
15
16
|
Dynamic: maintainer
|
@@ -6,17 +6,16 @@ import os
|
|
6
6
|
import unittest
|
7
7
|
from pathlib import Path
|
8
8
|
|
9
|
-
import httpx
|
10
9
|
from dotenv import load_dotenv
|
11
10
|
|
12
|
-
from rclone_api import Process, Rclone, Remote
|
11
|
+
from rclone_api import Config, Process, Rclone, Remote
|
13
12
|
|
14
13
|
load_dotenv()
|
15
14
|
|
16
15
|
BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
|
17
16
|
|
18
17
|
|
19
|
-
def _generate_rclone_config() ->
|
18
|
+
def _generate_rclone_config(port: int) -> Config:
|
20
19
|
|
21
20
|
# BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
|
22
21
|
|
@@ -39,19 +38,17 @@ bucket = {BUCKET_NAME}
|
|
39
38
|
[webdav]
|
40
39
|
type = webdav
|
41
40
|
user = guest
|
42
|
-
#
|
41
|
+
# obscured password for "1234", use Rclone.obscure("1234") to generate
|
43
42
|
pass = d4IbQLV9W0JhI2tm5Zp88hpMtEg
|
44
|
-
url = http://localhost:
|
43
|
+
url = http://localhost:{port}
|
45
44
|
vendor = rclone
|
46
45
|
"""
|
47
46
|
|
48
|
-
|
49
|
-
out = Path("rclone_nfs.conf")
|
50
|
-
out.write_text(config_text, encoding="utf-8")
|
47
|
+
out = Config(config_text)
|
51
48
|
return out
|
52
49
|
|
53
50
|
|
54
|
-
class
|
51
|
+
class RcloneMountWebdavTester(unittest.TestCase):
|
55
52
|
"""Test rclone functionality."""
|
56
53
|
|
57
54
|
def setUp(self) -> None:
|
@@ -68,58 +65,17 @@ class RcloneNfsServeTest(unittest.TestCase):
|
|
68
65
|
f"Missing required environment variables: {', '.join(missing)}"
|
69
66
|
)
|
70
67
|
os.environ["RCLONE_API_VERBOSE"] = "1"
|
71
|
-
self.config = _generate_rclone_config()
|
72
|
-
self.rclone = Rclone(self.config)
|
73
|
-
|
74
|
-
def tearDown(self):
|
75
|
-
if self.config.exists():
|
76
|
-
self.config.unlink()
|
77
|
-
|
78
|
-
def test_serve_webdav(self) -> None:
|
79
|
-
"""Test basic NFS serve functionality."""
|
80
|
-
config = _generate_rclone_config()
|
81
|
-
rclone = Rclone(config)
|
82
|
-
|
83
|
-
# Start NFS server for the remote
|
84
|
-
remote = Remote("dst", rclone=rclone)
|
85
|
-
# serve = Remote("webdav", rclone=rclone)
|
86
|
-
test_addr = "localhost:8089"
|
87
|
-
user = "guest"
|
88
|
-
password = "1234"
|
89
|
-
|
90
|
-
process = rclone.serve_webdav(
|
91
|
-
f"{remote.name}:{BUCKET_NAME}", addr=test_addr, user=user, password=password
|
92
|
-
)
|
93
|
-
mount_proc: Process | None = None
|
94
|
-
|
95
|
-
try:
|
96
|
-
# Verify process is running
|
97
|
-
self.assertIsNone(process.poll())
|
98
|
-
response = httpx.get(f"http://{test_addr}/", auth=(user, password))
|
99
|
-
# Note that windows is kinda broken and returns internal server error
|
100
|
-
is_serving = response.status_code == 200
|
101
|
-
self.assertTrue(is_serving)
|
102
|
-
|
103
|
-
finally:
|
104
|
-
# Clean up
|
105
|
-
process.terminate()
|
106
|
-
process.wait()
|
107
|
-
if mount_proc:
|
108
|
-
mount_proc.terminate()
|
109
|
-
mount_proc.wait()
|
110
|
-
|
111
|
-
# Verify process terminated
|
112
|
-
self.assertIsNotNone(process.poll())
|
113
68
|
|
114
69
|
def test_serve_webdav_and_mount(self) -> None:
|
115
70
|
"""Test basic NFS serve functionality."""
|
116
|
-
|
71
|
+
port = 8090
|
72
|
+
config = _generate_rclone_config(port)
|
117
73
|
rclone = Rclone(config)
|
118
74
|
|
119
75
|
# Start NFS server for the remote
|
120
76
|
remote = Remote("dst", rclone=rclone)
|
121
77
|
# serve = Remote("webdav", rclone=rclone)
|
122
|
-
test_addr = "localhost:
|
78
|
+
test_addr = f"localhost:{port}"
|
123
79
|
user = "guest"
|
124
80
|
password = "1234"
|
125
81
|
|
@@ -136,8 +92,8 @@ class RcloneNfsServeTest(unittest.TestCase):
|
|
136
92
|
# test that the mount point exists
|
137
93
|
self.assertTrue(mount_point.exists())
|
138
94
|
# test the folder is not empty
|
139
|
-
|
140
|
-
self.
|
95
|
+
next_path = next(mount_point.iterdir())
|
96
|
+
self.assertIsNotNone(next_path)
|
141
97
|
|
142
98
|
finally:
|
143
99
|
# Clean up
|
@@ -7,7 +7,7 @@ import unittest
|
|
7
7
|
|
8
8
|
from dotenv import load_dotenv
|
9
9
|
|
10
|
-
from rclone_api import Config,
|
10
|
+
from rclone_api import Config, Rclone
|
11
11
|
|
12
12
|
load_dotenv()
|
13
13
|
|
@@ -31,13 +31,14 @@ provider = DigitalOcean
|
|
31
31
|
access_key_id = {BUCKET_KEY_PUBLIC}
|
32
32
|
secret_access_key = {BUCKET_KEY_SECRET}
|
33
33
|
endpoint = {BUCKET_URL}
|
34
|
+
bucket = {BUCKET_NAME}
|
34
35
|
"""
|
35
36
|
|
36
37
|
out = Config(config_text)
|
37
38
|
return out
|
38
39
|
|
39
40
|
|
40
|
-
class
|
41
|
+
class RcloneLsTests(unittest.TestCase):
|
41
42
|
"""Test rclone functionality."""
|
42
43
|
|
43
44
|
def setUp(self) -> None:
|
@@ -55,13 +56,11 @@ class RcloneWalkTest(unittest.TestCase):
|
|
55
56
|
)
|
56
57
|
os.environ["RCLONE_API_VERBOSE"] = "1"
|
57
58
|
|
58
|
-
def
|
59
|
+
def test_list_remotes(self) -> None:
|
59
60
|
rclone = Rclone(_generate_rclone_config())
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
print(dirlisting)
|
64
|
-
print("done")
|
61
|
+
obscured = rclone.obscure("1234")
|
62
|
+
self.assertNotEqual(obscured, "1234")
|
63
|
+
print()
|
65
64
|
|
66
65
|
|
67
66
|
if __name__ == "__main__":
|
@@ -0,0 +1,108 @@
|
|
1
|
+
"""
|
2
|
+
Unit test file.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import unittest
|
7
|
+
|
8
|
+
import httpx
|
9
|
+
from dotenv import load_dotenv
|
10
|
+
|
11
|
+
from rclone_api import Config, Process, Rclone, Remote
|
12
|
+
|
13
|
+
load_dotenv()
|
14
|
+
|
15
|
+
BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
|
16
|
+
|
17
|
+
|
18
|
+
def _generate_rclone_config(port: int) -> Config:
|
19
|
+
|
20
|
+
# BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
|
21
|
+
|
22
|
+
# Load additional environment variables
|
23
|
+
BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
|
24
|
+
BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
|
25
|
+
# BUCKET_URL = os.getenv("BUCKET_URL")
|
26
|
+
BUCKET_URL = "sfo3.digitaloceanspaces.com"
|
27
|
+
|
28
|
+
config_text = f"""
|
29
|
+
[dst]
|
30
|
+
type = s3
|
31
|
+
provider = DigitalOcean
|
32
|
+
access_key_id = {BUCKET_KEY_PUBLIC}
|
33
|
+
secret_access_key = {BUCKET_KEY_SECRET}
|
34
|
+
endpoint = {BUCKET_URL}
|
35
|
+
bucket = {BUCKET_NAME}
|
36
|
+
|
37
|
+
|
38
|
+
[webdav]
|
39
|
+
type = webdav
|
40
|
+
user = guest
|
41
|
+
# obscured password for "1234", use Rclone.obscure("1234") to generate
|
42
|
+
pass = d4IbQLV9W0JhI2tm5Zp88hpMtEg
|
43
|
+
url = http://localhost:{port}
|
44
|
+
vendor = rclone
|
45
|
+
"""
|
46
|
+
|
47
|
+
out = Config(config_text)
|
48
|
+
return out
|
49
|
+
|
50
|
+
|
51
|
+
class RcloneServeWebdavTester(unittest.TestCase):
|
52
|
+
"""Test rclone functionality."""
|
53
|
+
|
54
|
+
def setUp(self) -> None:
|
55
|
+
"""Check if all required environment variables are set before running tests."""
|
56
|
+
required_vars = [
|
57
|
+
"BUCKET_NAME",
|
58
|
+
"BUCKET_KEY_SECRET",
|
59
|
+
"BUCKET_KEY_PUBLIC",
|
60
|
+
"BUCKET_URL",
|
61
|
+
]
|
62
|
+
missing = [var for var in required_vars if not os.getenv(var)]
|
63
|
+
if missing:
|
64
|
+
self.skipTest(
|
65
|
+
f"Missing required environment variables: {', '.join(missing)}"
|
66
|
+
)
|
67
|
+
os.environ["RCLONE_API_VERBOSE"] = "1"
|
68
|
+
|
69
|
+
def test_serve_webdav(self) -> None:
|
70
|
+
"""Test basic NFS serve functionality."""
|
71
|
+
port = 8089
|
72
|
+
config = _generate_rclone_config(port)
|
73
|
+
rclone = Rclone(config)
|
74
|
+
|
75
|
+
# Start NFS server for the remote
|
76
|
+
remote = Remote("dst", rclone=rclone)
|
77
|
+
# serve = Remote("webdav", rclone=rclone)
|
78
|
+
test_addr = f"localhost:{port}"
|
79
|
+
user = "guest"
|
80
|
+
password = "1234"
|
81
|
+
|
82
|
+
process = rclone.serve_webdav(
|
83
|
+
f"{remote.name}:{BUCKET_NAME}", addr=test_addr, user=user, password=password
|
84
|
+
)
|
85
|
+
mount_proc: Process | None = None
|
86
|
+
|
87
|
+
try:
|
88
|
+
# Verify process is running
|
89
|
+
self.assertIsNone(process.poll())
|
90
|
+
response = httpx.get(f"http://{test_addr}/", auth=(user, password))
|
91
|
+
# Note that windows is kinda broken and returns internal server error
|
92
|
+
is_serving = response.status_code == 200
|
93
|
+
self.assertTrue(is_serving)
|
94
|
+
|
95
|
+
finally:
|
96
|
+
# Clean up
|
97
|
+
process.terminate()
|
98
|
+
process.wait()
|
99
|
+
if mount_proc:
|
100
|
+
mount_proc.terminate()
|
101
|
+
mount_proc.wait()
|
102
|
+
|
103
|
+
# Verify process terminated
|
104
|
+
self.assertIsNotNone(process.poll())
|
105
|
+
|
106
|
+
|
107
|
+
if __name__ == "__main__":
|
108
|
+
unittest.main()
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
Unit test file.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import unittest
|
7
|
+
|
8
|
+
from dotenv import load_dotenv
|
9
|
+
|
10
|
+
from rclone_api import Config, DirListing, Rclone
|
11
|
+
|
12
|
+
load_dotenv()
|
13
|
+
|
14
|
+
BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
|
15
|
+
|
16
|
+
|
17
|
+
def _generate_rclone_config() -> Config:
|
18
|
+
|
19
|
+
# BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
|
20
|
+
|
21
|
+
# Load additional environment variables
|
22
|
+
BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
|
23
|
+
BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
|
24
|
+
# BUCKET_URL = os.getenv("BUCKET_URL")
|
25
|
+
BUCKET_URL = "sfo3.digitaloceanspaces.com"
|
26
|
+
|
27
|
+
config_text = f"""
|
28
|
+
[dst]
|
29
|
+
type = s3
|
30
|
+
provider = DigitalOcean
|
31
|
+
access_key_id = {BUCKET_KEY_PUBLIC}
|
32
|
+
secret_access_key = {BUCKET_KEY_SECRET}
|
33
|
+
endpoint = {BUCKET_URL}
|
34
|
+
"""
|
35
|
+
|
36
|
+
out = Config(config_text)
|
37
|
+
return out
|
38
|
+
|
39
|
+
|
40
|
+
class RcloneWalkTest(unittest.TestCase):
|
41
|
+
"""Test rclone functionality."""
|
42
|
+
|
43
|
+
def setUp(self) -> None:
|
44
|
+
"""Check if all required environment variables are set before running tests."""
|
45
|
+
required_vars = [
|
46
|
+
"BUCKET_NAME",
|
47
|
+
"BUCKET_KEY_SECRET",
|
48
|
+
"BUCKET_KEY_PUBLIC",
|
49
|
+
"BUCKET_URL",
|
50
|
+
]
|
51
|
+
missing = [var for var in required_vars if not os.getenv(var)]
|
52
|
+
if missing:
|
53
|
+
self.skipTest(
|
54
|
+
f"Missing required environment variables: {', '.join(missing)}"
|
55
|
+
)
|
56
|
+
os.environ["RCLONE_API_VERBOSE"] = "1"
|
57
|
+
|
58
|
+
def test_walk(self) -> None:
|
59
|
+
rclone = Rclone(_generate_rclone_config())
|
60
|
+
# rclone.walk
|
61
|
+
dirlisting: DirListing
|
62
|
+
is_first = True
|
63
|
+
for dirlisting in rclone.walk(f"dst:{BUCKET_NAME}", max_depth=1):
|
64
|
+
if is_first:
|
65
|
+
# assert just one file
|
66
|
+
# assert len(dirlisting.files) == 1
|
67
|
+
self.assertEqual(len(dirlisting.files), 1)
|
68
|
+
# assert it's first.txt
|
69
|
+
self.assertEqual(dirlisting.files[0].name, "first.txt")
|
70
|
+
is_first = False
|
71
|
+
print(dirlisting)
|
72
|
+
print("done")
|
73
|
+
|
74
|
+
def test_walk_depth_first(self) -> None:
|
75
|
+
rclone = Rclone(_generate_rclone_config())
|
76
|
+
# rclone.walk
|
77
|
+
dirlisting: DirListing
|
78
|
+
for dirlisting in rclone.walk(
|
79
|
+
f"dst:{BUCKET_NAME}", max_depth=1, breadth_first=False
|
80
|
+
):
|
81
|
+
print(dirlisting)
|
82
|
+
print("done")
|
83
|
+
|
84
|
+
|
85
|
+
if __name__ == "__main__":
|
86
|
+
unittest.main()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|