Skip to content

Commit a364b95

Browse files
authored
Merge pull request #4 from maycuatroi/add-gui
Add GUI with PyQt6 for download
2 parents 718d0b8 + c50e27d commit a364b95

File tree

15 files changed

+193
-6
lines changed

15 files changed

+193
-6
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ jobs:
7070
run: make install
7171
- name: Run tests
7272
run: make test
73+
- name: Run tests GUI
74+
run: make testgui
7375

7476
tests_win:
7577
needs: linter
@@ -85,8 +87,10 @@ jobs:
8587
with:
8688
python-version: ${{ matrix.python-version }}
8789
- name: Install Pip
88-
run: pip install --user --upgrade pip
90+
run: python.exe -m pip install --upgrade pip
8991
- name: Install project
9092
run: pip install -e .[test]
9193
- name: run tests
9294
run: pytest -s -vvvv -l --tb=long tests
95+
- name: Run tests GUI
96+
run: make testgui

Makefile

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ show: ## Show the current environment.
2222
install: ## Install the project in dev mode.
2323
@if [ "$(USING_POETRY)" ]; then poetry install && exit; fi
2424
@echo "Don't forget to run 'make virtualenv' if you got errors."
25-
$(ENV_PREFIX)pip install -e .[test]
25+
$(ENV_PREFIX)pip install -e .[test,full]
2626

2727
.PHONY: fmt
2828
fmt: ## Format code using black & isort.
@@ -37,8 +37,14 @@ lint: ## Run pep8, black, mypy linters.
3737
$(ENV_PREFIX)mypy --ignore-missing-imports evo_downloader/
3838

3939
.PHONY: test
40-
test: lint ## Run tests and generate coverage report.
41-
$(ENV_PREFIX)pytest -v --cov-config .coveragerc --cov=evo_downloader -l --tb=short --maxfail=1 tests/
40+
test:
41+
$(ENV_PREFIX)pytest -v --cov-config .coveragerc --cov=evo_downloader -l --tb=short --maxfail=1 tests/test_libs/
42+
$(ENV_PREFIX)coverage xml
43+
$(ENV_PREFIX)coverage html
44+
45+
.PHONY: testgui
46+
testgui:
47+
$(ENV_PREFIX)pytest -v --cov-config .coveragerc --cov=evo_downloader -l --tb=short --maxfail=1 tests/test_gui/
4248
$(ENV_PREFIX)coverage xml
4349
$(ENV_PREFIX)coverage html
4450

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ This module contains the main functionality for downloading files, including sup
77

88
## Install it from PyPI
99

10+
### Lean Version
11+
1012
```bash
1113
pip install evo-downloader
1214
```
1315

16+
### Full Version with PyQt6
17+
18+
```bash
19+
pip install evo-downloader[full]
20+
```
21+
1422
## Usage
1523

1624
### As a Library
@@ -61,6 +69,28 @@ $ evo_downloader download https://github.com/maycuatroi/evo_downloader/archive/r
6169

6270
This example demonstrates how to use the `Downloader` class to download files programmatically.
6371

72+
### GUI Usage
73+
74+
You can also use `edownload` to launch the GUI.
75+
76+
```bash
77+
$ edownload gui
78+
```
79+
80+
The GUI allows you to input multiple download links and select the output folder. It also displays the download progress.
81+
82+
### Testing
83+
84+
To run tests, you need to install the testing dependencies, including `pyqt6`.
6485

86+
```bash
87+
pip install -r requirements-test.txt
88+
```
89+
90+
Then, you can run the tests using `pytest`.
91+
92+
```bash
93+
pytest
94+
```
6595

6696
_Evo Downloader developed with ❤️ by maycuatroi_

evo_downloader/cli.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import click
22
from evo_downloader.downloader import Downloader
3+
from evo_downloader.gui import DownloaderGUI
34

45

56
@click.group()
@@ -35,7 +36,22 @@ def download(file_urls, folder, num_threads):
3536
click.echo(f"An error occurred during download: {e}")
3637

3738

39+
@click.command()
40+
def gui():
41+
"""
42+
Launch the GUI for evo_downloader.
43+
"""
44+
import sys
45+
from PyQt6.QtWidgets import QApplication
46+
47+
app = QApplication(sys.argv)
48+
gui = DownloaderGUI()
49+
gui.show()
50+
sys.exit(app.exec())
51+
52+
3853
cli.add_command(download)
54+
cli.add_command(gui)
3955

4056
if __name__ == "__main__":
4157
cli()

