rclone-api 1.4.3__py2.py3-none-any.whl → 1.4.5__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.
@@ -8,8 +8,8 @@ from concurrent.futures import Future, ThreadPoolExecutor
8
8
  from dataclasses import dataclass
9
9
  from datetime import datetime
10
10
  from pathlib import Path
11
- from tempfile import TemporaryDirectory
12
11
 
12
+ from rclone_api import rclone_verbose
13
13
  from rclone_api.dir_listing import DirListing
14
14
  from rclone_api.http_server import HttpServer
15
15
  from rclone_api.rclone_impl import RcloneImpl
@@ -19,6 +19,8 @@ from rclone_api.types import (
19
19
  SizeSuffix,
20
20
  )
21
21
 
22
+ rclone_verbose(True)
23
+
22
24
 
23
25
  @dataclass
24
26
  class UploadPart:
@@ -47,6 +49,11 @@ def upload_task(self: RcloneImpl, upload_part: UploadPart) -> UploadPart:
47
49
  try:
48
50
  if upload_part.exception is not None:
49
51
  return upload_part
52
+ # print(f"Uploading {upload_part.chunk} to {upload_part.dst_part}")
53
+ msg = "\n#########################################\n"
54
+ msg += f"# Uploading {upload_part.chunk} to {upload_part.dst_part}\n"
55
+ msg += "#########################################\n"
56
+ print
50
57
  self.copy_to(upload_part.chunk.as_posix(), upload_part.dst_part)
51
58
  return upload_part
52
59
  except Exception as e:
@@ -249,6 +256,8 @@ def copy_file_parts(
249
256
  threads: int = 1,
250
257
  ) -> Exception | None:
251
258
  """Copy parts of a file from source to destination."""
259
+ from rclone_api.util import random_str
260
+
252
261
  if dst_dir.endswith("/"):
253
262
  dst_dir = dst_dir[:-1]
254
263
  src_size = self.size_file(src)
