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