|
1 /* |
|
2 * LDForge: LDraw parts authoring CAD |
|
3 * Copyright (C) 2013 - 2015 Teemu Piippo |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, either version 3 of the License, or |
|
8 * (at your option) any later version. |
|
9 * |
|
10 * This program is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 * GNU General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License |
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 */ |
|
18 |
|
19 #include <QDir> |
|
20 #include <QPushButton> |
|
21 #include <QFileDialog> |
|
22 #include <QMessageBox> |
|
23 #include <QTableWidget> |
|
24 #include "partdownloader.h" |
|
25 #include "partdownloadrequest.h" |
|
26 #include "ui_partdownloader.h" |
|
27 #include "basics.h" |
|
28 #include "mainwindow.h" |
|
29 #include "ldDocument.h" |
|
30 #include "glRenderer.h" |
|
31 |
|
32 ConfigOption (QString DownloadFilePath) |
|
33 ConfigOption (bool GuessDownloadPaths = true) |
|
34 ConfigOption (bool AutoCloseDownloadDialog = true) |
|
35 |
|
36 const char* g_unofficialLibraryURL = "http://ldraw.org/library/unofficial/"; |
|
37 |
|
38 PartDownloader::PartDownloader (QWidget* parent) : |
|
39 QDialog (parent), |
|
40 HierarchyElement (parent), |
|
41 ui (*new Ui_PartDownloader), |
|
42 m_source (SourceType (0)) |
|
43 { |
|
44 ui.setupUi (this); |
|
45 |
|
46 #ifdef USE_QT5 |
|
47 ui.progressTable->horizontalHeader()->setSectionResizeMode (QHeaderView::Stretch); |
|
48 #else |
|
49 ui.progressTable->horizontalHeader()->setResizeMode (PartLabelColumn, QHeaderView::Stretch); |
|
50 #endif |
|
51 |
|
52 m_downloadButton = new QPushButton (tr ("Download")); |
|
53 ui.buttonBox->addButton (m_downloadButton, QDialogButtonBox::ActionRole); |
|
54 button (Abort)->setEnabled (false); |
|
55 connect (ui.source, SIGNAL (currentIndexChanged (int)), this, SLOT (sourceChanged (int))); |
|
56 connect (ui.buttonBox, SIGNAL (clicked (QAbstractButton*)), this, SLOT (buttonClicked (QAbstractButton*))); |
|
57 } |
|
58 |
|
59 PartDownloader::~PartDownloader() |
|
60 { |
|
61 delete &ui; |
|
62 } |
|
63 |
|
64 void PartDownloader::checkValidPath() |
|
65 { |
|
66 QString path = downloadPath(); |
|
67 |
|
68 if (path.isEmpty() or not QDir (path).exists()) |
|
69 { |
|
70 QMessageBox::information(this, "Notice", "Please input a path for files to download."); |
|
71 path = QFileDialog::getExistingDirectory (this, "Path for downloaded files:"); |
|
72 |
|
73 if (path.isEmpty()) |
|
74 reject(); |
|
75 else |
|
76 m_config->setDownloadFilePath (path); |
|
77 } |
|
78 } |
|
79 |
|
80 QString PartDownloader::url() |
|
81 { |
|
82 QString destination; |
|
83 |
|
84 switch (sourceType()) |
|
85 { |
|
86 case PartsTracker: |
|
87 destination = ui.filename->text(); |
|
88 modifyDestination (destination); |
|
89 ui.filename->setText (destination); |
|
90 return g_unofficialLibraryURL + destination; |
|
91 |
|
92 case CustomURL: |
|
93 return ui.filename->text(); |
|
94 } |
|
95 |
|
96 // Shouldn't happen |
|
97 return ""; |
|
98 } |
|
99 |
|
100 void PartDownloader::modifyDestination (QString& dest) const |
|
101 { |
|
102 dest = dest.simplified(); |
|
103 |
|
104 // If the user doesn't want us to guess, stop right here. |
|
105 if (not m_config->guessDownloadPaths()) |
|
106 return; |
|
107 |
|
108 // Ensure .dat extension |
|
109 if (dest.right (4) != ".dat") |
|
110 { |
|
111 // Remove the existing extension, if any. It may be we're here over a |
|
112 // typo in the .dat extension. |
|
113 const int dotpos = dest.lastIndexOf ("."); |
|
114 |
|
115 if ((dotpos != -1) and (dotpos >= dest.length() - 4)) |
|
116 dest.chop (dest.length() - dotpos); |
|
117 |
|
118 dest += ".dat"; |
|
119 } |
|
120 |
|
121 // If the part starts with s\ or s/, then use parts/s/. Same goes with |
|
122 // 48\ and p/48/. |
|
123 if (isOneOf (dest.left (2), "s\\", "s/")) |
|
124 { |
|
125 dest.remove (0, 2); |
|
126 dest.prepend ("parts/s/"); |
|
127 } |
|
128 else if (isOneOf (dest.left (3), "48\\", "48/")) |
|
129 { |
|
130 dest.remove (0, 3); |
|
131 dest.prepend ("p/48/"); |
|
132 } |
|
133 |
|
134 /* Try determine where to put this part. We have four directories: |
|
135 parts/, parts/s/, p/, and p/48/. If we haven't already specified |
|
136 either parts/ or p/, we need to add it automatically. Part files |
|
137 are numbers wit a possible u prefix for parts with unknown number |
|
138 which can be followed by any of: |
|
139 - c** (composites) |
|
140 - d** (formed stickers) |
|
141 - p** (patterns) |
|
142 - a lowercase alphabetic letter for variants |
|
143 |
|
144 Subfiles (usually) have an s** prefix, in which case we use parts/s/. |
|
145 Note that the regex starts with a '^' so it won't catch already fully |
|
146 given part file names. */ |
|
147 QString partRegex = "^u?[0-9]+(c[0-9][0-9]+)*(d[0-9][0-9]+)*[a-z]?(p[0-9a-z][0-9a-z]+)*"; |
|
148 QString subpartRegex = partRegex + "s[0-9][0-9]+"; |
|
149 |
|
150 partRegex += "\\.dat$"; |
|
151 subpartRegex += "\\.dat$"; |
|
152 |
|
153 if (QRegExp (subpartRegex).exactMatch (dest)) |
|
154 dest.prepend ("parts/s/"); |
|
155 else if (QRegExp (partRegex).exactMatch (dest)) |
|
156 dest.prepend ("parts/"); |
|
157 else if (not dest.startsWith ("parts/") and not dest.startsWith ("p/")) |
|
158 dest.prepend ("p/"); |
|
159 } |
|
160 |
|
161 PartDownloader::SourceType PartDownloader::sourceType() const |
|
162 { |
|
163 return m_source; |
|
164 } |
|
165 |
|
166 void PartDownloader::setSourceType (SourceType src) |
|
167 { |
|
168 m_source = src; |
|
169 ui.source->setCurrentIndex (int (src)); |
|
170 } |
|
171 |
|
172 void PartDownloader::sourceChanged (int i) |
|
173 { |
|
174 if (i == CustomURL) |
|
175 ui.fileNameLabel->setText (tr ("URL:")); |
|
176 else |
|
177 ui.fileNameLabel->setText (tr ("File name:")); |
|
178 |
|
179 m_source = SourceType (i); |
|
180 } |
|
181 |
|
182 void PartDownloader::buttonClicked (QAbstractButton* btn) |
|
183 { |
|
184 if (btn == button (Close)) |
|
185 { |
|
186 reject(); |
|
187 } |
|
188 else if (btn == button (Abort)) |
|
189 { |
|
190 m_isAborted = true; |
|
191 |
|
192 for (PartDownloadRequest* req : m_requests) |
|
193 req->abort(); |
|
194 } |
|
195 else if (btn == button (Download)) |
|
196 { |
|
197 QString dest = ui.filename->text(); |
|
198 setPrimaryFile (nullptr); |
|
199 m_isAborted = false; |
|
200 |
|
201 if (sourceType() == CustomURL) |
|
202 dest = Basename (url()); |
|
203 |
|
204 modifyDestination (dest); |
|
205 |
|
206 if (QFile::exists (downloadPath() + DIRSLASH + dest)) |
|
207 { |
|
208 const QString overwritemsg = format (tr ("%1 already exists in download directory. Overwrite?"), dest); |
|
209 if (not Confirm (tr ("Overwrite?"), overwritemsg)) |
|
210 return; |
|
211 } |
|
212 |
|
213 downloadFile (dest, url(), true); |
|
214 } |
|
215 } |
|
216 |
|
217 void PartDownloader::downloadFile (QString dest, QString url, bool primary) |
|
218 { |
|
219 int row = ui.progressTable->rowCount(); |
|
220 |
|
221 // Don't download files repeadetly. |
|
222 if (m_filesToDownload.indexOf (dest) != -1) |
|
223 return; |
|
224 |
|
225 print ("Downloading %1 from %2\n", dest, url); |
|
226 modifyDestination (dest); |
|
227 PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); |
|
228 m_filesToDownload << dest; |
|
229 m_requests << req; |
|
230 ui.progressTable->insertRow (row); |
|
231 req->setTableRow (row); |
|
232 req->updateToTable(); |
|
233 m_downloadButton->setEnabled (false); |
|
234 ui.progressTable->setEnabled (true); |
|
235 ui.filename->setEnabled (false); |
|
236 ui.source->setEnabled (false); |
|
237 button (Close)->setEnabled (false); |
|
238 button (Abort)->setEnabled (true); |
|
239 button (Download)->setEnabled (false); |
|
240 } |
|
241 |
|
242 void PartDownloader::downloadFromPartsTracker (QString file) |
|
243 { |
|
244 modifyDestination (file); |
|
245 downloadFile (file, g_unofficialLibraryURL + file, false); |
|
246 } |
|
247 |
|
248 void PartDownloader::checkIfFinished() |
|
249 { |
|
250 bool failed = isAborted(); |
|
251 |
|
252 // If there is some download still working, we're not finished. |
|
253 for (PartDownloadRequest* req : m_requests) |
|
254 { |
|
255 if (not req->isFinished()) |
|
256 return; |
|
257 |
|
258 if (req->failed()) |
|
259 failed = true; |
|
260 } |
|
261 |
|
262 for (PartDownloadRequest* req : m_requests) |
|
263 delete req; |
|
264 |
|
265 m_requests.clear(); |
|
266 |
|
267 if (primaryFile()) |
|
268 emit primaryFileDownloaded(); |
|
269 |
|
270 for (LDDocument* f : m_files) |
|
271 f->reloadAllSubfiles(); |
|
272 |
|
273 if (m_config->autoCloseDownloadDialog() and not failed) |
|
274 { |
|
275 // Close automatically if desired. |
|
276 accept(); |
|
277 } |
|
278 else |
|
279 { |
|
280 // Allow the prompt be closed now. |
|
281 button (Abort)->setEnabled (false); |
|
282 button (Close)->setEnabled (true); |
|
283 } |
|
284 } |
|
285 |
|
286 QPushButton* PartDownloader::button (PartDownloader::Button i) |
|
287 { |
|
288 switch (i) |
|
289 { |
|
290 case Download: |
|
291 return m_downloadButton; |
|
292 |
|
293 case Abort: |
|
294 return qobject_cast<QPushButton*> (ui.buttonBox->button (QDialogButtonBox::Abort)); |
|
295 |
|
296 case Close: |
|
297 return qobject_cast<QPushButton*> (ui.buttonBox->button (QDialogButtonBox::Close)); |
|
298 } |
|
299 |
|
300 return nullptr; |
|
301 } |
|
302 |
|
303 void PartDownloader::addFile (LDDocument* f) |
|
304 { |
|
305 m_files << f; |
|
306 } |
|
307 |
|
308 bool PartDownloader::isAborted() const |
|
309 { |
|
310 return m_isAborted; |
|
311 } |
|
312 |
|
313 LDDocument* PartDownloader::primaryFile() const |
|
314 { |
|
315 return m_primaryFile; |
|
316 } |
|
317 |
|
318 void PartDownloader::setPrimaryFile (LDDocument* document) |
|
319 { |
|
320 m_primaryFile = document; |
|
321 } |
|
322 |
|
323 QString PartDownloader::downloadPath() |
|
324 { |
|
325 QString path = m_config->downloadFilePath(); |
|
326 |
|
327 if (DIRSLASH[0] != '/') |
|
328 path.replace (DIRSLASH, "/"); |
|
329 |
|
330 return path; |
|
331 } |
|
332 |
|
333 QTableWidget* PartDownloader::progressTable() const |
|
334 { |
|
335 return ui.progressTable; |
|
336 } |