rclone-api 1.1.94__py2.py3-none-any.whl → 1.1.96__py2.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.
@@ -130,27 +130,23 @@ def _run_profile(
130
130
  )
131
131
  bytes_count = 0
132
132
 
133
+ futures: list[Future[bytes | Exception]] = []
134
+ for i in range(num):
135
+ offset = SizeSuffix(i * chunk_size.as_int()) + offset
136
+ future = filechunker.fetch(offset.as_int(), size.as_int())
137
+ futures.append(future)
138
+
139
+ for future in futures:
140
+ bytes_or_err = future.result()
141
+ if isinstance(bytes_or_err, Exception):
142
+ assert False, f"Error: {bytes_or_err}"
143
+ futures.clear()
133
144
 
134
145
  start = time.time()
135
146
  net_io_start = psutil.net_io_counters()
136
147
 
137
- # bytes_or_err: bytes | Exception = rclone.copy_bytes(
138
- # src=src_file,
139
- # offset=offset.as_int(),
140
- # length=size.as_int(),
141
- # chunk_size=chunk_size,
142
- # direct_io=direct_io,
143
- # max_threads=transfers,
144
- # mount_log=mount_log,
145
- # )
146
-
147
- # bytes_or_err = filechunker.fetch(
148
- # (offset + SizeSuffix("1G")).as_int(), size.as_int()
149
- # )
150
-
151
148
  offset = SizeSuffix("1G")
152
149
 
153
- futures: list[Future[bytes | Exception]] = []
154
150
  for i in range(num):
155
151
  offset = SizeSuffix(i * chunk_size.as_int()) + offset
156
152
  future = filechunker.fetch(offset.as_int(), size.as_int())
@@ -165,7 +161,9 @@ def _run_profile(
165
161
  diff = (time.time() - start) / num
166
162
  net_io_end = psutil.net_io_counters()
167
163
  # self.assertEqual(len(bytes_or_err), size)
168
- assert bytes_count == SizeSuffix(size* num).as_int(), f"Length: {SizeSuffix(bytes_count)} != {SizeSuffix(size* num)}"
164
+ assert (
165
+ bytes_count == SizeSuffix(size * num).as_int()
166
+ ), f"Length: {SizeSuffix(bytes_count)} != {SizeSuffix(size* num)}"
169
167
 
170
168
  # print io stats
171
169
  bytes_sent = (net_io_end.bytes_sent - net_io_start.bytes_sent) // num
rclone_api/rclone.py CHANGED
@@ -777,9 +777,18 @@ class Rclone:
777
777
  endpoint_url=section.endpoint(),
778
778
  )
779
779
 
780
+ chunk_fetcher: MultiMountFileChunker = self.get_multi_mount_file_chunker(
781
+ src=src_path.parent.as_posix(),
782
+ chunk_size=chunk_size,
783
+ threads=read_threads,
784
+ mount_log=mount_log,
785
+ direct_io=True,
786
+ )
787
+
780
788
  client = S3Client(s3_creds)
