rclone-api 1.1.28__tar.gz → 1.1.30__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.
- {rclone_api-1.1.28 → rclone_api-1.1.30}/PKG-INFO +1 -1
- {rclone_api-1.1.28 → rclone_api-1.1.30}/pyproject.toml +1 -1
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/rclone.py +54 -3
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api.egg-info/PKG-INFO +1 -1
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api.egg-info/SOURCES.txt +1 -0
- rclone_api-1.1.30/tests/test_copy_bytes.py +97 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.aiderignore +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.github/workflows/lint.yml +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.github/workflows/push_macos.yml +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.github/workflows/push_ubuntu.yml +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.github/workflows/push_win.yml +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.gitignore +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.pylintrc +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.vscode/launch.json +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.vscode/settings.json +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/.vscode/tasks.json +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/LICENSE +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/MANIFEST.in +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/README.md +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/clean +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/install +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/lint +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/requirements.testing.txt +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/setup.cfg +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/setup.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/__init__.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/cmd/copy_large_s3.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/cmd/list_files.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/completed_process.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/config.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/convert.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/deprecated.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/diff.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/dir.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/dir_listing.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/exec.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/experimental/flags.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/experimental/flags_base.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/file.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/filelist.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/group_files.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/mount.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/process.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/remote.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/rpath.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/api.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/basic_ops.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/chunk_file.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/chunk_types.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/create.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/types.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/s3/upload_file_multipart.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/scan_missing_folders.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/types.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/util.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api/walk.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api.egg-info/entry_points.txt +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api.egg-info/requires.txt +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/test +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/archive/test_paramiko.py.disabled +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_cmd_list_files.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_copy.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_copy_file_resumable_s3.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_copy_files.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_diff.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_group_files.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_is_synced.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_ls.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_mount.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_mount_s3.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_obscure.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_rclone_config.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_remote_control.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_remotes.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_s3.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_scan_missing_folders.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_size_files.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_size_suffix.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tests/test_walk.py +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/tox.ini +0 -0
- {rclone_api-1.1.28 → rclone_api-1.1.30}/upload_package.sh +0 -0
|
@@ -691,7 +691,7 @@ class Rclone:
|
|
|
691
691
|
from rclone_api.util import S3PathInfo, random_str, split_s3_path
|
|
692
692
|
|
|
693
693
|
other_args: list[str] = ["--no-modtime", "--vfs-read-wait", "1s"]
|
|
694
|
-
chunk_size = chunk_size or SizeSuffix("
|
|
694
|
+
chunk_size = chunk_size or SizeSuffix("64M")
|
|
695
695
|
unit_chunk_size = chunk_size / read_threads
|
|
696
696
|
vfs_read_chunk_size = unit_chunk_size
|
|
697
697
|
vfs_read_chunk_size_limit = chunk_size
|
|
@@ -726,8 +726,6 @@ class Rclone:
|
|
|
726
726
|
verbose=False,
|
|
727
727
|
other_args=other_args,
|
|
728
728
|
):
|
|
729
|
-
# raise NotImplementedError("Not implemented yet")
|
|
730
|
-
|
|
731
729
|
path_info: S3PathInfo = split_s3_path(dst)
|
|
732
730
|
remote = path_info.remote
|
|
733
731
|
bucket_name = path_info.bucket
|
|
@@ -813,6 +811,59 @@ class Rclone:
|
|
|
813
811
|
)
|
|
814
812
|
return out
|
|
815
813
|
|
|
814
|
+
def copy_bytes(
|
|
815
|
+
self,
|
|
816
|
+
src: str,
|
|
817
|
+
offset: int,
|
|
818
|
+
length: int,
|
|
819
|
+
transfers: int = 16,
|
|
820
|
+
outfile: (
|
|
821
|
+
Path | None
|
|
822
|
+
) = None, # If supplied then bytes are written to this file and success returns bytes(0)
|
|
823
|
+
) -> bytes | Exception:
|
|
824
|
+
"""Copy bytes from a file to another file."""
|
|
825
|
+
from rclone_api.util import random_str
|
|
826
|
+
|
|
827
|
+
tmp_mnt = Path("tmp_mnt") / random_str(12)
|
|
828
|
+
src_parent_path = Path(src).parent.as_posix()
|
|
829
|
+
src_file = Path(src).name
|
|
830
|
+
other_args: list[str] = ["--no-modtime", "--vfs-read-wait", "1s"]
|
|
831
|
+
unit_chunk_size = length
|
|
832
|
+
vfs_read_chunk_size = unit_chunk_size
|
|
833
|
+
vfs_read_chunk_size_limit = length
|
|
834
|
+
vfs_read_chunk_streams = transfers
|
|
835
|
+
vfs_disk_space_total_size = length
|
|
836
|
+
other_args += ["--vfs-read-chunk-size", str(vfs_read_chunk_size)]
|
|
837
|
+
other_args += ["--vfs-read-chunk-size-limit", str(vfs_read_chunk_size_limit)]
|
|
838
|
+
other_args += ["--vfs-read-chunk-streams", str(vfs_read_chunk_streams)]
|
|
839
|
+
other_args += ["--vfs-disk-space-total-size", str(vfs_disk_space_total_size)]
|
|
840
|
+
other_args += ["--read-only"]
|
|
841
|
+
other_args += ["--direct-io"]
|
|
842
|
+
|
|
843
|
+
try:
|
|
844
|
+
# use scoped mount to do the read, then write the bytes to the destination
|
|
845
|
+
with self.scoped_mount(
|
|
846
|
+
src_parent_path,
|
|
847
|
+
tmp_mnt,
|
|
848
|
+
use_links=True,
|
|
849
|
+
verbose=False,
|
|
850
|
+
vfs_cache_mode="minimal",
|
|
851
|
+
other_args=other_args,
|
|
852
|
+
):
|
|
853
|
+
src_file_mnt = tmp_mnt / src_file
|
|
854
|
+
with open(src_file_mnt, "rb") as f:
|
|
855
|
+
f.seek(offset)
|
|
856
|
+
data = f.read(length)
|
|
857
|
+
if outfile is None:
|
|
858
|
+
return data
|
|
859
|
+
with open(outfile, "wb") as out:
|
|
860
|
+
out.write(data)
|
|
861
|
+
del data
|
|
862
|
+
return bytes(0)
|
|
863
|
+
|
|
864
|
+
except Exception as e:
|
|
865
|
+
return e
|
|
866
|
+
|
|
816
867
|
def copy_dir(
|
|
817
868
|
self, src: str | Dir, dst: str | Dir, args: list[str] | None = None
|
|
818
869
|
) -> CompletedProcess:
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit test file.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
from rclone_api import Config, Rclone
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _generate_rclone_config() -> Config:
|
|
20
|
+
|
|
21
|
+
# BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
|
|
22
|
+
|
|
23
|
+
# Load additional environment variables
|
|
24
|
+
BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
|
|
25
|
+
BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
|
|
26
|
+
# BUCKET_URL = os.getenv("BUCKET_URL")
|
|
27
|
+
BUCKET_URL = "sfo3.digitaloceanspaces.com"
|
|
28
|
+
|
|
29
|
+
config_text = f"""
|
|
30
|
+
[dst]
|
|
31
|
+
type = s3
|
|
32
|
+
provider = DigitalOcean
|
|
33
|
+
access_key_id = {BUCKET_KEY_PUBLIC}
|
|
34
|
+
secret_access_key = {BUCKET_KEY_SECRET}
|
|
35
|
+
endpoint = {BUCKET_URL}
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
out = Config(config_text)
|
|
39
|
+
return out
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RcloneCopyBytesTester(unittest.TestCase):
|
|
43
|
+
"""Test rclone functionality."""
|
|
44
|
+
|
|
45
|
+
def setUp(self) -> None:
|
|
46
|
+
"""Check if all required environment variables are set before running tests."""
|
|
47
|
+
required_vars = [
|
|
48
|
+
"BUCKET_NAME",
|
|
49
|
+
"BUCKET_KEY_SECRET",
|
|
50
|
+
"BUCKET_KEY_PUBLIC",
|
|
51
|
+
"BUCKET_URL",
|
|
52
|
+
]
|
|
53
|
+
missing = [var for var in required_vars if not os.getenv(var)]
|
|
54
|
+
if missing:
|
|
55
|
+
self.skipTest(
|
|
56
|
+
f"Missing required environment variables: {', '.join(missing)}"
|
|
57
|
+
)
|
|
58
|
+
os.environ["RCLONE_API_VERBOSE"] = "1"
|
|
59
|
+
|
|
60
|
+
def test_copy_bytes(self) -> None:
|
|
61
|
+
rclone = Rclone(_generate_rclone_config())
|
|
62
|
+
bytes_or_err: bytes | Exception = rclone.copy_bytes(
|
|
63
|
+
src="dst:rclone-api-unit-test/zachs_video/breaking_ai_mind.mp4",
|
|
64
|
+
offset=0,
|
|
65
|
+
length=1024 * 1024,
|
|
66
|
+
)
|
|
67
|
+
if isinstance(bytes_or_err, Exception):
|
|
68
|
+
print(bytes_or_err)
|
|
69
|
+
self.fail(f"Error: {bytes_or_err}")
|
|
70
|
+
assert isinstance(bytes_or_err, bytes)
|
|
71
|
+
self.assertEqual(
|
|
72
|
+
len(bytes_or_err), 1024 * 1024
|
|
73
|
+
) # , f"Length: {len(bytes_or_err)}"
|
|
74
|
+
|
|
75
|
+
def test_copy_bytes_to_temp_file(self) -> None:
|
|
76
|
+
|
|
77
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
78
|
+
tmp = Path(tmpdir) / "tmp.mp4"
|
|
79
|
+
rclone = Rclone(_generate_rclone_config())
|
|
80
|
+
bytes_or_err: bytes | Exception = rclone.copy_bytes(
|
|
81
|
+
src="dst:rclone-api-unit-test/zachs_video/breaking_ai_mind.mp4",
|
|
82
|
+
offset=0,
|
|
83
|
+
length=1024 * 1024,
|
|
84
|
+
outfile=tmp,
|
|
85
|
+
)
|
|
86
|
+
if isinstance(bytes_or_err, Exception):
|
|
87
|
+
print(bytes_or_err)
|
|
88
|
+
self.fail(f"Error: {bytes_or_err}")
|
|
89
|
+
assert isinstance(bytes_or_err, bytes)
|
|
90
|
+
self.assertEqual(len(bytes_or_err), 0)
|
|
91
|
+
self.assertTrue(tmp.exists())
|
|
92
|
+
tmp_size = tmp.stat().st_size
|
|
93
|
+
self.assertEqual(tmp_size, 1024 * 1024)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
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
|
|
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
|
|
File without changes
|