--- a/src/download.cc Mon Jan 20 23:44:22 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,542 +0,0 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013, 2014 Santeri 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 <http://www.gnu.org/licenses/>. - */ - -#include <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QDir> -#include <QProgressBar> -#include <QPushButton> -#include "download.h" -#include "ui_downloadfrom.h" -#include "types.h" -#include "gui.h" -#include "document.h" -#include "gldraw.h" -#include "configDialog.h" -#include "moc_download.cpp" - -cfg (String, net_downloadpath, ""); -cfg (Bool, net_guesspaths, true); -cfg (Bool, net_autoclose, true); - -const QString g_unofficialLibraryURL ("http://ldraw.org/library/unofficial/"); - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::staticBegin() -{ - QString path = getDownloadPath(); - - if (path == "" || QDir (path).exists() == false) - { - critical (PartDownloader::tr ("You need to specify a valid path for " - "downloaded files in the configuration to download paths.")); - - (new ConfigDialog (ConfigDialog::DownloadTab, null))->exec(); - return; - } - - PartDownloader* dlg = new PartDownloader; - dlg->exec(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString PartDownloader::getDownloadPath() -{ - QString path = net_downloadpath; - -#if DIRSLASH_CHAR != '/' - path.replace (DIRSLASH, "/"); -#endif - - return path; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::PartDownloader (QWidget* parent) : QDialog (parent) -{ - setInterface (new Ui_DownloadFrom); - getInterface()->setupUi (this); - getInterface()->fname->setFocus(); - getInterface()->progress->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); - - setDownloadButton (new QPushButton (tr ("Download"))); - getInterface()->buttonBox->addButton (getDownloadButton(), QDialogButtonBox::ActionRole); - getButton (Abort)->setEnabled (false); - - connect (getInterface()->source, SIGNAL (currentIndexChanged (int)), - this, SLOT (sourceChanged (int))); - connect (getInterface()->buttonBox, SIGNAL (clicked (QAbstractButton*)), - this, SLOT (buttonClicked (QAbstractButton*))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::~PartDownloader() -{ - delete getInterface(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QString PartDownloader::getURL() const -{ - const Source src = getSource(); - QString dest; - - switch (src) - { - case PartsTracker: - dest = getInterface()->fname->text(); - modifyDestination (dest); - return g_unofficialLibraryURL + dest; - - case CustomURL: - return getInterface()->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 (net_guesspaths == false) - 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 && 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 (dest.left (2) == "s\\" || dest.left (2) == "s/") - { - dest.remove (0, 2); - dest.prepend ("parts/s/"); - } elif (dest.left (3) == "48\\" || dest.left (3) == "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/"); - elif (QRegExp (partRegex).exactMatch (dest)) - dest.prepend ("parts/"); - elif (dest.left (6) != "parts/" && dest.left (2) != "p/") - dest.prepend ("p/"); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloader::Source PartDownloader::getSource() const -{ - return (Source) getInterface()->source->currentIndex(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::sourceChanged (int i) -{ - if (i == CustomURL) - getInterface()->fileNameLabel->setText (tr ("URL:")); - else - getInterface()->fileNameLabel->setText (tr ("File name:")); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::buttonClicked (QAbstractButton* btn) -{ - if (btn == getButton (Close)) - { - reject(); - } - elif (btn == getButton (Abort)) - { - setAborted (true); - - for (PartDownloadRequest* req : getRequests()) - req->abort(); - } - elif (btn == getButton (Download)) - { - QString dest = getInterface()->fname->text(); - setPrimaryFile (null); - setAborted (false); - - if (getSource() == CustomURL) - dest = basename (getURL()); - - modifyDestination (dest); - - if (QFile::exists (PartDownloader::getDownloadPath() + DIRSLASH + dest)) - { - const QString overwritemsg = fmt (tr ("%1 already exists in download directory. Overwrite?"), dest); - if (!confirm (tr ("Overwrite?"), overwritemsg)) - return; - } - - getDownloadButton()->setEnabled (false); - getInterface()->progress->setEnabled (true); - getInterface()->fname->setEnabled (false); - getInterface()->source->setEnabled (false); - downloadFile (dest, getURL(), true); - getButton (Close)->setEnabled (false); - getButton (Abort)->setEnabled (true); - getButton (Download)->setEnabled (false); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::downloadFile (QString dest, QString url, bool primary) -{ - const int row = getInterface()->progress->rowCount(); - - // Don't download files repeadetly. - if (getFilesToDownload().indexOf (dest) != -1) - return; - - modifyDestination (dest); - log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); - PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); - - pushToFilesToDownload (dest); - pushToRequests (req); - getInterface()->progress->insertRow (row); - req->setTableRow (row); - req->updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloader::checkIfFinished() -{ - bool failed = isAborted(); - - // If there is some download still working, we're not finished. - for (PartDownloadRequest* req : getRequests()) - { - if (!req->isFinished()) - return; - - if (req->getState() == PartDownloadRequest::EFailed) - failed = true; - } - - for (PartDownloadRequest* req : getRequests()) - delete req; - - clearRequests(); - - // Update everything now - if (getPrimaryFile()) - { - LDDocument::setCurrent (getPrimaryFile()); - reloadAllSubfiles(); - g_win->doFullRefresh(); - g_win->R()->resetAngles(); - } - - if (net_autoclose && !failed) - { - // Close automatically if desired. - accept(); - } - else - { - // Allow the prompt be closed now. - getButton (Abort)->setEnabled (false); - getButton (Close)->setEnabled (true); - } -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -QPushButton* PartDownloader::getButton (PartDownloader::Button i) -{ - switch (i) - { - case Download: - return getDownloadButton(); - - case Abort: - return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Abort)); - - case Close: - return qobject_cast<QPushButton*> (getInterface()->buttonBox->button (QDialogButtonBox::Close)); - } - - return null; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::PartDownloadRequest (QString url, QString dest, bool primary, PartDownloader* parent) : - QObject (parent), - m_State (ERequesting), - m_Prompt (parent), - m_URL (url), - m_Destinaton (dest), - m_FilePath (PartDownloader::getDownloadPath() + DIRSLASH + dest), - m_NAM (new QNetworkAccessManager), - m_FirstUpdate (true), - m_Primary (primary), - m_FilePointer (null) -{ - // Make sure that we have a valid destination. - QString dirpath = dirname (getFilePath()); - - QDir dir (dirpath); - - if (!dir.exists()) - { - log ("Creating %1...\n", dirpath); - - if (!dir.mkpath (dirpath)) - critical (fmt (tr ("Couldn't create the directory %1!"), dirpath)); - } - - setReply (getNAM()->get (QNetworkRequest (QUrl (url)))); - connect (getReply(), SIGNAL (finished()), this, SLOT (downloadFinished())); - connect (getReply(), SIGNAL (readyRead()), this, SLOT (readyRead())); - connect (getReply(), SIGNAL (downloadProgress (qint64, qint64)), - this, SLOT (downloadProgress (qint64, qint64))); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -PartDownloadRequest::~PartDownloadRequest() {} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::updateToTable() -{ - const int labelcol = PartDownloader::PartLabelColumn, - progcol = PartDownloader::ProgressColumn; - QTableWidget* table = getPrompt()->getInterface()->progress; - QProgressBar* prog; - - switch (getState()) - { - case ERequesting: - case EDownloading: - { - prog = qobject_cast<QProgressBar*> (table->cellWidget (getTableRow(), progcol)); - - if (!prog) - { - prog = new QProgressBar; - table->setCellWidget (getTableRow(), progcol, prog); - } - - prog->setRange (0, getBytesTotal()); - prog->setValue (getBytesRead()); - } break; - - case EFinished: - case EFailed: - { - const QString text = (getState() == EFinished) - ? "<b><span style=\"color: #080\">FINISHED</span></b>" - : "<b><span style=\"color: #800\">FAILED</span></b>"; - - QLabel* lb = new QLabel (text); - lb->setAlignment (Qt::AlignCenter); - table->setCellWidget (getTableRow(), progcol, lb); - } break; - } - - QLabel* lb = qobject_cast<QLabel*> (table->cellWidget (getTableRow(), labelcol)); - - if (isFirstUpdate()) - { - lb = new QLabel (fmt ("<b>%1</b>", getDestinaton()), table); - table->setCellWidget (getTableRow(), labelcol, lb); - } - - // Make sure that the cell is big enough to contain the label - if (table->columnWidth (labelcol) < lb->width()) - table->setColumnWidth (labelcol, lb->width()); - - setFirstUpdate (true); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadFinished() -{ - if (getReply()->error() != QNetworkReply::NoError) - { - if (isPrimary() && !getPrompt()->isAborted()) - critical (getReply()->errorString()); - - setState (EFailed); - } - elif (getState() != EFailed) - setState (EFinished); - - setBytesRead (getBytesTotal()); - updateToTable(); - - if (getFilePointer()) - { - getFilePointer()->close(); - delete getFilePointer(); - setFilePointer (null); - - if (getState() == EFailed) - QFile::remove (getFilePath()); - } - - if (getState() != EFinished) - { - getPrompt()->checkIfFinished(); - return; - } - - // Try to load this file now. - LDDocument* f = openDocument (getFilePath(), false); - - if (!f) - return; - - f->setImplicit (!isPrimary()); - - // 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->getObjects()) - { - LDError* err = dynamic_cast<LDError*> (obj); - - if (!err || err->getFileReferenced().isEmpty()) - continue; - - QString dest = err->getFileReferenced(); - getPrompt()->modifyDestination (dest); - getPrompt()->downloadFile (dest, g_unofficialLibraryURL + dest, false); - } - - if (isPrimary()) - { - addRecentFile (getFilePath()); - getPrompt()->setPrimaryFile (f); - } - - getPrompt()->checkIfFinished(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::downloadProgress (int64 recv, int64 total) -{ - setBytesRead (recv); - setBytesTotal (total); - setState (EDownloading); - updateToTable(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::readyRead() -{ - if (getState() == EFailed) - return; - - if (getFilePointer() == null) - { - replaceInFilePath ("\\", "/"); - - // We have already asked the user whether we can overwrite so we're good - // to go here. - setFilePointer (new QFile (getFilePath().toLocal8Bit())); - - if (!getFilePointer()->open (QIODevice::WriteOnly)) - { - critical (fmt (tr ("Couldn't open %1 for writing: %2"), getFilePath(), strerror (errno))); - setState (EFailed); - getReply()->abort(); - updateToTable(); - getPrompt()->checkIfFinished(); - return; - } - } - - getFilePointer()->write (getReply()->readAll()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -bool PartDownloadRequest::isFinished() const -{ - return getState() == EFinished || getState() == EFailed; -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void PartDownloadRequest::abort() -{ - getReply()->abort(); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -DEFINE_ACTION (DownloadFrom, 0) -{ - PartDownloader::staticBegin(); -}