- 1. Жоба құрылымы
- 2. Widget.h
- 3. Widget.cpp
- 4. Downloader.h
- 5. Downloader.cpp
- 6. Қорытынды
Форумда Qt кітапханасы арқылы үлкен файлдарды жүктеп алу туралы сұрақтан кейін мен кейбір жобаларымды көтердім және ұқсас функционалдылықты пайдалана отырып, егжей-тегжейлі нұсқаулық дайындадым. Сонымен қатар, файлдарды жүктеп алу мәселесі қайта бағыттауларға қатысты болды. Әдепкі бойынша, QNetworkAccessManager файлдарды жүктеп алу және беттерді алу үшін қайта бағыттауларды орындамайды, сондықтан сұрауда сәйкес төлсипатты орнату керек, сонда бәрі жұмыс істейді, бірақ барлығын толығырақ қарастырайық.
Қолданбада келесі функция болады.
- Жүктеп алу үшін мақсатты URL мекенжайын енгізу үшін QLineEdit
- ReadOnly режимінде жүктеп алу үшін мақсатты каталогқа кіру үшін QLineEdit. Біз оны QFileDialog көмегімен толтырамыз.
- Жүктеп алу барысын көрсететін QProgressBar
- Жүктеп алудан бас тарту түймесі
Біздің жүктеушіміз келесідей болады
Жоба құрылымы
Жоба мыналардан тұрады
- FileDownloader.pro - жоба профилі
- Downloader.h - файлдарды жүктеуге арналған сыныптың тақырып файлы
- Downloader.cpp - файлдарды жүктеуге арналған сыныпты іске асыру файлы
- Widget.h - қолданба терезесінің тақырып файлы
- Widget.cpp - қолданба терезесін іске асыру файлы
- Widget.ui – қолданбалы терезенің графикалық пішіні
- main.cpp - қолданбаның негізгі функциясы бар файл
FileDownloader.pro, main.cpp, Widget.ui қарастырылмайды, алғашқы екеуі әдепкі бойынша жасалады, соңғысы Qt Designer графикалық редакторы арқылы жасалады, оны қоса берілген жобаның өзінде қараңыз. ең соңында мақалаға.
Widget.h
Тақырып файлы интерфейс түймелерін өңдеу үшін барлық қажетті ұяшықтарды жариялайды, сонымен қатар стектегі файлдарды жүктеп алу класын жариялайды.
#ifndef WIDGET_H #define WIDGET_H #include "Downloader.h" #include <QWidget> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget(); private slots: // Слот для старта загрузки void onDownloadButtonClicked(); // Слот для выбора каталога для скачивания void onSelectTargetFolderButtonClicked(); // Слот для отмены загрузки void onCancelButtonClicked(); // Слот для обновления прогресса загрузки void onUpdateProgress(qint64 bytesReceived, qint64 bytesTotal); private: Ui::Widget *ui; Downloader m_downloader; // Класс для скачивания }; #endif // WIDGET_H
Widget.cpp
#include "Widget.h" #include "ui_Widget.h" #include <QFileDialog> #include <QStandardPaths> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); // Подключаемся к слотам connect(ui->downloadPushButton, &QPushButton::clicked, this, &Widget::onDownloadButtonClicked); connect(ui->selectTargetFolderPushButton, &QPushButton::clicked, this, &Widget::onSelectTargetFolderButtonClicked); connect(ui->cancelPushButton, &QPushButton::clicked, this, &Widget::onCancelButtonClicked); connect(&m_downloader, &Downloader::updateDownloadProgress, this, &Widget::onUpdateProgress); } Widget::~Widget() { delete ui; } void Widget::onDownloadButtonClicked() { // Запускаем скачивание файла передавая в качестве аргументов // путь к каталогу, куда будем закачивать файлы // url, где находится файл m_downloader.get(ui->targetFolderLineEdit->text(), ui->urlLineEdit->text()); } void Widget::onSelectTargetFolderButtonClicked() { // Выбор целевого каталога для скачивания QString targetFolder = QFileDialog::getExistingDirectory(this, tr("Select folder"), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); ui->targetFolderLineEdit->setText(targetFolder); } void Widget::onCancelButtonClicked() { // Отмена загрузки m_downloader.cancelDownload(); ui->downloadProgressBar->setMaximum(100); ui->downloadProgressBar->setValue(0); } void Widget::onUpdateProgress(qint64 bytesReceived, qint64 bytesTotal) { // Обновляем прогресс загрузки ui->downloadProgressBar->setMaximum(bytesTotal); ui->downloadProgressBar->setValue(bytesReceived); }
Downloader.h
Ал енді жүктеу барысын тексеруді ескере отырып, файлдарды жүктеп алу класын қарастырайық.
Маңызды мәселе - үлкен файлдарды бірте-бірте өңдеу керек, оларды бір сұрауда оқу мүмкін емес. Сондықтан біз ағымдағы сұрауға жауап беру нысанынан QNetworkReply::readyRead сигналын өңдеуіміз керек. Бұл сигнал буферде біз оқи алатын деректер болған кезде шығарылады.
Жүктеп алу аяқталғанда ғана QNetworkAccessManager аяқталды сигналын шығарады, ол файлды жабады және сұрауға ағымдағы жауаптың нысанын жоюмен байланысты тоқтатады.
#ifndef DOWNLOADER_H #define DOWNLOADER_H #include <QNetworkAccessManager> class QNetworkReply; class QFile; class Downloader : public QObject { Q_OBJECT using BaseClass = QObject; public: explicit Downloader(QObject* parent = nullptr); // Метод для запуска скачиввания bool get(const QString& targetFolder, const QUrl& url); public slots: // Метод отмены загрузки void cancelDownload(); signals: // Сигнал передающий информацию о прогрессе загрузки void updateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); private slots: // Слот для постепенного считывания загружаемых данных void onReadyRead(); // Слот для обработки завершения запроса void onReply(QNetworkReply* reply); private: QNetworkReply* m_currentReply {nullptr}; // Текущий обрабатываемый запрос QFile* m_file {nullptr}; // Текущий файл в который идёт запись QNetworkAccessManager m_manager; // Сетевой менеджер для загрузки файлов }; #endif // DOWNLOADER_H
Downloader.cpp
#include "Downloader.h" #include <QNetworkReply> #include <QNetworkRequest> #include <QFile> #include <QDir> Downloader::Downloader(QObject* parent) : BaseClass(parent) { // Подключаемся к сигналу finished connect(&m_manager, &QNetworkAccessManager::finished, this, &Downloader::onReply); } bool Downloader::get(const QString& targetFolder, const QUrl& url) { if (targetFolder.isEmpty() || url.isEmpty()) { return false; } // Cоздаём объект класса файла для скачивания // здесь имеется целевая директория и имя файла, которое выделяется из URL m_file = new QFile(targetFolder + QDir::separator() + url.fileName()); // Пробуем открыть файл if (!m_file->open(QIODevice::WriteOnly)) { delete m_file; m_file = nullptr; return false; } // Создаём запрос QNetworkRequest request(url); // Обязательно разрешаем переходить по редиректам request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); // Запускаем скачивание m_currentReply = m_manager.get(request); // После чего сразу подключаемся к сигналам о готовности данных к чтению и обновлению прогресса скачивания connect(m_currentReply, &QNetworkReply::readyRead, this, &Downloader::onReadyRead); connect(m_currentReply, &QNetworkReply::downloadProgress, this, &Downloader::updateDownloadProgress); return true; } void Downloader::onReadyRead() { // Если есть данные и файл открыт if (m_file) { // записываем их в файл m_file->write(m_currentReply->readAll()); } } void Downloader::cancelDownload() { // Отмена запроса if (m_currentReply) { m_currentReply->abort(); } } void Downloader::onReply(QNetworkReply* reply) { // По завершению запроса if (reply->error() == QNetworkReply::NoError) { // сохраням файл m_file->flush(); m_file->close(); } else { // Или удаляем его в случае ошибки m_file->remove(); } delete m_file; m_file = nullptr; reply->deleteLater(); }
Қорытынды
Осылайша, бізде берілген URL мекенжайында қажетті файлды жүктеп алып, оны мақсатты каталогқа орналастыра алатын қолданба бар.
Жобаны жүктеп алу сілтемесі Жүктеу құралы
не могу понять как обработать ошибку некорректной ссылки?
Пример: "ftp://cddis.gsfc.nasa.gov/pub/slr/data/npt_crd/gracea/2010/gracea_20100101.npt.Z"
У вас скорее всего ошибка превышения интервала ожидания - QNetworkReply::TimeoutError
у меня никак не получается обработать ошибку некорректной ссылки(
Здравствуйте. В вашем примере не обновляется прогресс-бар. А если точнее - он выставляет 100% после закачки, и всё. Во время самой закачки просто 0%. Сомневаюсь что так задумано, но всё же, как сделать чтобы обновлялся каждый кусок?
Какого размера пробуете скчивать файлы и на какой скорости у вас работает интернет.
Есть ещё один неприятный момент в том, что не на всех платформах правильно передаётся информация о прогрессе закачки.
Так что какую используете операционную систему?
А почему вы не сделаете обработку ошибок в слоте onResult.
Проблема была в ресурсе, с которого скачиваю. Пытался тянуть с google disc - а там ограничение 25мб на прямые ссылки. Поэтому он и втупал.
ну.. хз.. Github может быть, там вам и контроль версии и всё остальное, вроде и ссылки на метки прямые.
вызываю метод get у m_downloader в другом методе и приложение начинает вылетать. В чем ошибка?
Это дебажить нужно, может быть что угодно.
Здпавствуйте Евгений.
Прошу совета, вот из-за этой строчки кода:
connect(&m_manager, &QNetworkAccessManager::finished, this, &Downloader::onReply);
Выдает такую ошибку: D:\Qt\5.10.1\mingw53_32\include\QtCore\qglobal.h:762: ошибка: static assertion failed: Signal and slot arguments are not compatible.
#define Q_STATIC_ASSERT_X(Condition, Message) static_assert(bool(Condition), Message)
Что это может быть?
Добрый день.
Вы полностью скопировали тот код? Или самостоятельно писали свой вариант, посматривая в эту статью?
Выглядит так, что у вас несовместимый сигнал со слотом.
Покажите сигнатуру метода Downloader::onReply
Писал свой вариант, вот сигнатура метода Downloader::onReply:
Точнее будет сказать что я набивал руками Ваш код)))
Понятно )) Вы допустили ошибку.
Вот ваша строчка
Вот моя строчка
В вашем случае используется ссылка, а в моём случае указатель. Это разные вещи.
Используйте, пожалуйста, диалог вставки программного кода. Это кнопка с символом <> в редакторе.
Дело в том, что в редакторе комментариев используется markdown синтаксис, поэтому нужна специальная разметка кода. Диалог добавляет её автоматически.
))) Спасибо Евгений! Впреть буду внимательнее)))
Дня доброго.
Прочитал ваш урок. Вроде все понял. Но взялся реализовывать этот принцип в своем проекте(загрузка файлов на фтп) и уперся что не могу понять как реализовать progressbar.
вот код:
Как сюда интегрировать progressBar?
Сделайте поправку что новичек в програмированнии.
Реализуйте сначала правильно закачку по ftp. А потом уже внедряйте прогресс бар.
Ошибки, которые сразу бросаются в глаза
Вообще ваш код сам по себе работает? Если работает, то сначала реализуйте как в статье, чтобы без QEventLoop было. Поскольку этой петлёй вы морозите GUI, скорее всего поэтому ничего и не работает.
Код сам по себе работает. Файлы на фтп грузяться.
QEventLoop скорее всего морозит GUI, поскольку тормозит выполнение всего остального программного кода в приложении, ожидая выполнения работы QNetworkAccessManager. Вам нужно переписать так, как показано в примере. Тем более, что QNetworkAccessManager может выполнять несколько запросов одновременно и асинхронно. Нет нужды создавать объект QNetworkAccessManager в каждом слоте. Тем более, что более одного при таком подходе вы не создадите. GUI просто не будет реагировать на событие мыши, пока не выполнится загрузка.