rclone-api 1.0.0__tar.gz → 1.0.2__tar.gz

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.
Files changed (33) hide show
  1. rclone_api-1.0.2/.gitignore +141 -0
  2. rclone_api-1.0.2/.pylintrc +2 -0
  3. rclone_api-1.0.2/.vscode/launch.json +44 -0
  4. rclone_api-1.0.2/.vscode/settings.json +29 -0
  5. rclone_api-1.0.2/.vscode/tasks.json +41 -0
  6. {rclone_api-1.0.0/src/rclone_api.egg-info → rclone_api-1.0.2}/PKG-INFO +1 -1
  7. rclone_api-1.0.2/clean +21 -0
  8. rclone_api-1.0.2/install +37 -0
  9. rclone_api-1.0.2/lint +15 -0
  10. {rclone_api-1.0.0 → rclone_api-1.0.2}/pyproject.toml +1 -1
  11. rclone_api-1.0.2/src/rclone_api/__init__.py +4 -0
  12. rclone_api-1.0.2/src/rclone_api/file.py +77 -0
  13. rclone_api-1.0.2/src/rclone_api/rclone.py +43 -0
  14. rclone_api-1.0.2/src/rclone_api/types.py +34 -0
  15. rclone_api-1.0.2/src/rclone_api/util.py +49 -0
  16. {rclone_api-1.0.0 → rclone_api-1.0.2/src/rclone_api.egg-info}/PKG-INFO +1 -1
  17. {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api.egg-info/SOURCES.txt +14 -0
  18. rclone_api-1.0.2/test +5 -0
  19. rclone_api-1.0.2/tox.ini +24 -0
  20. rclone_api-1.0.2/upload_package.sh +10 -0
  21. rclone_api-1.0.0/src/rclone_api/__init__.py +0 -0
  22. rclone_api-1.0.0/src/rclone_api/rclone.py +0 -160
  23. {rclone_api-1.0.0 → rclone_api-1.0.2}/LICENSE +0 -0
  24. {rclone_api-1.0.0 → rclone_api-1.0.2}/MANIFEST.in +0 -0
  25. {rclone_api-1.0.0 → rclone_api-1.0.2}/README.md +0 -0
  26. {rclone_api-1.0.0 → rclone_api-1.0.2}/requirements.testing.txt +0 -0
  27. {rclone_api-1.0.0 → rclone_api-1.0.2}/setup.cfg +0 -0
  28. {rclone_api-1.0.0 → rclone_api-1.0.2}/setup.py +0 -0
  29. {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api/assets/example.txt +0 -0
  30. {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api/cli.py +0 -0
  31. {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  32. {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api.egg-info/top_level.txt +0 -0
  33. {rclone_api-1.0.0 → rclone_api-1.0.2}/tests/test_simple.py +0 -0
@@ -0,0 +1,141 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.pyc
6
+
7
+ # Generated commands
8
+ /zcmds/bin/
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ prod_env/
16
+ data/
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ pip-wheel-metadata/
30
+ share/python-wheels/
31
+ *.egg-info/
32
+ .installed.cfg
33
+ *.egg
34
+ MANIFEST
35
+
36
+ # PyInstaller
37
+ # Usually these files are written by a python script from a template
38
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
39
+ *.manifest
40
+ *.spec
41
+
42
+ # Installer logs
43
+ pip-log.txt
44
+ pip-delete-this-directory.txt
45
+
46
+ # Unit test / coverage reports
47
+ htmlcov/
48
+ .tox/
49
+ .nox/
50
+ .coverage
51
+ .coverage.*
52
+ .cache
53
+ nosetests.xml
54
+ coverage.xml
55
+ *.cover
56
+ *.py,cover
57
+ .hypothesis/
58
+ .pytest_cache/
59
+
60
+ # Translations
61
+ *.mo
62
+ *.pot
63
+
64
+ # Django stuff:
65
+ *.log
66
+ local_settings.py
67
+ db.sqlite3
68
+ db.sqlite3-journal
69
+
70
+ # Flask stuff:
71
+ instance/
72
+ .webassets-cache
73
+
74
+ # Scrapy stuff:
75
+ .scrapy
76
+
77
+ # Sphinx documentation
78
+ docs/_build/
79
+
80
+ # PyBuilder
81
+ target/
82
+
83
+ # Jupyter Notebook
84
+ .ipynb_checkpoints
85
+
86
+ # IPython
87
+ profile_default/
88
+ ipython_config.py
89
+
90
+ # pyenv
91
+ .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
101
+ __pypackages__/
102
+
103
+ # Celery stuff
104
+ celerybeat-schedule
105
+ celerybeat.pid
106
+
107
+ # SageMath parsed files
108
+ *.sage.py
109
+
110
+ # Environments
111
+ .env
112
+ .venv
113
+ env/
114
+ venv/
115
+ ENV/
116
+ env.bak/
117
+ venv.bak/
118
+
119
+ # Spyder project settings
120
+ .spyderproject
121
+ .spyproject
122
+
123
+ # Rope project settings
124
+ .ropeproject
125
+
126
+ # mkdocs documentation
127
+ /site
128
+
129
+ # mypy
130
+ .mypy_cache/
131
+ .dmypy.json
132
+ dmypy.json
133
+
134
+ # Pyre type checker
135
+ .pyre/
136
+ .activate.sh
137
+ activate
138
+
139
+ # Generated by mac
140
+ **/.DS_Store
141
+ uv.lock
@@ -0,0 +1,2 @@
1
+ [MESSAGES CONTROL]
2
+ disable=missing-module-docstring,missing-class-docstring,missing-function-docstring,line-too-long,raise-missing-from,too-few-public-methods,too-many-return-statements,fixme,too-many-locals,too-many-branches,too-many-statements,too-many-arguments,too-many-instance-attributes,too-many-ancestors,too-many-lines,too-many-public-methods,too-many-boolean-expressions,too-many-locals,too-many-branches,too-many-statements,too-many-arguments,too-many-instance-attributes,too-many-ancestors,too-many-lines,too-many-public-methods,too-many-boolean-expressions,R0801
@@ -0,0 +1,44 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python: Current File (Integrated Terminal)",
9
+ "type": "python",
10
+ "request": "launch",
11
+ "subProcess": true,
12
+ "program": "${file}",
13
+ "console": "integratedTerminal",
14
+ "justMyCode": false
15
+ },
16
+ {
17
+ "name": "Python: Remote Attach",
18
+ "type": "python",
19
+ "request": "attach",
20
+ "port": 5678,
21
+ "host": "localhost",
22
+ "pathMappings": [
23
+ {
24
+ "localRoot": "${workspaceFolder}",
25
+ "remoteRoot": "."
26
+ }
27
+ ]
28
+ },
29
+ {
30
+ "name": "Python: Module",
31
+ "type": "python",
32
+ "request": "launch",
33
+ "module": "enter-your-module-name-here",
34
+ "console": "integratedTerminal"
35
+ },
36
+ {
37
+ "name": "Python: Current File (External Terminal)",
38
+ "type": "python",
39
+ "request": "launch",
40
+ "program": "${file}",
41
+ "console": "externalTerminal"
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "python.autoComplete.extraPaths": [
3
+ "."
4
+ ],
5
+ "python.linting.pylintEnabled": true,
6
+ "python.linting.enabled": true,
7
+ "terminal.integrated.defaultProfile.windows": "Git Bash",
8
+ "terminal.integrated.profiles.windows": {
9
+ "Git Bash": {
10
+ "path": "C:\\Program Files\\Git\\bin\\bash.exe",
11
+ "args": ["--cd=."]
12
+ }
13
+ },
14
+ // adds activate virtualenv to terminal
15
+ "terminal.integrated.env.windows": {
16
+ "VIRTUAL_ENV": "${workspaceFolder}/venv"
17
+ },
18
+ "files.eol": "\n", // Unix
19
+ "editor.tabSize": 4,
20
+ "editor.insertSpaces": true,
21
+ "editor.detectIndentation": false,
22
+ "editor.formatOnSave": false,
23
+ "python.formatting.provider": "black",
24
+ "python.formatting.blackArgs": [
25
+ ],
26
+ "python.analysis.extraPaths": [
27
+ "."
28
+ ]
29
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
3
+ // for the documentation about the tasks.json format
4
+ "version": "2.0.0",
5
+ "tasks": [
6
+ {
7
+ "label": "Lint",
8
+ "type": "shell",
9
+ "command": ". ./activate.sh && ./lint.sh",
10
+ "group": "build",
11
+ "options": {
12
+ "cwd": "${workspaceRoot}"
13
+ },
14
+ "presentation": {
15
+ "echo": true,
16
+ "reveal": "always",
17
+ "focus": true,
18
+ "panel": "shared",
19
+ "clear": true
20
+ },
21
+ "problemMatcher": []
22
+ },
23
+ {
24
+ "label": "Tox",
25
+ "type": "shell",
26
+ "command": "tox",
27
+ "group": "build",
28
+ "options": {
29
+ "cwd": "${workspaceRoot}"
30
+ },
31
+ "presentation": {
32
+ "echo": true,
33
+ "reveal": "always",
34
+ "focus": true,
35
+ "panel": "shared",
36
+ "clear": true
37
+ },
38
+ "problemMatcher": []
39
+ },
40
+ ]
41
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
rclone_api-1.0.2/clean ADDED
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ set -x
3
+
4
+ rm -rf build
5
+ rm -rf dist
6
+ rm -rf venv
7
+ rm -rf .venv
8
+
9
+ rm -rf *.egg-info
10
+ rm -rf .eggs
11
+ rm -rf .tox
12
+ rm -rf .cache
13
+ rm -rf .pytest_cache
14
+ rm -rf .mypy_cache
15
+ rm -rf .coverage
16
+
17
+ # remove all *.pyc files
18
+ find . -name "*.pyc" -exec rm -rf {} \;
19
+ # remove all *.egg files
20
+ find . -name "*.egg" -exec rm -rf {} \;
21
+
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Check if UV is not found
5
+ if ! command -v uv &> /dev/null; then
6
+ # If Darwin (macOS), use brew to install UV
7
+ if [[ "$OSTYPE" == "darwin"* ]]; then
8
+ brew install uv
9
+ else
10
+ # If it's Windows, use pip to install UV, else use pip3
11
+ if [[ "$OSTYPE" == "msys" ]]; then
12
+ pip install uv
13
+ else
14
+ pip3 install uv
15
+ fi
16
+ fi
17
+ fi
18
+
19
+ uv venv --python 3.11 --seed
20
+ uv run pip install -e .
21
+
22
+ # If requirements.testing.txt exists, then install it
23
+ if [[ -f requirements.testing.txt ]]; then
24
+ uv run pip install -r requirements.testing.txt
25
+ fi
26
+
27
+ # If activate exists, delete it
28
+ if [[ -f activate ]]; then
29
+ rm activate
30
+ fi
31
+
32
+ # If Windows, then symlink .venv/Scripts/activate to .venv/bin/activate
33
+ if [[ "$OSTYPE" == "msys" ]]; then
34
+ ln -s .venv/Scripts/activate ./activate
35
+ else
36
+ ln -s .venv/bin/activate ./activate
37
+ fi
rclone_api-1.0.2/lint ADDED
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo Running ruff src
5
+ uv run ruff check --fix src
6
+ echo Running ruff tests
7
+ uv run ruff check --fix tests
8
+ echo Running black src tests
9
+ uv run black src tests
10
+ echo Running isort src tests
11
+ uv run isort --profile black src tests
12
+ echo Running mypy src
13
+ uv run mypy src tests
14
+ echo Linting complete!
15
+ exit 0
@@ -14,7 +14,7 @@ dependencies = [
14
14
 
15
15
  ]
16
16
  # Change this with the version number bump.
17
- version = "1.0.0"
17
+ version = "1.0.2"
18
18
 
19
19
  [tool.setuptools]
20
20
  package-dir = {"" = "src"}
@@ -0,0 +1,4 @@
1
+ from .rclone import File, Rclone
2
+ from .types import Config, Remote
3
+
4
+ __all__ = ["Rclone", "File", "Config", "Remote"]
@@ -0,0 +1,77 @@
1
+ import json
2
+ from typing import Any
3
+
4
+
5
+ class File:
6
+ """Remote file dataclass."""
7
+
8
+ def __init__(
9
+ self,
10
+ path: str,
11
+ name: str,
12
+ size: int,
13
+ mime_type: str,
14
+ mod_time: str,
15
+ is_dir: bool,
16
+ ) -> None:
17
+ from rclone_api.rclone import Rclone
18
+
19
+ self.path = path
20
+ self.name = name
21
+ self.size = size
22
+ self.mime_type = mime_type
23
+ self.mod_time = mod_time
24
+ self.is_dir = is_dir
25
+ self.rclone: Rclone | None = None
26
+
27
+ def set_rclone(self, rclone: Any) -> None:
28
+ """Set the rclone object."""
29
+ from rclone_api.rclone import Rclone
30
+
31
+ assert isinstance(rclone, Rclone)
32
+ self.rclone = rclone
33
+
34
+ @staticmethod
35
+ def from_dict(data: dict) -> "File":
36
+ """Create a File from a dictionary."""
37
+ return File(
38
+ data["Path"],
39
+ data["Name"],
40
+ data["Size"],
41
+ data["MimeType"],
42
+ data["ModTime"],
43
+ data["IsDir"],
44
+ # data["IsBucket"],
45
+ )
46
+
47
+ @staticmethod
48
+ def from_array(data: list[dict]) -> list["File"]:
49
+ """Create a File from a dictionary."""
50
+ out: list[File] = []
51
+ for d in data:
52
+ file: File = File.from_dict(d)
53
+ out.append(file)
54
+ return out
55
+
56
+ @staticmethod
57
+ def from_json_str(json_str: str) -> list["File"]:
58
+ """Create a File from a JSON string."""
59
+ json_obj = json.loads(json_str)
60
+ if isinstance(json_obj, dict):
61
+ return [File.from_dict(json_obj)]
62
+ return File.from_array(json_obj)
63
+
64
+ def to_json(self) -> dict:
65
+ return {
66
+ "Path": self.path,
67
+ "Name": self.name,
68
+ "Size": self.size,
69
+ "MimeType": self.mime_type,
70
+ "ModTime": self.mod_time,
71
+ "IsDir": self.is_dir,
72
+ # "IsBucket": self.is_bucket,
73
+ }
74
+
75
+ def __str__(self) -> str:
76
+ out = self.to_json()
77
+ return json.dumps(out)
@@ -0,0 +1,43 @@
1
+ """
2
+ Unit test file.
3
+ """
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from rclone_api.file import File
9
+ from rclone_api.types import Config, RcloneExec, Remote
10
+ from rclone_api.util import get_rclone_exe
11
+
12
+
13
+ class Rclone:
14
+ def __init__(
15
+ self, rclone_conf: Path | Config, rclone_exe: Path | None = None
16
+ ) -> None:
17
+ if isinstance(rclone_conf, Path):
18
+ if not rclone_conf.exists():
19
+ raise ValueError(f"Rclone config file not found: {rclone_conf}")
20
+ self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
21
+
22
+ def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
23
+ return self._exec.execute(cmd)
24
+
25
+ def ls(self, path: str | Remote) -> list[File]:
26
+ cmd = ["lsjson", str(path)]
27
+ cp = self._run(cmd)
28
+ text = cp.stdout
29
+ out: list[File] = File.from_json_str(text)
30
+ for o in out:
31
+ o.set_rclone(self)
32
+ return out
33
+
34
+ def listremotes(self) -> list[Remote]:
35
+ cmd = ["listremotes"]
36
+ cp = self._run(cmd)
37
+ text: str = cp.stdout
38
+ tmp = text.splitlines()
39
+ tmp = [t.strip() for t in tmp]
40
+ # strip out ":" from the end
41
+ tmp = [t.replace(":", "") for t in tmp]
42
+ out = [Remote(name=t) for t in tmp]
43
+ return out
@@ -0,0 +1,34 @@
1
+ import subprocess
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+
6
+ @dataclass
7
+ class Config:
8
+ """Rclone configuration dataclass."""
9
+
10
+ text: str
11
+
12
+
13
+ @dataclass
14
+ class RcloneExec:
15
+ """Rclone execution dataclass."""
16
+
17
+ rclone_config: Path | Config
18
+ rclone_exe: Path
19
+
20
+ def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
21
+ """Execute rclone command."""
22
+ from rclone_api.util import rclone_execute
23
+
24
+ return rclone_execute(cmd, self.rclone_config, self.rclone_exe)
25
+
26
+
27
+ class Remote:
28
+ """Remote dataclass."""
29
+
30
+ def __init__(self, name: str) -> None:
31
+ self.name = name
32
+
33
+ def __str__(self) -> str:
34
+ return f"{self.name}:"
@@ -0,0 +1,49 @@
1
+ import shutil
2
+ import subprocess
3
+ from pathlib import Path
4
+ from tempfile import TemporaryDirectory
5
+
6
+ from rclone_api.types import Config
7
+
8
+
9
+ def get_rclone_exe(rclone_exe: Path | None) -> Path:
10
+ if rclone_exe is None:
11
+
12
+ rclone_which_path = shutil.which("rclone")
13
+ if rclone_which_path is None:
14
+ raise ValueError("rclone executable not found")
15
+ return Path(rclone_which_path)
16
+ return rclone_exe
17
+
18
+
19
+ def rclone_execute(
20
+ cmd: list[str],
21
+ rclone_conf: Path | Config,
22
+ rclone_exe: Path,
23
+ verbose: bool = False,
24
+ ) -> subprocess.CompletedProcess:
25
+ print(subprocess.list2cmdline(cmd))
26
+ tempdir: TemporaryDirectory | None = None
27
+
28
+ try:
29
+ if isinstance(rclone_conf, Config):
30
+ tempdir = TemporaryDirectory()
31
+ tmpfile = Path(tempdir.name) / "rclone.conf"
32
+ tmpfile.write_text(rclone_conf.text, encoding="utf-8")
33
+ rclone_conf = tmpfile
34
+ cmd = (
35
+ [str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
36
+ )
37
+ if verbose:
38
+ cmd_str = subprocess.list2cmdline(cmd)
39
+ print(f"Running: {cmd_str}")
40
+ cp = subprocess.run(
41
+ cmd, capture_output=True, encoding="utf-8", check=True, shell=False
42
+ )
43
+ return cp
44
+ finally:
45
+ if tempdir:
46
+ try:
47
+ tempdir.cleanup()
48
+ except Exception as e:
49
+ print(f"Error cleaning up tempdir: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -1,12 +1,26 @@
1
+ .gitignore
2
+ .pylintrc
1
3
  LICENSE
2
4
  MANIFEST.in
3
5
  README.md
6
+ clean
7
+ install
8
+ lint
4
9
  pyproject.toml
5
10
  requirements.testing.txt
6
11
  setup.py
12
+ test
13
+ tox.ini
14
+ upload_package.sh
15
+ .vscode/launch.json
16
+ .vscode/settings.json
17
+ .vscode/tasks.json
7
18
  src/rclone_api/__init__.py
8
19
  src/rclone_api/cli.py
20
+ src/rclone_api/file.py
9
21
  src/rclone_api/rclone.py
22
+ src/rclone_api/types.py
23
+ src/rclone_api/util.py
10
24
  src/rclone_api.egg-info/PKG-INFO
11
25
  src/rclone_api.egg-info/SOURCES.txt
12
26
  src/rclone_api.egg-info/dependency_links.txt
rclone_api-1.0.2/test ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+ echo "Running unittests"
5
+ uv run pytest -n auto tests -v
@@ -0,0 +1,24 @@
1
+ # content of: tox.ini , put in same dir as setup.py
2
+ [tox]
3
+ envlist = py310
4
+
5
+ [gh-actions]
6
+ python =
7
+ 3.10: py310
8
+
9
+ [flake8]
10
+ per-file-ignores = __init__.py:F401
11
+ ignore = E501, E203, W503
12
+
13
+ [testenv]
14
+ # install pytest in the virtualenv where commands will be executed
15
+ deps =
16
+ -r{toxinidir}/requirements.testing.txt
17
+ commands =
18
+ # NOTE: you can run any command line tool here - not just tests
19
+ ruff src
20
+ ruff tests
21
+ flake8 src tests
22
+ pylint src tests
23
+ mypy src tests
24
+ pytest tests -n auto -vv
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ set -e
3
+ rm -rf build dist
4
+ . ./activate
5
+ pip install wheel twine
6
+ echo "Building Source and Wheel (universal) distribution…"
7
+ python setup.py sdist bdist_wheel --universal
8
+ echo "Uploading the package to PyPI via Twine…"
9
+ twine upload dist/* --verbose
10
+ # echo Pushing git tags…
File without changes
@@ -1,160 +0,0 @@
1
- """
2
- Unit test file.
3
- """
4
-
5
- import json
6
- import shutil
7
- import subprocess
8
- from dataclasses import dataclass
9
- from pathlib import Path
10
- from tempfile import TemporaryDirectory
11
-
12
-
13
- @dataclass
14
- class RcloneConfig:
15
- """Rclone configuration dataclass."""
16
-
17
- text: str
18
-
19
-
20
- def _rclone_execute(
21
- cmd: list[str],
22
- rclone_conf: Path | RcloneConfig,
23
- rclone_exe: Path,
24
- verbose: bool = False,
25
- ) -> subprocess.CompletedProcess:
26
- print(subprocess.list2cmdline(cmd))
27
- tempdir: TemporaryDirectory | None = None
28
-
29
- try:
30
- if isinstance(rclone_conf, RcloneConfig):
31
- tempdir = TemporaryDirectory()
32
- tmpfile = Path(tempdir.name) / "rclone.conf"
33
- tmpfile.write_text(rclone_conf.text, encoding="utf-8")
34
- rclone_conf = tmpfile
35
- cmd = (
36
- [str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
37
- )
38
- if verbose:
39
- cmd_str = subprocess.list2cmdline(cmd)
40
- print(f"Running: {cmd_str}")
41
- cp = subprocess.run(
42
- cmd, capture_output=True, encoding="utf-8", check=True, shell=False
43
- )
44
- return cp
45
- finally:
46
- if tempdir:
47
- try:
48
- tempdir.cleanup()
49
- except Exception as e:
50
- print(f"Error cleaning up tempdir: {e}")
51
-
52
-
53
- @dataclass
54
- class RcloneExec:
55
- """Rclone execution dataclass."""
56
-
57
- rclone_config: Path | RcloneConfig
58
- rclone_exe: Path
59
-
60
- def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
61
- """Execute rclone command."""
62
- return _rclone_execute(cmd, self.rclone_config, self.rclone_exe)
63
-
64
-
65
- @dataclass
66
- class RemoteFile:
67
- """Remote file dataclass."""
68
-
69
- path: str
70
- name: str
71
- size: int
72
- mime_type: str
73
- mod_time: str
74
- is_dir: bool
75
- # is_bucket: bool
76
-
77
- @staticmethod
78
- def from_dict(data: dict) -> "RemoteFile":
79
- """Create a RemoteFile from a dictionary."""
80
- return RemoteFile(
81
- data["Path"],
82
- data["Name"],
83
- data["Size"],
84
- data["MimeType"],
85
- data["ModTime"],
86
- data["IsDir"],
87
- # data["IsBucket"],
88
- )
89
-
90
- @staticmethod
91
- def from_array(data: list[dict]) -> list["RemoteFile"]:
92
- """Create a RemoteFile from a dictionary."""
93
- out: list[RemoteFile] = []
94
- for d in data:
95
- file: RemoteFile = RemoteFile.from_dict(d)
96
- out.append(file)
97
- return out
98
-
99
- @staticmethod
100
- def from_json_str(json_str: str) -> list["RemoteFile"]:
101
- """Create a RemoteFile from a JSON string."""
102
- json_obj = json.loads(json_str)
103
- if isinstance(json_obj, dict):
104
- return [RemoteFile.from_dict(json_obj)]
105
- return RemoteFile.from_array(json_obj)
106
-
107
- def to_json(self) -> dict:
108
- return {
109
- "Path": self.path,
110
- "Name": self.name,
111
- "Size": self.size,
112
- "MimeType": self.mime_type,
113
- "ModTime": self.mod_time,
114
- "IsDir": self.is_dir,
115
- # "IsBucket": self.is_bucket,
116
- }
117
-
118
- def __str__(self) -> str:
119
- out = self.to_json()
120
- return json.dumps(out)
121
-
122
-
123
- def _get_rclone_exe(rclone_exe: Path | None) -> Path:
124
- if rclone_exe is None:
125
-
126
- rclone_which_path = shutil.which("rclone")
127
- if rclone_which_path is None:
128
- raise ValueError("rclone executable not found")
129
- return Path(rclone_which_path)
130
- return rclone_exe
131
-
132
-
133
- class Rclone:
134
- def __init__(
135
- self, rclone_conf: Path | RcloneConfig, rclone_exe: Path | None = None
136
- ) -> None:
137
- if isinstance(rclone_conf, Path):
138
- if not rclone_conf.exists():
139
- raise ValueError(f"Rclone config file not found: {rclone_conf}")
140
- self._exec = RcloneExec(rclone_conf, _get_rclone_exe(rclone_exe))
141
-
142
- def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
143
- return self._exec.execute(cmd)
144
-
145
- def ls(self, path: str) -> list[RemoteFile]:
146
- cmd = ["lsjson", path]
147
- cp = self._run(cmd)
148
- text = cp.stdout
149
- out: list[RemoteFile] = RemoteFile.from_json_str(text)
150
- return out
151
-
152
- def listremotes(self) -> list[str]:
153
- cmd = ["listremotes"]
154
- cp = self._run(cmd)
155
- text = cp.stdout
156
- out = text.splitlines()
157
- out = [o.strip() for o in out]
158
- # strip out ":" from the end
159
- out = [o.replace(":", "") for o in out]
160
- return out
File without changes
File without changes
File without changes
File without changes
File without changes