781
- config: S3MutliPartUploadConfig = S3MutliPartUploadConfig(
789
+ upload_config: S3MutliPartUploadConfig = S3MutliPartUploadConfig(
782
790
  chunk_size=chunk_size.as_int(),
791
+ chunk_fetcher=chunk_fetcher.fetch,
783
792
  max_write_threads=write_threads,
784
793
  retries=retries,
785
794
  resume_path_json=save_state_json,
@@ -791,10 +800,7 @@ class Rclone:
791
800
  print(f"Uploading {name} to {s3_key} in bucket {bucket_name}")
792
801
  print(f"Source: {src_path}")
793
802
  print(f"bucket_name: {bucket_name}")
794
- print(f"upload_config: {config}")
795
-
796
- upload_target: S3UploadTarget
797
- upload_config: S3MutliPartUploadConfig
803
+ print(f"upload_config: {upload_config}")
798
804
 
799
805
  upload_target = S3UploadTarget(
800
806
  bucket_name=bucket_name,
@@ -802,18 +808,14 @@ class Rclone:
802
808
  s3_key=s3_key,
803
809
  )
804
810
 
805
- upload_config = S3MutliPartUploadConfig(
806
- chunk_size=chunk_size.as_int(),
807
- retries=retries,
808
- max_write_threads=write_threads,
809
- resume_path_json=save_state_json,
810
- max_chunks_before_suspension=max_chunks_before_suspension,
811
- )
812
-
813
- out: MultiUploadResult = client.upload_file_multipart(
814
- upload_target=upload_target, upload_config=upload_config
815
- )
816
- return out
811
+ try:
812
+ out: MultiUploadResult = client.upload_file_multipart(
813
+ upload_target=upload_target,
814
+ upload_config=upload_config,
815
+ )
816
+ return out
817
+ finally:
818
+ chunk_fetcher.close()
817
819
 
818
820
  def _copy_bytes(
819
821
  self,
rclone_api/s3/api.py CHANGED
@@ -11,7 +11,10 @@ from rclone_api.s3.basic_ops import (
11
11
  )
12
12
  from rclone_api.s3.create import create_s3_client
13
13
  from rclone_api.s3.types import S3Credentials, S3MutliPartUploadConfig, S3UploadTarget
14
- from rclone_api.s3.upload_file_multipart import MultiUploadResult, upload_file_multipart
14
+ from rclone_api.s3.upload_file_multipart import (
15
+ MultiUploadResult,
16
+ upload_file_multipart,
17
+ )
15
18
 
16
19
  _MIN_THRESHOLD_FOR_CHUNKING = 5 * 1024 * 1024
17
20
 
@@ -67,6 +70,7 @@ class S3Client:
67
70
 
68
71
  out = upload_file_multipart(
69
72
  s3_client=self.client,
73
+ chunk_fetcher=upload_config.chunk_fetcher,
70
74
  bucket_name=bucket_name,
71
75
  file_path=upload_target.src_file,
72
76
  object_name=upload_target.s3_key,
@@ -1,8 +1,10 @@
1
1
  import time
2
2
  import warnings
3
+ from concurrent.futures import Future
3
4
  from pathlib import Path
4
5
  from queue import Queue
5
6
  from threading import Event
7
+ from typing import Callable
6
8
 
7
9
  from rclone_api.s3.chunk_types import FileChunk, UploadState
8
10
  from rclone_api.util import locked_print
@@ -27,12 +29,16 @@ def _get_file_size(file_path: Path, timeout: int = 60) -> int:
27
29
 
28
30
  def file_chunker(
29
31
  upload_state: UploadState,
32
+ chunk_fetcher: Callable[[int, int], Future[bytes | Exception]],
30
33
  max_chunks: int | None,
31
34
  cancel_signal: Event,
32
35
  output: Queue[FileChunk | None],
33
36
  ) -> None:
34
37
  count = 0
35
38
 
39
+ if False:
40
+ print(chunk_fetcher)
41
+
36
42
  def should_stop() -> bool:
37
43
  nonlocal count
38
44
 
@@ -41,14 +47,6 @@ def file_chunker(
41
47
  if count >= max_chunks:
42
48
  return True
43
49
  count += 1
44
- if count > 10 and count % 10 == 0:
45
- # recheck that the file size has not changed
46
- file_size = _get_file_size(upload_state.upload_info.src_file_path)
47
- if file_size != upload_state.upload_info.file_size:
48
- locked_print(
49
- f"File size changed, cannot resume, expected {upload_state.upload_info.file_size}, got {file_size}"
50
- )
51
- raise ValueError("File size changed, cannot resume")
52
50
  return False
53
51
 
54
52
  upload_info = upload_state.upload_info
@@ -58,7 +56,6 @@ def file_chunker(
58
56
  # Mounted files may take a while to appear, so keep retrying.
59
57
 
60
58
  try:
61
- file_size = _get_file_size(src, timeout=60)
62
59
  part_number = 1
63
60
  done_part_numbers: set[int] = {
64
61
  p.part_number for p in upload_state.parts if p is not None
@@ -86,25 +83,56 @@ def file_chunker(
86
83
  break
87
84
  assert curr_parth_num is not None
88
85
  offset = (curr_parth_num - 1) * chunk_size
86
+ file_size = upload_info.file_size
89
87
 
90
88
  assert offset < file_size, f"Offset {offset} is greater than file size"
91
89
 
92
90
  # Open the file, seek, read the chunk, and close immediately.
93
- with open(file_path, "rb") as f:
94
- f.seek(offset)
95
- data = f.read(chunk_size)
96
-
97
- if not data:
98
- warnings.warn(f"Empty data for part {part_number} of {file_path}")
99
-
100
- file_chunk = FileChunk(
101
- src,
102
- upload_id=upload_info.upload_id,
103
- part_number=part_number,
104
- data=data, # After this, data should not be reused.
105
- )
106
- done_part_numbers.add(part_number)
107
- output.put(file_chunk)
91
+ # with open(file_path, "rb") as f:
92
+ # f.seek(offset)
93
+ # data = f.read(chunk_size)
94
+
95
+ # data = chunk_fetcher(offset, chunk_size).result()
96
+
97
+ assert curr_parth_num is not None
98
+ cpn: int = curr_parth_num
99
+
100
+ def on_complete(
101
+ fut: Future[bytes | Exception],
102
+ part_number: int = cpn,
103
+ file_path: Path = file_path,
104
+ ) -> None:
105
+ data: bytes | Exception = fut.result()
106
+ if isinstance(data, Exception):
107
+ warnings.warn(
108
+ f"Error reading file: {data}, skipping part {part_number}"
109
+ )
110
+ return
111
+
112
+ if isinstance(data, Exception):
113
+ err: Exception = data
114
+ warnings.warn(
115
+ f"Error reading file: {err}, skipping part {part_number}"
116
+ )
117
+ return
118
+
119
+ if not data:
120
+ warnings.warn(f"Empty data for part {part_number} of {file_path}")
121
+
122
+ file_chunk = FileChunk(
123
+ src,
124
+ upload_id=upload_info.upload_id,
125
+ part_number=part_number,
126
+ data=data, # After this, data should not be reused.
127
+ )
128
+ done_part_numbers.add(part_number)
129
+ output.put(file_chunk)
130
+
131
+ fut = chunk_fetcher(curr_parth_num, chunk_size)
132
+ fut.add_done_callback(on_complete)
133
+ # wait until the output queue can accept the next chunk
134
+ while output.full():
135
+ time.sleep(0.1)
108
136
  except Exception as e:
109
137
 
110
138
  warnings.warn(f"Error reading file: {e}")
rclone_api/s3/types.py CHANGED
@@ -1,6 +1,8 @@
1
+ from concurrent.futures import Future
1
2
  from dataclasses import dataclass
2
3
  from enum import Enum
3
4
  from pathlib import Path
5
+ from typing import Callable
4
6
 
5
7
 
6
8
  class S3Provider(Enum):
@@ -45,6 +47,7 @@ class S3MutliPartUploadConfig:
45
47
 
46
48
  chunk_size: int
47
49
  retries: int
50
+ chunk_fetcher: Callable[[int, int], Future[bytes | Exception]]
48
51
  resume_path_json: Path
49
52
  max_write_threads: int
50
53
  max_chunks_before_suspension: int | None = None
@@ -2,10 +2,11 @@ import _thread
2
2
  import os
3
3
  import traceback
4
4
  import warnings
5
- from concurrent.futures import ThreadPoolExecutor
5
+ from concurrent.futures import Future, ThreadPoolExecutor
6
6
  from pathlib import Path
7
7
  from queue import Queue
8
8
  from threading import Event, Thread
9
+ from typing import Callable
9
10
 
10
11
  from botocore.client import BaseClient
11
12
 
@@ -117,6 +118,7 @@ def _abort_previous_upload(upload_state: UploadState) -> None:
117
118
 
118
119
  def upload_file_multipart(
119
120
  s3_client: BaseClient,
121
+ chunk_fetcher: Callable[[int, int], Future[bytes | Exception]],
120
122
  bucket_name: str,
121
123
  file_path: Path,
122
124
  object_name: str,
@@ -211,6 +213,7 @@ def upload_file_multipart(
211
213
 
212
214
  def chunker_task(
213
215
  upload_state=upload_state,
216
+ chunk_fetcher=chunk_fetcher,
214
217
  output=filechunks,
215
218
  max_chunks=max_chunks_before_suspension,
216
219
  cancel_signal=cancel_chunker_event,
@@ -219,6 +222,7 @@ def upload_file_multipart(
219
222
  try:
220
223
  file_chunker(
221
224
  upload_state=upload_state,
225
+ chunk_fetcher=chunk_fetcher,
222
226
  output=output,
223
227
  max_chunks=max_chunks,
224
228
  cancel_signal=cancel_signal,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.1.94
3
+ Version: 1.1.96
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -13,7 +13,7 @@ rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
13
13
  rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
14
14
  rclone_api/mount.py,sha256=pU-YTHTLYMXuNHVkT9ACS1wdZKazXXAhVWQUD6ZvP40,14535
15
15
  rclone_api/process.py,sha256=rBj_S86jC6nqCYop-jq8r9eMSteKeObxUrJMgH8LZvI,5084
16
- rclone_api/rclone.py,sha256=5sF9t6IRzn4GMoSynW4mfFIYweve2Aomm3J5mCPASvE,48837
16
+ rclone_api/rclone.py,sha256=0UW3gQ93DgZjvNspxyoi_ae_PfbNq32tHb3wVpa6Eq4,48893
17
17
  rclone_api/remote.py,sha256=O9WDUFQy9f6oT1HdUbTixK2eg0xtBBm8k4Xl6aa6K00,431
18
18
  rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
19
19
  rclone_api/scan_missing_folders.py,sha256=Kulca2Q6WZodt00ATFHkmqqInuoPvBkhTcS9703y6po,4740
@@ -25,17 +25,17 @@ rclone_api/cmd/copy_large_s3.py,sha256=a-NtpIhy3gj1umIyS0JG1qQMCbsLQoi6VP6u2NjBG
25
25
  rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
26
26
  rclone_api/experimental/flags.py,sha256=qCVD--fSTmzlk9hloRLr0q9elzAOFzPsvVpKM3aB1Mk,2739
27
27
  rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
28
- rclone_api/profile/mount_copy_bytes.py,sha256=-WfEv4K21Juaxqkz3O3aDJmdWM-0ZdlaFIeIJ0Tju2Y,8414
29
- rclone_api/s3/api.py,sha256=qxtRDUpHYqJ7StJRtP8U_PbF_BvYRg705568SyvF-R0,3770
28
+ rclone_api/profile/mount_copy_bytes.py,sha256=_bd9oergTuCdj1P6AVh_pC6GmtCpLFQYwiTdhwXeYZE,8404
29
+ rclone_api/s3/api.py,sha256=Plhtw_R9wrhN1fNzMI14ZFkHlvKdehUpdJ07P5cw9HI,3842
30
30
  rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
31
- rclone_api/s3/chunk_file.py,sha256=D6wM9Nuu73LNGC8JCCfevqjF3qdZ21mQxYQClFLZLMU,3726
31
+ rclone_api/s3/chunk_file.py,sha256=Gro77c-mHQUvW41fw3aFtSrc6ByOcdGt0_padnip6yc,4568
32
32
  rclone_api/s3/chunk_types.py,sha256=LbXayXY1KgVU1LkdbASD_BQ7TpVpwVnzMjtz--8LBaE,10316
33
33
  rclone_api/s3/create.py,sha256=wgfkapv_j904CfKuWyiBIWJVxfAx_ftemFSUV14aT68,3149
34
- rclone_api/s3/types.py,sha256=yBnJ38Tjk6RlydJ-sqZ7DSfyFloy8KDYJ0mv3vlOzLE,1388
35
- rclone_api/s3/upload_file_multipart.py,sha256=y9azNAU8QH5Ovwz33V2HZwNmJdlFjJg-jrXLZ1gtMds,10364
36
- rclone_api-1.1.94.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
37
- rclone_api-1.1.94.dist-info/METADATA,sha256=9ghyMbSCUTVKJUJ4Qrrdipdn37g-wnXxfa5nToR_gyM,4537
38
- rclone_api-1.1.94.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
39
- rclone_api-1.1.94.dist-info/entry_points.txt,sha256=TV8kwP3FRzYwUEr0RLC7aJh0W03SAefIJNXTJ-FdMIQ,200
40
- rclone_api-1.1.94.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
41
- rclone_api-1.1.94.dist-info/RECORD,,
34
+ rclone_api/s3/types.py,sha256=MFZkVXB-rUHa-d3b5gAt5qKPP1tv3FrUmy7Wq4HQgjc,1521
35
+ rclone_api/s3/upload_file_multipart.py,sha256=frfz8CDW2x2DCPKW0fcZCaABq-vA8gcv26bO8mYNEf8,10550
36
+ rclone_api-1.1.96.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
37
+ rclone_api-1.1.96.dist-info/METADATA,sha256=CbTS1pfpa01wYwzt-47lFLHlEaz7R92EGIbE6YsXkc8,4537
38
+ rclone_api-1.1.96.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
39
+ rclone_api-1.1.96.dist-info/entry_points.txt,sha256=TV8kwP3FRzYwUEr0RLC7aJh0W03SAefIJNXTJ-FdMIQ,200
40
+ rclone_api-1.1.96.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
41
+ rclone_api-1.1.96.dist-info/RECORD,,