rclone-api 1.0.12__tar.gz → 1.0.14__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. rclone_api-1.0.14/.github/workflows/lint.yml +35 -0
  2. rclone_api-1.0.14/.github/workflows/push_macos.yml +32 -0
  3. rclone_api-1.0.14/.github/workflows/push_ubuntu.yml +32 -0
  4. rclone_api-1.0.14/.github/workflows/push_win.yml +34 -0
  5. rclone_api-1.0.14/PKG-INFO +34 -0
  6. rclone_api-1.0.14/README.md +18 -0
  7. {rclone_api-1.0.12 → rclone_api-1.0.14}/pyproject.toml +44 -44
  8. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/__init__.py +1 -1
  9. rclone_api-1.0.14/src/rclone_api/config.py +8 -0
  10. rclone_api-1.0.14/src/rclone_api/convert.py +31 -0
  11. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/dir.py +23 -1
  12. rclone_api-1.0.14/src/rclone_api/dir_listing.py +40 -0
  13. rclone_api-1.0.12/src/rclone_api/config.py → rclone_api-1.0.14/src/rclone_api/exec.py +1 -1
  14. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/file.py +14 -2
  15. rclone_api-1.0.14/src/rclone_api/rclone.py +214 -0
  16. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/rpath.py +27 -9
  17. rclone_api-1.0.14/src/rclone_api/util.py +113 -0
  18. rclone_api-1.0.14/src/rclone_api/walk.py +70 -0
  19. rclone_api-1.0.14/src/rclone_api.egg-info/PKG-INFO +34 -0
  20. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api.egg-info/SOURCES.txt +12 -2
  21. rclone_api-1.0.14/src/rclone_api.egg-info/requires.txt +1 -0
  22. rclone_api-1.0.14/tests/test_copy.py +106 -0
  23. rclone_api-1.0.14/tests/test_is_synced.py +75 -0
  24. rclone_api-1.0.14/tests/test_ls.py +117 -0
  25. rclone_api-1.0.14/tests/test_remotes.py +70 -0
  26. rclone_api-1.0.14/tests/test_walk.py +68 -0
  27. rclone_api-1.0.12/PKG-INFO +0 -36
  28. rclone_api-1.0.12/README.md +0 -21
  29. rclone_api-1.0.12/src/rclone_api/dir_listing.py +0 -14
  30. rclone_api-1.0.12/src/rclone_api/rclone.py +0 -90
  31. rclone_api-1.0.12/src/rclone_api/types.py +0 -24
  32. rclone_api-1.0.12/src/rclone_api/util.py +0 -49
  33. rclone_api-1.0.12/src/rclone_api/walk.py +0 -87
  34. rclone_api-1.0.12/src/rclone_api.egg-info/PKG-INFO +0 -36
  35. rclone_api-1.0.12/tests/test_simple.py +0 -16
  36. {rclone_api-1.0.12 → rclone_api-1.0.14}/.aiderignore +0 -0
  37. {rclone_api-1.0.12 → rclone_api-1.0.14}/.gitignore +0 -0
  38. {rclone_api-1.0.12 → rclone_api-1.0.14}/.pylintrc +0 -0
  39. {rclone_api-1.0.12 → rclone_api-1.0.14}/.vscode/launch.json +0 -0
  40. {rclone_api-1.0.12 → rclone_api-1.0.14}/.vscode/settings.json +0 -0
  41. {rclone_api-1.0.12 → rclone_api-1.0.14}/.vscode/tasks.json +0 -0
  42. {rclone_api-1.0.12 → rclone_api-1.0.14}/LICENSE +0 -0
  43. {rclone_api-1.0.12 → rclone_api-1.0.14}/MANIFEST.in +0 -0
  44. {rclone_api-1.0.12 → rclone_api-1.0.14}/clean +0 -0
  45. {rclone_api-1.0.12 → rclone_api-1.0.14}/install +0 -0
  46. {rclone_api-1.0.12 → rclone_api-1.0.14}/lint +0 -0
  47. {rclone_api-1.0.12 → rclone_api-1.0.14}/requirements.testing.txt +0 -0
  48. {rclone_api-1.0.12 → rclone_api-1.0.14}/setup.cfg +0 -0
  49. {rclone_api-1.0.12 → rclone_api-1.0.14}/setup.py +0 -0
  50. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/assets/example.txt +0 -0
  51. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/cli.py +0 -0
  52. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api/remote.py +0 -0
  53. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  54. {rclone_api-1.0.12 → rclone_api-1.0.14}/src/rclone_api.egg-info/top_level.txt +0 -0
  55. {rclone_api-1.0.12 → rclone_api-1.0.14}/test +0 -0
  56. {rclone_api-1.0.12 → rclone_api-1.0.14}/tox.ini +0 -0
  57. {rclone_api-1.0.12 → rclone_api-1.0.14}/upload_package.sh +0 -0
