1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "librarydownload.h"
24 
25 #include <librepcb/common/fileio/fileutils.h>
26 #include <librepcb/common/network/filedownload.h>
27 #include <librepcb/library/library.h>
28 
29 #include <QtCore>
30 
31 /*******************************************************************************
32  *  Namespace
33  ******************************************************************************/
34 namespace librepcb {
35 namespace library {
36 namespace manager {
37 
38 /*******************************************************************************
39  *  Constructors / Destructor
40  ******************************************************************************/
41 
LibraryDownload(const QUrl & urlToZip,const FilePath & destDir)42 LibraryDownload::LibraryDownload(const QUrl& urlToZip,
43                                  const FilePath& destDir) noexcept
44   : QObject(nullptr),
45     mDestDir(destDir),
46     mTempDestDir(destDir.toStr() % ".tmp"),
47     mTempZipFile(mDestDir.toStr() % ".zip") {
48   mFileDownload.reset(new FileDownload(urlToZip, mTempZipFile));
49   mFileDownload->setZipExtractionDirectory(mTempDestDir);
50   connect(mFileDownload.data(), &FileDownload::progressState, this,
51           &LibraryDownload::progressState, Qt::QueuedConnection);
52   connect(mFileDownload.data(), &FileDownload::progressPercent, this,
53           &LibraryDownload::progressPercent, Qt::QueuedConnection);
54   connect(mFileDownload.data(), &FileDownload::errored, this,
55           &LibraryDownload::downloadErrored, Qt::QueuedConnection);
56   connect(mFileDownload.data(), &FileDownload::aborted, this,
57           &LibraryDownload::downloadAborted, Qt::QueuedConnection);
58   connect(mFileDownload.data(), &FileDownload::succeeded, this,
59           &LibraryDownload::downloadSucceeded, Qt::QueuedConnection);
60   connect(this, &LibraryDownload::abortRequested, mFileDownload.data(),
61           &FileDownload::abort, Qt::QueuedConnection);
62 }
63 
~LibraryDownload()64 LibraryDownload::~LibraryDownload() noexcept {
65   abort();
66 }
67 
68 /*******************************************************************************
69  *  Setters
70  ******************************************************************************/
71 
setExpectedZipFileSize(qint64 bytes)72 void LibraryDownload::setExpectedZipFileSize(qint64 bytes) noexcept {
73   if (mFileDownload) {
74     mFileDownload->setExpectedReplyContentSize(bytes);
75   } else {
76     qCritical() << "Calling this method after start() is not allowed!";
77   }
78 }
79 
setExpectedChecksum(QCryptographicHash::Algorithm algorithm,const QByteArray & checksum)80 void LibraryDownload::setExpectedChecksum(
81     QCryptographicHash::Algorithm algorithm,
82     const QByteArray& checksum) noexcept {
83   if (mFileDownload) {
84     mFileDownload->setExpectedChecksum(algorithm, checksum);
85   } else {
86     qCritical() << "Calling this method after start() is not allowed!";
87   }
88 }
89 
90 /*******************************************************************************
91  *  Public Slots
92  ******************************************************************************/
93 
start()94 void LibraryDownload::start() noexcept {
95   if (!mFileDownload) {
96     qCritical() << "Calling this method multiple times is not allowed!";
97     return;
98   }
99 
100   // Delete the temporary destination directory if it already exists. It might
101   // be left there after a failed or aborted download attempt.
102   if (mTempDestDir.isExistingDir()) {
103     try {
104       FileUtils::removeDirRecursively(mTempDestDir);
105     } catch (const Exception& e) {
106       emit finished(false, e.getMsg());
107       return;
108     }
109   }
110 
111   // Delete the temporary ZIP file if it already exists. It might
112   // be left there after a failed or aborted download attempt.
113   if (mTempZipFile.isExistingFile()) {
114     try {
115       FileUtils::removeFile(mTempZipFile);
116     } catch (const Exception& e) {
117       emit finished(false, e.getMsg());
118       return;
119     }
120   }
121 
122   // Release ownership of the FileDownload object because it will be deleted by
123   // itself after the download finished!
124   mFileDownload.take()->start();
125 }
126 
abort()127 void LibraryDownload::abort() noexcept {
128   emit abortRequested();
129 }
130 
131 /*******************************************************************************
132  *  Private Methods
133  ******************************************************************************/
134 
downloadErrored(const QString & errMsg)135 void LibraryDownload::downloadErrored(const QString& errMsg) noexcept {
136   emit LibraryDownload::finished(false, errMsg);
137 }
138 
downloadAborted()139 void LibraryDownload::downloadAborted() noexcept {
140   emit LibraryDownload::finished(false, QString());
141 }
142 
downloadSucceeded()143 void LibraryDownload::downloadSucceeded() noexcept {
144   // check if directory contains a library
145   FilePath libDir = getPathToLibDir();
146   if (!libDir.isValid()) {
147     try {
148       FileUtils::removeDirRecursively(mDestDir);
149     } catch (...) {
150     }  // clean up
151     emit finished(
152         false,
153         tr("The downloaded ZIP file does not contain a LibrePCB library."));
154     return;
155   }
156 
157   // back-up existing library (if any)
158   FilePath backupDir = FilePath(mDestDir.toStr() % ".backup");
159   try {
160     FileUtils::removeDirRecursively(backupDir);  // can throw
161     if (mDestDir.isExistingDir())
162       FileUtils::move(mDestDir, backupDir);  // can throw
163   } catch (const Exception& e) {
164     try {
165       FileUtils::removeDirRecursively(backupDir);
166     } catch (...) {
167     }
168     emit finished(false, e.getMsg());
169     return;
170   }
171 
172   // move downloaded directory to destination
173   try {
174     FileUtils::move(libDir, mDestDir);  // can throw
175   } catch (const Exception& e) {
176     try {
177       FileUtils::removeDirRecursively(mDestDir);
178       FileUtils::move(backupDir, mDestDir);
179       FileUtils::removeDirRecursively(mTempDestDir);
180     } catch (...) {
181     }
182     emit finished(false, e.getMsg());
183     return;
184   }
185 
186   // clean up
187   try {
188     FileUtils::removeDirRecursively(mTempDestDir);  // can throw
189     FileUtils::removeDirRecursively(backupDir);  // can throw
190   } catch (...) {
191   }
192 
193   emit finished(true, QString());
194 }
195 
getPathToLibDir()196 FilePath LibraryDownload::getPathToLibDir() noexcept {
197   if (library::Library::isValidElementDirectory<library::Library>(
198           mTempDestDir)) {
199     return mTempDestDir;
200   }
201 
202   QStringList subdirs =
203       QDir(mTempDestDir.toStr()).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
204   if (subdirs.count() != 1) {
205     return FilePath();
206   }
207 
208   FilePath subdir = mTempDestDir.getPathTo(subdirs.first());
209   if (library::Library::isValidElementDirectory<library::Library>(subdir)) {
210     return subdir;
211   } else {
212     return FilePath();
213   }
214 }
215 
216 /*******************************************************************************
217  *  End of File
218  ******************************************************************************/
219 
220 }  // namespace manager
221 }  // namespace library
222 }  // namespace librepcb
223