annotate widgets/FileFinder.cpp @ 454:e2a40fdadd8c

Various fixes: * Fix handling of HTTP redirects (avoiding crashes... I hope) * Fix failure to delete FFT models when a feature extraction model transformer was abandoned (also a cause of crashes in the past) * Fix deadlock when said transform was abandoned before its source model was ready because the session was being cleared (and so the source model would never be ready)
author Chris Cannam
date Fri, 28 Nov 2008 13:36:13 +0000
parents 33b7f5e54d60
children 035d62c4cddf
rev   line source
Chris@378 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@378 2
Chris@378 3 /*
Chris@378 4 Sonic Visualiser
Chris@378 5 An audio file viewer and annotation editor.
Chris@378 6 Centre for Digital Music, Queen Mary, University of London.
Chris@378 7 This file copyright 2007 QMUL.
Chris@378 8
Chris@378 9 This program is free software; you can redistribute it and/or
Chris@378 10 modify it under the terms of the GNU General Public License as
Chris@378 11 published by the Free Software Foundation; either version 2 of the
Chris@378 12 License, or (at your option) any later version. See the file
Chris@378 13 COPYING included with this distribution for more information.
Chris@378 14 */
Chris@378 15
Chris@378 16 #include "FileFinder.h"
Chris@378 17 #include "data/fileio/FileSource.h"
Chris@378 18 #include "data/fileio/AudioFileReaderFactory.h"
Chris@378 19 #include "data/fileio/DataFileReaderFactory.h"
Chris@410 20 #include "rdf/RDFImporter.h"
Chris@378 21
Chris@378 22 #include <QFileInfo>
Chris@378 23 #include <QMessageBox>
Chris@378 24 #include <QFileDialog>
Chris@378 25 #include <QInputDialog>
Chris@378 26 #include <QImageReader>
Chris@378 27 #include <QSettings>
Chris@378 28
Chris@378 29 #include <iostream>
Chris@378 30
Chris@378 31 FileFinder *
Chris@378 32 FileFinder::m_instance = 0;
Chris@378 33
Chris@378 34 FileFinder::FileFinder() :
Chris@378 35 m_lastLocatedLocation("")
Chris@378 36 {
Chris@378 37 }
Chris@378 38
Chris@378 39 FileFinder::~FileFinder()
Chris@378 40 {
Chris@378 41 }
Chris@378 42
Chris@378 43 FileFinder *
Chris@378 44 FileFinder::getInstance()
Chris@378 45 {
Chris@378 46 if (m_instance == 0) {
Chris@378 47 m_instance = new FileFinder();
Chris@378 48 }
Chris@378 49 return m_instance;
Chris@378 50 }
Chris@378 51
Chris@378 52 QString
Chris@378 53 FileFinder::getOpenFileName(FileType type, QString fallbackLocation)
Chris@378 54 {
Chris@378 55 QString settingsKey;
Chris@378 56 QString lastPath = fallbackLocation;
Chris@378 57
Chris@378 58 QString title = tr("Select file");
Chris@378 59 QString filter = tr("All files (*.*)");
Chris@378 60
Chris@378 61 switch (type) {
Chris@378 62
Chris@378 63 case SessionFile:
Chris@378 64 settingsKey = "sessionpath";
Chris@378 65 title = tr("Select a session file");
Chris@378 66 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@378 67 break;
Chris@378 68
Chris@378 69 case AudioFile:
Chris@378 70 settingsKey = "audiopath";
Chris@378 71 title = "Select an audio file";
Chris@378 72 filter = tr("Audio files (%1)\nAll files (*.*)")
Chris@378 73 .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@378 74 break;
Chris@378 75
Chris@378 76 case LayerFile:
Chris@378 77 settingsKey = "layerpath";
Chris@410 78 filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%3)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()).arg(RDFImporter::getKnownExtensions()).arg(RDFImporter::getKnownExtensions());
Chris@378 79 break;
Chris@378 80
Chris@378 81 case LayerFileNoMidi:
Chris@378 82 settingsKey = "layerpath";
Chris@410 83 filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%3)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()).arg(RDFImporter::getKnownExtensions()).arg(RDFImporter::getKnownExtensions());
Chris@378 84 break;
Chris@378 85
Chris@378 86 case SessionOrAudioFile:
Chris@378 87 settingsKey = "lastpath";
Chris@378 88 filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)")
Chris@378 89 .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@378 90 break;
Chris@378 91
Chris@378 92 case ImageFile:
Chris@378 93 settingsKey = "imagepath";
Chris@378 94 {
Chris@378 95 QStringList fmts;
Chris@378 96 QList<QByteArray> formats = QImageReader::supportedImageFormats();
Chris@378 97 for (QList<QByteArray>::iterator i = formats.begin();
Chris@378 98 i != formats.end(); ++i) {
Chris@378 99 fmts.push_back(QString("*.%1")
Chris@378 100 .arg(QString::fromLocal8Bit(*i).toLower()));
Chris@378 101 }
Chris@378 102 filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" "));
Chris@378 103 }
Chris@378 104 break;
Chris@378 105
Chris@378 106 case AnyFile:
Chris@378 107 settingsKey = "lastpath";
Chris@410 108 filter = tr("All supported files (*.sv %1 %2 %3)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)")
Chris@378 109 .arg(AudioFileReaderFactory::getKnownExtensions())
Chris@410 110 .arg(DataFileReaderFactory::getKnownExtensions())
Chris@410 111 .arg(RDFImporter::getKnownExtensions());
Chris@378 112 break;
Chris@378 113 };
Chris@378 114
Chris@378 115 if (lastPath == "") {
Chris@378 116 char *home = getenv("HOME");
Chris@378 117 if (home) lastPath = home;
Chris@378 118 else lastPath = ".";
Chris@378 119 } else if (QFileInfo(lastPath).isDir()) {
Chris@378 120 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@378 121 } else {
Chris@378 122 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@378 123 }
Chris@378 124
Chris@378 125 QSettings settings;
Chris@378 126 settings.beginGroup("FileFinder");
Chris@378 127 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@378 128
Chris@378 129 QString path = "";
Chris@378 130
Chris@378 131 // Use our own QFileDialog just for symmetry with getSaveFileName below
Chris@378 132
Chris@378 133 QFileDialog dialog;
Chris@378 134 dialog.setFilters(filter.split('\n'));
Chris@378 135 dialog.setWindowTitle(title);
Chris@378 136 dialog.setDirectory(lastPath);
Chris@378 137
Chris@378 138 dialog.setAcceptMode(QFileDialog::AcceptOpen);
Chris@378 139 dialog.setFileMode(QFileDialog::ExistingFile);
Chris@378 140
Chris@378 141 if (dialog.exec()) {
Chris@378 142 QStringList files = dialog.selectedFiles();
Chris@378 143 if (!files.empty()) path = *files.begin();
Chris@378 144
Chris@378 145 QFileInfo fi(path);
Chris@378 146
Chris@378 147 if (!fi.exists()) {
Chris@378 148
Chris@378 149 QMessageBox::critical(0, tr("File does not exist"),
Chris@378 150 tr("File \"%1\" does not exist").arg(path));
Chris@378 151 path = "";
Chris@378 152
Chris@378 153 } else if (!fi.isReadable()) {
Chris@378 154
Chris@378 155 QMessageBox::critical(0, tr("File is not readable"),
Chris@378 156 tr("File \"%1\" can not be read").arg(path));
Chris@378 157 path = "";
Chris@378 158
Chris@378 159 } else if (fi.isDir()) {
Chris@378 160
Chris@378 161 QMessageBox::critical(0, tr("Directory selected"),
Chris@378 162 tr("File \"%1\" is a directory").arg(path));
Chris@378 163 path = "";
Chris@378 164
Chris@378 165 } else if (!fi.isFile()) {
Chris@378 166
Chris@378 167 QMessageBox::critical(0, tr("Non-file selected"),
Chris@378 168 tr("Path \"%1\" is not a file").arg(path));
Chris@378 169 path = "";
Chris@378 170
Chris@378 171 } else if (fi.size() == 0) {
Chris@378 172
Chris@378 173 QMessageBox::critical(0, tr("File is empty"),
Chris@378 174 tr("File \"%1\" is empty").arg(path));
Chris@378 175 path = "";
Chris@378 176 }
Chris@378 177 }
Chris@378 178
Chris@378 179 if (path != "") {
Chris@378 180 settings.setValue(settingsKey,
Chris@378 181 QFileInfo(path).absoluteDir().canonicalPath());
Chris@378 182 }
Chris@378 183
Chris@378 184 return path;
Chris@378 185 }
Chris@378 186
Chris@378 187 QString
Chris@378 188 FileFinder::getSaveFileName(FileType type, QString fallbackLocation)
Chris@378 189 {
Chris@378 190 QString settingsKey;
Chris@378 191 QString lastPath = fallbackLocation;
Chris@378 192
Chris@378 193 QString title = tr("Select file");
Chris@378 194 QString filter = tr("All files (*.*)");
Chris@378 195
Chris@378 196 switch (type) {
Chris@378 197
Chris@378 198 case SessionFile:
Chris@378 199 settingsKey = "savesessionpath";
Chris@378 200 title = tr("Select a session file");
Chris@378 201 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@378 202 break;
Chris@378 203
Chris@378 204 case AudioFile:
Chris@378 205 settingsKey = "saveaudiopath";
Chris@378 206 title = "Select an audio file";
Chris@378 207 title = tr("Select a file to export to");
Chris@378 208 filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
Chris@378 209 break;
Chris@378 210
Chris@378 211 case LayerFile:
Chris@378 212 settingsKey = "savelayerpath";
Chris@378 213 title = tr("Select a file to export to");
Chris@378 214 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)");
Chris@378 215 break;
Chris@378 216
Chris@378 217 case LayerFileNoMidi:
Chris@378 218 settingsKey = "savelayerpath";
Chris@378 219 title = tr("Select a file to export to");
Chris@378 220 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
Chris@378 221 break;
Chris@378 222
Chris@378 223 case SessionOrAudioFile:
Chris@378 224 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl;
Chris@378 225 abort();
Chris@378 226
Chris@378 227 case ImageFile:
Chris@378 228 settingsKey = "saveimagepath";
Chris@378 229 title = tr("Select a file to export to");
Chris@378 230 filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
Chris@378 231 break;
Chris@378 232
Chris@378 233 case AnyFile:
Chris@378 234 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl;
Chris@378 235 abort();
Chris@378 236 };
Chris@378 237
Chris@378 238 if (lastPath == "") {
Chris@378 239 char *home = getenv("HOME");
Chris@378 240 if (home) lastPath = home;
Chris@378 241 else lastPath = ".";
Chris@378 242 } else if (QFileInfo(lastPath).isDir()) {
Chris@378 243 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@378 244 } else {
Chris@378 245 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@378 246 }
Chris@378 247
Chris@378 248 QSettings settings;
Chris@378 249 settings.beginGroup("FileFinder");
Chris@378 250 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@378 251
Chris@378 252 QString path = "";
Chris@378 253
Chris@378 254 // Use our own QFileDialog instead of static functions, as we may
Chris@378 255 // need to adjust the file extension based on the selected filter
Chris@378 256
Chris@378 257 QFileDialog dialog;
Chris@378 258 dialog.setFilters(filter.split('\n'));
Chris@378 259 dialog.setWindowTitle(title);
Chris@378 260 dialog.setDirectory(lastPath);
Chris@378 261
Chris@378 262 dialog.setAcceptMode(QFileDialog::AcceptSave);
Chris@378 263 dialog.setFileMode(QFileDialog::AnyFile);
Chris@378 264 dialog.setConfirmOverwrite(false); // we'll do that
Chris@378 265
Chris@378 266 if (type == SessionFile) {
Chris@378 267 dialog.setDefaultSuffix("sv");
Chris@378 268 } else if (type == AudioFile) {
Chris@378 269 dialog.setDefaultSuffix("wav");
Chris@378 270 } else if (type == ImageFile) {
Chris@378 271 dialog.setDefaultSuffix("png");
Chris@378 272 }
Chris@378 273
Chris@378 274 bool good = false;
Chris@378 275
Chris@378 276 while (!good) {
Chris@378 277
Chris@378 278 path = "";
Chris@378 279
Chris@378 280 if (!dialog.exec()) break;
Chris@378 281
Chris@378 282 QStringList files = dialog.selectedFiles();
Chris@378 283 if (files.empty()) break;
Chris@378 284 path = *files.begin();
Chris@378 285
Chris@378 286 QFileInfo fi(path);
Chris@378 287
Chris@378 288 std::cerr << "type = " << type << ", suffix = " << fi.suffix().toStdString() << std::endl;
Chris@378 289
Chris@378 290 if ((type == LayerFile || type == LayerFileNoMidi)
Chris@378 291 && fi.suffix() == "") {
Chris@378 292 QString expectedExtension;
Chris@378 293 QString selectedFilter = dialog.selectedFilter();
Chris@378 294 if (selectedFilter.contains(".svl")) {
Chris@378 295 expectedExtension = "svl";
Chris@378 296 } else if (selectedFilter.contains(".txt")) {
Chris@378 297 expectedExtension = "txt";
Chris@378 298 } else if (selectedFilter.contains(".csv")) {
Chris@378 299 expectedExtension = "csv";
Chris@378 300 } else if (selectedFilter.contains(".mid")) {
Chris@378 301 expectedExtension = "mid";
Chris@378 302 }
Chris@378 303 std::cerr << "expected extension = " << expectedExtension.toStdString() << std::endl;
Chris@378 304 if (expectedExtension != "") {
Chris@378 305 path = QString("%1.%2").arg(path).arg(expectedExtension);
Chris@378 306 fi = QFileInfo(path);
Chris@378 307 }
Chris@378 308 }
Chris@378 309
Chris@378 310 if (fi.isDir()) {
Chris@378 311 QMessageBox::critical(0, tr("Directory selected"),
Chris@378 312 tr("File \"%1\" is a directory").arg(path));
Chris@378 313 continue;
Chris@378 314 }
Chris@378 315
Chris@378 316 if (fi.exists()) {
Chris@378 317 if (QMessageBox::question(0, tr("File exists"),
Chris@378 318 tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
Chris@378 319 QMessageBox::Ok,
Chris@378 320 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@378 321 continue;
Chris@378 322 }
Chris@378 323 }
Chris@378 324
Chris@378 325 good = true;
Chris@378 326 }
Chris@378 327
Chris@378 328 if (path != "") {
Chris@378 329 settings.setValue(settingsKey,
Chris@378 330 QFileInfo(path).absoluteDir().canonicalPath());
Chris@378 331 }
Chris@378 332
Chris@378 333 return path;
Chris@378 334 }
Chris@378 335
Chris@378 336 void
Chris@378 337 FileFinder::registerLastOpenedFilePath(FileType type, QString path)
Chris@378 338 {
Chris@378 339 QString settingsKey;
Chris@378 340
Chris@378 341 switch (type) {
Chris@378 342 case SessionFile:
Chris@378 343 settingsKey = "sessionpath";
Chris@378 344 break;
Chris@378 345
Chris@378 346 case AudioFile:
Chris@378 347 settingsKey = "audiopath";
Chris@378 348 break;
Chris@378 349
Chris@378 350 case LayerFile:
Chris@378 351 settingsKey = "layerpath";
Chris@378 352 break;
Chris@378 353
Chris@378 354 case LayerFileNoMidi:
Chris@378 355 settingsKey = "layerpath";
Chris@378 356 break;
Chris@378 357
Chris@378 358 case SessionOrAudioFile:
Chris@378 359 settingsKey = "lastpath";
Chris@378 360 break;
Chris@378 361
Chris@378 362 case ImageFile:
Chris@378 363 settingsKey = "imagepath";
Chris@378 364 break;
Chris@378 365
Chris@378 366 case AnyFile:
Chris@378 367 settingsKey = "lastpath";
Chris@378 368 break;
Chris@378 369 }
Chris@378 370
Chris@378 371 if (path != "") {
Chris@378 372 QSettings settings;
Chris@378 373 settings.beginGroup("FileFinder");
Chris@378 374 path = QFileInfo(path).absoluteDir().canonicalPath();
Chris@378 375 settings.setValue(settingsKey, path);
Chris@378 376 settings.setValue("lastpath", path);
Chris@378 377 }
Chris@378 378 }
Chris@378 379
Chris@378 380 QString
Chris@378 381 FileFinder::find(FileType type, QString location, QString lastKnownLocation)
Chris@378 382 {
Chris@378 383 if (FileSource::canHandleScheme(location)) {
Chris@378 384 if (FileSource(location).isAvailable()) {
Chris@378 385 std::cerr << "FileFinder::find: ok, it's available... returning" << std::endl;
Chris@378 386 return location;
Chris@378 387 }
Chris@378 388 }
Chris@378 389
Chris@378 390 if (QFileInfo(location).exists()) return location;
Chris@378 391
Chris@378 392 QString foundAt = "";
Chris@378 393
Chris@378 394 if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
Chris@378 395 return foundAt;
Chris@378 396 }
Chris@378 397
Chris@378 398 if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
Chris@378 399 return foundAt;
Chris@378 400 }
Chris@378 401
Chris@378 402 return locateInteractive(type, location);
Chris@378 403 }
Chris@378 404
Chris@378 405 QString
Chris@378 406 FileFinder::findRelative(QString location, QString relativeTo)
Chris@378 407 {
Chris@378 408 if (relativeTo == "") return "";
Chris@378 409
Chris@378 410 std::cerr << "Looking for \"" << location.toStdString() << "\" next to \""
Chris@378 411 << relativeTo.toStdString() << "\"..." << std::endl;
Chris@378 412
Chris@378 413 QString fileName;
Chris@378 414 QString resolved;
Chris@378 415
Chris@378 416 if (FileSource::isRemote(location)) {
Chris@378 417 fileName = QUrl(location).path().section('/', -1, -1,
Chris@378 418 QString::SectionSkipEmpty);
Chris@378 419 } else {
Chris@378 420 if (QUrl(location).scheme() == "file") {
Chris@378 421 location = QUrl(location).toLocalFile();
Chris@378 422 }
Chris@378 423 fileName = QFileInfo(location).fileName();
Chris@378 424 }
Chris@378 425
Chris@378 426 if (FileSource::isRemote(relativeTo)) {
Chris@378 427 resolved = QUrl(relativeTo).resolved(fileName).toString();
Chris@378 428 if (!FileSource(resolved).isAvailable()) resolved = "";
Chris@378 429 std::cerr << "resolved: " << resolved.toStdString() << std::endl;
Chris@378 430 } else {
Chris@378 431 if (QUrl(relativeTo).scheme() == "file") {
Chris@378 432 relativeTo = QUrl(relativeTo).toLocalFile();
Chris@378 433 }
Chris@378 434 resolved = QFileInfo(relativeTo).dir().filePath(fileName);
Chris@378 435 if (!QFileInfo(resolved).exists() ||
Chris@378 436 !QFileInfo(resolved).isFile() ||
Chris@378 437 !QFileInfo(resolved).isReadable()) {
Chris@378 438 resolved = "";
Chris@378 439 }
Chris@378 440 }
Chris@378 441
Chris@378 442 return resolved;
Chris@378 443 }
Chris@378 444
Chris@378 445 QString
Chris@378 446 FileFinder::locateInteractive(FileType type, QString thing)
Chris@378 447 {
Chris@378 448 QString question;
Chris@378 449 if (type == AudioFile) {
Chris@378 450 question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?");
Chris@378 451 } else {
Chris@378 452 question = tr("File \"%1\" could not be opened.\nDo you want to locate it?");
Chris@378 453 }
Chris@378 454
Chris@378 455 QString path = "";
Chris@378 456 bool done = false;
Chris@378 457
Chris@378 458 while (!done) {
Chris@378 459
Chris@378 460 int rv = QMessageBox::question
Chris@378 461 (0,
Chris@378 462 tr("Failed to open file"),
Chris@378 463 question.arg(thing),
Chris@378 464 tr("Locate file..."),
Chris@378 465 tr("Use URL..."),
Chris@378 466 tr("Cancel"),
Chris@378 467 0, 2);
Chris@378 468
Chris@378 469 switch (rv) {
Chris@378 470
Chris@378 471 case 0: // Locate file
Chris@378 472
Chris@378 473 if (QFileInfo(thing).dir().exists()) {
Chris@378 474 path = QFileInfo(thing).dir().canonicalPath();
Chris@378 475 }
Chris@378 476
Chris@378 477 path = getOpenFileName(type, path);
Chris@378 478 done = (path != "");
Chris@378 479 break;
Chris@378 480
Chris@378 481 case 1: // Use URL
Chris@378 482 {
Chris@378 483 bool ok = false;
Chris@378 484 path = QInputDialog::getText
Chris@378 485 (0, tr("Use URL"),
Chris@378 486 tr("Please enter the URL to use for this file:"),
Chris@378 487 QLineEdit::Normal, "", &ok);
Chris@378 488
Chris@378 489 if (ok && path != "") {
Chris@378 490 if (FileSource(path).isAvailable()) {
Chris@378 491 done = true;
Chris@378 492 } else {
Chris@378 493 QMessageBox::critical
Chris@378 494 (0, tr("Failed to open location"),
Chris@378 495 tr("URL \"%1\" could not be opened").arg(path));
Chris@378 496 path = "";
Chris@378 497 }
Chris@378 498 }
Chris@378 499 break;
Chris@378 500 }
Chris@378 501
Chris@378 502 case 2: // Cancel
Chris@378 503 path = "";
Chris@378 504 done = true;
Chris@378 505 break;
Chris@378 506 }
Chris@378 507 }
Chris@378 508
Chris@378 509 if (path != "") m_lastLocatedLocation = path;
Chris@378 510 return path;
Chris@378 511 }
Chris@378 512
Chris@378 513