Chris@208
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@208
|
2
|
Chris@208
|
3 /*
|
Chris@208
|
4 Sonic Visualiser
|
Chris@208
|
5 An audio file viewer and annotation editor.
|
Chris@208
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@208
|
7 This file copyright 2007 QMUL.
|
Chris@208
|
8
|
Chris@208
|
9 This program is free software; you can redistribute it and/or
|
Chris@208
|
10 modify it under the terms of the GNU General Public License as
|
Chris@208
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@208
|
12 License, or (at your option) any later version. See the file
|
Chris@208
|
13 COPYING included with this distribution for more information.
|
Chris@208
|
14 */
|
Chris@208
|
15
|
Chris@317
|
16 #include "FileSource.h"
|
Chris@208
|
17 #include "base/TempDirectory.h"
|
Chris@208
|
18 #include "base/Exceptions.h"
|
Chris@208
|
19
|
Chris@208
|
20 #include <QHttp>
|
Chris@208
|
21 #include <QFtp>
|
Chris@208
|
22 #include <QFileInfo>
|
Chris@208
|
23 #include <QDir>
|
Chris@208
|
24 #include <QApplication>
|
Chris@208
|
25 #include <QProgressDialog>
|
Chris@210
|
26 #include <QHttpResponseHeader>
|
Chris@208
|
27
|
Chris@208
|
28 #include <iostream>
|
Chris@208
|
29
|
Chris@355
|
30 #define DEBUG_FILE_SOURCE 1
|
Chris@327
|
31
|
Chris@208
|
32 int
|
Chris@317
|
33 FileSource::m_count = 0;
|
Chris@208
|
34
|
Chris@208
|
35 QMutex
|
Chris@317
|
36 FileSource::m_fileCreationMutex;
|
Chris@208
|
37
|
Chris@317
|
38 FileSource::RemoteRefCountMap
|
Chris@317
|
39 FileSource::m_refCountMap;
|
Chris@304
|
40
|
Chris@317
|
41 FileSource::RemoteLocalMap
|
Chris@317
|
42 FileSource::m_remoteLocalMap;
|
Chris@304
|
43
|
Chris@304
|
44 QMutex
|
Chris@317
|
45 FileSource::m_mapMutex;
|
Chris@304
|
46
|
Chris@317
|
47 FileSource::FileSource(QString fileOrUrl, bool showProgress) :
|
Chris@316
|
48 m_url(fileOrUrl),
|
Chris@316
|
49 m_ftp(0),
|
Chris@316
|
50 m_http(0),
|
Chris@316
|
51 m_localFile(0),
|
Chris@316
|
52 m_ok(false),
|
Chris@316
|
53 m_lastStatus(0),
|
Chris@316
|
54 m_remote(isRemote(fileOrUrl)),
|
Chris@316
|
55 m_done(false),
|
Chris@316
|
56 m_leaveLocalFile(false),
|
Chris@316
|
57 m_progressDialog(0),
|
Chris@316
|
58 m_progressShowTimer(this),
|
Chris@316
|
59 m_refCounted(false)
|
Chris@316
|
60 {
|
Chris@327
|
61 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
62 std::cerr << "FileSource::FileSource(" << fileOrUrl.toStdString() << ")" << std::endl;
|
Chris@327
|
63 #endif
|
Chris@316
|
64
|
Chris@316
|
65 if (!canHandleScheme(m_url)) {
|
Chris@317
|
66 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
|
Chris@316
|
67 m_errorString = tr("Unsupported scheme in URL");
|
Chris@316
|
68 return;
|
Chris@316
|
69 }
|
Chris@316
|
70
|
Chris@316
|
71 init(showProgress);
|
Chris@316
|
72
|
Chris@316
|
73 if (isRemote() &&
|
Chris@316
|
74 (fileOrUrl.contains('%') ||
|
Chris@316
|
75 fileOrUrl.contains("--"))) { // for IDNA
|
Chris@316
|
76
|
Chris@316
|
77 waitForStatus();
|
Chris@316
|
78
|
Chris@316
|
79 if (!isAvailable()) {
|
Chris@336
|
80
|
Chris@316
|
81 // The URL was created on the assumption that the string
|
Chris@316
|
82 // was human-readable. Let's try again, this time
|
Chris@316
|
83 // assuming it was already encoded.
|
Chris@317
|
84 std::cerr << "FileSource::FileSource: Failed to retrieve URL \""
|
Chris@316
|
85 << fileOrUrl.toStdString()
|
Chris@316
|
86 << "\" as human-readable URL; "
|
Chris@316
|
87 << "trying again treating it as encoded URL"
|
Chris@316
|
88 << std::endl;
|
Chris@336
|
89
|
Chris@336
|
90 // even though our cache file doesn't exist (because the
|
Chris@336
|
91 // resource was 404), we still need to ensure we're no
|
Chris@336
|
92 // longer associating a filename with this url in the
|
Chris@336
|
93 // refcount map -- or createCacheFile will think we've
|
Chris@336
|
94 // already done all the work and no request will be sent
|
Chris@336
|
95 deleteCacheFile();
|
Chris@336
|
96
|
Chris@316
|
97 m_url.setEncodedUrl(fileOrUrl.toAscii());
|
Chris@336
|
98
|
Chris@336
|
99 m_ok = false;
|
Chris@336
|
100 m_done = false;
|
Chris@336
|
101 m_lastStatus = 0;
|
Chris@316
|
102 init(showProgress);
|
Chris@316
|
103 }
|
Chris@316
|
104 }
|
Chris@325
|
105
|
Chris@325
|
106 if (!isRemote()) {
|
Chris@325
|
107 emit statusAvailable();
|
Chris@325
|
108 emit ready();
|
Chris@325
|
109 }
|
Chris@316
|
110 }
|
Chris@316
|
111
|
Chris@317
|
112 FileSource::FileSource(QUrl url, bool showProgress) :
|
Chris@304
|
113 m_url(url),
|
Chris@208
|
114 m_ftp(0),
|
Chris@208
|
115 m_http(0),
|
Chris@208
|
116 m_localFile(0),
|
Chris@208
|
117 m_ok(false),
|
Chris@210
|
118 m_lastStatus(0),
|
Chris@316
|
119 m_remote(isRemote(url.toString())),
|
Chris@208
|
120 m_done(false),
|
Chris@316
|
121 m_leaveLocalFile(false),
|
Chris@210
|
122 m_progressDialog(0),
|
Chris@304
|
123 m_progressShowTimer(this),
|
Chris@316
|
124 m_refCounted(false)
|
Chris@208
|
125 {
|
Chris@327
|
126 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
127 std::cerr << "FileSource::FileSource(" << url.toString().toStdString() << ") [as url]" << std::endl;
|
Chris@327
|
128 #endif
|
Chris@316
|
129
|
Chris@316
|
130 if (!canHandleScheme(m_url)) {
|
Chris@317
|
131 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
|
Chris@316
|
132 m_errorString = tr("Unsupported scheme in URL");
|
Chris@208
|
133 return;
|
Chris@208
|
134 }
|
Chris@208
|
135
|
Chris@316
|
136 init(showProgress);
|
Chris@316
|
137 }
|
Chris@304
|
138
|
Chris@317
|
139 FileSource::FileSource(const FileSource &rf) :
|
Chris@316
|
140 QObject(),
|
Chris@316
|
141 m_url(rf.m_url),
|
Chris@316
|
142 m_ftp(0),
|
Chris@316
|
143 m_http(0),
|
Chris@316
|
144 m_localFile(0),
|
Chris@316
|
145 m_ok(rf.m_ok),
|
Chris@316
|
146 m_lastStatus(rf.m_lastStatus),
|
Chris@316
|
147 m_remote(rf.m_remote),
|
Chris@316
|
148 m_done(false),
|
Chris@316
|
149 m_leaveLocalFile(false),
|
Chris@316
|
150 m_progressDialog(0),
|
Chris@316
|
151 m_progressShowTimer(0),
|
Chris@316
|
152 m_refCounted(false)
|
Chris@316
|
153 {
|
Chris@327
|
154 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
155 std::cerr << "FileSource::FileSource(" << m_url.toString().toStdString() << ") [copy ctor]" << std::endl;
|
Chris@327
|
156 #endif
|
Chris@304
|
157
|
Chris@316
|
158 if (!canHandleScheme(m_url)) {
|
Chris@317
|
159 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
|
Chris@316
|
160 m_errorString = tr("Unsupported scheme in URL");
|
Chris@304
|
161 return;
|
Chris@304
|
162 }
|
Chris@304
|
163
|
Chris@316
|
164 if (!isRemote()) {
|
Chris@316
|
165 m_localFilename = rf.m_localFilename;
|
Chris@316
|
166 } else {
|
Chris@316
|
167 QMutexLocker locker(&m_mapMutex);
|
Chris@327
|
168 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
169 std::cerr << "FileSource::FileSource(copy ctor): ref count is "
|
Chris@316
|
170 << m_refCountMap[m_url] << std::endl;
|
Chris@327
|
171 #endif
|
Chris@316
|
172 if (m_refCountMap[m_url] > 0) {
|
Chris@316
|
173 m_refCountMap[m_url]++;
|
Chris@327
|
174 #ifdef DEBUG_FILE_SOURCE
|
Chris@316
|
175 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
|
Chris@327
|
176 #endif
|
Chris@316
|
177 m_localFilename = m_remoteLocalMap[m_url];
|
Chris@316
|
178 m_refCounted = true;
|
Chris@316
|
179 } else {
|
Chris@316
|
180 m_ok = false;
|
Chris@316
|
181 m_lastStatus = 404;
|
Chris@316
|
182 }
|
Chris@316
|
183 }
|
Chris@316
|
184
|
Chris@316
|
185 m_done = true;
|
Chris@316
|
186 }
|
Chris@316
|
187
|
Chris@317
|
188 FileSource::~FileSource()
|
Chris@316
|
189 {
|
Chris@327
|
190 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
191 std::cerr << "FileSource(" << m_url.toString().toStdString() << ")::~FileSource" << std::endl;
|
Chris@327
|
192 #endif
|
Chris@316
|
193
|
Chris@316
|
194 cleanup();
|
Chris@316
|
195
|
Chris@316
|
196 if (isRemote() && !m_leaveLocalFile) deleteCacheFile();
|
Chris@316
|
197 }
|
Chris@316
|
198
|
Chris@316
|
199 void
|
Chris@317
|
200 FileSource::init(bool showProgress)
|
Chris@316
|
201 {
|
Chris@316
|
202 if (!isRemote()) {
|
Chris@355
|
203 #ifdef DEBUG_FILE_SOURCE
|
Chris@355
|
204 std::cerr << "FileSource::init: Not a remote URL" << std::endl;
|
Chris@355
|
205 #endif
|
Chris@355
|
206 bool literal = false;
|
Chris@316
|
207 m_localFilename = m_url.toLocalFile();
|
Chris@342
|
208 if (m_localFilename == "") {
|
Chris@342
|
209 // QUrl may have mishandled the scheme (e.g. in a DOS path)
|
Chris@342
|
210 m_localFilename = m_url.toString();
|
Chris@355
|
211 literal = true;
|
Chris@342
|
212 }
|
Chris@355
|
213 #ifdef DEBUG_FILE_SOURCE
|
Chris@355
|
214 std::cerr << "FileSource::init: URL translates to local filename \""
|
Chris@355
|
215 << m_localFilename.toStdString() << "\"" << std::endl;
|
Chris@355
|
216 #endif
|
Chris@316
|
217 m_ok = true;
|
Chris@355
|
218 m_lastStatus = 200;
|
Chris@355
|
219
|
Chris@316
|
220 if (!QFileInfo(m_localFilename).exists()) {
|
Chris@355
|
221 if (literal) {
|
Chris@355
|
222 m_lastStatus = 404;
|
Chris@355
|
223 } else {
|
Chris@355
|
224 // Again, QUrl may have been mistreating us --
|
Chris@355
|
225 // e.g. dropping a part that looks like query data
|
Chris@355
|
226 m_localFilename = m_url.toString();
|
Chris@355
|
227 literal = true;
|
Chris@355
|
228 if (!QFileInfo(m_localFilename).exists()) {
|
Chris@355
|
229 m_lastStatus = 404;
|
Chris@355
|
230 }
|
Chris@355
|
231 }
|
Chris@316
|
232 }
|
Chris@355
|
233
|
Chris@316
|
234 m_done = true;
|
Chris@316
|
235 return;
|
Chris@316
|
236 }
|
Chris@316
|
237
|
Chris@316
|
238 if (createCacheFile()) {
|
Chris@327
|
239 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
240 std::cerr << "FileSource::init: Already have this one" << std::endl;
|
Chris@327
|
241 #endif
|
Chris@316
|
242 m_ok = true;
|
Chris@316
|
243 if (!QFileInfo(m_localFilename).exists()) {
|
Chris@316
|
244 m_lastStatus = 404;
|
Chris@316
|
245 } else {
|
Chris@316
|
246 m_lastStatus = 200;
|
Chris@316
|
247 }
|
Chris@316
|
248 m_done = true;
|
Chris@316
|
249 return;
|
Chris@316
|
250 }
|
Chris@316
|
251
|
Chris@208
|
252 if (m_localFilename == "") return;
|
Chris@208
|
253 m_localFile = new QFile(m_localFilename);
|
Chris@208
|
254 m_localFile->open(QFile::WriteOnly);
|
Chris@208
|
255
|
Chris@316
|
256 QString scheme = m_url.scheme().toLower();
|
Chris@316
|
257
|
Chris@327
|
258 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
259 std::cerr << "FileSource::init: Don't have local copy of \""
|
Chris@316
|
260 << m_url.toString().toStdString() << "\", retrieving" << std::endl;
|
Chris@327
|
261 #endif
|
Chris@208
|
262
|
Chris@208
|
263 if (scheme == "http") {
|
Chris@316
|
264 initHttp();
|
Chris@208
|
265 } else if (scheme == "ftp") {
|
Chris@316
|
266 initFtp();
|
Chris@316
|
267 } else {
|
Chris@316
|
268 m_remote = false;
|
Chris@316
|
269 m_ok = false;
|
Chris@208
|
270 }
|
Chris@208
|
271
|
Chris@208
|
272 if (m_ok) {
|
Chris@316
|
273
|
Chris@316
|
274 QMutexLocker locker(&m_mapMutex);
|
Chris@316
|
275
|
Chris@316
|
276 if (m_refCountMap[m_url] > 0) {
|
Chris@316
|
277 // someone else has been doing the same thing at the same time,
|
Chris@316
|
278 // but has got there first
|
Chris@316
|
279 cleanup();
|
Chris@316
|
280 m_refCountMap[m_url]++;
|
Chris@327
|
281 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
282 std::cerr << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << std::endl;
|
Chris@327
|
283 #endif
|
Chris@316
|
284 m_localFilename = m_remoteLocalMap[m_url];
|
Chris@316
|
285 m_refCounted = true;
|
Chris@316
|
286 m_ok = true;
|
Chris@316
|
287 if (!QFileInfo(m_localFilename).exists()) {
|
Chris@316
|
288 m_lastStatus = 404;
|
Chris@316
|
289 }
|
Chris@316
|
290 m_done = true;
|
Chris@316
|
291 return;
|
Chris@316
|
292 }
|
Chris@304
|
293
|
Chris@304
|
294 m_remoteLocalMap[m_url] = m_localFilename;
|
Chris@304
|
295 m_refCountMap[m_url]++;
|
Chris@316
|
296 m_refCounted = true;
|
Chris@304
|
297
|
Chris@316
|
298 if (showProgress) {
|
Chris@316
|
299 m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(m_url.toString()), tr("Cancel"), 0, 100);
|
Chris@316
|
300 m_progressDialog->hide();
|
Chris@316
|
301 connect(&m_progressShowTimer, SIGNAL(timeout()),
|
Chris@316
|
302 this, SLOT(showProgressDialog()));
|
Chris@316
|
303 connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled()));
|
Chris@316
|
304 m_progressShowTimer.setSingleShot(true);
|
Chris@316
|
305 m_progressShowTimer.start(2000);
|
Chris@316
|
306 }
|
Chris@208
|
307 }
|
Chris@208
|
308 }
|
Chris@208
|
309
|
Chris@316
|
310 void
|
Chris@317
|
311 FileSource::initHttp()
|
Chris@208
|
312 {
|
Chris@316
|
313 m_ok = true;
|
Chris@316
|
314 m_http = new QHttp(m_url.host(), m_url.port(80));
|
Chris@316
|
315 connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool)));
|
Chris@316
|
316 connect(m_http, SIGNAL(dataReadProgress(int, int)),
|
Chris@316
|
317 this, SLOT(dataReadProgress(int, int)));
|
Chris@316
|
318 connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
|
Chris@316
|
319 this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &)));
|
Chris@316
|
320
|
Chris@316
|
321 // I don't quite understand this. url.path() returns a path
|
Chris@316
|
322 // without percent encoding; for example, spaces appear as
|
Chris@316
|
323 // literal spaces. This generally won't work if sent to the
|
Chris@316
|
324 // server directly. You can retrieve a correctly encoded URL
|
Chris@316
|
325 // from QUrl using url.toEncoded(), but that gives you the
|
Chris@316
|
326 // whole URL; there doesn't seem to be any way to retrieve
|
Chris@316
|
327 // only an encoded path. Furthermore there doesn't seem to be
|
Chris@316
|
328 // any way to convert a retrieved path into an encoded path
|
Chris@316
|
329 // without explicitly specifying that you don't want the path
|
Chris@316
|
330 // separators ("/") to be encoded. (Besides being painful to
|
Chris@316
|
331 // manage, I don't see how this can work correctly in any case
|
Chris@316
|
332 // where a percent-encoded "/" is supposed to appear within a
|
Chris@316
|
333 // path element?) There also seems to be no way to retrieve
|
Chris@316
|
334 // the path plus query string, i.e. everything that I need to
|
Chris@316
|
335 // send to the HTTP server. And no way for QHttp to take a
|
Chris@316
|
336 // QUrl argument. I'm obviously missing something.
|
Chris@316
|
337
|
Chris@316
|
338 // So, two ways to do this: query the bits from the URL,
|
Chris@316
|
339 // encode them individually, and glue them back together
|
Chris@316
|
340 // again...
|
Chris@316
|
341 /*
|
Chris@316
|
342 QString path = QUrl::toPercentEncoding(m_url.path(), "/");
|
Chris@316
|
343 QList<QPair<QString, QString> > query = m_url.queryItems();
|
Chris@316
|
344 if (!query.empty()) {
|
Chris@316
|
345 QStringList q2;
|
Chris@316
|
346 for (QList<QPair<QString, QString> >::iterator i = query.begin();
|
Chris@316
|
347 i != query.end(); ++i) {
|
Chris@316
|
348 q2.push_back(QString("%1=%3")
|
Chris@316
|
349 .arg(QString(QUrl::toPercentEncoding(i->first)))
|
Chris@316
|
350 .arg(QString(QUrl::toPercentEncoding(i->second))));
|
Chris@316
|
351 }
|
Chris@316
|
352 path = QString("%1%2%3")
|
Chris@316
|
353 .arg(path).arg("?")
|
Chris@316
|
354 .arg(q2.join("&"));
|
Chris@316
|
355 }
|
Chris@316
|
356 */
|
Chris@316
|
357
|
Chris@316
|
358 // ...or, much simpler but relying on knowledge about the
|
Chris@316
|
359 // scheme://host/path/path/query etc format of the URL, we can
|
Chris@316
|
360 // get the whole URL ready-encoded and then split it on "/" as
|
Chris@316
|
361 // appropriate...
|
Chris@316
|
362
|
Chris@316
|
363 QString path = "/" + QString(m_url.toEncoded()).section('/', 3);
|
Chris@316
|
364
|
Chris@327
|
365 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
366 std::cerr << "FileSource: path is \""
|
Chris@316
|
367 << path.toStdString() << "\"" << std::endl;
|
Chris@327
|
368 #endif
|
Chris@316
|
369
|
Chris@316
|
370 m_http->get(path, m_localFile);
|
Chris@316
|
371 }
|
Chris@316
|
372
|
Chris@316
|
373 void
|
Chris@317
|
374 FileSource::initFtp()
|
Chris@316
|
375 {
|
Chris@316
|
376 m_ok = true;
|
Chris@316
|
377 m_ftp = new QFtp;
|
Chris@316
|
378 connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool)));
|
Chris@316
|
379 connect(m_ftp, SIGNAL(commandFinished(int, bool)),
|
Chris@316
|
380 this, SLOT(ftpCommandFinished(int, bool)));
|
Chris@316
|
381 connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
|
Chris@316
|
382 this, SLOT(dataTransferProgress(qint64, qint64)));
|
Chris@316
|
383 m_ftp->connectToHost(m_url.host(), m_url.port(21));
|
Chris@316
|
384
|
Chris@316
|
385 QString username = m_url.userName();
|
Chris@316
|
386 if (username == "") {
|
Chris@316
|
387 username = "anonymous";
|
Chris@316
|
388 }
|
Chris@316
|
389
|
Chris@316
|
390 QString password = m_url.password();
|
Chris@316
|
391 if (password == "") {
|
Chris@316
|
392 password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST"));
|
Chris@316
|
393 }
|
Chris@316
|
394
|
Chris@316
|
395 m_ftp->login(username, password);
|
Chris@316
|
396
|
Chris@316
|
397 QString dirpath = m_url.path().section('/', 0, -2);
|
Chris@316
|
398 QString filename = m_url.path().section('/', -1);
|
Chris@316
|
399
|
Chris@316
|
400 if (dirpath == "") dirpath = "/";
|
Chris@316
|
401 m_ftp->cd(dirpath);
|
Chris@316
|
402 m_ftp->get(filename, m_localFile);
|
Chris@211
|
403 }
|
Chris@211
|
404
|
Chris@211
|
405 void
|
Chris@317
|
406 FileSource::cleanup()
|
Chris@211
|
407 {
|
Chris@211
|
408 m_done = true;
|
Chris@214
|
409 if (m_http) {
|
Chris@287
|
410 QHttp *h = m_http;
|
Chris@214
|
411 m_http = 0;
|
Chris@287
|
412 h->abort();
|
Chris@287
|
413 h->deleteLater();
|
Chris@214
|
414 }
|
Chris@214
|
415 if (m_ftp) {
|
Chris@287
|
416 QFtp *f = m_ftp;
|
Chris@214
|
417 m_ftp = 0;
|
Chris@287
|
418 f->abort();
|
Chris@287
|
419 f->deleteLater();
|
Chris@214
|
420 }
|
Chris@211
|
421 delete m_progressDialog;
|
Chris@211
|
422 m_progressDialog = 0;
|
Chris@304
|
423 delete m_localFile; // does not actually delete the file
|
Chris@211
|
424 m_localFile = 0;
|
Chris@208
|
425 }
|
Chris@208
|
426
|
Chris@208
|
427 bool
|
Chris@317
|
428 FileSource::isRemote(QString fileOrUrl)
|
Chris@304
|
429 {
|
Chris@342
|
430 // Note that a "scheme" with length 1 is probably a DOS drive letter
|
Chris@316
|
431 QString scheme = QUrl(fileOrUrl).scheme().toLower();
|
Chris@342
|
432 if (scheme == "" || scheme == "file" || scheme.length() == 1) return false;
|
Chris@342
|
433 return true;
|
Chris@304
|
434 }
|
Chris@304
|
435
|
Chris@304
|
436 bool
|
Chris@317
|
437 FileSource::canHandleScheme(QUrl url)
|
Chris@208
|
438 {
|
Chris@342
|
439 // Note that a "scheme" with length 1 is probably a DOS drive letter
|
Chris@208
|
440 QString scheme = url.scheme().toLower();
|
Chris@316
|
441 return (scheme == "http" || scheme == "ftp" ||
|
Chris@342
|
442 scheme == "file" || scheme == "" || scheme.length() == 1);
|
Chris@208
|
443 }
|
Chris@208
|
444
|
Chris@210
|
445 bool
|
Chris@317
|
446 FileSource::isAvailable()
|
Chris@210
|
447 {
|
Chris@316
|
448 waitForStatus();
|
Chris@211
|
449 bool available = true;
|
Chris@211
|
450 if (!m_ok) available = false;
|
Chris@211
|
451 else available = (m_lastStatus / 100 == 2);
|
Chris@327
|
452 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
453 std::cerr << "FileSource::isAvailable: " << (available ? "yes" : "no")
|
Chris@211
|
454 << std::endl;
|
Chris@327
|
455 #endif
|
Chris@211
|
456 return available;
|
Chris@210
|
457 }
|
Chris@210
|
458
|
Chris@208
|
459 void
|
Chris@317
|
460 FileSource::waitForStatus()
|
Chris@316
|
461 {
|
Chris@316
|
462 while (m_ok && (!m_done && m_lastStatus == 0)) {
|
Chris@316
|
463 // std::cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << std::endl;
|
Chris@316
|
464 QApplication::processEvents();
|
Chris@316
|
465 }
|
Chris@316
|
466 }
|
Chris@316
|
467
|
Chris@316
|
468 void
|
Chris@317
|
469 FileSource::waitForData()
|
Chris@208
|
470 {
|
Chris@211
|
471 while (m_ok && !m_done) {
|
Chris@208
|
472 QApplication::processEvents();
|
Chris@208
|
473 }
|
Chris@208
|
474 }
|
Chris@208
|
475
|
Chris@316
|
476 void
|
Chris@317
|
477 FileSource::setLeaveLocalFile(bool leave)
|
Chris@316
|
478 {
|
Chris@316
|
479 m_leaveLocalFile = leave;
|
Chris@316
|
480 }
|
Chris@316
|
481
|
Chris@208
|
482 bool
|
Chris@317
|
483 FileSource::isOK() const
|
Chris@208
|
484 {
|
Chris@208
|
485 return m_ok;
|
Chris@208
|
486 }
|
Chris@208
|
487
|
Chris@208
|
488 bool
|
Chris@317
|
489 FileSource::isDone() const
|
Chris@208
|
490 {
|
Chris@208
|
491 return m_done;
|
Chris@208
|
492 }
|
Chris@208
|
493
|
Chris@316
|
494 bool
|
Chris@317
|
495 FileSource::isRemote() const
|
Chris@316
|
496 {
|
Chris@316
|
497 return m_remote;
|
Chris@316
|
498 }
|
Chris@316
|
499
|
Chris@316
|
500 QString
|
Chris@317
|
501 FileSource::getLocation() const
|
Chris@316
|
502 {
|
Chris@316
|
503 return m_url.toString();
|
Chris@316
|
504 }
|
Chris@316
|
505
|
Chris@208
|
506 QString
|
Chris@317
|
507 FileSource::getLocalFilename() const
|
Chris@208
|
508 {
|
Chris@208
|
509 return m_localFilename;
|
Chris@208
|
510 }
|
Chris@208
|
511
|
Chris@208
|
512 QString
|
Chris@317
|
513 FileSource::getContentType() const
|
Chris@316
|
514 {
|
Chris@316
|
515 return m_contentType;
|
Chris@316
|
516 }
|
Chris@316
|
517
|
Chris@316
|
518 QString
|
Chris@317
|
519 FileSource::getExtension() const
|
Chris@316
|
520 {
|
Chris@316
|
521 if (m_localFilename != "") {
|
Chris@316
|
522 return QFileInfo(m_localFilename).suffix().toLower();
|
Chris@316
|
523 } else {
|
Chris@316
|
524 return QFileInfo(m_url.toLocalFile()).suffix().toLower();
|
Chris@316
|
525 }
|
Chris@316
|
526 }
|
Chris@316
|
527
|
Chris@316
|
528 QString
|
Chris@317
|
529 FileSource::getErrorString() const
|
Chris@208
|
530 {
|
Chris@208
|
531 return m_errorString;
|
Chris@208
|
532 }
|
Chris@208
|
533
|
Chris@208
|
534 void
|
Chris@317
|
535 FileSource::dataReadProgress(int done, int total)
|
Chris@208
|
536 {
|
Chris@208
|
537 dataTransferProgress(done, total);
|
Chris@208
|
538 }
|
Chris@208
|
539
|
Chris@208
|
540 void
|
Chris@317
|
541 FileSource::httpResponseHeaderReceived(const QHttpResponseHeader &resp)
|
Chris@210
|
542 {
|
Chris@210
|
543 m_lastStatus = resp.statusCode();
|
Chris@210
|
544 if (m_lastStatus / 100 >= 4) {
|
Chris@210
|
545 m_errorString = QString("%1 %2")
|
Chris@210
|
546 .arg(resp.statusCode()).arg(resp.reasonPhrase());
|
Chris@327
|
547 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
548 std::cerr << "FileSource::responseHeaderReceived: "
|
Chris@211
|
549 << m_errorString.toStdString() << std::endl;
|
Chris@327
|
550 #endif
|
Chris@211
|
551 } else {
|
Chris@327
|
552 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
553 std::cerr << "FileSource::responseHeaderReceived: "
|
Chris@211
|
554 << m_lastStatus << std::endl;
|
Chris@327
|
555 #endif
|
Chris@315
|
556 if (resp.hasContentType()) m_contentType = resp.contentType();
|
Chris@325
|
557 }
|
Chris@325
|
558 emit statusAvailable();
|
Chris@210
|
559 }
|
Chris@210
|
560
|
Chris@210
|
561 void
|
Chris@317
|
562 FileSource::ftpCommandFinished(int id, bool error)
|
Chris@214
|
563 {
|
Chris@327
|
564 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
565 std::cerr << "FileSource::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
|
Chris@327
|
566 #endif
|
Chris@214
|
567
|
Chris@214
|
568 if (!m_ftp) return;
|
Chris@214
|
569
|
Chris@214
|
570 QFtp::Command command = m_ftp->currentCommand();
|
Chris@214
|
571
|
Chris@214
|
572 if (!error) {
|
Chris@327
|
573 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
574 std::cerr << "FileSource::ftpCommandFinished: success for command "
|
Chris@214
|
575 << command << std::endl;
|
Chris@327
|
576 #endif
|
Chris@214
|
577 return;
|
Chris@214
|
578 }
|
Chris@214
|
579
|
Chris@214
|
580 if (command == QFtp::ConnectToHost) {
|
Chris@214
|
581 m_errorString = tr("Failed to connect to FTP server");
|
Chris@214
|
582 } else if (command == QFtp::Login) {
|
Chris@214
|
583 m_errorString = tr("Login failed");
|
Chris@214
|
584 } else if (command == QFtp::Cd) {
|
Chris@214
|
585 m_errorString = tr("Failed to change to correct directory");
|
Chris@214
|
586 } else if (command == QFtp::Get) {
|
Chris@214
|
587 m_errorString = tr("FTP download aborted");
|
Chris@214
|
588 }
|
Chris@214
|
589
|
Chris@214
|
590 m_lastStatus = 400; // for done()
|
Chris@214
|
591 }
|
Chris@214
|
592
|
Chris@214
|
593 void
|
Chris@317
|
594 FileSource::dataTransferProgress(qint64 done, qint64 total)
|
Chris@208
|
595 {
|
Chris@208
|
596 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
|
Chris@208
|
597 emit progress(percent);
|
Chris@208
|
598
|
Chris@327
|
599 if (!m_progressDialog) return;
|
Chris@327
|
600
|
Chris@211
|
601 if (percent > 0) {
|
Chris@211
|
602 m_progressDialog->setValue(percent);
|
Chris@211
|
603 m_progressDialog->show();
|
Chris@211
|
604 }
|
Chris@210
|
605 }
|
Chris@210
|
606
|
Chris@210
|
607 void
|
Chris@317
|
608 FileSource::cancelled()
|
Chris@210
|
609 {
|
Chris@210
|
610 m_done = true;
|
Chris@316
|
611 cleanup();
|
Chris@316
|
612
|
Chris@210
|
613 m_ok = false;
|
Chris@210
|
614 m_errorString = tr("Download cancelled");
|
Chris@208
|
615 }
|
Chris@208
|
616
|
Chris@208
|
617 void
|
Chris@317
|
618 FileSource::done(bool error)
|
Chris@208
|
619 {
|
Chris@327
|
620 emit progress(100);
|
Chris@327
|
621
|
Chris@327
|
622 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
623 std::cerr << "FileSource::done(" << error << ")" << std::endl;
|
Chris@327
|
624 #endif
|
Chris@211
|
625
|
Chris@211
|
626 if (m_done) return;
|
Chris@211
|
627
|
Chris@208
|
628 if (error) {
|
Chris@208
|
629 if (m_http) {
|
Chris@208
|
630 m_errorString = m_http->errorString();
|
Chris@208
|
631 } else if (m_ftp) {
|
Chris@208
|
632 m_errorString = m_ftp->errorString();
|
Chris@208
|
633 }
|
Chris@208
|
634 }
|
Chris@208
|
635
|
Chris@210
|
636 if (m_lastStatus / 100 >= 4) {
|
Chris@211
|
637 error = true;
|
Chris@210
|
638 }
|
Chris@210
|
639
|
Chris@211
|
640 cleanup();
|
Chris@208
|
641
|
Chris@211
|
642 if (!error) {
|
Chris@208
|
643 QFileInfo fi(m_localFilename);
|
Chris@208
|
644 if (!fi.exists()) {
|
Chris@208
|
645 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
|
Chris@211
|
646 error = true;
|
Chris@208
|
647 } else if (fi.size() == 0) {
|
Chris@208
|
648 m_errorString = tr("File contains no data!");
|
Chris@211
|
649 error = true;
|
Chris@208
|
650 }
|
Chris@208
|
651 }
|
Chris@211
|
652
|
Chris@211
|
653 if (error) {
|
Chris@327
|
654 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
655 std::cerr << "FileSource::done: error is " << error << ", deleting cache file" << std::endl;
|
Chris@327
|
656 #endif
|
Chris@316
|
657 deleteCacheFile();
|
Chris@211
|
658 }
|
Chris@211
|
659
|
Chris@211
|
660 m_ok = !error;
|
Chris@211
|
661 m_done = true;
|
Chris@304
|
662 emit ready();
|
Chris@211
|
663 }
|
Chris@211
|
664
|
Chris@211
|
665 void
|
Chris@317
|
666 FileSource::deleteCacheFile()
|
Chris@211
|
667 {
|
Chris@327
|
668 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
669 std::cerr << "FileSource::deleteCacheFile(\"" << m_localFilename.toStdString() << "\")" << std::endl;
|
Chris@327
|
670 #endif
|
Chris@211
|
671
|
Chris@211
|
672 cleanup();
|
Chris@211
|
673
|
Chris@316
|
674 if (m_localFilename == "") {
|
Chris@316
|
675 return;
|
Chris@316
|
676 }
|
Chris@211
|
677
|
Chris@316
|
678 if (!isRemote()) {
|
Chris@327
|
679 #ifdef DEBUG_FILE_SOURCE
|
Chris@316
|
680 std::cerr << "not a cache file" << std::endl;
|
Chris@327
|
681 #endif
|
Chris@316
|
682 return;
|
Chris@316
|
683 }
|
Chris@316
|
684
|
Chris@316
|
685 if (m_refCounted) {
|
Chris@304
|
686
|
Chris@304
|
687 QMutexLocker locker(&m_mapMutex);
|
Chris@316
|
688 m_refCounted = false;
|
Chris@304
|
689
|
Chris@304
|
690 if (m_refCountMap[m_url] > 0) {
|
Chris@304
|
691 m_refCountMap[m_url]--;
|
Chris@327
|
692 #ifdef DEBUG_FILE_SOURCE
|
Chris@316
|
693 std::cerr << "reduced ref count to " << m_refCountMap[m_url] << std::endl;
|
Chris@327
|
694 #endif
|
Chris@304
|
695 if (m_refCountMap[m_url] > 0) {
|
Chris@304
|
696 m_done = true;
|
Chris@304
|
697 return;
|
Chris@304
|
698 }
|
Chris@304
|
699 }
|
Chris@304
|
700 }
|
Chris@304
|
701
|
Chris@211
|
702 m_fileCreationMutex.lock();
|
Chris@211
|
703
|
Chris@211
|
704 if (!QFile(m_localFilename).remove()) {
|
Chris@327
|
705 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
706 std::cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
|
Chris@327
|
707 #endif
|
Chris@211
|
708 } else {
|
Chris@327
|
709 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
710 std::cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename.toStdString() << "\"" << std::endl;
|
Chris@327
|
711 #endif
|
Chris@211
|
712 m_localFilename = "";
|
Chris@211
|
713 }
|
Chris@211
|
714
|
Chris@211
|
715 m_fileCreationMutex.unlock();
|
Chris@211
|
716
|
Chris@208
|
717 m_done = true;
|
Chris@208
|
718 }
|
Chris@208
|
719
|
Chris@210
|
720 void
|
Chris@317
|
721 FileSource::showProgressDialog()
|
Chris@210
|
722 {
|
Chris@210
|
723 if (m_progressDialog) m_progressDialog->show();
|
Chris@210
|
724 }
|
Chris@210
|
725
|
Chris@316
|
726 bool
|
Chris@317
|
727 FileSource::createCacheFile()
|
Chris@208
|
728 {
|
Chris@316
|
729 {
|
Chris@316
|
730 QMutexLocker locker(&m_mapMutex);
|
Chris@316
|
731
|
Chris@327
|
732 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
733 std::cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << std::endl;
|
Chris@327
|
734 #endif
|
Chris@316
|
735
|
Chris@316
|
736 if (m_refCountMap[m_url] > 0) {
|
Chris@316
|
737 m_refCountMap[m_url]++;
|
Chris@316
|
738 m_localFilename = m_remoteLocalMap[m_url];
|
Chris@327
|
739 #ifdef DEBUG_FILE_SOURCE
|
Chris@316
|
740 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
|
Chris@327
|
741 #endif
|
Chris@316
|
742 m_refCounted = true;
|
Chris@316
|
743 return true;
|
Chris@316
|
744 }
|
Chris@316
|
745 }
|
Chris@316
|
746
|
Chris@208
|
747 QDir dir;
|
Chris@208
|
748 try {
|
Chris@208
|
749 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
|
Chris@208
|
750 } catch (DirectoryCreationFailed f) {
|
Chris@327
|
751 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
752 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
|
Chris@327
|
753 #endif
|
Chris@208
|
754 return "";
|
Chris@208
|
755 }
|
Chris@208
|
756
|
Chris@316
|
757 QString filepart = m_url.path().section('/', -1, -1,
|
Chris@316
|
758 QString::SectionSkipEmpty);
|
Chris@208
|
759
|
Chris@208
|
760 QString extension = filepart.section('.', -1);
|
Chris@208
|
761 QString base = filepart;
|
Chris@208
|
762 if (extension != "") {
|
Chris@208
|
763 base = base.left(base.length() - extension.length() - 1);
|
Chris@208
|
764 }
|
Chris@208
|
765 if (base == "") base = "remote";
|
Chris@208
|
766
|
Chris@208
|
767 QString filename;
|
Chris@208
|
768
|
Chris@208
|
769 if (extension == "") {
|
Chris@208
|
770 filename = base;
|
Chris@208
|
771 } else {
|
Chris@208
|
772 filename = QString("%1.%2").arg(base).arg(extension);
|
Chris@208
|
773 }
|
Chris@208
|
774
|
Chris@208
|
775 QString filepath(dir.filePath(filename));
|
Chris@208
|
776
|
Chris@327
|
777 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
778 std::cerr << "FileSource::createCacheFile: URL is \"" << m_url.toString().toStdString() << "\", dir is \"" << dir.path().toStdString() << "\", base \"" << base.toStdString() << "\", extension \"" << extension.toStdString() << "\", filebase \"" << filename.toStdString() << "\", filename \"" << filepath.toStdString() << "\"" << std::endl;
|
Chris@327
|
779 #endif
|
Chris@208
|
780
|
Chris@316
|
781 QMutexLocker fcLocker(&m_fileCreationMutex);
|
Chris@316
|
782
|
Chris@208
|
783 ++m_count;
|
Chris@208
|
784
|
Chris@208
|
785 if (QFileInfo(filepath).exists() ||
|
Chris@208
|
786 !QFile(filepath).open(QFile::WriteOnly)) {
|
Chris@208
|
787
|
Chris@327
|
788 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
789 std::cerr << "FileSource::createCacheFile: Failed to create local file \""
|
Chris@208
|
790 << filepath.toStdString() << "\" for URL \""
|
Chris@316
|
791 << m_url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
|
Chris@327
|
792 #endif
|
Chris@208
|
793
|
Chris@208
|
794 if (extension == "") {
|
Chris@208
|
795 filename = QString("%1_%2").arg(base).arg(m_count);
|
Chris@208
|
796 } else {
|
Chris@208
|
797 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
|
Chris@208
|
798 }
|
Chris@208
|
799 filepath = dir.filePath(filename);
|
Chris@208
|
800
|
Chris@208
|
801 if (QFileInfo(filepath).exists() ||
|
Chris@208
|
802 !QFile(filepath).open(QFile::WriteOnly)) {
|
Chris@208
|
803
|
Chris@327
|
804 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
805 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \""
|
Chris@208
|
806 << filepath.toStdString() << "\" for URL \""
|
Chris@316
|
807 << m_url.toString().toStdString() << "\" (or file already exists)" << std::endl;
|
Chris@327
|
808 #endif
|
Chris@208
|
809
|
Chris@208
|
810 return "";
|
Chris@208
|
811 }
|
Chris@208
|
812 }
|
Chris@208
|
813
|
Chris@327
|
814 #ifdef DEBUG_FILE_SOURCE
|
Chris@317
|
815 std::cerr << "FileSource::createCacheFile: url "
|
Chris@316
|
816 << m_url.toString().toStdString() << " -> local filename "
|
Chris@316
|
817 << filepath.toStdString() << std::endl;
|
Chris@327
|
818 #endif
|
Chris@316
|
819
|
Chris@316
|
820 m_localFilename = filepath;
|
Chris@208
|
821
|
Chris@316
|
822 return false;
|
Chris@208
|
823 }
|
Chris@327
|
824
|
Chris@327
|
825 FileSourceProgressPrinter::FileSourceProgressPrinter() :
|
Chris@327
|
826 m_lastProgress(0)
|
Chris@327
|
827 {
|
Chris@327
|
828 }
|
Chris@327
|
829
|
Chris@327
|
830 FileSourceProgressPrinter::~FileSourceProgressPrinter()
|
Chris@327
|
831 {
|
Chris@327
|
832 if (m_lastProgress > 0 && m_lastProgress != 100) {
|
Chris@327
|
833 std::cerr << "\r\n";
|
Chris@327
|
834 }
|
Chris@327
|
835 }
|
Chris@327
|
836
|
Chris@327
|
837 void
|
Chris@327
|
838 FileSourceProgressPrinter::progress(int progress)
|
Chris@327
|
839 {
|
Chris@327
|
840 if (progress == m_lastProgress) return;
|
Chris@327
|
841 if (progress == 100) std::cerr << "\r\n";
|
Chris@327
|
842 else std::cerr << "\r" << progress << "%";
|
Chris@327
|
843 m_lastProgress = progress;
|
Chris@327
|
844 }
|
Chris@327
|
845
|