@@ -311,65 +320,72 @@ def copy_file_parts(
311
320
  print(info_json)
312
321
 
313
322
  finished_tasks: list[UploadPart] = []
323
+ tmp_dir = str(Path("chunks") / random_str(12))
324
+ import atexit
325
+ import shutil
326
+
327
+ atexit.register(lambda: shutil.rmtree(tmp_dir, ignore_errors=True))
314
328
 
315
329
  with self.serve_http(src_dir) as http_server:
316
- with TemporaryDirectory() as tmp_dir:
317
- tmpdir: Path = Path(tmp_dir)
318
- write_semaphore = threading.Semaphore(threads)
319
- with ThreadPoolExecutor(max_workers=threads) as upload_executor:
320
- with ThreadPoolExecutor(max_workers=threads) as read_executor:
321
- for part_info in part_infos:
322
- part_number: int = part_info.part_number
323
- range: Range = part_info.range
324
- offset: SizeSuffix = SizeSuffix(range.start)
325
- length: SizeSuffix = SizeSuffix(range.end - range.start)
326
- end = offset + length
327
- suffix = _gen_name(part_number, offset, end)
328
- part_dst = f"{dst_dir}/{suffix}"
329
-
330
- def _read_task(
330
+ tmpdir: Path = Path(tmp_dir)
331
+ write_semaphore = threading.Semaphore(threads)
332
+ with ThreadPoolExecutor(max_workers=threads) as upload_executor:
333
+ with ThreadPoolExecutor(max_workers=threads) as read_executor:
334
+ for part_info in part_infos:
335
+ part_number: int = part_info.part_number
336
+ range: Range = part_info.range
337
+ offset: SizeSuffix = SizeSuffix(range.start)
338
+ length: SizeSuffix = SizeSuffix(range.end - range.start)
339
+ end = offset + length
340
+ suffix = _gen_name(part_number, offset, end)
341
+ part_dst = f"{dst_dir}/{suffix}"
342
+
343
+ def _read_task(
344
+ src_name=src_name,
345
+ http_server=http_server,
346
+ tmpdir=tmpdir,
347
+ offset=offset,
348
+ length=length,
349
+ part_dst=part_dst,
350
+ ) -> UploadPart:
351
+ return read_task(
331
352
  src_name=src_name,
332
353
  http_server=http_server,
333
354
  tmpdir=tmpdir,
334
355
  offset=offset,
335
356
  length=length,
336
357
  part_dst=part_dst,
337
- ) -> UploadPart:
338
- return read_task(
339
- src_name=src_name,
340
- http_server=http_server,
341
- tmpdir=tmpdir,
342
- offset=offset,
343
- length=length,
344
- part_dst=part_dst,
345
- )
346
-
347
- read_fut: Future[UploadPart] = read_executor.submit(_read_task)
348
-
349
- # Releases the semaphore when the write task is done
350
- def queue_upload_task(
351
- read_fut=read_fut,
352
- ) -> None:
353
- upload_part = read_fut.result()
354
- upload_fut: Future[UploadPart] = upload_executor.submit(
355
- upload_task, self, upload_part
356
- )
357
- # SEMAPHORE RELEASE!!!
358
- upload_fut.add_done_callback(
359
- lambda _: write_semaphore.release()
360
- )
361
- upload_fut.add_done_callback(
362
- lambda fut: finished_tasks.append(fut.result())
363
- )
364
-
365
- read_fut.add_done_callback(queue_upload_task)
366
- # SEMAPHORE ACQUIRE!!!
367
- # If we are back filled on the writers, then we stall.
368
- write_semaphore.acquire()
358
+ )
359
+
360
+ read_fut: Future[UploadPart] = read_executor.submit(_read_task)
361
+
362
+ # Releases the semaphore when the write task is done
363
+ def queue_upload_task(
364
+ read_fut=read_fut,
365
+ ) -> None:
366
+ upload_part = read_fut.result()
367
+ upload_fut: Future[UploadPart] = upload_executor.submit(
368
+ upload_task, self, upload_part
369
+ )
370
+ # SEMAPHORE RELEASE!!!
371
+ upload_fut.add_done_callback(
372
+ lambda _: write_semaphore.release()
373
+ )
374
+ upload_fut.add_done_callback(
375
+ lambda fut: finished_tasks.append(fut.result())
376
+ )
377
+
378
+ read_fut.add_done_callback(queue_upload_task)
379
+ # SEMAPHORE ACQUIRE!!!
380
+ # If we are back filled on the writers, then we stall.
381
+ write_semaphore.acquire()
369
382
 
370
383
  exceptions: list[Exception] = [
371
384
  t.exception for t in finished_tasks if t.exception is not None
372
385
  ]
386
+
387
+ shutil.rmtree(tmp_dir, ignore_errors=True)
388
+
373
389
  if len(exceptions) > 0:
374
390
  return Exception(f"Failed to copy parts: {exceptions}", exceptions)
375
391
 
rclone_api/http_server.py CHANGED
@@ -3,6 +3,7 @@ Unit test file for testing rclone mount functionality.
3
3
  """
4
4
 
5
5
  import tempfile
6
+ import time
6
7
  import warnings
7
8
  from concurrent.futures import Future, ThreadPoolExecutor
8
9
  from pathlib import Path
@@ -63,33 +64,46 @@ class HttpServer:
63
64
  self, path: str, dst: Path, range: Range | None = None
64
65
  ) -> Path | Exception:
65
66
  """Get bytes from the server."""
66
- if not dst.parent.exists():
67
- dst.parent.mkdir(parents=True, exist_ok=True)
68
- headers: dict[str, str] = {}
69
- if range:
70
- headers.update(range.to_header())
71
- url = self._get_file_url(path)
72
- try:
73
- with httpx.stream(
74
- "GET", url, headers=headers, timeout=_TIMEOUT
75
- ) as response:
76
- response.raise_for_status()
77
- with open(dst, "wb") as file:
78
- for chunk in response.iter_bytes(chunk_size=8192):
79
- if chunk:
80
- file.write(chunk)
81
- else:
82
- assert response.is_closed
83
- # print(f"Downloaded bytes {start}-{end} to {dst}")
84
- if range:
85
- print(f"Downloaded bytes {range.start}-{range.end} to {dst}")
86
- else:
87
- size = dst.stat().st_size
88
- print(f"Downloaded {size} bytes to {dst}")
89
- return dst
90
- except Exception as e:
91
- warnings.warn(f"Failed to download {url} to {dst}: {e}")
92
- return e
67
+
68
+ def task() -> Path | Exception:
69
+
70
+ if not dst.parent.exists():
71
+ dst.parent.mkdir(parents=True, exist_ok=True)
72
+ headers: dict[str, str] = {}
73
+ if range:
74
+ headers.update(range.to_header())
75
+ url = self._get_file_url(path)
76
+ try:
77
+ with httpx.stream(
78
+ "GET", url, headers=headers, timeout=_TIMEOUT
79
+ ) as response:
80
+ response.raise_for_status()
81
+ with open(dst, "wb") as file:
82
+ for chunk in response.iter_bytes(chunk_size=8192):
83
+ if chunk:
84
+ file.write(chunk)
85
+ else:
86
+ assert response.is_closed
87
+ # print(f"Downloaded bytes {start}-{end} to {dst}")
88
+ if range:
89
+ print(f"Downloaded bytes {range.start}-{range.end} to {dst}")
90
+ else:
91
+ size = dst.stat().st_size
92
+ print(f"Downloaded {size} bytes to {dst}")
93
+ return dst
94
+ except Exception as e:
95
+ warnings.warn(f"Failed to download {url} to {dst}: {e}")
96
+ return e
97
+
98
+ retries = 3
99
+ for i in _range(retries):
100
+ out = task()
101
+ if not isinstance(out, Exception):
102
+ return out
103
+ warnings.warn(f"Failed to download {path} to {dst}: {out}, retrying ({i})")
104
+ time.sleep(10)
105
+ else:
106
+ return Exception(f"Failed to download {path} to {dst}")
93
107
 
94
108
  def download_multi_threaded(
95
109
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.4.3
3
+ Version: 1.4.5
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -14,7 +14,7 @@ rclone_api/file_part.py,sha256=i6ByS5_sae8Eba-4imBVTxd-xKC8ExWy7NR8QGr0ors,6155
14
14
  rclone_api/file_stream.py,sha256=_W3qnwCuigqA0hzXl2q5pAxSZDRaUSwet4BkT0lpnzs,1431
15
15
  rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
16
16
  rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
17
- rclone_api/http_server.py,sha256=YrILbxK0Xpnpkm8l9uGEXH_AOCGPUX8Cx33jKVtu7ZM,8207
17
+ rclone_api/http_server.py,sha256=3fPBV6l50erTe32DyeJBNmsDrn5KuujsbmEAbx13T-c,8720
18
18
  rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
19
19
  rclone_api/mount.py,sha256=TE_VIBMW7J1UkF_6HRCt8oi_jGdMov4S51bm2OgxFAM,10045
20
20
  rclone_api/process.py,sha256=BGXJTZVT__jeaDyjN8_kRycliOhkBErMPdHO1hKRvJE,5271
@@ -32,7 +32,7 @@ rclone_api/cmd/save_to_db.py,sha256=ylvnhg_yzexM-m6Zr7XDiswvoDVSl56ELuFAdb9gqBY,
32
32
  rclone_api/db/__init__.py,sha256=OSRUdnSWUlDTOHmjdjVmxYTUNpTbtaJ5Ll9sl-PfZg0,40
33
33
  rclone_api/db/db.py,sha256=YRnYrCaXHwytQt07uEZ_mMpvPHo9-0IWcOb95fVOOfs,10086
34
34
  rclone_api/db/models.py,sha256=v7qaXUehvsDvU51uk69JI23fSIs9JFGcOa-Tv1c_wVs,1600
35
- rclone_api/detail/copy_file_parts.py,sha256=KgLl5vVF67RLqZ1aUmRR98BvoqTrbcFzPY9oZd0yyI0,12316
35
+ rclone_api/detail/copy_file_parts.py,sha256=UQ9nZQftZtUVLrofbG6Y1-rBppI8F4qE5lKAlAJC570,12642
36
36
  rclone_api/detail/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
37
37
  rclone_api/experimental/flags.py,sha256=qCVD--fSTmzlk9hloRLr0q9elzAOFzPsvVpKM3aB1Mk,2739
38
38
  rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
@@ -47,9 +47,9 @@ rclone_api/s3/multipart/file_info.py,sha256=8v_07_eADo0K-Nsv7F0Ac1wcv3lkIsrR3MaR
47
47
  rclone_api/s3/multipart/finished_piece.py,sha256=TcwA58-qgKBiskfHrePoCWaSSep6Za9psZEpzrLUUhE,1199
48
48
  rclone_api/s3/multipart/upload_info.py,sha256=d6_OfzFR_vtDzCEegFfzCfWi2kUBUV4aXZzqAEVp1c4,1874
49
49
  rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
50
- rclone_api-1.4.3.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
51
- rclone_api-1.4.3.dist-info/METADATA,sha256=7YDI5CVaVfOl9Wd-vKdPGRSIXCaX6nLR1yI9LTYsWc4,4627
52
- rclone_api-1.4.3.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
53
- rclone_api-1.4.3.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
54
- rclone_api-1.4.3.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
55
- rclone_api-1.4.3.dist-info/RECORD,,
50
+ rclone_api-1.4.5.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
51
+ rclone_api-1.4.5.dist-info/METADATA,sha256=6U0dTVTIP3w6wYGOeDTMJDjPWOUjEe-3kft24IlVjYc,4627
52
+ rclone_api-1.4.5.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
53
+ rclone_api-1.4.5.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
54
+ rclone_api-1.4.5.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
55
+ rclone_api-1.4.5.dist-info/RECORD,,