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