annotate data/fileio/FileFinder.cpp @ 316:3a6725f285d6

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