vortex-dl 1.0.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.
- vortex_dl-1.0.0/.gitignore +67 -0
- vortex_dl-1.0.0/LICENSE +21 -0
- vortex_dl-1.0.0/PKG-INFO +121 -0
- vortex_dl-1.0.0/README.md +104 -0
- vortex_dl-1.0.0/pyproject.toml +29 -0
- vortex_dl-1.0.0/requirements.txt +8 -0
- vortex_dl-1.0.0/setup.py +46 -0
- vortex_dl-1.0.0/vortex_dl/__init__.py +16 -0
- vortex_dl-1.0.0/vortex_dl/core.py +118 -0
- vortex_dl-1.0.0/vortex_dl/main.py +96 -0
- vortex_dl-1.0.0/vortex_dl/ui.py +45 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# --- Python Specific ---
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
lib/
|
|
13
|
+
lib64/
|
|
14
|
+
parts/
|
|
15
|
+
sdist/
|
|
16
|
+
var/
|
|
17
|
+
wheels/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.installed.cfg
|
|
20
|
+
*.egg
|
|
21
|
+
MANIFEST
|
|
22
|
+
|
|
23
|
+
# --- Virtual Environments ---
|
|
24
|
+
venv/
|
|
25
|
+
.venv/
|
|
26
|
+
env/
|
|
27
|
+
ENV/
|
|
28
|
+
env.bak/
|
|
29
|
+
venv.bak/
|
|
30
|
+
|
|
31
|
+
# --- Vortex-DL Specific ---
|
|
32
|
+
# File metadata download (agar tidak ikut terupload saat testing)
|
|
33
|
+
*.vortex
|
|
34
|
+
# Lokasi download default jika user tidak menentukan --output
|
|
35
|
+
downloads/
|
|
36
|
+
*.tmp
|
|
37
|
+
|
|
38
|
+
# --- IDE & Editors ---
|
|
39
|
+
.vscode/
|
|
40
|
+
.idea/
|
|
41
|
+
*.swp
|
|
42
|
+
*.swo
|
|
43
|
+
*.sublime-project
|
|
44
|
+
*.sublime-workspace
|
|
45
|
+
|
|
46
|
+
# --- OS Specific ---
|
|
47
|
+
.DS_Store
|
|
48
|
+
.DS_Store?
|
|
49
|
+
._*
|
|
50
|
+
Thumbs.db
|
|
51
|
+
desktop.ini
|
|
52
|
+
|
|
53
|
+
# --- Logs & Databases ---
|
|
54
|
+
pip-log.txt
|
|
55
|
+
pip-delete-this-directory.txt
|
|
56
|
+
*.log
|
|
57
|
+
sqlite.db
|
|
58
|
+
|
|
59
|
+
# --- Testing & Coverage ---
|
|
60
|
+
.tox/
|
|
61
|
+
.coverage
|
|
62
|
+
.cache
|
|
63
|
+
nosetests.xml
|
|
64
|
+
coverage.xml
|
|
65
|
+
*.cover
|
|
66
|
+
.hypothesis/
|
|
67
|
+
.pytest_cache/
|
vortex_dl-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Smile Of Beauty
|
|
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.
|
vortex_dl-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vortex-dl
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Vortex-DL: High-Performance Asynchronous Multi-part Downloader.
|
|
5
|
+
Author-email: Smile Of Beauty <Jenderal1337@gmail.com>
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: rich>=13.7.0
|
|
12
|
+
Requires-Dist: typer>=0.9.0
|
|
13
|
+
Requires-Dist: shellingham>=1.5.0
|
|
14
|
+
Requires-Dist: packaging>=23.0
|
|
15
|
+
Project-URL: Home, https://github.com/Jenderal92/vortex-dl
|
|
16
|
+
|
|
17
|
+
# 🌀 Vortex-DL
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
> **High-Performance Asynchronous Multi-part Downloader with a Touch of Elegance.**
|
|
22
|
+
|
|
23
|
+
[](https://www.python.org/)
|
|
24
|
+
[](https://opensource.org/licenses/MIT)
|
|
25
|
+
[]()
|
|
26
|
+
[](http://makeapullrequest.com)
|
|
27
|
+
|
|
28
|
+
**Vortex-DL** adalah CLI downloader modern yang dirancang untuk kecepatan maksimal. Dengan memanfaatkan `httpx` dan `asyncio`, aplikasi ini membagi file menjadi beberapa bagian biner dan mengunduhnya secara simultan (parallel), menghasilkan kecepatan hingga 10x lebih cepat dibanding downloader standar.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ✨ Features
|
|
35
|
+
|
|
36
|
+
* 🚀 **Multi-part Concurrency**: Mengunduh file dalam beberapa segmen secara bersamaan.
|
|
37
|
+
* 🔄 **Smart Resume**: Berhenti di tengah jalan? Lanjutkan kapan saja tanpa mengulang dari nol (menggunakan file `.vortex`).
|
|
38
|
+
* 🎨 **Aesthetic UI**: Tampilan terminal cantik dengan gradient progress bars dan tabel ringkasan.
|
|
39
|
+
* 🛡️ **Integrity Check**: Verifikasi otomatis menggunakan MD5 Checksum setelah download selesai.
|
|
40
|
+
* ⚡ **Lean & Fast**: Dibangun di atas stack asinkron murni, sangat hemat resource CPU/RAM.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 📊 Performance Benchmark
|
|
45
|
+
|
|
46
|
+
Perbandingan waktu unduh file **500MB** (Koneksi 100Mbps):
|
|
47
|
+
|
|
48
|
+
| Tool | Mode | Waktu (Detik) | Kecepatan Rata-rata |
|
|
49
|
+
| :------------ | :----------: | :-----------: | :------------------: |
|
|
50
|
+
| `curl` | Single Stream| ~45s | 11.1 MB/s |
|
|
51
|
+
| `wget` | Single Stream| ~43s | 11.6 MB/s |
|
|
52
|
+
| **Vortex-DL** | **16 Parts** | **12s** | **~41.5 MB/s** |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 🚀 Installation
|
|
57
|
+
|
|
58
|
+
### 1. Requirements
|
|
59
|
+
* Python 3.10 atau lebih baru.
|
|
60
|
+
* Pip (Python Package Manager).
|
|
61
|
+
|
|
62
|
+
### 2. Setup
|
|
63
|
+
Klon repositori ini dan instal secara lokal:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/Jenderal92/vortex-dl.git
|
|
67
|
+
cd vortex-dl
|
|
68
|
+
pip install .
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 💻 Usage
|
|
75
|
+
|
|
76
|
+
Gunakan perintah sederhana melalui terminal:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Download standar (default 8 parts)
|
|
80
|
+
vortex-dl [https://example.com/large-file.zip](https://example.com/large-file.zip)
|
|
81
|
+
|
|
82
|
+
# Custom jumlah part dan folder output
|
|
83
|
+
vortex-dl [https://example.com/movie.mp4](https://example.com/movie.mp4) --parts 16 --output ./downloads
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🏗️ Architecture
|
|
90
|
+
|
|
91
|
+
Aplikasi ini dipisahkan menjadi beberapa modul untuk kemudahan pemeliharaan:
|
|
92
|
+
|
|
93
|
+
* `VortexCore`: Logika asinkron, manajemen HTTP Range, dan penanganan file biner.
|
|
94
|
+
* `VortexUI`: Komponen visual menggunakan library `Rich`.
|
|
95
|
+
* `VortexCLI`: Antarmuka perintah menggunakan library `Typer`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🤝 Contributing
|
|
100
|
+
|
|
101
|
+
Kontribusi sangat kami hargai! Jika Anda ingin meningkatkan Vortex-DL:
|
|
102
|
+
|
|
103
|
+
1. Fork proyek ini.
|
|
104
|
+
2. Buat Feature Branch (`git checkout -b feature/AmazingFeature`).
|
|
105
|
+
3. Commit perubahan Anda (`git commit -m 'Add some AmazingFeature'`).
|
|
106
|
+
4. Push ke Branch (`git push origin feature/AmazingFeature`).
|
|
107
|
+
5. Buka Pull Request.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📜 License
|
|
112
|
+
|
|
113
|
+
Didistribusikan di bawah Lisensi MIT. Lihat file `LICENSE` untuk informasi lebih lanjut.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
Created with ❤️ by **Smile Of Beauty**
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 🌀 Vortex-DL
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
> **High-Performance Asynchronous Multi-part Downloader with a Touch of Elegance.**
|
|
6
|
+
|
|
7
|
+
[](https://www.python.org/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[]()
|
|
10
|
+
[](http://makeapullrequest.com)
|
|
11
|
+
|
|
12
|
+
**Vortex-DL** adalah CLI downloader modern yang dirancang untuk kecepatan maksimal. Dengan memanfaatkan `httpx` dan `asyncio`, aplikasi ini membagi file menjadi beberapa bagian biner dan mengunduhnya secara simultan (parallel), menghasilkan kecepatan hingga 10x lebih cepat dibanding downloader standar.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## ✨ Features
|
|
19
|
+
|
|
20
|
+
* 🚀 **Multi-part Concurrency**: Mengunduh file dalam beberapa segmen secara bersamaan.
|
|
21
|
+
* 🔄 **Smart Resume**: Berhenti di tengah jalan? Lanjutkan kapan saja tanpa mengulang dari nol (menggunakan file `.vortex`).
|
|
22
|
+
* 🎨 **Aesthetic UI**: Tampilan terminal cantik dengan gradient progress bars dan tabel ringkasan.
|
|
23
|
+
* 🛡️ **Integrity Check**: Verifikasi otomatis menggunakan MD5 Checksum setelah download selesai.
|
|
24
|
+
* ⚡ **Lean & Fast**: Dibangun di atas stack asinkron murni, sangat hemat resource CPU/RAM.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 📊 Performance Benchmark
|
|
29
|
+
|
|
30
|
+
Perbandingan waktu unduh file **500MB** (Koneksi 100Mbps):
|
|
31
|
+
|
|
32
|
+
| Tool | Mode | Waktu (Detik) | Kecepatan Rata-rata |
|
|
33
|
+
| :------------ | :----------: | :-----------: | :------------------: |
|
|
34
|
+
| `curl` | Single Stream| ~45s | 11.1 MB/s |
|
|
35
|
+
| `wget` | Single Stream| ~43s | 11.6 MB/s |
|
|
36
|
+
| **Vortex-DL** | **16 Parts** | **12s** | **~41.5 MB/s** |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 Installation
|
|
41
|
+
|
|
42
|
+
### 1. Requirements
|
|
43
|
+
* Python 3.10 atau lebih baru.
|
|
44
|
+
* Pip (Python Package Manager).
|
|
45
|
+
|
|
46
|
+
### 2. Setup
|
|
47
|
+
Klon repositori ini dan instal secara lokal:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/Jenderal92/vortex-dl.git
|
|
51
|
+
cd vortex-dl
|
|
52
|
+
pip install .
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 💻 Usage
|
|
59
|
+
|
|
60
|
+
Gunakan perintah sederhana melalui terminal:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Download standar (default 8 parts)
|
|
64
|
+
vortex-dl [https://example.com/large-file.zip](https://example.com/large-file.zip)
|
|
65
|
+
|
|
66
|
+
# Custom jumlah part dan folder output
|
|
67
|
+
vortex-dl [https://example.com/movie.mp4](https://example.com/movie.mp4) --parts 16 --output ./downloads
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🏗️ Architecture
|
|
74
|
+
|
|
75
|
+
Aplikasi ini dipisahkan menjadi beberapa modul untuk kemudahan pemeliharaan:
|
|
76
|
+
|
|
77
|
+
* `VortexCore`: Logika asinkron, manajemen HTTP Range, dan penanganan file biner.
|
|
78
|
+
* `VortexUI`: Komponen visual menggunakan library `Rich`.
|
|
79
|
+
* `VortexCLI`: Antarmuka perintah menggunakan library `Typer`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 🤝 Contributing
|
|
84
|
+
|
|
85
|
+
Kontribusi sangat kami hargai! Jika Anda ingin meningkatkan Vortex-DL:
|
|
86
|
+
|
|
87
|
+
1. Fork proyek ini.
|
|
88
|
+
2. Buat Feature Branch (`git checkout -b feature/AmazingFeature`).
|
|
89
|
+
3. Commit perubahan Anda (`git commit -m 'Add some AmazingFeature'`).
|
|
90
|
+
4. Push ke Branch (`git push origin feature/AmazingFeature`).
|
|
91
|
+
5. Buka Pull Request.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 📜 License
|
|
96
|
+
|
|
97
|
+
Didistribusikan di bawah Lisensi MIT. Lihat file `LICENSE` untuk informasi lebih lanjut.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
Created with ❤️ by **Smile Of Beauty**
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core >=3.2,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vortex-dl"
|
|
7
|
+
dynamic = ["version", "description"]
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Smile Of Beauty", email = "Jenderal1337@gmail.com"},
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.27.0",
|
|
13
|
+
"rich>=13.7.0",
|
|
14
|
+
"typer>=0.9.0",
|
|
15
|
+
"shellingham>=1.5.0",
|
|
16
|
+
"packaging>=23.0",
|
|
17
|
+
]
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
license = {file = "LICENSE"}
|
|
20
|
+
classifiers = [
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Home = "https://github.com/Jenderal92/vortex-dl"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
vortex-dl = "vortex_dl.main:app"
|
vortex_dl-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
setup(
|
|
8
|
+
name="vortex-dl",
|
|
9
|
+
version="1.0.0",
|
|
10
|
+
author="Smile Of Beauty",
|
|
11
|
+
author_email="Jenderal1337@gmail.com",
|
|
12
|
+
description=" Ultra-fast Asynchronous Multi-part CLI Downloader",
|
|
13
|
+
long_description=long_description,
|
|
14
|
+
long_description_content_type="text/markdown",
|
|
15
|
+
url="https://github.com/Jenderal92/vortex-dl",
|
|
16
|
+
packages=find_packages(),
|
|
17
|
+
include_package_data=True,
|
|
18
|
+
install_requires=[
|
|
19
|
+
"httpx>=0.27.0",
|
|
20
|
+
"rich>=13.7.0",
|
|
21
|
+
"typer>=0.9.0",
|
|
22
|
+
"shellingham>=1.5.0",
|
|
23
|
+
"packaging>=23.0",
|
|
24
|
+
],
|
|
25
|
+
classifiers=[
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Topic :: Utilities",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"License :: OSI Approved :: MIT License",
|
|
33
|
+
"Operating System :: OS Independent",
|
|
34
|
+
"Environment :: Console",
|
|
35
|
+
],
|
|
36
|
+
python_requires=">=3.10",
|
|
37
|
+
entry_points={
|
|
38
|
+
"console_scripts": [
|
|
39
|
+
"vortex-dl=vortex_dl.main:app",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
project_urls={
|
|
43
|
+
"Bug Reports": "https://github.com/Jenderal92/vortex-dl/issues",
|
|
44
|
+
"Source": "https://github.com/Jenderal92/vortex-dl",
|
|
45
|
+
},
|
|
46
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vortex-DL: High-Performance Asynchronous Multi-part Downloader.
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Sebuah tool CLI untuk mengunduh file dengan kecepatan maksimal
|
|
6
|
+
menggunakan teknik multi-part concurrency dan UI terminal yang estetik.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .core import VortexCore
|
|
10
|
+
from .ui import VortexUI
|
|
11
|
+
|
|
12
|
+
__version__ = "1.0.0"
|
|
13
|
+
__author__ = "Smile Of Beauty"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["VortexCore", "VortexUI"]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import httpx
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import hashlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Any, List
|
|
8
|
+
from packaging import version
|
|
9
|
+
|
|
10
|
+
class VortexCore:
|
|
11
|
+
def __init__(self, url: str, parts: int = 8, output_dir: str = "."):
|
|
12
|
+
self.url = url
|
|
13
|
+
self.parts = parts
|
|
14
|
+
self.output_dir = Path(output_dir)
|
|
15
|
+
self.timeout = httpx.Timeout(10.0, connect=60.0, read=None)
|
|
16
|
+
self.metadata = {}
|
|
17
|
+
self.state = []
|
|
18
|
+
|
|
19
|
+
def _get_state_path(self) -> Path:
|
|
20
|
+
return self.output_dir / f"{self.metadata['name']}.vortex"
|
|
21
|
+
|
|
22
|
+
async def get_metadata(self) -> Dict[str, Any]:
|
|
23
|
+
async with httpx.AsyncClient(follow_redirects=True, timeout=self.timeout) as client:
|
|
24
|
+
resp = await client.head(self.url)
|
|
25
|
+
size = int(resp.headers.get("Content-Length", 0))
|
|
26
|
+
cd = resp.headers.get("Content-Disposition")
|
|
27
|
+
|
|
28
|
+
if cd and "filename=" in cd:
|
|
29
|
+
name = cd.split("filename=")[1].strip('"')
|
|
30
|
+
else:
|
|
31
|
+
name = self.url.split("/")[-1].split("?")[0] or "download_vortex"
|
|
32
|
+
|
|
33
|
+
accept_ranges = resp.headers.get("Accept-Ranges") == "bytes"
|
|
34
|
+
self.metadata = {"name": name, "size": size, "ranges": accept_ranges}
|
|
35
|
+
|
|
36
|
+
state_path = self._get_state_path()
|
|
37
|
+
if state_path.exists() and accept_ranges:
|
|
38
|
+
with open(state_path, "r") as f:
|
|
39
|
+
self.state = json.load(f)
|
|
40
|
+
self.parts = len(self.state)
|
|
41
|
+
else:
|
|
42
|
+
self.state = []
|
|
43
|
+
self._prepare_new_state(size)
|
|
44
|
+
return self.metadata
|
|
45
|
+
|
|
46
|
+
def _prepare_new_state(self, size: int):
|
|
47
|
+
chunk_size = size // self.parts
|
|
48
|
+
for i in range(self.parts):
|
|
49
|
+
start = i * chunk_size
|
|
50
|
+
end = size - 1 if i == self.parts - 1 else (i + 1) * chunk_size - 1
|
|
51
|
+
self.state.append({
|
|
52
|
+
"id": i, "start": start, "end": end,
|
|
53
|
+
"current": start, "completed": False
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
async def download_part(self, client: httpx.AsyncClient, part_id: int, file_path: Path, callback):
|
|
57
|
+
p = self.state[part_id]
|
|
58
|
+
if p["completed"]:
|
|
59
|
+
await callback(part_id, (p["current"] - p["start"]))
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
headers = {"Range": f"bytes={p['current']}-{p['end']}"}
|
|
63
|
+
try:
|
|
64
|
+
async with client.stream("GET", self.url, headers=headers) as resp:
|
|
65
|
+
resp.raise_for_status()
|
|
66
|
+
with open(file_path, "rb+") as f:
|
|
67
|
+
f.seek(p["current"])
|
|
68
|
+
async for chunk in resp.aiter_bytes():
|
|
69
|
+
f.write(chunk)
|
|
70
|
+
chunk_len = len(chunk)
|
|
71
|
+
p["current"] += chunk_len
|
|
72
|
+
await callback(part_id, chunk_len)
|
|
73
|
+
p["completed"] = True
|
|
74
|
+
self._save_checkpoint()
|
|
75
|
+
except Exception:
|
|
76
|
+
self._save_checkpoint()
|
|
77
|
+
|
|
78
|
+
def _save_checkpoint(self):
|
|
79
|
+
with open(self._get_state_path(), "w") as f:
|
|
80
|
+
json.dump(self.state, f)
|
|
81
|
+
|
|
82
|
+
async def start(self, progress_callback) -> str:
|
|
83
|
+
meta = self.metadata
|
|
84
|
+
output_path = self.output_dir / meta['name']
|
|
85
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
if not output_path.exists():
|
|
87
|
+
with open(output_path, "wb") as f:
|
|
88
|
+
f.truncate(meta['size'])
|
|
89
|
+
|
|
90
|
+
async with httpx.AsyncClient(timeout=self.timeout, follow_redirects=True) as client:
|
|
91
|
+
tasks = [self.download_part(client, i, output_path, progress_callback) for i in range(self.parts)]
|
|
92
|
+
await asyncio.gather(*tasks)
|
|
93
|
+
if all(p["completed"] for p in self.state):
|
|
94
|
+
self._get_state_path().unlink(missing_ok=True)
|
|
95
|
+
return str(output_path)
|
|
96
|
+
|
|
97
|
+
def get_checksum(self, file_path: str) -> str:
|
|
98
|
+
hash_md5 = hashlib.md5()
|
|
99
|
+
with open(file_path, "rb") as f:
|
|
100
|
+
for chunk in iter(lambda: f.read(4096), b""):
|
|
101
|
+
hash_md5.update(chunk)
|
|
102
|
+
return hash_md5.hexdigest()
|
|
103
|
+
|
|
104
|
+
async def check_for_updates(current_version: str):
|
|
105
|
+
repo_url = "https://api.github.com/repos/Jenderal92/vortex-dl/releases/latest"
|
|
106
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
107
|
+
try:
|
|
108
|
+
response = await client.get(repo_url)
|
|
109
|
+
if response.status_code == 200:
|
|
110
|
+
latest_data = response.json()
|
|
111
|
+
raw_tag = latest_data['tag_name']
|
|
112
|
+
latest_v = re.sub(r'^[^0-9]+', '', raw_tag)
|
|
113
|
+
|
|
114
|
+
if version.parse(latest_v) > version.parse(current_version):
|
|
115
|
+
return latest_v
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
return None
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
import typer
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from .core import VortexCore, check_for_updates
|
|
11
|
+
from .ui import VortexUI
|
|
12
|
+
|
|
13
|
+
__version__ = "0.9.0"
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(help="Vortex-DL: High-Performance Async Downloader")
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def run_vortex(url: str, parts: int, output: str):
|
|
20
|
+
console.clear()
|
|
21
|
+
new_v = await check_for_updates(__version__)
|
|
22
|
+
if new_v:
|
|
23
|
+
console.print(
|
|
24
|
+
f"[bold yellow]🔔 Update Tersedia:[/] v{new_v} (Anda menggunakan v{__version__})"
|
|
25
|
+
)
|
|
26
|
+
console.print(
|
|
27
|
+
"[dim]Jalankan 'pip install --upgrade vortex-dl' untuk memperbarui.[/]\n"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console.print(VortexUI.header())
|
|
31
|
+
|
|
32
|
+
core = VortexCore(url, parts, output)
|
|
33
|
+
|
|
34
|
+
with console.status("[bold yellow]Menghubungkan ke server...[/]"):
|
|
35
|
+
try:
|
|
36
|
+
meta = await core.get_metadata()
|
|
37
|
+
except Exception as e:
|
|
38
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
info_table = Table.grid(padding=(0, 2))
|
|
42
|
+
info_table.add_row("[cyan]File:[/]", meta["name"])
|
|
43
|
+
info_table.add_row("[cyan]Size:[/]", f"{meta['size'] / 1e6:.2f} MB")
|
|
44
|
+
console.print(info_table)
|
|
45
|
+
console.print("")
|
|
46
|
+
|
|
47
|
+
progress = VortexUI.create_progress()
|
|
48
|
+
|
|
49
|
+
with Live(progress, refresh_per_second=10):
|
|
50
|
+
total_task = progress.add_task("[white]Total Progress", total=meta["size"])
|
|
51
|
+
|
|
52
|
+
part_tasks = []
|
|
53
|
+
for i, p in enumerate(core.state):
|
|
54
|
+
completed_in_part = p["current"] - p["start"]
|
|
55
|
+
task = progress.add_task(
|
|
56
|
+
f" ↳ Part {i+1}",
|
|
57
|
+
total=(p["end"] - p["start"] + 1),
|
|
58
|
+
completed=completed_in_part,
|
|
59
|
+
)
|
|
60
|
+
part_tasks.append(task)
|
|
61
|
+
progress.update(total_task, advance=completed_in_part)
|
|
62
|
+
|
|
63
|
+
async def update_ui(p_id: int, chunk_len: int):
|
|
64
|
+
progress.update(total_task, advance=chunk_len)
|
|
65
|
+
progress.update(part_tasks[p_id], advance=chunk_len)
|
|
66
|
+
|
|
67
|
+
start_t = time.time()
|
|
68
|
+
try:
|
|
69
|
+
file_path = await core.start(update_ui)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
console.print(f"\n[bold red]Terhenti:[/] {e}")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
duration = time.time() - start_t
|
|
75
|
+
|
|
76
|
+
speed = (meta["size"] / 1e6) / duration if duration > 0 else 0
|
|
77
|
+
console.print(f"\n[bold green]✔ Selesai dalam {duration:.2f} detik![/]")
|
|
78
|
+
console.print(f"[yellow]Lokasi:[/] {file_path}")
|
|
79
|
+
console.print(f"[yellow]Speed :[/] {speed:.2f} MB/s")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.command()
|
|
83
|
+
def download(
|
|
84
|
+
url: str = typer.Argument(..., help="URL file"),
|
|
85
|
+
parts: int = typer.Option(8, "--parts", "-p"),
|
|
86
|
+
output: str = typer.Option(".", "--output", "-o"),
|
|
87
|
+
):
|
|
88
|
+
try:
|
|
89
|
+
asyncio.run(run_vortex(url, parts, output))
|
|
90
|
+
except KeyboardInterrupt:
|
|
91
|
+
console.print("\n[bold red]✖ Dibatalkan.[/]")
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
app()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.progress import (
|
|
5
|
+
Progress,
|
|
6
|
+
BarColumn,
|
|
7
|
+
TextColumn,
|
|
8
|
+
TransferSpeedColumn,
|
|
9
|
+
TimeRemainingColumn,
|
|
10
|
+
SpinnerColumn,
|
|
11
|
+
DownloadColumn
|
|
12
|
+
)
|
|
13
|
+
from rich.style import Style
|
|
14
|
+
|
|
15
|
+
class VortexUI:
|
|
16
|
+
@staticmethod
|
|
17
|
+
def header() -> Panel:
|
|
18
|
+
grid = Table.grid(expand=True)
|
|
19
|
+
grid.add_column(justify="center", ratio=1)
|
|
20
|
+
grid.add_row(
|
|
21
|
+
"[bold italic #00ffff]🌀 VORTEX-DL[/bold italic #00ffff]\n"
|
|
22
|
+
"[dim white]The Ultra-Fast Asynchronous Multi-part Downloader[/dim white]"
|
|
23
|
+
)
|
|
24
|
+
return Panel(
|
|
25
|
+
grid,
|
|
26
|
+
style="on #000033",
|
|
27
|
+
border_style="#6200ee",
|
|
28
|
+
padding=(1, 2),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def create_progress() -> Progress:
|
|
33
|
+
return Progress(
|
|
34
|
+
SpinnerColumn(spinner_name="dots12", style="bold cyan"),
|
|
35
|
+
TextColumn("[bold blue]{task.description}"),
|
|
36
|
+
BarColumn(
|
|
37
|
+
bar_width=None,
|
|
38
|
+
complete_style=Style(color="#00ffff"),
|
|
39
|
+
finished_style=Style(color="#00ff00")
|
|
40
|
+
),
|
|
41
|
+
"[progress.percentage]{task.percentage:>3.0f}%",
|
|
42
|
+
DownloadColumn(),
|
|
43
|
+
TransferSpeedColumn(),
|
|
44
|
+
TimeRemainingColumn(),
|
|
45
|
+
)
|