rclone-api 1.5.8__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.
Files changed (61) hide show
  1. rclone_api/__init__.py +951 -0
  2. rclone_api/assets/example.txt +1 -0
  3. rclone_api/cli.py +15 -0
  4. rclone_api/cmd/analyze.py +51 -0
  5. rclone_api/cmd/copy_large_s3.py +111 -0
  6. rclone_api/cmd/copy_large_s3_finish.py +81 -0
  7. rclone_api/cmd/list_files.py +27 -0
  8. rclone_api/cmd/save_to_db.py +77 -0
  9. rclone_api/completed_process.py +60 -0
  10. rclone_api/config.py +87 -0
  11. rclone_api/convert.py +31 -0
  12. rclone_api/db/__init__.py +3 -0
  13. rclone_api/db/db.py +277 -0
  14. rclone_api/db/models.py +57 -0
  15. rclone_api/deprecated.py +24 -0
  16. rclone_api/detail/copy_file_parts_resumable.py +42 -0
  17. rclone_api/detail/walk.py +116 -0
  18. rclone_api/diff.py +164 -0
  19. rclone_api/dir.py +113 -0
  20. rclone_api/dir_listing.py +66 -0
  21. rclone_api/exec.py +40 -0
  22. rclone_api/experimental/flags.py +89 -0
  23. rclone_api/experimental/flags_base.py +58 -0
  24. rclone_api/file.py +205 -0
  25. rclone_api/file_item.py +68 -0
  26. rclone_api/file_part.py +198 -0
  27. rclone_api/file_stream.py +52 -0
  28. rclone_api/filelist.py +30 -0
  29. rclone_api/group_files.py +256 -0
  30. rclone_api/http_server.py +244 -0
  31. rclone_api/install.py +95 -0
  32. rclone_api/log.py +44 -0
  33. rclone_api/mount.py +55 -0
  34. rclone_api/mount_util.py +247 -0
  35. rclone_api/process.py +187 -0
  36. rclone_api/rclone_impl.py +1285 -0
  37. rclone_api/remote.py +21 -0
  38. rclone_api/rpath.py +102 -0
  39. rclone_api/s3/api.py +109 -0
  40. rclone_api/s3/basic_ops.py +61 -0
  41. rclone_api/s3/chunk_task.py +187 -0
  42. rclone_api/s3/create.py +107 -0
  43. rclone_api/s3/multipart/file_info.py +7 -0
  44. rclone_api/s3/multipart/finished_piece.py +69 -0
  45. rclone_api/s3/multipart/info_json.py +239 -0
  46. rclone_api/s3/multipart/merge_state.py +147 -0
  47. rclone_api/s3/multipart/upload_info.py +62 -0
  48. rclone_api/s3/multipart/upload_parts_inline.py +356 -0
  49. rclone_api/s3/multipart/upload_parts_resumable.py +304 -0
  50. rclone_api/s3/multipart/upload_parts_server_side_merge.py +546 -0
  51. rclone_api/s3/multipart/upload_state.py +165 -0
  52. rclone_api/s3/types.py +67 -0
  53. rclone_api/scan_missing_folders.py +153 -0
  54. rclone_api/types.py +402 -0
  55. rclone_api/util.py +324 -0
  56. rclone_api-1.5.8.dist-info/LICENSE +21 -0
  57. rclone_api-1.5.8.dist-info/METADATA +969 -0
  58. rclone_api-1.5.8.dist-info/RECORD +61 -0
  59. rclone_api-1.5.8.dist-info/WHEEL +5 -0
  60. rclone_api-1.5.8.dist-info/entry_points.txt +5 -0
  61. rclone_api-1.5.8.dist-info/top_level.txt +1 -0
