| |
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 } |