annotate data/fileio/RemoteFile.cpp @ 295:a2dc34ce146a

* Window should be centred on its nominal time. I'm not sure what the reasoning was behind the previous formulations of these two lines.
author Chris Cannam
date Thu, 06 Sep 2007 15:14:47 +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 }