File indexing completed on 2025-01-05 04:00:11
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2020-11-14 0007 * Description : Files downloader 0008 * 0009 * SPDX-FileCopyrightText: 2020-2021 by Maik Qualmann <metzpinguin at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "filesdownloader.h" 0016 0017 // Qt includes 0018 0019 #include <QDir> 0020 #include <QLabel> 0021 #include <QTimer> 0022 #include <QPointer> 0023 #include <QCheckBox> 0024 #include <QByteArray> 0025 #include <QMessageBox> 0026 #include <QPushButton> 0027 #include <QVBoxLayout> 0028 #include <QProgressBar> 0029 #include <QApplication> 0030 #include <QStandardPaths> 0031 #include <QNetworkRequest> 0032 #include <QDialogButtonBox> 0033 #include <QCryptographicHash> 0034 #include <QNetworkAccessManager> 0035 0036 // KDE includes 0037 0038 #include <klocalizedstring.h> 0039 0040 // Local includes 0041 0042 #include "digikam_debug.h" 0043 #include "digikam_globals.h" 0044 #include "dxmlguiwindow.h" 0045 #include "systemsettings.h" 0046 #include "itempropertiestab.h" 0047 0048 namespace Digikam 0049 { 0050 0051 class Q_DECL_HIDDEN FilesDownloader::Private 0052 { 0053 public: 0054 0055 explicit Private() 0056 : downloadUrls( { QLatin1String("https://files.kde.org/digikam/"), 0057 QLatin1String("https://mirror.faigner.de/kde/files/digikam/"), 0058 QLatin1String("https://kde-applicationdata.mirrors.omnilance.com/digikam/"), 0059 QLatin1String("https://mirrors.ocf.berkeley.edu/kde-applicationdata/digikam/") } ), 0060 mirrorIndex (0), 0061 redirects (0), 0062 total (0), 0063 count (0), 0064 buttons (nullptr), 0065 progress (nullptr), 0066 facesEngineCheck(nullptr), 0067 aestheticCheck (nullptr), 0068 autoTagsCheck (nullptr), 0069 nameLabel (nullptr), 0070 infoLabel (nullptr), 0071 sizeLabel (nullptr), 0072 reply (nullptr), 0073 netMngr (nullptr), 0074 system (qApp->applicationName()) 0075 { 0076 } 0077 0078 const QStringList downloadUrls; 0079 0080 QString error; 0081 0082 QList<DownloadInfo> files; 0083 DownloadInfo currentInfo; 0084 0085 int mirrorIndex; 0086 int redirects; 0087 int total; 0088 int count; 0089 0090 QDialogButtonBox* buttons; 0091 QProgressBar* progress; 0092 QCheckBox* facesEngineCheck; 0093 QCheckBox* aestheticCheck; 0094 QCheckBox* autoTagsCheck; 0095 QLabel* nameLabel; 0096 QLabel* infoLabel; 0097 QLabel* sizeLabel; 0098 0099 QNetworkReply* reply; 0100 QNetworkAccessManager* netMngr; 0101 0102 SystemSettings system; 0103 }; 0104 0105 FilesDownloader::FilesDownloader(QWidget* const parent) 0106 : QDialog(parent), 0107 d (new Private) 0108 { 0109 createDownloadInfo(); 0110 } 0111 0112 FilesDownloader::~FilesDownloader() 0113 { 0114 if (d->reply) 0115 { 0116 d->reply->abort(); 0117 d->reply = nullptr; 0118 } 0119 0120 delete d; 0121 } 0122 0123 bool FilesDownloader::checkDownloadFiles() const 0124 { 0125 QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0126 QLatin1String("digikam/facesengine"), 0127 QStandardPaths::LocateDirectory); 0128 0129 if (path.isEmpty()) 0130 { 0131 return false; 0132 } 0133 0134 Q_FOREACH (const DownloadInfo& info, d->files) 0135 { 0136 QFileInfo fileInfo(path + QLatin1Char('/') + info.name); 0137 0138 if (!fileInfo.exists() || (fileInfo.size() != info.size)) 0139 { 0140 return false; 0141 } 0142 } 0143 0144 return true; 0145 } 0146 0147 void FilesDownloader::startDownload() 0148 { 0149 setWindowTitle(i18nc("@title:window", "Download Required Files")); 0150 setMinimumHeight(250); 0151 setMinimumWidth(600); 0152 0153 QWidget* const mainWidget = new QWidget(this); 0154 QVBoxLayout* const vBox = new QVBoxLayout(mainWidget); 0155 0156 d->buttons = new QDialogButtonBox(QDialogButtonBox::Help | 0157 QDialogButtonBox::Ok | 0158 QDialogButtonBox::Close, 0159 mainWidget); 0160 d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); 0161 d->buttons->button(QDialogButtonBox::Ok)->setText(i18n("Download")); 0162 d->buttons->button(QDialogButtonBox::Ok)->setIcon(QIcon::fromTheme(QLatin1String("edit-download"))); 0163 0164 d->infoLabel = new QLabel(mainWidget); 0165 d->sizeLabel = new QLabel(mainWidget); 0166 0167 d->infoLabel->setWordWrap(true); 0168 0169 d->facesEngineCheck = new QCheckBox(i18n("Use Face Detection feature"), mainWidget); 0170 d->aestheticCheck = new QCheckBox(i18n("Use Aesthetic Detection feature"), mainWidget); 0171 d->autoTagsCheck = new QCheckBox(i18n("Use Auto Tags Assignment feature"), mainWidget); 0172 0173 d->progress = new QProgressBar(mainWidget); 0174 d->progress->setMinimum(0); 0175 d->progress->setMaximum(1); 0176 d->progress->setValue(0); 0177 0178 d->nameLabel = new QLabel(mainWidget); 0179 0180 vBox->addWidget(d->infoLabel); 0181 vBox->addWidget(d->sizeLabel); 0182 vBox->addStretch(1); 0183 vBox->addWidget(d->facesEngineCheck); 0184 vBox->addWidget(d->aestheticCheck); 0185 vBox->addWidget(d->autoTagsCheck); 0186 vBox->addStretch(1); 0187 vBox->addWidget(d->nameLabel); 0188 vBox->addWidget(d->progress); 0189 vBox->addWidget(d->buttons); 0190 0191 setLayout(vBox); 0192 0193 connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), 0194 this, SLOT(slotDownload())); 0195 0196 connect(d->buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), 0197 this, SLOT(slotHelp())); 0198 0199 connect(d->buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), 0200 this, SLOT(reject())); 0201 0202 d->facesEngineCheck->setChecked(d->system.enableFaceEngine); 0203 d->aestheticCheck->setChecked(d->system.enableAesthetic); 0204 d->autoTagsCheck->setChecked(d->system.enableAutoTags); 0205 0206 connect(d->facesEngineCheck, SIGNAL(toggled(bool)), 0207 this, SLOT(slotUpdateDownloadInfo())); 0208 0209 connect(d->aestheticCheck, SIGNAL(toggled(bool)), 0210 this, SLOT(slotUpdateDownloadInfo())); 0211 0212 connect(d->autoTagsCheck, SIGNAL(toggled(bool)), 0213 this, SLOT(slotUpdateDownloadInfo())); 0214 0215 slotUpdateDownloadInfo(); 0216 0217 (void)exec(); 0218 } 0219 0220 void FilesDownloader::slotDownload() 0221 { 0222 d->buttons->button(QDialogButtonBox::Ok)->setEnabled(false); 0223 d->facesEngineCheck->setEnabled(false); 0224 d->aestheticCheck->setEnabled(false); 0225 d->autoTagsCheck->setEnabled(false); 0226 0227 if (d->error.isEmpty()) 0228 { 0229 while (!d->files.isEmpty()) 0230 { 0231 d->currentInfo = d->files.takeFirst(); 0232 0233 if (!downloadExists(d->currentInfo)) 0234 { 0235 d->count++; 0236 download(); 0237 0238 return; 0239 } 0240 } 0241 0242 QMessageBox::information(this, qApp->applicationName(), 0243 i18n("All files were downloaded successfully.")); 0244 0245 close(); 0246 } 0247 else 0248 { 0249 QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Critical, 0250 i18nc("@title:window", "Download Error"), 0251 i18n("An error occurred during the download.\n\n" 0252 "File: %1\n\n%2\n\n" 0253 "You can try again or continue the " 0254 "download at the next start.", 0255 d->currentInfo.name, d->error), 0256 QMessageBox::Yes | QMessageBox::Cancel, 0257 qApp->activeWindow()); 0258 0259 msgBox->button(QMessageBox::Yes)->setText(i18nc("@action:button", "Try Again")); 0260 msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("edit-download"))); 0261 0262 int result = msgBox->exec(); 0263 delete msgBox; 0264 0265 if (result == QMessageBox::Yes) 0266 { 0267 d->error.clear(); 0268 d->mirrorIndex++; 0269 0270 if (d->mirrorIndex >= d->downloadUrls.size()) 0271 { 0272 d->mirrorIndex = 0; 0273 } 0274 0275 download(); 0276 0277 return; 0278 } 0279 0280 close(); 0281 } 0282 } 0283 0284 void FilesDownloader::download() 0285 { 0286 if (!d->netMngr) 0287 { 0288 d->netMngr = new QNetworkAccessManager(this); 0289 d->netMngr->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); 0290 0291 connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), 0292 this, SLOT(slotDownloaded(QNetworkReply*))); 0293 } 0294 0295 QUrl request(d->downloadUrls.at(d->mirrorIndex) + 0296 d->currentInfo.path + d->currentInfo.name); 0297 0298 d->redirects = 0; 0299 createRequest(request); 0300 } 0301 0302 void FilesDownloader::nextDownload() 0303 { 0304 QTimer::singleShot(100, this, SLOT(slotDownload())); 0305 } 0306 0307 void FilesDownloader::createRequest(const QUrl& url) 0308 { 0309 d->progress->setMaximum(d->currentInfo.size); 0310 d->progress->setValue(0); 0311 printDownloadInfo(url); 0312 0313 d->redirects++; 0314 d->reply = d->netMngr->get(QNetworkRequest(url)); 0315 0316 connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), 0317 this, SLOT(slotDownloadProgress(qint64,qint64))); 0318 0319 connect(d->reply, SIGNAL(sslErrors(QList<QSslError>)), 0320 d->reply, SLOT(ignoreSslErrors())); 0321 } 0322 0323 void FilesDownloader::printDownloadInfo(const QUrl& url) 0324 { 0325 QString text = QString::fromUtf8("%1 (%2://%3) (%4/%5)") 0326 .arg(d->currentInfo.name) 0327 .arg(url.scheme()) 0328 .arg(url.host()) 0329 .arg(d->count) 0330 .arg(d->total); 0331 0332 d->nameLabel->setText(text); 0333 } 0334 0335 bool FilesDownloader::downloadExists(const DownloadInfo& info) const 0336 { 0337 QString path = getFacesEnginePath() + 0338 QString::fromLatin1("/%1").arg(info.name); 0339 0340 return (!path.isEmpty() && (QFileInfo(path).size() == info.size)); 0341 } 0342 0343 void FilesDownloader::reject() 0344 { 0345 if (d->reply) 0346 { 0347 d->reply->abort(); 0348 d->reply = nullptr; 0349 } 0350 0351 QDialog::reject(); 0352 } 0353 0354 void FilesDownloader::slotDownloaded(QNetworkReply* reply) 0355 { 0356 if (reply != d->reply) 0357 { 0358 return; 0359 } 0360 0361 d->reply = nullptr; 0362 0363 if ((reply->error() != QNetworkReply::NoError) && 0364 (reply->error() != QNetworkReply::InsecureRedirectError)) 0365 { 0366 d->error = reply->errorString(); 0367 0368 reply->deleteLater(); 0369 0370 nextDownload(); 0371 0372 return; 0373 } 0374 0375 QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 0376 0377 if (redirectUrl.isValid() && (reply->url() != redirectUrl) && (d->redirects < 10)) 0378 { 0379 createRequest(redirectUrl); 0380 0381 reply->deleteLater(); 0382 0383 return; 0384 } 0385 0386 QCryptographicHash sha256(QCryptographicHash::Sha256); 0387 0388 QByteArray data = reply->readAll(); 0389 0390 sha256.addData(data); 0391 0392 if (d->currentInfo.hash != QString::fromLatin1(sha256.result().toHex())) 0393 { 0394 d->error = i18n("Checksum is incorrect."); 0395 0396 reply->deleteLater(); 0397 0398 nextDownload(); 0399 0400 return; 0401 } 0402 0403 QString path = getFacesEnginePath(); 0404 0405 if (!QFileInfo::exists(path)) 0406 { 0407 QDir().mkpath(path); 0408 } 0409 0410 QFile file(path + QLatin1Char('/') + d->currentInfo.name); 0411 0412 if (file.open(QIODevice::WriteOnly)) 0413 { 0414 qint64 written = file.write(data); 0415 0416 if (written != d->currentInfo.size) 0417 { 0418 d->error = i18n("File write error."); 0419 } 0420 0421 file.close(); 0422 } 0423 else 0424 { 0425 d->error = i18n("File open error."); 0426 } 0427 0428 reply->deleteLater(); 0429 0430 nextDownload(); 0431 } 0432 0433 void FilesDownloader::slotDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) 0434 { 0435 if (d->reply && (bytesReceived > d->currentInfo.size)) 0436 { 0437 d->reply->abort(); 0438 d->reply = nullptr; 0439 0440 d->error = i18n("File on the server is too large."); 0441 0442 nextDownload(); 0443 0444 return; 0445 } 0446 0447 d->progress->setMaximum(bytesTotal); 0448 d->progress->setValue(bytesReceived); 0449 } 0450 0451 QString FilesDownloader::getFacesEnginePath() const 0452 { 0453 QString appPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); 0454 appPath += QLatin1String("/digikam/facesengine"); 0455 0456 return appPath; 0457 } 0458 0459 void FilesDownloader::slotHelp() 0460 { 0461 openOnlineDocumentation(QLatin1String("getting_started"), QLatin1String("quick_start"), QLatin1String("firstrun-downloads")); 0462 } 0463 0464 void FilesDownloader::createDownloadInfo() 0465 { 0466 d->files.clear(); 0467 0468 if (d->system.enableFaceEngine) 0469 { 0470 d->files << DownloadInfo(QLatin1String("facesengine/shape-predictor/"), 0471 QLatin1String("shapepredictor.dat"), 0472 QLatin1String("6f3d2a59dc30c7c9166983224dcf5732b25de734fff1e36ff1f3047ef90ed82b"), 0473 67740572 0474 ); 0475 0476 d->files << DownloadInfo(QLatin1String("facesengine/dnnface/"), 0477 QLatin1String("yolov3-face.cfg"), 0478 QLatin1String("f6563bd6923fd6500d2c2d6025f32ebdba916a85e5c9798351d916909f62aaf5"), 0479 8334 0480 ); 0481 0482 d->files << DownloadInfo(QLatin1String("facesengine/dnnface/"), 0483 QLatin1String("yolov3-wider_16000.weights"), 0484 QLatin1String("a88f3b3882e3cce1e553a81d42beef6202cb9afc3db88e7944f9ffbcc369e7df"), 0485 246305388 0486 ); 0487 0488 if (qApp->applicationName() == QLatin1String("digikam")) 0489 { 0490 d->files << DownloadInfo(QLatin1String("facesengine/dnnface/"), 0491 QLatin1String("openface_nn4.small2.v1.t7"), 0492 QLatin1String("9b72d54aeb24a64a8135dca8e792f7cc675c99a884a6940350a6cedcf7b7ba08"), 0493 31510785 0494 ); 0495 0496 d->files << DownloadInfo(QLatin1String("facesengine/dnnface/"), 0497 QLatin1String("deploy.prototxt"), 0498 QLatin1String("f62621cac923d6f37bd669298c428bb7ee72233b5f8c3389bb893e35ebbcf795"), 0499 28092 0500 ); 0501 0502 d->files << DownloadInfo(QLatin1String("facesengine/dnnface/"), 0503 QLatin1String("res10_300x300_ssd_iter_140000_fp16.caffemodel"), 0504 QLatin1String("510ffd2471bd81e3fcc88a5beb4eae4fb445ccf8333ebc54e7302b83f4158a76"), 0505 5351047 0506 ); 0507 } 0508 } 0509 0510 if (d->system.enableAesthetic) 0511 { 0512 d->files << DownloadInfo(QLatin1String("aestheticdetector/"), 0513 QLatin1String("weights_inceptionv3_299.pb"), 0514 QLatin1String("8923e3daff71c07533b9023ef32c69d8c058d1e0931d76d8b81241a201138538"), 0515 88007527 0516 ); 0517 } 0518 0519 if (d->system.enableAutoTags) 0520 { 0521 d->files << DownloadInfo(QLatin1String("autotags/"), 0522 QLatin1String("yolov5n_batch_16_s320.onnx"), 0523 QLatin1String("43cf201144e6918354892a2fda7ccb07b3df1abc136ddac3b5c968dffc847009"), 0524 7616008 0525 ); 0526 0527 d->files << DownloadInfo(QLatin1String("autotags/"), 0528 QLatin1String("yolov5x_batch_16_s320.onnx"), 0529 QLatin1String("e56b5024e6d29f8a35dbfbdd67bbdb69593fb67bfb275915081f9a463fcd355b"), 0530 347010839 0531 ); 0532 0533 d->files << DownloadInfo(QLatin1String("autotags/"), 0534 QLatin1String("resnet50.onnx"), 0535 QLatin1String("490e2761519cbf8a4433f880c2aa16b457730085cf9a8aab1e43d82bcadba4f1"), 0536 102146365 0537 ); 0538 0539 d->files << DownloadInfo(QLatin1String("autotags/"), 0540 QLatin1String("coco.names"), 0541 QLatin1String("634a1132eb33f8091d60f2c346ababe8b905ae08387037aed883953b7329af84"), 0542 625 0543 ); 0544 0545 d->files << DownloadInfo(QLatin1String("autotags/"), 0546 QLatin1String("classification_classes_ILSVRC2012.txt"), 0547 QLatin1String("4eb3da435cf544e4a6f390f62c84cb9c9bb68cf8b14e97f8a063452382e5efd2"), 0548 21675 0549 ); 0550 } 0551 0552 } 0553 0554 void FilesDownloader::slotUpdateDownloadInfo() 0555 { 0556 QString path = QDir::toNativeSeparators(getFacesEnginePath()); 0557 0558 d->system.enableFaceEngine = d->facesEngineCheck->isChecked(); 0559 d->system.enableAesthetic = d->aestheticCheck->isChecked(); 0560 d->system.enableAutoTags = d->autoTagsCheck->isChecked(); 0561 d->system.saveSettings(); 0562 0563 createDownloadInfo(); 0564 0565 qint64 size = 0; 0566 d->total = 0; 0567 0568 Q_FOREACH (const DownloadInfo& info, d->files) 0569 { 0570 if (!downloadExists(info)) 0571 { 0572 // cppcheck-suppress useStlAlgorithm 0573 size += info.size; 0574 d->total++; 0575 } 0576 } 0577 0578 QString sizeString = ItemPropertiesTab::humanReadableBytesCount(size); 0579 0580 d->infoLabel->setText(i18nc("%1: folder path", 0581 "<p>For face detection, the red-eye removal tool, the classification " 0582 "of images according to aesthetics or the automatic assignment " 0583 "of tags to images, digiKam requires some large binary files. You " 0584 "can choose which feature you want to use.</p>" 0585 "<p>Some of these files were not found. Click “Download” to begin " 0586 "downloading the files you need. You can close this dialog, you " 0587 "will be asked again the next time you start digiKam. Without " 0588 "these files the selected features will not work.</p>" 0589 "<p>The files will be downloaded to %1.</p>" 0590 "<p><b>After the successful download you have to restart digiKam.</b></p>", path)); 0591 if (size > 0) 0592 { 0593 d->sizeLabel->setText(i18nc("%1: file counter, %2: disk size with unit", 0594 "The download requires %1 files with a size of %2.", 0595 d->total, sizeString)); 0596 0597 d->buttons->button(QDialogButtonBox::Ok)->setEnabled(true); 0598 } 0599 else 0600 { 0601 d->sizeLabel->setText(i18n("All files of the selected features were found.")); 0602 0603 d->buttons->button(QDialogButtonBox::Ok)->setEnabled(false); 0604 } 0605 } 0606 0607 //----------------------------------------------------------------------------- 0608 0609 DownloadInfo::DownloadInfo() 0610 : size(0) 0611 { 0612 } 0613 0614 DownloadInfo::DownloadInfo(const QString& _path, 0615 const QString& _name, 0616 const QString& _hash, 0617 const qint64& _size) 0618 : path(_path), 0619 name(_name), 0620 hash(_hash), 0621 size(_size) 0622 { 0623 } 0624 0625 DownloadInfo::DownloadInfo(const DownloadInfo& other) 0626 : path(other.path), 0627 name(other.name), 0628 hash(other.hash), 0629 size(other.size) 0630 { 0631 } 0632 0633 DownloadInfo::~DownloadInfo() 0634 { 0635 } 0636 0637 DownloadInfo& DownloadInfo::operator=(const DownloadInfo& other) 0638 { 0639 path = other.path; 0640 name = other.name; 0641 hash = other.hash; 0642 size = other.size; 0643 0644 return *this; 0645 } 0646 0647 } // namespace Digikam 0648 0649 #include "moc_filesdownloader.cpp"