@@ -0,0 +1,35 @@
1
+ name: Linting
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ matrix:
10
+ python-version: [3.11]
11
+ os: [ubuntu-latest]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python ${{ matrix.python-version }}
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: ${{ matrix.python-version }}
18
+ - uses: actions/cache@v4
19
+ name: Configure pip caching
20
+ with:
21
+ path: ~/.cache/pip
22
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
23
+ restore-keys: |
24
+ ${{ runner.os }}-pip-
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v5
28
+
29
+ - name: Install
30
+ run: |
31
+ python -m pip install uv
32
+ ./install
33
+ - name: Run Linting
34
+ run: |
35
+ ./lint
@@ -0,0 +1,32 @@
1
+ name: MacOS_Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ matrix:
10
+ python-version: [3.11]
11
+ os: [macos-latest]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python ${{ matrix.python-version }}
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: ${{ matrix.python-version }}
18
+ - uses: actions/cache@v4
19
+ name: Configure pip caching
20
+ with:
21
+ path: ~/.cache/pip
22
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
23
+ restore-keys: |
24
+ ${{ runner.os }}-pip-
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ - name: Install
28
+ run: |
29
+ ./install
30
+ - name: Run Tests
31
+ run: |
32
+ ./test
@@ -0,0 +1,32 @@
1
+ name: Ubuntu_Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ matrix:
10
+ python-version: [3.11]
11
+ os: [ubuntu-latest]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python ${{ matrix.python-version }}
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: ${{ matrix.python-version }}
18
+ - uses: actions/cache@v4
19
+ name: Configure pip caching
20
+ with:
21
+ path: ~/.cache/pip
22
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
23
+ restore-keys: |
24
+ ${{ runner.os }}-pip-
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ - name: Install
28
+ run: |
29
+ ./install
30
+ - name: Run Tests
31
+ run: |
32
+ ./test
@@ -0,0 +1,34 @@
1
+ name: Win_Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ matrix:
10
+ python-version: [3.11]
11
+ os: [windows-latest]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up Python ${{ matrix.python-version }}
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: ${{ matrix.python-version }}
18
+ - uses: actions/cache@v4
19
+ name: Configure pip caching
20
+ with:
21
+ path: ~/.cache/pip
22
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
23
+ restore-keys: |
24
+ ${{ runner.os }}-pip-
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ - name: Install
28
+ run: |
29
+ ./install
30
+ shell: bash
31
+ - name: Run Tests
32
+ run: |
33
+ ./test
34
+ shell: bash
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.2
2
+ Name: rclone_api
3
+ Version: 1.0.14
4
+ Summary: rclone api in python
5
+ Home-page: https://github.com/zackees/rclone-api
6
+ Maintainer: Zachary Vorhies
7
+ License: BSD 3-Clause License
8
+ Keywords: template-python-cmd
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: python-dotenv>=1.0.0
14
+ Dynamic: home-page
15
+ Dynamic: maintainer
16
+
17
+ # rclone-api
18
+
19
+ [![Linting](https://github.com/zackees/rclone-api/actions/workflows/lint.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/lint.yml)
20
+ [![MacOS_Tests](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml)
21
+ [![Ubuntu_Tests](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml)
22
+ [![Win_Tests](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml)
23
+
24
+ Api version of rclone. It's well tested. It's just released so this readme is a little to be desired.
25
+
26
+ To develop software, run `. ./activate`
27
+
28
+ # Windows
29
+
30
+ This environment requires you to use `git-bash`.
31
+
32
+ # Linting
33
+
34
+ Run `./lint`
@@ -0,0 +1,18 @@
1
+ # rclone-api
2
+
3
+ [![Linting](https://github.com/zackees/rclone-api/actions/workflows/lint.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/lint.yml)
4
+ [![MacOS_Tests](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml)
5
+ [![Ubuntu_Tests](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml)
6
+ [![Win_Tests](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml/badge.svg)](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml)
7
+
8
+ Api version of rclone. It's well tested. It's just released so this readme is a little to be desired.
9
+
10
+ To develop software, run `. ./activate`
11
+
12
+ # Windows
13
+
14
+ This environment requires you to use `git-bash`.
15
+
16
+ # Linting
17
+
18
+ Run `./lint`
@@ -1,44 +1,44 @@
1
- [build-system]
2
- requires = ["setuptools>=65.5.1", "setuptools-scm", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "rclone_api"
7
- readme = "README.md"
8
- description = "rclone api in python"
9
- requires-python = ">=3.7"
10
- keywords = ["template-python-cmd"]
11
- license = { text = "BSD 3-Clause License" }
12
- classifiers = ["Programming Language :: Python :: 3"]
13
- dependencies = [
14
-
15
- ]
16
- # Change this with the version number bump.
17
- version = "1.0.12"
18
-
19
- [tool.setuptools]
20
- package-dir = {"" = "src"}
21
-
22
- [tool.ruff]
23
- line-length = 200
24
-
25
- [tool.pylint."MESSAGES CONTROL"]
26
- good-names = [
27
- "c",
28
- "i",
29
- "ok",
30
- "id",
31
- "e",
32
- "f"
33
- ]
34
- disable = [
35
- "missing-function-docstring",
36
- "missing-module-docstring"
37
- ]
38
-
39
- [tool.isort]
40
- profile = "black"
41
-
42
- [tool.mypy]
43
- ignore_missing_imports = true
44
- disable_error_code = ["import-untyped"]
1
+ [build-system]
2
+ requires = ["setuptools>=65.5.1", "setuptools-scm", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rclone_api"
7
+ readme = "README.md"
8
+ description = "rclone api in python"
9
+ requires-python = ">=3.10"
10
+ keywords = ["template-python-cmd"]
11
+ license = { text = "BSD 3-Clause License" }
12
+ classifiers = ["Programming Language :: Python :: 3"]
13
+ dependencies = [
14
+ "python-dotenv>=1.0.0"
15
+ ]
16
+ # Change this with the version number bump.
17
+ version = "1.0.14"
18
+
19
+ [tool.setuptools]
20
+ package-dir = {"" = "src"}
21
+
22
+ [tool.ruff]
23
+ line-length = 200
24
+
25
+ [tool.pylint."MESSAGES CONTROL"]
26
+ good-names = [
27
+ "c",
28
+ "i",
29
+ "ok",
30
+ "id",
31
+ "e",
32
+ "f"
33
+ ]
34
+ disable = [
35
+ "missing-function-docstring",
36
+ "missing-module-docstring"
37
+ ]
38
+
39
+ [tool.isort]
40
+ profile = "black"
41
+
42
+ [tool.mypy]
43
+ ignore_missing_imports = true
44
+ disable_error_code = ["import-untyped"]
@@ -1,9 +1,9 @@
1
+ from .config import Config
1
2
  from .dir import Dir
2
3
  from .dir_listing import DirListing
3
4
  from .file import File
4
5
  from .rclone import Rclone
5
6
  from .remote import Remote
6
7
  from .rpath import RPath
7
- from .types import Config
8
8
 
9
9
  __all__ = ["Rclone", "File", "Config", "Remote", "Dir", "RPath", "DirListing"]
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Config:
6
+ """Rclone configuration dataclass."""
7
+
8
+ text: str
@@ -0,0 +1,31 @@
1
+ from rclone_api.dir import Dir
2
+ from rclone_api.file import File
3
+ from rclone_api.remote import Remote
4
+
5
+
6
+ def convert_to_filestr_list(files: str | File | list[str] | list[File]) -> list[str]:
7
+ out: list[str] = []
8
+ if isinstance(files, str):
9
+ out.append(files)
10
+ elif isinstance(files, File):
11
+ out.append(str(files.path))
12
+ elif isinstance(files, list):
13
+ for f in files:
14
+ if isinstance(f, File):
15
+ f = str(f.path)
16
+ out.append(f)
17
+ else:
18
+ raise ValueError(f"Invalid type for file: {type(files)}")
19
+ return out
20
+
21
+
22
+ def convert_to_str(file_or_dir: str | File | Dir | Remote) -> str:
23
+ if isinstance(file_or_dir, str):
24
+ return file_or_dir
25
+ if isinstance(file_or_dir, File):
26
+ return str(file_or_dir.path)
27
+ if isinstance(file_or_dir, Dir):
28
+ return str(file_or_dir.path)
29
+ if isinstance(file_or_dir, Remote):
30
+ return str(file_or_dir)
31
+ raise ValueError(f"Invalid type for file_or_dir: {type(file_or_dir)}")
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from typing import Generator
2
3
 
3
4
  from rclone_api.dir_listing import DirListing
@@ -8,6 +9,14 @@ from rclone_api.rpath import RPath
8
9
  class Dir:
9
10
  """Remote file dataclass."""
10
11
 
12
+ @property
13
+ def remote(self) -> Remote:
14
+ return self.path.remote
15
+
16
+ @property
17
+ def name(self) -> str:
18
+ return self.path.name
19
+
11
20
  def __init__(self, path: RPath | Remote) -> None:
12
21
  """Initialize Dir with either an RPath or Remote.
13
22
 
@@ -17,6 +26,7 @@ class Dir:
17
26
  if isinstance(path, Remote):
18
27
  # Need to create an RPath for the Remote's root
19
28
  self.path = RPath(
29
+ remote=path,
20
30
  path=str(path),
21
31
  name=str(path),
22
32
  size=0,
@@ -28,11 +38,14 @@ class Dir:
28
38
  self.path.set_rclone(path.rclone)
29
39
  else:
30
40
  self.path = path
41
+ # self.path.set_rclone(self.path.remote.rclone)
42
+ assert self.path.rclone is not None
31
43
 
32
44
  def ls(self, max_depth: int = 0) -> DirListing:
33
45
  """List files and directories in the given path."""
34
46
  assert self.path.rclone is not None
35
- return self.path.rclone.ls(self.path.path, max_depth=max_depth)
47
+ dir = Dir(self.path)
48
+ return self.path.rclone.ls(dir, max_depth=max_depth)
36
49
 
37
50
  def walk(self, max_depth: int = -1) -> Generator[DirListing, None, None]:
38
51
  """List files and directories in the given path."""
@@ -41,5 +54,14 @@ class Dir:
41
54
  assert self.path.rclone is not None
42
55
  return walk(self, max_depth=max_depth)
43
56
 
57
+ def to_json(self) -> dict:
58
+ """Convert the Dir to a JSON serializable dictionary."""
59
+ return self.path.to_json()
60
+
44
61
  def __str__(self) -> str:
45
62
  return str(self.path)
63
+
64
+ def __repr__(self) -> str:
65
+ data = self.path.to_json()
66
+ data_str = json.dumps(data)
67
+ return data_str
@@ -0,0 +1,40 @@
1
+ import json
2
+
3
+ from rclone_api.rpath import RPath
4
+
5
+
6
+ class DirListing:
7
+ """Remote file dataclass."""
8
+
9
+ def __init__(self, dirs_and_files: list[RPath]) -> None:
10
+ from rclone_api.dir import Dir
11
+ from rclone_api.file import File
12
+
13
+ self.dirs: list[Dir] = [Dir(d) for d in dirs_and_files if d.is_dir]
14
+ self.files: list[File] = [File(f) for f in dirs_and_files if not f.is_dir]
15
+
16
+ def __str__(self) -> str:
17
+ n_files = len(self.files)
18
+ n_dirs = len(self.dirs)
19
+ msg = f"Files: {n_files}\n"
20
+ if n_files > 0:
21
+ for f in self.files:
22
+ msg += f" {f}\n"
23
+ msg += f"Dirs: {n_dirs}\n"
24
+ if n_dirs > 0:
25
+ for d in self.dirs:
26
+ msg += f" {d}\n"
27
+ return msg
28
+
29
+ def __repr__(self) -> str:
30
+ dirs: list = []
31
+ files: list = []
32
+ for d in self.dirs:
33
+ dirs.append(d.path.to_json())
34
+ for f in self.files:
35
+ files.append(f.path.to_json())
36
+ json_obj = {
37
+ "dirs": dirs,
38
+ "files": files,
39
+ }
40
+ return json.dumps(json_obj, indent=2)
@@ -2,7 +2,7 @@ import subprocess
2
2
  from dataclasses import dataclass
3
3
  from pathlib import Path
4
4
 
5
- from rclone_api.types import Config
5
+ from rclone_api.config import Config
6
6
 
7
7
 
8
8
  @dataclass
@@ -12,6 +12,10 @@ class File:
12
12
  ) -> None:
13
13
  self.path = path
14
14
 
15
+ @property
16
+ def name(self) -> str:
17
+ return self.path.name
18
+
15
19
  def read_text(self) -> str:
16
20
  """Read the file contents as bytes.
17
21
 
@@ -30,6 +34,14 @@ class File:
30
34
  result = self.path.rclone._run(["cat", self.path.path])
31
35
  return result.stdout
32
36
 
37
+ def to_json(self) -> dict:
38
+ """Convert the File to a JSON serializable dictionary."""
39
+ return self.path.to_json()
40
+
33
41
  def __str__(self) -> str:
34
- out = self.path.to_json()
35
- return json.dumps(out)
42
+ return str(self.path)
43
+
44
+ def __repr__(self) -> str:
45
+ data = self.path.to_json()
46
+ data_str = json.dumps(data)
47
+ return data_str
@@ -0,0 +1,214 @@
1
+ """
2
+ Unit test file.
3
+ """
4
+
5
+ import subprocess
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from fnmatch import fnmatch
8
+ from pathlib import Path
9
+ from typing import Generator
10
+
11
+ from rclone_api import Dir
12
+ from rclone_api.config import Config
13
+ from rclone_api.convert import convert_to_filestr_list, convert_to_str
14
+ from rclone_api.dir_listing import DirListing
15
+ from rclone_api.exec import RcloneExec
16
+ from rclone_api.file import File
17
+ from rclone_api.remote import Remote
18
+ from rclone_api.rpath import RPath
19
+ from rclone_api.util import get_rclone_exe, to_path
20
+ from rclone_api.walk import walk
21
+
22
+
23
+ class Rclone:
24
+ def __init__(
25
+ self, rclone_conf: Path | Config, rclone_exe: Path | None = None
26
+ ) -> None:
27
+ if isinstance(rclone_conf, Path):
28
+ if not rclone_conf.exists():
29
+ raise ValueError(f"Rclone config file not found: {rclone_conf}")
30
+ self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
31
+
32
+ def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
33
+ return self._exec.execute(cmd)
34
+
35
+ def ls(
36
+ self,
37
+ path: Dir | Remote | str,
38
+ max_depth: int | None = None,
39
+ glob: str | None = None,
40
+ ) -> DirListing:
41
+ """List files in the given path.
42
+
43
+ Args:
44
+ path: Remote path or Remote object to list
45
+ max_depth: Maximum recursion depth (0 means no recursion)
46
+
47
+ Returns:
48
+ List of File objects found at the path
49
+ """
50
+
51
+ if isinstance(path, str):
52
+ path = Dir(
53
+ to_path(path, self)
54
+ ) # assume it's a directory if ls is being called.
55
+
56
+ cmd = ["lsjson"]
57
+ if max_depth is not None:
58
+ cmd.append("--recursive")
59
+ if max_depth > -1:
60
+ cmd.append("--max-depth")
61
+ cmd.append(str(max_depth))
62
+ cmd.append(str(path))
63
+ remote = path.remote if isinstance(path, Dir) else path
64
+ assert isinstance(remote, Remote)
65
+
66
+ cp = self._run(cmd)
67
+ text = cp.stdout
68
+ parent_path: str | None = None
69
+ if isinstance(path, Dir):
70
+ parent_path = path.path.path
71
+ paths: list[RPath] = RPath.from_json_str(text, remote, parent_path=parent_path)
72
+ # print(parent_path)
73
+ for o in paths:
74
+ o.set_rclone(self)
75
+
76
+ # do we have a glob pattern?
77
+ if glob is not None:
78
+ paths = [p for p in paths if fnmatch(p.path, glob)]
79
+ return DirListing(paths)
80
+
81
+ def listremotes(self) -> list[Remote]:
82
+ cmd = ["listremotes"]
83
+ cp = self._run(cmd)
84
+ text: str = cp.stdout
85
+ tmp = text.splitlines()
86
+ tmp = [t.strip() for t in tmp]
87
+ # strip out ":" from the end
88
+ tmp = [t.replace(":", "") for t in tmp]
89
+ out = [Remote(name=t, rclone=self) for t in tmp]
90
+ return out
91
+
92
+ def walk(
93
+ self, path: Dir | Remote | str, max_depth: int = -1
94
+ ) -> Generator[DirListing, None, None]:
95
+ """Walk through the given path recursively.
96
+
97
+ Args:
98
+ path: Remote path or Remote object to walk through
99
+ max_depth: Maximum depth to traverse (-1 for unlimited)
100
+
101
+ Yields:
102
+ DirListing: Directory listing for each directory encountered
103
+ """
104
+ if isinstance(path, Dir):
105
+ # Create a Remote object for the path
106
+ remote = path.remote
107
+ rpath = RPath(
108
+ remote=remote,
109
+ path=path.path.path,
110
+ name=path.path.name,
111
+ size=0,
112
+ mime_type="inode/directory",
113
+ mod_time="",
114
+ is_dir=True,
115
+ )
116
+ rpath.set_rclone(self)
117
+ dir_obj = Dir(rpath)
118
+ elif isinstance(path, str):
119
+ dir_obj = Dir(to_path(path, self))
120
+ elif isinstance(path, Remote):
121
+ dir_obj = Dir(path)
122
+ else:
123
+ assert f"Invalid type for path: {type(path)}"
124
+
125
+ yield from walk(dir_obj, max_depth=max_depth)
126
+
127
+ def copyfile(self, src: File | str, dst: File | str) -> None:
128
+ """Copy a single file from source to destination.
129
+
130
+ Args:
131
+ src: Source file path (including remote if applicable)
132
+ dst: Destination file path (including remote if applicable)
133
+
134
+ Raises:
135
+ subprocess.CalledProcessError: If the copy operation fails
136
+ """
137
+ src = src if isinstance(src, str) else str(src.path)
138
+ dst = dst if isinstance(dst, str) else str(dst.path)
139
+ cmd_list: list[str] = ["copyto", src, dst]
140
+ self._run(cmd_list)
141
+
142
+ def copyfiles(self, filelist: dict[File, File] | dict[str, str]) -> None:
143
+ """Copy multiple files from source to destination.
144
+
145
+ Warning - slow.
146
+
147
+ Args:
148
+ payload: Dictionary of source and destination file paths
149
+ """
150
+ str_dict: dict[str, str] = {}
151
+ for src, dst in filelist.items():
152
+ src = src if isinstance(src, str) else str(src.path)
153
+ dst = dst if isinstance(dst, str) else str(dst.path)
154
+ str_dict[src] = dst
155
+
156
+ with ThreadPoolExecutor(max_workers=64) as executor:
157
+ for src, dst in str_dict.items(): # warning - slow
158
+ cmd_list: list[str] = ["copyto", src, dst]
159
+ # self._run(cmd_list)
160
+ executor.submit(self._run, cmd_list)
161
+
162
+ def copy(self, src: Dir, dst: Dir) -> None:
163
+ """Copy files from source to destination.
164
+
165
+ Args:
166
+ src: Source directory
167
+ dst: Destination directory
168
+ """
169
+ src_dir = src.path.path
170
+ dst_dir = dst.path.path
171
+ cmd_list: list[str] = ["copy", src_dir, dst_dir]
172
+ self._run(cmd_list)
173
+
174
+ def purge(self, path: Dir | str) -> None:
175
+ """Purge a directory"""
176
+ # path should always be a string
177
+ path = path if isinstance(path, str) else str(path.path)
178
+ cmd_list: list[str] = ["purge", str(path)]
179
+ self._run(cmd_list)
180
+
181
+ def deletefiles(self, files: str | File | list[str] | list[File]) -> None:
182
+ """Delete a directory"""
183
+ payload: list[str] = convert_to_filestr_list(files)
184
+ cmd_list: list[str] = ["delete"] + payload
185
+ self._run(cmd_list)
186
+
187
+ def exists(self, path: Dir | Remote | str | File) -> bool:
188
+ """Check if a file or directory exists."""
189
+ arg: str = convert_to_str(path)
190
+ assert isinstance(arg, str)
191
+ try:
192
+ self.ls(arg)
193
+ return True
194
+ except subprocess.CalledProcessError:
195
+ return False
196
+
197
+ def is_synced(self, src: str | Dir, dst: str | Dir) -> bool:
198
+ """Check if two directories are in sync."""
199
+ src = convert_to_str(src)
200
+ dst = convert_to_str(dst)
201
+ cmd_list: list[str] = ["check", str(src), str(dst)]
202
+ try:
203
+ self._run(cmd_list)
204
+ return True
205
+ except subprocess.CalledProcessError:
206
+ return False
207
+
208
+ def copy_dir(self, src: str | Dir, dst: str | Dir) -> None:
209
+ """Copy a directory from source to destination."""
210
+ # convert src to str, also dst
211
+ src = convert_to_str(src)
212
+ dst = convert_to_str(dst)
213
+ cmd_list: list[str] = ["copy", src, dst]
214
+ self._run(cmd_list)