cloudhop 0.6.0__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.
cloudhop-0.6.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Husam Soboh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudhop
3
+ Version: 0.6.0
4
+ Summary: The easiest way to copy files between cloud storage services
5
+ Author: Husam Soboh
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/husamsoboh-cyber/cloudhop
8
+ Project-URL: Issues, https://github.com/husamsoboh-cyber/cloudhop/issues
9
+ Keywords: cloud,file-transfer,rclone,google-drive,onedrive,dropbox,icloud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
20
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
21
+ [![PyPI](https://img.shields.io/pypi/v/cloudhop.svg)](https://pypi.org/project/cloudhop/)
22
+ [![GitHub stars](https://img.shields.io/github/stars/husamsoboh-cyber/cloudhop.svg?style=social)](https://github.com/husamsoboh-cyber/cloudhop)
23
+
24
+ # CloudHop - Free Cloud File Transfer
25
+
26
+ **Switching cloud providers? CloudHop copies everything for you. Free, open source, runs on your machine.**
27
+
28
+ ![CloudHop Dashboard](screenshots/dashboard.png)
29
+
30
+ ## Download / Install
31
+
32
+ **Mac** - Download `CloudHop.dmg` from [Releases](https://github.com/husamsoboh-cyber/cloudhop/releases)
33
+
34
+ **Windows** - Download `CloudHop-Setup.exe` from [Releases](https://github.com/husamsoboh-cyber/cloudhop/releases)
35
+
36
+ **pip**
37
+ ```bash
38
+ pip install cloudhop && cloudhop
39
+ ```
40
+
41
+ **From source**
42
+ ```bash
43
+ git clone https://github.com/husamsoboh-cyber/cloudhop && cd cloudhop && pip install -e . && cloudhop
44
+ ```
45
+
46
+ ## Why CloudHop?
47
+
48
+ - **Free and open source** -- no limits, no account needed
49
+ - **Runs on your machine** -- files never touch our servers
50
+ - **Works with 70+ cloud providers** -- Google Drive, OneDrive, Dropbox, iCloud, MEGA, S3, Proton Drive...
51
+ - **Visual wizard** -- no command line needed
52
+ - **Pause and resume** across restarts
53
+
54
+ ## How is this different from...
55
+
56
+ **rclone?**
57
+ CloudHop uses rclone as its engine. If you're comfortable with CLI, you don't need this. CloudHop adds a visual wizard and live dashboard.
58
+
59
+ **MultCloud / CloudFuze?**
60
+ Those are paid SaaS that route files through their servers. CloudHop is free and your files transfer directly between providers.
61
+
62
+ **Download and re-upload?**
63
+ That requires local disk space and 2x transfer time. CloudHop uses server-side copy where supported.
64
+
65
+ ## How it works
66
+
67
+ 1. **Run CloudHop** -- launch the app or run `cloudhop` in a terminal
68
+ 2. **Pick source** -- choose where your files are (e.g., OneDrive)
69
+ 3. **Pick destination** -- choose where to copy them (e.g., Google Drive)
70
+ 4. **Configure options** -- set parallel transfers, exclude folders, limit bandwidth
71
+ 5. **Connect accounts** -- authorize each cloud provider in your browser
72
+ 6. **Start transfer** -- watch progress in the live dashboard with speed charts and ETA
73
+
74
+ ## CLI Usage
75
+
76
+ ```bash
77
+ cloudhop source: dest: [--transfers=8] [--bwlimit=10M] [--exclude="*.tmp"]
78
+ ```
79
+
80
+ ## Supported Providers
81
+
82
+ Google Drive, OneDrive, Dropbox, iCloud Drive, MEGA, Amazon S3, Proton Drive, Local Folder + 70 more via rclone
83
+
84
+ ## Links
85
+
86
+ [Security](SECURITY.md) | [Contributing](CONTRIBUTING.md) | [Changelog](CHANGELOG.md)
87
+
88
+ ## Built by
89
+
90
+ Built by an orthodontist who needed to move 500GB between cloud services and found no free, simple tool to do it.
91
+
92
+ ## Donate
93
+
94
+ If CloudHop saved you time, consider supporting development:
95
+
96
+ [GitHub Sponsors](https://github.com/sponsors/husamsoboh-cyber) | [Buy Me a Coffee](https://buymeacoffee.com/husamsoboh)
97
+
98
+ ## License
99
+
100
+ MIT License -- see [LICENSE](LICENSE) for details.
@@ -0,0 +1,82 @@
1
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
2
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
3
+ [![PyPI](https://img.shields.io/pypi/v/cloudhop.svg)](https://pypi.org/project/cloudhop/)
4
+ [![GitHub stars](https://img.shields.io/github/stars/husamsoboh-cyber/cloudhop.svg?style=social)](https://github.com/husamsoboh-cyber/cloudhop)
5
+
6
+ # CloudHop - Free Cloud File Transfer
7
+
8
+ **Switching cloud providers? CloudHop copies everything for you. Free, open source, runs on your machine.**
9
+
10
+ ![CloudHop Dashboard](screenshots/dashboard.png)
11
+
12
+ ## Download / Install
13
+
14
+ **Mac** - Download `CloudHop.dmg` from [Releases](https://github.com/husamsoboh-cyber/cloudhop/releases)
15
+
16
+ **Windows** - Download `CloudHop-Setup.exe` from [Releases](https://github.com/husamsoboh-cyber/cloudhop/releases)
17
+
18
+ **pip**
19
+ ```bash
20
+ pip install cloudhop && cloudhop
21
+ ```
22
+
23
+ **From source**
24
+ ```bash
25
+ git clone https://github.com/husamsoboh-cyber/cloudhop && cd cloudhop && pip install -e . && cloudhop
26
+ ```
27
+
28
+ ## Why CloudHop?
29
+
30
+ - **Free and open source** -- no limits, no account needed
31
+ - **Runs on your machine** -- files never touch our servers
32
+ - **Works with 70+ cloud providers** -- Google Drive, OneDrive, Dropbox, iCloud, MEGA, S3, Proton Drive...
33
+ - **Visual wizard** -- no command line needed
34
+ - **Pause and resume** across restarts
35
+
36
+ ## How is this different from...
37
+
38
+ **rclone?**
39
+ CloudHop uses rclone as its engine. If you're comfortable with CLI, you don't need this. CloudHop adds a visual wizard and live dashboard.
40
+
41
+ **MultCloud / CloudFuze?**
42
+ Those are paid SaaS that route files through their servers. CloudHop is free and your files transfer directly between providers.
43
+
44
+ **Download and re-upload?**
45
+ That requires local disk space and 2x transfer time. CloudHop uses server-side copy where supported.
46
+
47
+ ## How it works
48
+
49
+ 1. **Run CloudHop** -- launch the app or run `cloudhop` in a terminal
50
+ 2. **Pick source** -- choose where your files are (e.g., OneDrive)
51
+ 3. **Pick destination** -- choose where to copy them (e.g., Google Drive)
52
+ 4. **Configure options** -- set parallel transfers, exclude folders, limit bandwidth
53
+ 5. **Connect accounts** -- authorize each cloud provider in your browser
54
+ 6. **Start transfer** -- watch progress in the live dashboard with speed charts and ETA
55
+
56
+ ## CLI Usage
57
+
58
+ ```bash
59
+ cloudhop source: dest: [--transfers=8] [--bwlimit=10M] [--exclude="*.tmp"]
60
+ ```
61
+
62
+ ## Supported Providers
63
+
64
+ Google Drive, OneDrive, Dropbox, iCloud Drive, MEGA, Amazon S3, Proton Drive, Local Folder + 70 more via rclone
65
+
66
+ ## Links
67
+
68
+ [Security](SECURITY.md) | [Contributing](CONTRIBUTING.md) | [Changelog](CHANGELOG.md)
69
+
70
+ ## Built by
71
+
72
+ Built by an orthodontist who needed to move 500GB between cloud services and found no free, simple tool to do it.
73
+
74
+ ## Donate
75
+
76
+ If CloudHop saved you time, consider supporting development:
77
+
78
+ [GitHub Sponsors](https://github.com/sponsors/husamsoboh-cyber) | [Buy Me a Coffee](https://buymeacoffee.com/husamsoboh)
79
+
80
+ ## License
81
+
82
+ MIT License -- see [LICENSE](LICENSE) for details.
@@ -0,0 +1,2 @@
1
+ """CloudHop - The easiest way to copy files between cloud storage services."""
2
+ __version__ = "0.6.0"
@@ -0,0 +1,4 @@
1
+ """Entry point for python -m cloudhop."""
2
+ from .cli import main
3
+
4
+ main()
@@ -0,0 +1,200 @@
1
+ """CloudHop CLI entry point."""
2
+ import sys
3
+ import signal
4
+ import platform
5
+ import webbrowser
6
+ import threading
7
+ import http.server
8
+ from typing import Any, List
9
+
10
+ from .server import CloudHopHandler
11
+ from .transfer import TransferManager, ensure_rclone
12
+ from .utils import PORT
13
+
14
+
15
+ def main() -> None:
16
+ """Main entry point -- wizard mode (no args) or CLI mode (source dest [flags])."""
17
+ args = sys.argv[1:]
18
+ manager = TransferManager()
19
+ CloudHopHandler.manager = manager
20
+
21
+ signal.signal(signal.SIGTERM, lambda signum, frame: _signal_handler(manager))
22
+
23
+ if len(args) == 0:
24
+ # Web wizard mode
25
+ print()
26
+ print(" +==================================================+")
27
+ print(" | Welcome to CloudHop |")
28
+ print(" | |")
29
+ print(" | Starting web setup wizard... |")
30
+ print(" +==================================================+")
31
+ if platform.system() == "Windows":
32
+ print(" Note: Some features (pause/resume) may not work on Windows.")
33
+ print(" For best results, use macOS or Linux.")
34
+ print()
35
+ start_dashboard(manager, start_rclone=False)
36
+ else:
37
+ # CLI mode for advanced users
38
+ ensure_rclone()
39
+ parse_cli_args(manager, args)
40
+ print()
41
+ if manager.rclone_pid and not manager.rclone_cmd:
42
+ # Attach mode - monitoring existing process
43
+ print(f" CloudHop - Monitoring PID {manager.rclone_pid}")
44
+ print(f" Log: {manager.log_file}")
45
+ start_dashboard(manager, start_rclone=False)
46
+ else:
47
+ print(" CloudHop - Advanced Mode")
48
+ print(f" Command: {' '.join(manager.rclone_cmd)}")
49
+ start_dashboard(manager, start_rclone=True)
50
+
51
+
52
+ def start_dashboard(manager: TransferManager, start_rclone: bool = False) -> None:
53
+ """Start the web dashboard and optionally the rclone process."""
54
+ import subprocess
55
+
56
+ # Load RCLONE_CMD from state if not set (enables resume after restart)
57
+ if not manager.rclone_cmd and "rclone_cmd" in manager.state:
58
+ manager.rclone_cmd = manager.state["rclone_cmd"]
59
+ # Restore LOG_FILE from the saved command
60
+ for arg in manager.rclone_cmd:
61
+ if arg.startswith("--log-file="):
62
+ manager.log_file = arg.split("=", 1)[1]
63
+ break
64
+
65
+ # Initial full log scan
66
+ print()
67
+ print(" Scanning transfer log...")
68
+ manager.scan_full_log()
69
+ session_count = len(manager.state.get("sessions", []))
70
+ if session_count > 0:
71
+ print(f" Found {session_count} previous session(s)")
72
+
73
+ # Start background scanner
74
+ scanner = threading.Thread(target=manager.background_scanner, daemon=True)
75
+ scanner.start()
76
+
77
+ # Start rclone if requested
78
+ if start_rclone and manager.rclone_cmd:
79
+ print(" Starting file transfer...")
80
+ proc = subprocess.Popen(
81
+ manager.rclone_cmd,
82
+ stdout=subprocess.DEVNULL,
83
+ stderr=subprocess.DEVNULL,
84
+ start_new_session=True,
85
+ )
86
+ manager.rclone_pid = proc.pid
87
+ print(f" Transfer started (PID {proc.pid})")
88
+ manager.transfer_active = True
89
+
90
+ port = PORT
91
+
92
+ print()
93
+ print(f" CloudHop: http://localhost:{port}")
94
+ print()
95
+ if manager.transfer_active:
96
+ print(" Open the link above in your browser to monitor progress.")
97
+ else:
98
+ print(" Open the link above in your browser to start the setup wizard.")
99
+ print(" Press Ctrl+C to stop the server.")
100
+ print()
101
+
102
+ server = None
103
+ for try_port in range(port, port + 5):
104
+ try:
105
+ server = http.server.ThreadingHTTPServer(("127.0.0.1", try_port), CloudHopHandler)
106
+ if try_port != port:
107
+ print(f" Port {port} was busy, using port {try_port} instead.")
108
+ port = try_port
109
+ CloudHopHandler.actual_port = port
110
+ break
111
+ except OSError as e:
112
+ if ("Address already in use" in str(e) or e.errno == 48) and try_port < port + 4:
113
+ continue
114
+ if "Address already in use" in str(e) or e.errno == 48:
115
+ print(f"\n Error: Ports {port}-{port + 4} are all in use.")
116
+ print(" Please stop the other process(es) and try again.\n")
117
+ sys.exit(1)
118
+ raise
119
+
120
+ if server is None:
121
+ print(f"\n Error: Could not bind to any port in range {port}-{port + 4}.\n")
122
+ sys.exit(1)
123
+
124
+ # Try to open browser automatically (after port binding succeeds)
125
+ try:
126
+ webbrowser.open(f"http://localhost:{port}")
127
+ except Exception:
128
+ pass
129
+
130
+ try:
131
+ server.serve_forever()
132
+ except KeyboardInterrupt:
133
+ _signal_handler(manager)
134
+
135
+
136
+ def parse_cli_args(manager: TransferManager, args: List[str]) -> None:
137
+ """Parse CLI arguments for advanced usage: cloudhop source: dest: [flags]"""
138
+ source = None
139
+ dest = None
140
+ extra_flags = []
141
+ attach_pid = None
142
+ attach_log = None
143
+
144
+ for arg in args:
145
+ if arg.startswith("--attach-pid="):
146
+ try:
147
+ attach_pid = int(arg.split("=", 1)[1])
148
+ except ValueError:
149
+ print(f" Error: --attach-pid requires a numeric PID")
150
+ sys.exit(1)
151
+ elif arg.startswith("--attach-log="):
152
+ attach_log = arg.split("=", 1)[1]
153
+ elif arg.startswith("--"):
154
+ extra_flags.append(arg)
155
+ elif source is None:
156
+ source = arg
157
+ elif dest is None:
158
+ dest = arg
159
+ else:
160
+ extra_flags.append(arg)
161
+
162
+ if not source or not dest:
163
+ print("Usage: cloudhop <source> <destination> [--flags]")
164
+ print("Example: cloudhop onedrive: gdrive:backup --transfers=8")
165
+ print()
166
+ print("Or just run without arguments for the interactive wizard:")
167
+ print(" cloudhop")
168
+ sys.exit(1)
169
+
170
+ manager.set_transfer_paths(source, dest)
171
+
172
+ # Attach to an existing rclone process instead of starting a new one
173
+ if attach_pid:
174
+ manager.rclone_pid = attach_pid
175
+ manager.transfer_active = True
176
+ if attach_log:
177
+ manager.log_file = attach_log
178
+ return
179
+
180
+ manager.rclone_cmd = [
181
+ "rclone", "copy", source, dest,
182
+ f"--log-file={manager.log_file}",
183
+ "--log-level=INFO",
184
+ "--stats=10s",
185
+ "--stats-log-level=INFO",
186
+ ] + extra_flags
187
+
188
+ # Add default transfers if not specified
189
+ if not any(f.startswith("--transfers") for f in extra_flags):
190
+ manager.rclone_cmd.append("--transfers=8")
191
+ if not any(f.startswith("--checkers") for f in extra_flags):
192
+ manager.rclone_cmd.append("--checkers=16")
193
+
194
+
195
+ def _signal_handler(manager: TransferManager) -> None:
196
+ print("\n CloudHop stopped.")
197
+ if manager.transfer_active:
198
+ print(" (The file transfer continues in the background)")
199
+ print()
200
+ sys.exit(0)