annotate data/fileio/RemoteFile.cpp @ 296:2b6c99b607f1

...
author Chris Cannam
date Fri, 21 Sep 2007 09:13:11 +0000
parents 557e00480279
children 4fc6f49436b3
rev   line source
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@208 16 #include "RemoteFile.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@208 30 int
Chris@208 31 RemoteFile::m_count = 0;
Chris@208 32
Chris@208 33 QMutex
Chris@208 34 RemoteFile::m_fileCreationMutex;
Chris@208 35
Chris@208 36 RemoteFile::RemoteFile(QUrl url) :
Chris@208 37 m_ftp(0),
Chris@208 38 m_http(0),
Chris@208 39 m_localFile(0),
Chris@208 40 m_ok(false),
Chris@210 41 m_lastStatus(0),
Chris@208 42 m_done(false),
Chris@210 43 m_progressDialog(0),
Chris@210 44 m_progressShowTimer(this)
Chris@208 45 {
Chris@208 46 if (!canHandleScheme(url)) {
Chris@208 47 std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl;
Chris@208 48 return;
Chris@208 49 }
Chris@208 50
Chris@208 51 m_localFilename = createLocalFile(url);
Chris@208 52 if (m_localFilename == "") return;
Chris@208 53 m_localFile = new QFile(m_localFilename);
Chris@208 54 m_localFile->open(QFile::WriteOnly);
Chris@208 55
Chris@208 56 QString scheme = url.scheme().toLower();
Chris@208 57
Chris@208 58 if (scheme == "http") {
Chris@208 59
Chris@211 60 m_ok = true;
Chris@208 61 m_http = new QHttp(url.host(), url.port(80));
Chris@208 62 connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@208 63 connect(m_http, SIGNAL(dataReadProgress(int, int)),
Chris@208 64 this, SLOT(dataReadProgress(int, int)));
Chris@210 65 connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
Chris@214 66 this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &)));
Chris@279 67
Chris@279 68 // I don't quite understand this. url.path() returns a path
Chris@279 69 // without percent encoding; for example, spaces appear as
Chris@279 70 // literal spaces. This generally won't work if sent to the
Chris@279 71 // server directly. You can retrieve a correctly encoded URL
Chris@279 72 // from QUrl using url.toEncoded(), but that gives you the
Chris@279 73 // whole URL; there doesn't seem to be any way to retrieve
Chris@279 74 // only an encoded path. Furthermore there doesn't seem to be
Chris@279 75 // any way to convert a retrieved path into an encoded path
Chris@279 76 // without explicitly specifying that you don't want the path
Chris@279 77 // separators ("/") to be encoded. (Besides being painful to
Chris@279 78 // manage, I don't see how this can work correctly in any case
Chris@279 79 // where a percent-encoded "/" is supposed to appear within a
Chris@279 80 // path element?) There also seems to be no way to retrieve
Chris@279 81 // the path plus query string, i.e. everything that I need to
Chris@279 82 // send to the HTTP server. And no way for QHttp to take a
Chris@279 83 // QUrl argument. I'm obviously missing something.
Chris@279 84
Chris@279 85 // So, two ways to do this: query the bits from the URL,
Chris@279 86 // encode them individually, and glue them back together
Chris@279 87 // again...
Chris@279 88 /*
Chris@279 89 QString path = QUrl::toPercentEncoding(url.path(), "/");
Chris@279 90 QList<QPair<QString, QString> > query = url.queryItems();
Chris@279 91 if (!query.empty()) {
Chris@279 92 QStringList q2;
Chris@279 93 for (QList<QPair<QString, QString> >::iterator i = query.begin();
Chris@279 94 i != query.end(); ++i) {
Chris@279 95 q2.push_back(QString("%1=%3")
Chris@279 96 .arg(QString(QUrl::toPercentEncoding(i->first)))
Chris@279 97 .arg(QString(QUrl::toPercentEncoding(i->second))));
Chris@279 98 }
Chris@279 99 path = QString("%1%2%3")
Chris@279 100 .arg(path).arg("?")
Chris@279 101 .arg(q2.join("&"));
Chris@279 102 }
Chris@279 103 */
Chris@279 104
Chris@279 105 // ...or, much simpler but relying on knowledge about the
Chris@279 106 // scheme://host/path/path/query etc format of the URL, we can
Chris@279 107 // get the whole URL ready-encoded and then split it on "/" as
Chris@279 108 // appropriate...
Chris@279 109
Chris@279 110 QString path = "/" + QString(url.toEncoded()).section('/', 3);
Chris@279 111
Chris@279 112 std::cerr << "RemoteFile: path is \""
Chris@279 113 << path.toStdString() << "\"" << std::endl;
Chris@279 114
Chris@278 115 m_http->get(path, m_localFile);
Chris@208 116
Chris@208 117 } else if (scheme == "ftp") {
Chris@208 118
Chris@211 119 m_ok = true;
Chris@208 120 m_ftp = new QFtp;
Chris@208 121 connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@214 122 connect(m_ftp, SIGNAL(commandFinished(int, bool)),
Chris@214 123 this, SLOT(ftpCommandFinished(int, bool)));
Chris@208 124 connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
Chris@208 125 this, SLOT(dataTransferProgress(qint64, qint64)));
Chris@208 126 m_ftp->connectToHost(url.host(), url.port(21));
Chris@208 127
Chris@208 128 QString username = url.userName();
Chris@208 129 if (username == "") {
Chris@208 130 username = "anonymous";
Chris@208 131 }
Chris@208 132
Chris@208 133 QString password = url.password();
Chris@208 134 if (password == "") {
Chris@208 135 password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST"));
Chris@208 136 }
Chris@208 137
Chris@214 138 m_ftp->login(username, password);
Chris@214 139
Chris@214 140 QString dirpath = url.path().section('/', 0, -2);
Chris@214 141 QString filename = url.path().section('/', -1);
Chris@214 142
Chris@214 143 if (dirpath == "") dirpath = "/";
Chris@214 144 m_ftp->cd(dirpath);
Chris@214 145 m_ftp->get(filename, m_localFile);
Chris@208 146 }
Chris@208 147
Chris@208 148 if (m_ok) {
Chris@208 149 m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(url.toString()), tr("Cancel"), 0, 100);
Chris@210 150 m_progressDialog->hide();
Chris@210 151 connect(&m_progressShowTimer, SIGNAL(timeout()),
Chris@210 152 this, SLOT(showProgressDialog()));
Chris@210 153 connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled()));
Chris@210 154 m_progressShowTimer.setSingleShot(true);
Chris@210 155 m_progressShowTimer.start(2000);
Chris@208 156 }
Chris@208 157 }
Chris@208 158
Chris@208 159 RemoteFile::~RemoteFile()
Chris@208 160 {
Chris@211 161 cleanup();
Chris@211 162 }
Chris@211 163
Chris@211 164 void
Chris@211 165 RemoteFile::cleanup()
Chris@211 166 {
Chris@211 167 m_done = true;
Chris@214 168 if (m_http) {
Chris@287 169 QHttp *h = m_http;
Chris@214 170 m_http = 0;
Chris@287 171 h->abort();
Chris@287 172 h->deleteLater();
Chris@214 173 }
Chris@214 174 if (m_ftp) {
Chris@287 175 QFtp *f = m_ftp;
Chris@214 176 m_ftp = 0;
Chris@287 177 f->abort();
Chris@287 178 f->deleteLater();
Chris@214 179 }
Chris@211 180 delete m_progressDialog;
Chris@211 181 m_progressDialog = 0;
Chris@208 182 delete m_localFile;
Chris@211 183 m_localFile = 0;
Chris@208 184 }
Chris@208 185
Chris@208 186 bool
Chris@208 187 RemoteFile::canHandleScheme(QUrl url)
Chris@208 188 {
Chris@208 189 QString scheme = url.scheme().toLower();
Chris@208 190 return (scheme == "http" || scheme == "ftp");
Chris@208 191 }
Chris@208 192
Chris@210 193 bool
Chris@210 194 RemoteFile::isAvailable()
Chris@210 195 {
Chris@211 196 while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@210 197 QApplication::processEvents();
Chris@210 198 }
Chris@211 199 bool available = true;
Chris@211 200 if (!m_ok) available = false;
Chris@211 201 else available = (m_lastStatus / 100 == 2);
Chris@211 202 std::cerr << "RemoteFile::isAvailable: " << (available ? "yes" : "no")
Chris@211 203 << std::endl;
Chris@211 204 return available;
Chris@210 205 }
Chris@210 206
Chris@208 207 void
Chris@208 208 RemoteFile::wait()
Chris@208 209 {
Chris@211 210 while (m_ok && !m_done) {
Chris@208 211 QApplication::processEvents();
Chris@208 212 }
Chris@208 213 }
Chris@208 214
Chris@208 215 bool
Chris@208 216 RemoteFile::isOK() const
Chris@208 217 {
Chris@208 218 return m_ok;
Chris@208 219 }
Chris@208 220
Chris@208 221 bool
Chris@208 222 RemoteFile::isDone() const
Chris@208 223 {
Chris@208 224 return m_done;
Chris@208 225 }
Chris@208 226
Chris@208 227 QString
Chris@208 228 RemoteFile::getLocalFilename() const
Chris@208 229 {
Chris@208 230 return m_localFilename;
Chris@208 231 }
Chris@208 232
Chris@208 233 QString
Chris@208 234 RemoteFile::getErrorString() const
Chris@208 235 {
Chris@208 236 return m_errorString;
Chris@208 237 }
Chris@208 238
Chris@208 239 void
Chris@208 240 RemoteFile::dataReadProgress(int done, int total)
Chris@208 241 {
Chris@208 242 dataTransferProgress(done, total);
Chris@208 243 }
Chris@208 244
Chris@208 245 void
Chris@214 246 RemoteFile::httpResponseHeaderReceived(const QHttpResponseHeader &resp)
Chris@210 247 {
Chris@210 248 m_lastStatus = resp.statusCode();
Chris@210 249 if (m_lastStatus / 100 >= 4) {
Chris@210 250 m_errorString = QString("%1 %2")
Chris@210 251 .arg(resp.statusCode()).arg(resp.reasonPhrase());
Chris@211 252 std::cerr << "RemoteFile::responseHeaderReceived: "
Chris@211 253 << m_errorString.toStdString() << std::endl;
Chris@211 254 } else {
Chris@211 255 std::cerr << "RemoteFile::responseHeaderReceived: "
Chris@211 256 << m_lastStatus << std::endl;
Chris@211 257 }
Chris@210 258 }
Chris@210 259
Chris@210 260 void
Chris@214 261 RemoteFile::ftpCommandFinished(int id, bool error)
Chris@214 262 {
Chris@214 263 std::cerr << "RemoteFile::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
Chris@214 264
Chris@214 265 if (!m_ftp) return;
Chris@214 266
Chris@214 267 QFtp::Command command = m_ftp->currentCommand();
Chris@214 268
Chris@214 269 if (!error) {
Chris@214 270 std::cerr << "RemoteFile::ftpCommandFinished: success for command "
Chris@214 271 << command << std::endl;
Chris@214 272 return;
Chris@214 273 }
Chris@214 274
Chris@214 275 if (command == QFtp::ConnectToHost) {
Chris@214 276 m_errorString = tr("Failed to connect to FTP server");
Chris@214 277 } else if (command == QFtp::Login) {
Chris@214 278 m_errorString = tr("Login failed");
Chris@214 279 } else if (command == QFtp::Cd) {
Chris@214 280 m_errorString = tr("Failed to change to correct directory");
Chris@214 281 } else if (command == QFtp::Get) {
Chris@214 282 m_errorString = tr("FTP download aborted");
Chris@214 283 }
Chris@214 284
Chris@214 285 m_lastStatus = 400; // for done()
Chris@214 286 }
Chris@214 287
Chris@214 288 void
Chris@208 289 RemoteFile::dataTransferProgress(qint64 done, qint64 total)
Chris@208 290 {
Chris@211 291 if (!m_progressDialog) return;
Chris@211 292
Chris@208 293 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 294 emit progress(percent);
Chris@208 295
Chris@211 296 if (percent > 0) {
Chris@211 297 m_progressDialog->setValue(percent);
Chris@211 298 m_progressDialog->show();
Chris@211 299 }
Chris@210 300 }
Chris@210 301
Chris@210 302 void
Chris@210 303 RemoteFile::cancelled()
Chris@210 304 {
Chris@211 305 deleteLocalFile();
Chris@210 306 m_done = true;
Chris@210 307 m_ok = false;
Chris@210 308 m_errorString = tr("Download cancelled");
Chris@208 309 }
Chris@208 310
Chris@208 311 void
Chris@208 312 RemoteFile::done(bool error)
Chris@208 313 {
Chris@214 314 std::cerr << "RemoteFile::done(" << error << ")" << std::endl;
Chris@211 315
Chris@211 316 if (m_done) return;
Chris@211 317
Chris@208 318 emit progress(100);
Chris@210 319
Chris@208 320 if (error) {
Chris@208 321 if (m_http) {
Chris@208 322 m_errorString = m_http->errorString();
Chris@208 323 } else if (m_ftp) {
Chris@208 324 m_errorString = m_ftp->errorString();
Chris@208 325 }
Chris@208 326 }
Chris@208 327
Chris@210 328 if (m_lastStatus / 100 >= 4) {
Chris@211 329 error = true;
Chris@210 330 }
Chris@210 331
Chris@211 332 cleanup();
Chris@208 333
Chris@211 334 if (!error) {
Chris@208 335 QFileInfo fi(m_localFilename);
Chris@208 336 if (!fi.exists()) {
Chris@208 337 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 338 error = true;
Chris@208 339 } else if (fi.size() == 0) {
Chris@208 340 m_errorString = tr("File contains no data!");
Chris@211 341 error = true;
Chris@208 342 }
Chris@208 343 }
Chris@211 344
Chris@211 345 if (error) {
Chris@211 346 deleteLocalFile();
Chris@211 347 }
Chris@211 348
Chris@211 349 m_ok = !error;
Chris@211 350 m_done = true;
Chris@211 351 }
Chris@211 352
Chris@211 353 void
Chris@211 354 RemoteFile::deleteLocalFile()
Chris@211 355 {
Chris@211 356 // std::cerr << "RemoteFile::deleteLocalFile" << std::endl;
Chris@211 357
Chris@211 358 cleanup();
Chris@211 359
Chris@211 360 if (m_localFilename == "") return;
Chris@211 361
Chris@211 362 m_fileCreationMutex.lock();
Chris@211 363
Chris@211 364 if (!QFile(m_localFilename).remove()) {
Chris@211 365 std::cerr << "RemoteFile::deleteLocalFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@211 366 } else {
Chris@211 367 m_localFilename = "";
Chris@211 368 }
Chris@211 369
Chris@211 370 m_fileCreationMutex.unlock();
Chris@211 371
Chris@208 372 m_done = true;
Chris@208 373 }
Chris@208 374
Chris@210 375 void
Chris@210 376 RemoteFile::showProgressDialog()
Chris@210 377 {
Chris@210 378 if (m_progressDialog) m_progressDialog->show();
Chris@210 379 }
Chris@210 380
Chris@208 381 QString
Chris@208 382 RemoteFile::createLocalFile(QUrl url)
Chris@208 383 {
Chris@208 384 QDir dir;
Chris@208 385 try {
Chris@208 386 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 387 } catch (DirectoryCreationFailed f) {
Chris@208 388 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@208 389 return "";
Chris@208 390 }
Chris@208 391
Chris@208 392 QString filepart = url.path().section('/', -1, -1,
Chris@208 393 QString::SectionSkipEmpty);
Chris@208 394
Chris@208 395 QString extension = filepart.section('.', -1);
Chris@208 396 QString base = filepart;
Chris@208 397 if (extension != "") {
Chris@208 398 base = base.left(base.length() - extension.length() - 1);
Chris@208 399 }
Chris@208 400 if (base == "") base = "remote";
Chris@208 401
Chris@208 402 QString filename;
Chris@208 403
Chris@208 404 if (extension == "") {
Chris@208 405 filename = base;
Chris@208 406 } else {
Chris@208 407 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 408 }
Chris@208 409
Chris@208 410 QString filepath(dir.filePath(filename));
Chris@208 411
Chris@208 412 std::cerr << "RemoteFile::createLocalFile: URL is \"" << url.toString().toStdString() << "\", dir is \"" << dir.path().toStdString() << "\", base \"" << base.toStdString() << "\", extension \"" << extension.toStdString() << "\", filebase \"" << filename.toStdString() << "\", filename \"" << filepath.toStdString() << "\"" << std::endl;
Chris@208 413
Chris@208 414 m_fileCreationMutex.lock();
Chris@208 415 ++m_count;
Chris@208 416
Chris@208 417 if (QFileInfo(filepath).exists() ||
Chris@208 418 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 419
Chris@208 420 std::cerr << "RemoteFile::createLocalFile: Failed to create local file \""
Chris@208 421 << filepath.toStdString() << "\" for URL \""
Chris@208 422 << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@208 423
Chris@208 424
Chris@208 425 if (extension == "") {
Chris@208 426 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 427 } else {
Chris@208 428 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 429 }
Chris@208 430 filepath = dir.filePath(filename);
Chris@208 431
Chris@208 432 if (QFileInfo(filepath).exists() ||
Chris@208 433 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 434
Chris@208 435 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \""
Chris@208 436 << filepath.toStdString() << "\" for URL \""
Chris@208 437 << url.toString().toStdString() << "\" (or file already exists)" << std::endl;
Chris@208 438
Chris@208 439 m_fileCreationMutex.unlock();
Chris@208 440 return "";
Chris@208 441 }
Chris@208 442 }
Chris@208 443
Chris@208 444 m_fileCreationMutex.unlock();
Chris@208 445
Chris@208 446 std::cerr << "RemoteFile::createLocalFile: url "
Chris@208 447 << url.toString().toStdString() << " -> local filename "
Chris@208 448 << filepath.toStdString() << std::endl;
Chris@208 449
Chris@208 450 return filepath;
Chris@208 451 }