evo_downloader/gui.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from PyQt5.QtWidgets import (
2+
QApplication,
3+
QWidget,
4+
QVBoxLayout,
5+
QHBoxLayout,
6+
QLabel,
7+
QLineEdit,
8+
QPushButton,
9+
QFileDialog,
10+
QProgressBar,
11+
QTextEdit,
12+
)
13+
from PyQt5.QtCore import QThread, pyqtSignal
14+
import sys
15+
import os
16+
from evo_downloader.downloader import Downloader
17+
18+
19+
class DownloadThread(QThread):
20+
progress_signal = pyqtSignal(int)
21+
finished_signal = pyqtSignal()
22+
23+
def __init__(self, urls, folder):
24+
super().__init__()
25+
self.urls = urls
26+
self.folder = folder
27+
28+
def run(self):
29+
downloader = Downloader()
30+
total_files = len(self.urls)
31+
for i, url in enumerate(self.urls):
32+
downloader.download_files([url], self.folder)
33+
self.progress_signal.emit(int((i + 1) / total_files * 100))
34+
self.finished_signal.emit()
35+
36+
37+
class DownloaderGUI(QWidget):
38+
def __init__(self):
39+
super().__init__()
40+
self.init_ui()
41+
42+
def init_ui(self):
43+
self.setWindowTitle("Evo Downloader")
44+
self.setGeometry(100, 100, 600, 400)
45+
46+
layout = QVBoxLayout()
47+
48+
# URL input
49+
url_layout = QHBoxLayout()
50+
self.url_label = QLabel("URLs:")
51+
self.url_input = QTextEdit()
52+
url_layout.addWidget(self.url_label)
53+
url_layout.addWidget(self.url_input)
54+
layout.addLayout(url_layout)
55+
56+
# Output folder selection
57+
folder_layout = QHBoxLayout()
58+
self.folder_label = QLabel("Output Folder:")
59+
self.folder_input = QLineEdit()
60+
default_download_folder = os.path.expanduser("~/Downloads")
61+
self.folder_input.setText(os.path.abspath(default_download_folder))
62+
self.folder_button = QPushButton("Browse")
63+
self.folder_button.clicked.connect(self.select_folder)
64+
folder_layout.addWidget(self.folder_label)
65+
folder_layout.addWidget(self.folder_input)
66+
folder_layout.addWidget(self.folder_button)
67+
layout.addLayout(folder_layout)
68+
69+
# Download button
70+
self.download_button = QPushButton("Download")
71+
self.download_button.clicked.connect(self.start_download)
72+
layout.addWidget(self.download_button)
73+
74+
# Progress bar
75+
self.progress_bar = QProgressBar()
76+
layout.addWidget(self.progress_bar)
77+
78+
self.setLayout(layout)
79+
80+
def select_folder(self):
81+
folder = QFileDialog.getExistingDirectory(self, "Select Output Folder", os.path.expanduser("~/Downloads"))
82+
if folder:
83+
self.folder_input.setText(folder)
84+
85+
def start_download(self):
86+
urls = self.url_input.toPlainText().strip().split("\n")
87+
folder = self.folder_input.text().strip()
88+
if not urls or not folder:
89+
return
90+
91+
self.download_thread = DownloadThread(urls, folder)
92+
self.download_thread.progress_signal.connect(self.update_progress)
93+
self.download_thread.finished_signal.connect(self.download_finished)
94+
self.download_thread.start()
95+
96+
def update_progress(self, value):
97+
self.progress_bar.setValue(value)
98+
99+
def download_finished(self):
100+
self.progress_bar.setValue(100)
101+
102+
103+
if __name__ == "__main__":
104+
app = QApplication(sys.argv)
105+
gui = DownloaderGUI()
106+
gui.show()
107+
sys.exit(app.exec())

requirements-test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ codecov
99
mypy
1010
gitchangelog
1111
mkdocs
12+
pyqt5
13+
pytest-qt

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ requests
22
rich
33
click
44
humanize
5-
types-requests
5+
types-requests

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ def read_requirements(path):
3737
packages=find_packages(exclude=["tests", ".github"]),
3838
install_requires=read_requirements("requirements.txt"),
3939
entry_points={"console_scripts": ["edownload = evo_downloader.__main__:main"]},
40-
extras_require={"test": read_requirements("requirements-test.txt")},
40+
extras_require={"test": read_requirements("requirements-test.txt"), "full": ["pyqt5"]},
4141
)

tests/pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
qt_api=pyqt5

tests/test_gui/__init__.py

Whitespace-only changes.

tests/test_gui/test_gui.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import unittest
2+
3+
from PyQt5.QtCore import Qt
4+
from evo_downloader.gui import DownloaderGUI
5+
6+
7+
def test_start_download(qtbot):
8+
gui = DownloaderGUI()
9+
qtbot.addWidget(gui)
10+
gui.url_input.setPlainText(
11+
"http://images.cocodataset.org/annotations/image_info_test2014.zip\nhttps://github.com/maycuatroi/evo_downloader/archive/refs/heads/main.zip"
12+
)
13+
14+
gui.folder_input.setText("./test/download_folder")
15+
qtbot.mouseClick(gui.download_button, Qt.MouseButton.LeftButton)
16+
gui.download_thread.wait()
17+
18+
19+
if __name__ == "__main__":
20+
unittest.main()

tests/test_libs/__init__.py

Whitespace-only changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)