# HG changeset patch # User Teemu Piippo # Date 1441547842 -10800 # Node ID 09e1a3e272ecfdbbcc39a940f56ed7fb73e299fb # Parent 23436e487f0c908e0bed12191936bf1f4613afdc Split PartDownloadRequest into its own file Moved ui/downloadfrom.ui to src/partdownloader.ui diff -r 23436e487f0c -r 09e1a3e272ec CMakeLists.txt --- a/CMakeLists.txt Sun Sep 06 16:42:57 2015 +0300 +++ b/CMakeLists.txt Sun Sep 06 16:57:22 2015 +0300 @@ -52,7 +52,8 @@ src/mainwindow.cpp src/messageLog.cpp src/miscallenous.cpp - src/partDownloader.cpp + src/partdownloader.cpp + src/partdownloadrequest.cpp src/primitives.cpp src/radioGroup.cpp src/ringFinder.cpp @@ -102,7 +103,8 @@ src/mainwindow.h src/messageLog.h src/miscallenous.h - src/partDownloader.h + src/partdownloader.h + src/partdownloadrequest.h src/primitives.h src/radioGroup.h src/ringFinder.h @@ -131,7 +133,6 @@ ui/about.ui ui/addhistoryline.ui ui/coverer.ui - ui/downloadfrom.ui ui/edger2.ui ui/editraw.ui ui/extprogpath.ui @@ -145,6 +146,7 @@ ui/rotpoint.ui ui/ytruder.ui src/mainwindow.ui + src/partdownloader.ui src/dialogs/colorselector.ui src/dialogs/configdialog.ui src/dialogs/ldrawpathdialog.ui diff -r 23436e487f0c -r 09e1a3e272ec src/ldDocument.cpp --- a/src/ldDocument.cpp Sun Sep 06 16:42:57 2015 +0300 +++ b/src/ldDocument.cpp Sun Sep 06 16:57:22 2015 +0300 @@ -28,7 +28,7 @@ #include "editHistory.h" #include "glRenderer.h" #include "glCompiler.h" -#include "partDownloader.h" +#include "partdownloader.h" #include "ldpaths.h" #include "documentloader.h" #include "dialogs/openprogressdialog.h" diff -r 23436e487f0c -r 09e1a3e272ec src/partDownloader.cpp --- a/src/partDownloader.cpp Sun Sep 06 16:42:57 2015 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,599 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "partDownloader.h" -#include "ui_downloadfrom.h" -#include "basics.h" -#include "mainwindow.h" -#include "ldDocument.h" -#include "glRenderer.h" - -ConfigOption (QString DownloadFilePath) -ConfigOption (bool GuessDownloadPaths = true) -ConfigOption (bool AutoCloseDownloadDialog = true) - -const char* g_unofficialLibraryURL = "http://ldraw.org/library/unofficial/"; - -PartDownloader::PartDownloader (QWidget* parent) : - QDialog (parent), - HierarchyElement (parent), - ui (*new Ui_DownloadFrom), - m_source (SourceType (0)) -{ - ui.setupUi (this); - ui.fname->setFocus(); - -#ifdef USE_QT5 - ui.progress->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch); -#else - ui.progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); -#endif - - m_downloadButton = new QPushButton (tr ("Download")); - ui.buttonBox->addButton (m_downloadButton, QDialogButtonBox::ActionRole); - button (Abort)->setEnabled (false); - connect (ui.source, SIGNAL (currentIndexChanged (int)), this, SLOT (sourceChanged (int))); - connect (ui.buttonBox, SIGNAL (clicked (QAbstractButton*)), this, SLOT (buttonClicked (QAbstractButton*))); -} - -PartDownloader::~PartDownloader() -{ - delete &ui; -} - -void PartDownloader::checkValidPath() -{ - QString path = downloadPath(); - - if (path.isEmpty() or not QDir (path).exists()) - { - QMessageBox::information(this, "Notice", "Please input a path for files to download."); - path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:"); - - if (path.isEmpty()) - reject(); - else - m_config->setDownloadFilePath (path); - } -} - -QString PartDownloader::url() -{ - const SourceType src = sourceType(); - QString dest; - - switch (src) - { - case PartsTracker: - dest = ui.fname->text(); - modifyDestination (dest); - ui.fname->setText (dest); - return g_unofficialLibraryURL + dest; - - case CustomURL: - return ui.fname->text(); - } - - // Shouldn't happen - return ""; -} - -void PartDownloader::modifyDestination (QString& dest) const -{ - dest = dest.simplified(); - - // If the user doesn't want us to guess, stop right here. - if (not m_config->guessDownloadPaths()) - return; - - // Ensure .dat extension - if (dest.right (4) != ".dat") - { - // Remove the existing extension, if any. It may be we're here over a - // typo in the .dat extension. - const int dotpos = dest.lastIndexOf ("."); - - if ((dotpos != -1) and (dotpos >= dest.length() - 4)) - dest.chop (dest.length() - dotpos); - - dest += ".dat"; - } - - // If the part starts with s\ or s/, then use parts/s/. Same goes with - // 48\ and p/48/. - if (isOneOf (dest.left (2), "s\\", "s/")) - { - dest.remove (0, 2); - dest.prepend ("parts/s/"); - } - else if (isOneOf (dest.left (3), "48\\", "48/")) - { - dest.remove (0, 3); - dest.prepend ("p/48/"); - } - - /* Try determine where to put this part. We have four directories: - parts/, parts/s/, p/, and p/48/. If we haven't already specified - either parts/ or p/, we need to add it automatically. Part files - are numbers wit a possible u prefix for parts with unknown number - which can be followed by any of: - - c** (composites) - - d** (formed stickers) - - p** (patterns) - - a lowercase alphabetic letter for variants - - Subfiles (usually) have an s** prefix, in which case we use parts/s/. - Note that the regex starts with a '^' so it won't catch already fully - given part file names. */ - QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; - QString subpartRegex = partRegex + "s[0-9][0-9]+"; - - partRegex += "\\.dat$"; - subpartRegex += "\\.dat$"; - - if (QRegExp (subpartRegex).exactMatch (dest)) - dest.prepend ("parts/s/"); - else if (QRegExp (partRegex).exactMatch (dest)) - dest.prepend ("parts/"); - else if (not dest.startsWith ("parts/") and not dest.startsWith ("p/")) - dest.prepend ("p/"); -} - -PartDownloader::SourceType PartDownloader::sourceType() const -{ - return m_source; -} - -void PartDownloader::setSourceType (SourceType src) -{ - m_source = src; - ui.source->setCurrentIndex (int (src)); -} - -void PartDownloader::sourceChanged (int i) -{ - if (i == CustomURL) - ui.fileNameLabel->setText (tr ("URL:")); - else - ui.fileNameLabel->setText (tr ("File name:")); - - m_source = SourceType (i); -} - -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ - if (btn == button (Close)) - { - reject(); - } - else if (btn == button (Abort)) - { - m_isAborted = true; - - for (PartDownloadRequest* req : m_requests) - req->abort(); - } - else if (btn == button (Download)) - { - QString dest = ui.fname->text(); - setPrimaryFile (nullptr); - m_isAborted = false; - - if (sourceType() == CustomURL) - dest = Basename (url()); - - modifyDestination (dest); - - if (QFile::exists (downloadPath() + DIRSLASH + dest)) - { - const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (not Confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - downloadFile (dest, url(), true); - } -} - -void PartDownloader::downloadFile (QString dest, QString url, bool primary) -{ - int row = ui.progress->rowCount(); - - // Don't download files repeadetly. - if (m_filesToDownload.indexOf (dest) != -1) - return; - - print ("Downloading %1 from %2\n", dest, url); - modifyDestination (dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - m_filesToDownload << dest; - m_requests << req; - ui.progress->insertRow (row); - req->setTableRow (row); - req->updateToTable(); - m_downloadButton->setEnabled (false); - ui.progress->setEnabled (true); - ui.fname->setEnabled (false); - ui.source->setEnabled (false); - button (Close)->setEnabled (false); - button (Abort)->setEnabled (true); - button (Download)->setEnabled (false); -} - -void PartDownloader::downloadFromPartsTracker (QString file) -{ - modifyDestination (file); - downloadFile (file, g_unofficialLibraryURL + file, false); -} - -void PartDownloader::checkIfFinished() -{ - bool failed = isAborted(); - - // If there is some download still working, we're not finished. - for (PartDownloadRequest* req : m_requests) - { - if (not req->isFinished()) - return; - - if (req->failed()) - failed = true; - } - - for (PartDownloadRequest* req : m_requests) - delete req; - - m_requests.clear(); - - if (primaryFile()) - emit primaryFileDownloaded(); - - for (LDDocument* f : m_files) - f->reloadAllSubfiles(); - - if (m_config->autoCloseDownloadDialog() and not failed) - { - // Close automatically if desired. - accept(); - } - else - { - // Allow the prompt be closed now. - button (Abort)->setEnabled (false); - button (Close)->setEnabled (true); - } -} - -QPushButton* PartDownloader::button (PartDownloader::Button i) -{ - switch (i) - { - case Download: - return m_downloadButton; - - case Abort: - return qobject_cast (ui.buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast (ui.buttonBox->button (QDialogButtonBox::Close)); - } - - return nullptr; -} - -void PartDownloader::addFile (LDDocument* f) -{ - m_files << f; -} - -bool PartDownloader::isAborted() const -{ - return m_isAborted; -} - -LDDocument* PartDownloader::primaryFile() const -{ - return m_primaryFile; -} - -void PartDownloader::setPrimaryFile (LDDocument* document) -{ - m_primaryFile = document; -} - -QString PartDownloader::downloadPath() -{ - QString path = m_config->downloadFilePath(); - - if (DIRSLASH[0] != '/') - path.replace (DIRSLASH, "/"); - - return path; -} - -QTableWidget* PartDownloader::progressTable() const -{ - return ui.progress; -} - -// -// --------------------------------------------------------------------------------------------------------------------- -// - -PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) : - QObject (parent), - m_state (State::Requesting), - m_prompt (parent), - m_url (url), - m_destination (dest), - m_filePath (parent->downloadPath() + DIRSLASH + dest), - m_networkManager (new QNetworkAccessManager), - m_isFirstUpdate (true), - m_isPrimary (primary), - m_filePointer (nullptr) -{ - // Make sure that we have a valid destination. - QString dirpath = Dirname (filePath()); - QDir dir (dirpath); - - if (not dir.exists()) - { - print ("Creating %1...\n", dirpath); - - if (not dir.mkpath (dirpath)) - Critical (format (tr ("Couldn't create the directory %1!"), dirpath)); - } - - m_networkReply = m_networkManager->get (QNetworkRequest (QUrl (url))); - connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readFromNetworkReply())); - connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)), - this, SLOT (updateDownloadProgress (qint64, qint64))); -} - -PartDownloadRequest::~PartDownloadRequest() {} - -bool PartDownloadRequest::failed() const -{ - return m_state == State::Failed; -} - -int PartDownloadRequest::tableRow() const -{ - return m_tableRow; -} - -void PartDownloadRequest::setTableRow (int value) -{ - m_tableRow = value; -} - -PartDownloader* PartDownloadRequest::prompt() const -{ - return m_prompt; -} - -QString PartDownloadRequest::url() const -{ - return m_url; -} - -QString PartDownloadRequest::destination() const -{ - return m_destination; -} - -QString PartDownloadRequest::filePath() const -{ - return m_filePath; -} - -bool PartDownloadRequest::isFirstUpdate() const -{ - return m_isFirstUpdate; -} - -qint64 PartDownloadRequest::numBytesRead() const -{ - return m_numBytesRead; -} - -qint64 PartDownloadRequest::numBytesTotal() const -{ - return m_numBytesTotal; -} - -bool PartDownloadRequest::isPrimary() const -{ - return m_isPrimary; -} - -QNetworkReply* PartDownloadRequest::networkReply() const -{ - return m_networkReply; -} - -void PartDownloadRequest::updateToTable() -{ - QTableWidget* table = prompt()->progressTable(); - - switch (m_state) - { - case State::Requesting: - case State::Downloading: - { - QWidget* cellwidget = table->cellWidget (tableRow(), PartDownloader::ProgressColumn); - QProgressBar* progressBar = qobject_cast (cellwidget); - - if (not progressBar) - { - progressBar = new QProgressBar; - table->setCellWidget (tableRow(), PartDownloader::ProgressColumn, progressBar); - } - - progressBar->setRange (0, numBytesTotal()); - progressBar->setValue (numBytesRead()); - } - break; - - case State::Finished: - case State::Failed: - { - const QString text = (m_state == State::Finished) - ? "FINISHED" - : "FAILED"; - - QLabel* lb = new QLabel (text); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (tableRow(), PartDownloader::ProgressColumn, lb); - } - break; - } - - QLabel* label = qobject_cast (table->cellWidget (tableRow(), PartDownloader::PartLabelColumn)); - - if (isFirstUpdate()) - { - label = new QLabel (format ("%1", destination()), table); - table->setCellWidget (tableRow(), PartDownloader::PartLabelColumn, label); - } - - // Make sure that the cell is big enough to contain the label - if (table->columnWidth (PartDownloader::PartLabelColumn) < label->width()) - table->setColumnWidth (PartDownloader::PartLabelColumn, label->width()); - - m_isFirstUpdate = false; -} - -void PartDownloadRequest::downloadFinished() -{ - if (networkReply()->error() != QNetworkReply::NoError) - { - if (isPrimary() and not prompt()->isAborted()) - Critical (networkReply()->errorString()); - - print ("Unable to download %1: %2\n", destination(), networkReply()->errorString()); - m_state = State::Failed; - } - else if (m_state != State::Failed) - { - m_state = State::Finished; - } - - m_numBytesRead = numBytesTotal(); - updateToTable(); - - if (m_filePointer) - { - m_filePointer->close(); - delete m_filePointer; - m_filePointer = nullptr; - - if (m_state == State::Failed) - QFile::remove (filePath()); - } - - if (m_state != State::Finished) - { - prompt()->checkIfFinished(); - return; - } - - // Try to load this file now. - LDDocument* f = OpenDocument (filePath(), false, not isPrimary()); - - if (f == nullptr) - return; - - // Iterate through this file and check for errors. If there's any that stems - // from unknown file references, try resolve that by downloading the reference. - // This is why downloading a part may end up downloading multiple files, as - // it resolves dependencies. - for (LDObject* obj : f->objects()) - { - LDError* err = dynamic_cast (obj); - - if (err == nullptr or err->fileReferenced().isEmpty()) - continue; - - QString dest = err->fileReferenced(); - prompt()->downloadFromPartsTracker (dest); - } - - prompt()->addFile (f); - - if (isPrimary()) - { - AddRecentFile (filePath()); - prompt()->setPrimaryFile (f); - } - - prompt()->checkIfFinished(); -} - -void PartDownloadRequest::updateDownloadProgress (int64 recv, int64 total) -{ - m_numBytesRead = recv; - m_numBytesTotal = total; - m_state = State::Downloading; - updateToTable(); -} - -void PartDownloadRequest::readFromNetworkReply() -{ - if (m_state == State::Failed) - return; - - if (m_filePointer == nullptr) - { - m_filePath.replace ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good to go here. - m_filePointer = new QFile (filePath().toLocal8Bit()); - - if (not m_filePointer->open (QIODevice::WriteOnly)) - { - Critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno))); - m_state = State::Failed; - networkReply()->abort(); - updateToTable(); - prompt()->checkIfFinished(); - return; - } - } - - m_filePointer->write (networkReply()->readAll()); -} - -bool PartDownloadRequest::isFinished() const -{ - return isOneOf (m_state, State::Finished, State::Failed); -} - -void PartDownloadRequest::abort() -{ - networkReply()->abort(); -} diff -r 23436e487f0c -r 09e1a3e272ec src/partDownloader.h --- a/src/partDownloader.h Sun Sep 06 16:42:57 2015 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2015 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include "main.h" -#include "basics.h" -#include "ldDocument.h" - -class LDDocument; -class QFile; -class PartDownloadRequest; -class Ui_DownloadFrom; -class QNetworkAccessManager; -class QNetworkRequest; -class QNetworkReply; -class QAbstractButton; - -class PartDownloader : public QDialog, public HierarchyElement -{ - Q_OBJECT - -public: - enum SourceType - { - PartsTracker, - CustomURL, - }; - - enum Button - { - Download, - Abort, - Close - }; - - enum TableColumn - { - PartLabelColumn, - ProgressColumn, - }; - - using RequestList = QList; - - explicit PartDownloader (QWidget* parent = nullptr); - virtual ~PartDownloader(); - - void addFile (LDDocument* f); - QPushButton* button (Button i); - Q_SLOT void buttonClicked (QAbstractButton* btn); - Q_SLOT void checkIfFinished(); - void checkValidPath(); - void downloadFile (QString dest, QString url, bool primary); - void downloadFromPartsTracker (QString file); - QString downloadPath(); - bool isAborted() const; - void modifyDestination (QString& dest) const; - LDDocument* primaryFile() const; - class QTableWidget* progressTable() const; - void setPrimaryFile (LDDocument* document); - void setSourceType (SourceType src); - Q_SLOT void sourceChanged (int i); - SourceType sourceType() const; - QString url(); - -signals: - void primaryFileDownloaded(); - -private: - class Ui_DownloadFrom& ui; - QStringList m_filesToDownload; - RequestList m_requests; - QPushButton* m_downloadButton; - SourceType m_source; - QList m_files; - LDDocument* m_primaryFile; - bool m_isAborted; -}; - -class PartDownloadRequest : public QObject -{ - Q_OBJECT - -public: - enum class State - { - Requesting, - Downloading, - Finished, - Failed, - }; - - explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent); - virtual ~PartDownloadRequest(); - - Q_SLOT void abort(); - QString destination() const; - Q_SLOT void downloadFinished(); - bool failed() const; - QString filePath() const; - bool isFinished() const; - bool isFirstUpdate() const; - bool isPrimary() const; - QNetworkReply* networkReply() const; - qint64 numBytesRead() const; - qint64 numBytesTotal() const; - PartDownloader* prompt() const; - Q_SLOT void readFromNetworkReply(); - void setTableRow (int value); - int tableRow() const; - Q_SLOT void updateDownloadProgress (qint64 recv, qint64 total); - void updateToTable(); - QString url() const; - -private: - int m_tableRow; - State m_state; - PartDownloader* m_prompt; - QString m_url; - QString m_destination; - QString m_filePath; - QNetworkAccessManager* m_networkManager; - QNetworkReply* m_networkReply; - bool m_isFirstUpdate; - bool m_isPrimary; - qint64 m_numBytesRead; - qint64 m_numBytesTotal; - QFile* m_filePointer; -}; diff -r 23436e487f0c -r 09e1a3e272ec src/partdownloader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partdownloader.cpp Sun Sep 06 16:57:22 2015 +0300 @@ -0,0 +1,336 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "partdownloader.h" +#include "partdownloadrequest.h" +#include "ui_partdownloader.h" +#include "basics.h" +#include "mainwindow.h" +#include "ldDocument.h" +#include "glRenderer.h" + +ConfigOption (QString DownloadFilePath) +ConfigOption (bool GuessDownloadPaths = true) +ConfigOption (bool AutoCloseDownloadDialog = true) + +const char* g_unofficialLibraryURL = "http://ldraw.org/library/unofficial/"; + +PartDownloader::PartDownloader (QWidget* parent) : + QDialog (parent), + HierarchyElement (parent), + ui (*new Ui_PartDownloader), + m_source (SourceType (0)) +{ + ui.setupUi (this); + +#ifdef USE_QT5 + ui.progressTable->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch); +#else + ui.progressTable->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); +#endif + + m_downloadButton = new QPushButton (tr ("Download")); + ui.buttonBox->addButton (m_downloadButton, QDialogButtonBox::ActionRole); + button (Abort)->setEnabled (false); + connect (ui.source, SIGNAL (currentIndexChanged (int)), this, SLOT (sourceChanged (int))); + connect (ui.buttonBox, SIGNAL (clicked (QAbstractButton*)), this, SLOT (buttonClicked (QAbstractButton*))); +} + +PartDownloader::~PartDownloader() +{ + delete &ui; +} + +void PartDownloader::checkValidPath() +{ + QString path = downloadPath(); + + if (path.isEmpty() or not QDir (path).exists()) + { + QMessageBox::information(this, "Notice", "Please input a path for files to download."); + path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:"); + + if (path.isEmpty()) + reject(); + else + m_config->setDownloadFilePath (path); + } +} + +QString PartDownloader::url() +{ + QString destination; + + switch (sourceType()) + { + case PartsTracker: + destination = ui.filename->text(); + modifyDestination (destination); + ui.filename->setText (destination); + return g_unofficialLibraryURL + destination; + + case CustomURL: + return ui.filename->text(); + } + + // Shouldn't happen + return ""; +} + +void PartDownloader::modifyDestination (QString& dest) const +{ + dest = dest.simplified(); + + // If the user doesn't want us to guess, stop right here. + if (not m_config->guessDownloadPaths()) + return; + + // Ensure .dat extension + if (dest.right (4) != ".dat") + { + // Remove the existing extension, if any. It may be we're here over a + // typo in the .dat extension. + const int dotpos = dest.lastIndexOf ("."); + + if ((dotpos != -1) and (dotpos >= dest.length() - 4)) + dest.chop (dest.length() - dotpos); + + dest += ".dat"; + } + + // If the part starts with s\ or s/, then use parts/s/. Same goes with + // 48\ and p/48/. + if (isOneOf (dest.left (2), "s\\", "s/")) + { + dest.remove (0, 2); + dest.prepend ("parts/s/"); + } + else if (isOneOf (dest.left (3), "48\\", "48/")) + { + dest.remove (0, 3); + dest.prepend ("p/48/"); + } + + /* Try determine where to put this part. We have four directories: + parts/, parts/s/, p/, and p/48/. If we haven't already specified + either parts/ or p/, we need to add it automatically. Part files + are numbers wit a possible u prefix for parts with unknown number + which can be followed by any of: + - c** (composites) + - d** (formed stickers) + - p** (patterns) + - a lowercase alphabetic letter for variants + + Subfiles (usually) have an s** prefix, in which case we use parts/s/. + Note that the regex starts with a '^' so it won't catch already fully + given part file names. */ + QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; + QString subpartRegex = partRegex + "s[0-9][0-9]+"; + + partRegex += "\\.dat$"; + subpartRegex += "\\.dat$"; + + if (QRegExp (subpartRegex).exactMatch (dest)) + dest.prepend ("parts/s/"); + else if (QRegExp (partRegex).exactMatch (dest)) + dest.prepend ("parts/"); + else if (not dest.startsWith ("parts/") and not dest.startsWith ("p/")) + dest.prepend ("p/"); +} + +PartDownloader::SourceType PartDownloader::sourceType() const +{ + return m_source; +} + +void PartDownloader::setSourceType (SourceType src) +{ + m_source = src; + ui.source->setCurrentIndex (int (src)); +} + +void PartDownloader::sourceChanged (int i) +{ + if (i == CustomURL) + ui.fileNameLabel->setText (tr ("URL:")); + else + ui.fileNameLabel->setText (tr ("File name:")); + + m_source = SourceType (i); +} + +void PartDownloader::buttonClicked (QAbstractButton* btn) +{ + if (btn == button (Close)) + { + reject(); + } + else if (btn == button (Abort)) + { + m_isAborted = true; + + for (PartDownloadRequest* req : m_requests) + req->abort(); + } + else if (btn == button (Download)) + { + QString dest = ui.filename->text(); + setPrimaryFile (nullptr); + m_isAborted = false; + + if (sourceType() == CustomURL) + dest = Basename (url()); + + modifyDestination (dest); + + if (QFile::exists (downloadPath() + DIRSLASH + dest)) + { + const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); + if (not Confirm (tr ("Overwrite?"), overwritemsg)) + return; + } + + downloadFile (dest, url(), true); + } +} + +void PartDownloader::downloadFile (QString dest, QString url, bool primary) +{ + int row = ui.progressTable->rowCount(); + + // Don't download files repeadetly. + if (m_filesToDownload.indexOf (dest) != -1) + return; + + print ("Downloading %1 from %2\n", dest, url); + modifyDestination (dest); + PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); + m_filesToDownload << dest; + m_requests << req; + ui.progressTable->insertRow (row); + req->setTableRow (row); + req->updateToTable(); + m_downloadButton->setEnabled (false); + ui.progressTable->setEnabled (true); + ui.filename->setEnabled (false); + ui.source->setEnabled (false); + button (Close)->setEnabled (false); + button (Abort)->setEnabled (true); + button (Download)->setEnabled (false); +} + +void PartDownloader::downloadFromPartsTracker (QString file) +{ + modifyDestination (file); + downloadFile (file, g_unofficialLibraryURL + file, false); +} + +void PartDownloader::checkIfFinished() +{ + bool failed = isAborted(); + + // If there is some download still working, we're not finished. + for (PartDownloadRequest* req : m_requests) + { + if (not req->isFinished()) + return; + + if (req->failed()) + failed = true; + } + + for (PartDownloadRequest* req : m_requests) + delete req; + + m_requests.clear(); + + if (primaryFile()) + emit primaryFileDownloaded(); + + for (LDDocument* f : m_files) + f->reloadAllSubfiles(); + + if (m_config->autoCloseDownloadDialog() and not failed) + { + // Close automatically if desired. + accept(); + } + else + { + // Allow the prompt be closed now. + button (Abort)->setEnabled (false); + button (Close)->setEnabled (true); + } +} + +QPushButton* PartDownloader::button (PartDownloader::Button i) +{ + switch (i) + { + case Download: + return m_downloadButton; + + case Abort: + return qobject_cast (ui.buttonBox->button (QDialogButtonBox::Abort)); + + case Close: + return qobject_cast (ui.buttonBox->button (QDialogButtonBox::Close)); + } + + return nullptr; +} + +void PartDownloader::addFile (LDDocument* f) +{ + m_files << f; +} + +bool PartDownloader::isAborted() const +{ + return m_isAborted; +} + +LDDocument* PartDownloader::primaryFile() const +{ + return m_primaryFile; +} + +void PartDownloader::setPrimaryFile (LDDocument* document) +{ + m_primaryFile = document; +} + +QString PartDownloader::downloadPath() +{ + QString path = m_config->downloadFilePath(); + + if (DIRSLASH[0] != '/') + path.replace (DIRSLASH, "/"); + + return path; +} + +QTableWidget* PartDownloader::progressTable() const +{ + return ui.progressTable; +} diff -r 23436e487f0c -r 09e1a3e272ec src/partdownloader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partdownloader.h Sun Sep 06 16:57:22 2015 +0300 @@ -0,0 +1,93 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include "main.h" +#include "basics.h" +#include "ldDocument.h" + +class LDDocument; +class QFile; +class PartDownloadRequest; +class Ui_DownloadFrom; +class QNetworkAccessManager; +class QNetworkRequest; +class QNetworkReply; +class QAbstractButton; + +class PartDownloader : public QDialog, public HierarchyElement +{ + Q_OBJECT + +public: + enum SourceType + { + PartsTracker, + CustomURL, + }; + + enum Button + { + Download, + Abort, + Close + }; + + enum TableColumn + { + PartLabelColumn, + ProgressColumn, + }; + + using RequestList = QList; + + explicit PartDownloader (QWidget* parent = nullptr); + virtual ~PartDownloader(); + + void addFile (LDDocument* f); + QPushButton* button (Button i); + Q_SLOT void buttonClicked (QAbstractButton* btn); + Q_SLOT void checkIfFinished(); + void checkValidPath(); + void downloadFile (QString dest, QString url, bool primary); + void downloadFromPartsTracker (QString file); + QString downloadPath(); + bool isAborted() const; + void modifyDestination (QString& dest) const; + LDDocument* primaryFile() const; + class QTableWidget* progressTable() const; + void setPrimaryFile (LDDocument* document); + void setSourceType (SourceType src); + Q_SLOT void sourceChanged (int i); + SourceType sourceType() const; + QString url(); + +signals: + void primaryFileDownloaded(); + +private: + class Ui_PartDownloader& ui; + QStringList m_filesToDownload; + RequestList m_requests; + QPushButton* m_downloadButton; + SourceType m_source; + QList m_files; + LDDocument* m_primaryFile; + bool m_isAborted; +}; diff -r 23436e487f0c -r 09e1a3e272ec src/partdownloader.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partdownloader.ui Sun Sep 06 16:57:22 2015 +0300 @@ -0,0 +1,143 @@ + + + PartDownloader + + + + 0 + 0 + 546 + 405 + + + + Download from LDraw.org + + + + + + + 11 + 75 + true + + + + Download from LDraw.org + + + Qt::AlignCenter + + + + + + + + + File name: + + + + + + + + + + Source: + + + + + + + + 0 + 0 + + + + + Parts tracker + + + + + Custom URL + + + + + + + + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + false + + + + File + + + + 50 + false + + + + + + Status + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Abort|QDialogButtonBox::Close + + + + + + + filename + source + progressTable + + + + + buttonBox + rejected() + PartDownloader + reject() + + + 322 + 312 + + + 286 + 274 + + + + + diff -r 23436e487f0c -r 09e1a3e272ec src/partdownloadrequest.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partdownloadrequest.cpp Sun Sep 06 16:57:22 2015 +0300 @@ -0,0 +1,283 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "partdownloader.h" +#include "partdownloadrequest.h" +#include "mainwindow.h" + +PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) : + QObject (parent), + m_state (State::Requesting), + m_prompt (parent), + m_url (url), + m_destination (dest), + m_filePath (parent->downloadPath() + DIRSLASH + dest), + m_networkManager (new QNetworkAccessManager), + m_isFirstUpdate (true), + m_isPrimary (primary), + m_filePointer (nullptr) +{ + // Make sure that we have a valid destination. + QString dirpath = Dirname (filePath()); + QDir dir (dirpath); + + if (not dir.exists()) + { + print ("Creating %1...\n", dirpath); + + if (not dir.mkpath (dirpath)) + Critical (format (tr ("Couldn't create the directory %1!"), dirpath)); + } + + m_networkReply = m_networkManager->get (QNetworkRequest (QUrl (url))); + connect (networkReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); + connect (networkReply(), SIGNAL (readyRead()), this, SLOT (readFromNetworkReply())); + connect (networkReply(), SIGNAL (downloadProgress (qint64, qint64)), + this, SLOT (updateDownloadProgress (qint64, qint64))); +} + +PartDownloadRequest::~PartDownloadRequest() {} + +bool PartDownloadRequest::failed() const +{ + return m_state == State::Failed; +} + +int PartDownloadRequest::tableRow() const +{ + return m_tableRow; +} + +void PartDownloadRequest::setTableRow (int value) +{ + m_tableRow = value; +} + +PartDownloader* PartDownloadRequest::prompt() const +{ + return m_prompt; +} + +QString PartDownloadRequest::url() const +{ + return m_url; +} + +QString PartDownloadRequest::destination() const +{ + return m_destination; +} + +QString PartDownloadRequest::filePath() const +{ + return m_filePath; +} + +bool PartDownloadRequest::isFirstUpdate() const +{ + return m_isFirstUpdate; +} + +qint64 PartDownloadRequest::numBytesRead() const +{ + return m_numBytesRead; +} + +qint64 PartDownloadRequest::numBytesTotal() const +{ + return m_numBytesTotal; +} + +bool PartDownloadRequest::isPrimary() const +{ + return m_isPrimary; +} + +QNetworkReply* PartDownloadRequest::networkReply() const +{ + return m_networkReply; +} + +void PartDownloadRequest::updateToTable() +{ + QTableWidget* table = prompt()->progressTable(); + + switch (m_state) + { + case State::Requesting: + case State::Downloading: + { + QWidget* cellwidget = table->cellWidget (tableRow(), PartDownloader::ProgressColumn); + QProgressBar* progressBar = qobject_cast (cellwidget); + + if (not progressBar) + { + progressBar = new QProgressBar; + table->setCellWidget (tableRow(), PartDownloader::ProgressColumn, progressBar); + } + + progressBar->setRange (0, numBytesTotal()); + progressBar->setValue (numBytesRead()); + } + break; + + case State::Finished: + case State::Failed: + { + const QString text = (m_state == State::Finished) + ? "FINISHED" + : "FAILED"; + + QLabel* lb = new QLabel (text); + lb->setAlignment (Qt::AlignCenter); + table->setCellWidget (tableRow(), PartDownloader::ProgressColumn, lb); + } + break; + } + + QLabel* label = qobject_cast (table->cellWidget (tableRow(), PartDownloader::PartLabelColumn)); + + if (isFirstUpdate()) + { + label = new QLabel (format ("%1", destination()), table); + table->setCellWidget (tableRow(), PartDownloader::PartLabelColumn, label); + } + + // Make sure that the cell is big enough to contain the label + if (table->columnWidth (PartDownloader::PartLabelColumn) < label->width()) + table->setColumnWidth (PartDownloader::PartLabelColumn, label->width()); + + m_isFirstUpdate = false; +} + +void PartDownloadRequest::downloadFinished() +{ + if (networkReply()->error() != QNetworkReply::NoError) + { + if (isPrimary() and not prompt()->isAborted()) + Critical (networkReply()->errorString()); + + print ("Unable to download %1: %2\n", destination(), networkReply()->errorString()); + m_state = State::Failed; + } + else if (m_state != State::Failed) + { + m_state = State::Finished; + } + + m_numBytesRead = numBytesTotal(); + updateToTable(); + + if (m_filePointer) + { + m_filePointer->close(); + delete m_filePointer; + m_filePointer = nullptr; + + if (m_state == State::Failed) + QFile::remove (filePath()); + } + + if (m_state != State::Finished) + { + prompt()->checkIfFinished(); + return; + } + + // Try to load this file now. + LDDocument* f = OpenDocument (filePath(), false, not isPrimary()); + + if (f == nullptr) + return; + + // Iterate through this file and check for errors. If there's any that stems + // from unknown file references, try resolve that by downloading the reference. + // This is why downloading a part may end up downloading multiple files, as + // it resolves dependencies. + for (LDObject* obj : f->objects()) + { + LDError* err = dynamic_cast (obj); + + if (err == nullptr or err->fileReferenced().isEmpty()) + continue; + + QString dest = err->fileReferenced(); + prompt()->downloadFromPartsTracker (dest); + } + + prompt()->addFile (f); + + if (isPrimary()) + { + AddRecentFile (filePath()); + prompt()->setPrimaryFile (f); + } + + prompt()->checkIfFinished(); +} + +void PartDownloadRequest::updateDownloadProgress (int64 recv, int64 total) +{ + m_numBytesRead = recv; + m_numBytesTotal = total; + m_state = State::Downloading; + updateToTable(); +} + +void PartDownloadRequest::readFromNetworkReply() +{ + if (m_state == State::Failed) + return; + + if (m_filePointer == nullptr) + { + m_filePath.replace ("\\", "/"); + + // We have already asked the user whether we can overwrite so we're good to go here. + m_filePointer = new QFile (filePath().toLocal8Bit()); + + if (not m_filePointer->open (QIODevice::WriteOnly)) + { + Critical (format (tr ("Couldn't open %1 for writing: %2"), filePath(), strerror (errno))); + m_state = State::Failed; + networkReply()->abort(); + updateToTable(); + prompt()->checkIfFinished(); + return; + } + } + + m_filePointer->write (networkReply()->readAll()); +} + +bool PartDownloadRequest::isFinished() const +{ + return isOneOf (m_state, State::Finished, State::Failed); +} + +void PartDownloadRequest::abort() +{ + networkReply()->abort(); +} \ No newline at end of file diff -r 23436e487f0c -r 09e1a3e272ec src/partdownloadrequest.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/partdownloadrequest.h Sun Sep 06 16:57:22 2015 +0300 @@ -0,0 +1,73 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include "main.h" + +class PartDownloader; + +class PartDownloadRequest : public QObject +{ + Q_OBJECT + +public: + enum class State + { + Requesting, + Downloading, + Finished, + Failed, + }; + + explicit PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent); + virtual ~PartDownloadRequest(); + + Q_SLOT void abort(); + QString destination() const; + Q_SLOT void downloadFinished(); + bool failed() const; + QString filePath() const; + bool isFinished() const; + bool isFirstUpdate() const; + bool isPrimary() const; + QNetworkReply* networkReply() const; + qint64 numBytesRead() const; + qint64 numBytesTotal() const; + PartDownloader* prompt() const; + Q_SLOT void readFromNetworkReply(); + void setTableRow (int value); + int tableRow() const; + Q_SLOT void updateDownloadProgress (qint64 recv, qint64 total); + void updateToTable(); + QString url() const; + +private: + int m_tableRow; + State m_state; + PartDownloader* m_prompt; + QString m_url; + QString m_destination; + QString m_filePath; + class QNetworkAccessManager* m_networkManager; + class QNetworkReply* m_networkReply; + bool m_isFirstUpdate; + bool m_isPrimary; + qint64 m_numBytesRead; + qint64 m_numBytesTotal; + QFile* m_filePointer; +}; \ No newline at end of file diff -r 23436e487f0c -r 09e1a3e272ec src/toolsets/filetoolset.cpp --- a/src/toolsets/filetoolset.cpp Sun Sep 06 16:42:57 2015 +0300 +++ b/src/toolsets/filetoolset.cpp Sun Sep 06 16:57:22 2015 +0300 @@ -22,7 +22,7 @@ #include "../glRenderer.h" #include "../ldDocument.h" #include "../mainwindow.h" -#include "../partDownloader.h" +#include "../partdownloader.h" #include "../primitives.h" #include "../dialogs/configdialog.h" #include "../dialogs/ldrawpathdialog.h" diff -r 23436e487f0c -r 09e1a3e272ec ui/downloadfrom.ui --- a/ui/downloadfrom.ui Sun Sep 06 16:42:57 2015 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ - - - DownloadFrom - - - - 0 - 0 - 546 - 405 - - - - Download from LDraw.org - - - - - - - 11 - 75 - true - - - - Download from LDraw.org - - - Qt::AlignCenter - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Source: - - - - - - - - 0 - 0 - - - - - Parts tracker - - - - - Custom URL - - - - - - - - File name: - - - - - - - - - - - - false - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::NoSelection - - - false - - - - File - - - - 50 - false - - - - - - Status - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Abort|QDialogButtonBox::Close - - - - - - - - - buttonBox - rejected() - DownloadFrom - reject() - - - 322 - 312 - - - 286 - 274 - - - - -