rclone_api/process.py ADDED
@@ -0,0 +1,187 @@
1
+ import atexit
2
+ import subprocess
3
+ import threading
4
+ import weakref
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import psutil
10
+
11
+ from rclone_api.config import Config
12
+ from rclone_api.util import clear_temp_config_file, get_verbose, make_temp_config_file
13
+
14
+
15
+ @dataclass
16
+ class ProcessArgs:
17
+ cmd: list[str]
18
+ rclone_conf: Path | Config
19
+ rclone_exe: Path
20
+ cmd_list: list[str]
21
+ verbose: bool | None = None
22
+ capture_stdout: bool | None = None
23
+ log: Path | None = None
24
+
25
+
26
+ class Process:
27
+ def __init__(self, args: ProcessArgs) -> None:
28
+ assert (
29
+ args.rclone_exe.exists()
30
+ ), f"rclone executable not found: {args.rclone_exe}"
31
+ self.args = args
32
+ self.log = args.log
33
+ self.tempfile: Path | None = None
34
+
35
+ verbose = get_verbose(args.verbose)
36
+ # Create a temporary config file if needed.
37
+ if isinstance(args.rclone_conf, Config):
38
+ self.tempfile = make_temp_config_file()
39
+ self.tempfile.write_text(args.rclone_conf.text, encoding="utf-8")
40
+ rclone_conf = self.tempfile
41
+ else:
42
+ rclone_conf = args.rclone_conf
43
+
44
+ assert rclone_conf.exists(), f"rclone config not found: {rclone_conf}"
45
+
46
+ # Build the command.
47
+ self.cmd = (
48
+ [str(args.rclone_exe.resolve())]
49
+ + ["--config", str(rclone_conf.resolve())]
50
+ + args.cmd
51
+ )
52
+ if self.args.log:
53
+ self.args.log.parent.mkdir(parents=True, exist_ok=True)
54
+ self.cmd += ["--log-file", str(self.args.log)]
55
+ if verbose:
56
+ cmd_str = subprocess.list2cmdline(self.cmd)
57
+ print(f"Running: {cmd_str}")
58
+ kwargs: dict = {"shell": False}
59
+ if args.capture_stdout:
60
+ kwargs["stdout"] = subprocess.PIPE
61
+ kwargs["stderr"] = subprocess.STDOUT
62
+
63
+ self.process = subprocess.Popen(self.cmd, **kwargs) # type: ignore
64
+
65
+ # Register an atexit callback using a weak reference to avoid keeping the Process instance alive.
66
+ self_ref = weakref.ref(self)
67
+
68
+ def exit_cleanup():
69
+ obj = self_ref()
70
+ if obj is not None:
71
+ obj._atexit_terminate()
72
+
73
+ atexit.register(exit_cleanup)
74
+
75
+ def __enter__(self) -> "Process":
76
+ return self
77
+
78
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
79
+ self.terminate()
80
+ self.wait()
81
+ self.cleanup()
82
+
83
+ def cleanup(self) -> None:
84
+ if self.tempfile:
85
+ clear_temp_config_file(self.tempfile)
86
+
87
+ def _kill_process_tree(self) -> None:
88
+ """
89
+ Use psutil to recursively terminate the main process and all its child processes.
90
+ """
91
+ try:
92
+ parent = psutil.Process(self.process.pid)
93
+ except psutil.NoSuchProcess:
94
+ return
95
+
96
+ # Terminate child processes.
97
+ children = parent.children(recursive=True)
98
+ if children:
99
+ print(f"Terminating {len(children)} child processes...")
100
+ for child in children:
101
+ try:
102
+ child.terminate()
103
+ except Exception as e:
104
+ print(f"Error terminating child process {child.pid}: {e}")
105
+ psutil.wait_procs(children, timeout=2)
106
+ # Kill any that remain.
107
+ for child in children:
108
+ if child.is_running():
109
+ try:
110
+ child.kill()
111
+ except Exception as e:
112
+ print(f"Error killing child process {child.pid}: {e}")
113
+
114
+ # Terminate the parent process.
115
+ if parent.is_running():
116
+ try:
117
+ parent.terminate()
118
+ except Exception as e:
119
+ print(f"Error terminating process {parent.pid}: {e}")
120
+ try:
121
+ parent.wait(timeout=3)
122
+ except psutil.TimeoutExpired:
123
+ try:
124
+ parent.kill()
125
+ except Exception as e:
126
+ print(f"Error killing process {parent.pid}: {e}")
127
+
128
+ def _atexit_terminate(self) -> None:
129
+ """
130
+ This method is registered via atexit and uses psutil to clean up the process tree.
131
+ It runs in a daemon thread so that termination happens without blocking interpreter shutdown.
132
+ """
133
+ if self.process.poll() is None: # Process is still running.
134
+
135
+ def terminate_sequence():
136
+ self._kill_process_tree()
137
+
138
+ t = threading.Thread(target=terminate_sequence, daemon=True)
139
+ t.start()
140
+ t.join(timeout=3)
141
+
142
+ @property
143
+ def pid(self) -> int:
144
+ return self.process.pid
145
+
146
+ def __del__(self) -> None:
147
+ self.cleanup()
148
+
149
+ def kill(self) -> None:
150
+ """Forcefully kill the process tree."""
151
+ self._kill_process_tree()
152
+
153
+ def terminate(self) -> None:
154
+ """Gracefully terminate the process tree."""
155
+ self._kill_process_tree()
156
+
157
+ @property
158
+ def returncode(self) -> int | None:
159
+ return self.process.returncode
160
+
161
+ @property
162
+ def stdout(self) -> Any:
163
+ return self.process.stdout
164
+
165
+ @property
166
+ def stderr(self) -> Any:
167
+ return self.process.stderr
168
+
169
+ def poll(self) -> int | None:
170
+ return self.process.poll()
171
+
172
+ def wait(self) -> int:
173
+ return self.process.wait()
174
+
175
+ def send_signal(self, sig: int) -> None:
176
+ self.process.send_signal(sig)
177
+
178
+ def __str__(self) -> str:
179
+ state = ""
180
+ rtn = self.process.poll()
181
+ if rtn is None:
182
+ state = "running"
183
+ elif rtn != 0:
184
+ state = f"error: {rtn}"
185
+ else:
186
+ state = "finished ok"
187
+ return f"Process({self.cmd}, {state})"