annotate data/fileio/FileFinder.cpp @ 211:e2bbb58e6df6

Several changes related to referring to remote URLs for sessions and files: * Pull file dialog wrapper functions out from MainWindow into FileFinder * If a file referred to in a session is not found at its expected location, try a few other alternatives (same location as the session file or same location as the last audio file) before asking the user to locate it * Allow user to give a URL when locating an audio file, not just locate on the filesystem * Make wave file models remember the "original" location (e.g. URL) of the audio file, not just the actual location from which the data was loaded (e.g. local copy of that URL) -- when saving a session, use the original location so as not to refer to a temporary file * Clean up incompletely-downloaded local copies of files
author Chris Cannam
date Thu, 11 Jan 2007 13:29:58 +0000
parents a06afefe45ee
children 40db5491bcf8
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@211 25 #include <QSettings>
Chris@210 26
Chris@211 27 #include <iostream>
Chris@210 28
Chris@211 29 FileFinder *
Chris@211 30 FileFinder::m_instance = 0;
Chris@211 31
Chris@211 32 FileFinder::FileFinder() :
Chris@210 33 m_lastLocatedLocation("")
Chris@210 34 {
Chris@210 35 }
Chris@210 36
Chris@210 37 FileFinder::~FileFinder()
Chris@210 38 {
Chris@210 39 }
Chris@210 40
Chris@211 41 FileFinder *
Chris@211 42 FileFinder::getInstance()
Chris@211 43 {
Chris@211 44 if (m_instance == 0) {
Chris@211 45 m_instance = new FileFinder();
Chris@211 46 }
Chris@211 47 return m_instance;
Chris@211 48 }
Chris@211 49
Chris@210 50 QString
Chris@211 51 FileFinder::getOpenFileName(FileType type, QString fallbackLocation)
Chris@210 52 {
Chris@211 53 QString settingsKey;
Chris@211 54 QString lastPath = fallbackLocation;
Chris@211 55
Chris@211 56 QString title = tr("Select file");
Chris@211 57 QString filter = tr("All files (*.*)");
Chris@210 58
Chris@211 59 switch (type) {
Chris@210 60
Chris@211 61 case SessionFile:
Chris@211 62 settingsKey = "sessionpath";
Chris@211 63 title = tr("Select a session file");
Chris@211 64 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@211 65 break;
Chris@210 66
Chris@211 67 case AudioFile:
Chris@211 68 settingsKey = "audiopath";
Chris@211 69 title = "Select an audio file";
Chris@211 70 filter = tr("Audio files (%1)\nAll files (*.*)")
Chris@211 71 .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@211 72 break;
Chris@211 73
Chris@211 74 case LayerFile:
Chris@211 75 settingsKey = "layerpath";
Chris@211 76 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 77 break;
Chris@211 78
Chris@211 79 case SessionOrAudioFile:
Chris@211 80 settingsKey = "lastpath";
Chris@211 81 filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)")
Chris@211 82 .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@211 83 break;
Chris@211 84
Chris@211 85 case AnyFile:
Chris@211 86 settingsKey = "lastpath";
Chris@211 87 filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)")
Chris@211 88 .arg(AudioFileReaderFactory::getKnownExtensions())
Chris@211 89 .arg(DataFileReaderFactory::getKnownExtensions());
Chris@211 90 break;
Chris@211 91 };
Chris@211 92
Chris@211 93 if (lastPath == "") {
Chris@211 94 char *home = getenv("HOME");
Chris@211 95 if (home) lastPath = home;
Chris@211 96 else lastPath = ".";
Chris@211 97 } else if (QFileInfo(lastPath).isDir()) {
Chris@211 98 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@211 99 } else {
Chris@211 100 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@211 101 }
Chris@211 102
Chris@211 103 QSettings settings;
Chris@211 104 settings.beginGroup("FileFinder");
Chris@211 105 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@211 106
Chris@211 107 QString path = "";
Chris@211 108
Chris@211 109 // Use our own QFileDialog just for symmetry with getSaveFileName below
Chris@211 110
Chris@211 111 QFileDialog dialog;
Chris@211 112 dialog.setFilters(filter.split('\n'));
Chris@211 113 dialog.setWindowTitle(title);
Chris@211 114 dialog.setDirectory(lastPath);
Chris@211 115
Chris@211 116 dialog.setAcceptMode(QFileDialog::AcceptOpen);
Chris@211 117 dialog.setFileMode(QFileDialog::ExistingFile);
Chris@211 118
Chris@211 119 if (dialog.exec()) {
Chris@211 120 QStringList files = dialog.selectedFiles();
Chris@211 121 if (!files.empty()) path = *files.begin();
Chris@211 122
Chris@211 123 QFileInfo fi(path);
Chris@211 124
Chris@211 125 if (!fi.exists()) {
Chris@211 126
Chris@211 127 QMessageBox::critical(0, tr("File does not exist"),
Chris@211 128 tr("File \"%1\" does not exist").arg(path));
Chris@211 129 path = "";
Chris@211 130
Chris@211 131 } else if (!fi.isReadable()) {
Chris@211 132
Chris@211 133 QMessageBox::critical(0, tr("File is not readable"),
Chris@211 134 tr("File \"%1\" can not be read").arg(path));
Chris@211 135 path = "";
Chris@211 136
Chris@211 137 } else if (fi.isDir()) {
Chris@211 138
Chris@211 139 QMessageBox::critical(0, tr("Directory selected"),
Chris@211 140 tr("File \"%1\" is a directory").arg(path));
Chris@211 141 path = "";
Chris@211 142
Chris@211 143 } else if (!fi.isFile()) {
Chris@211 144
Chris@211 145 QMessageBox::critical(0, tr("Non-file selected"),
Chris@211 146 tr("Path \"%1\" is not a file").arg(path));
Chris@211 147 path = "";
Chris@211 148
Chris@211 149 } else if (fi.size() == 0) {
Chris@211 150
Chris@211 151 QMessageBox::critical(0, tr("File is empty"),
Chris@211 152 tr("File \"%1\" is empty").arg(path));
Chris@211 153 path = "";
Chris@211 154 }
Chris@211 155 }
Chris@211 156
Chris@211 157 if (path != "") {
Chris@211 158 settings.setValue(settingsKey,
Chris@211 159 QFileInfo(path).absoluteDir().canonicalPath());
Chris@211 160 }
Chris@211 161
Chris@211 162 return path;
Chris@211 163 }
Chris@211 164
Chris@211 165 QString
Chris@211 166 FileFinder::getSaveFileName(FileType type, QString fallbackLocation)
Chris@211 167 {
Chris@211 168 QString settingsKey;
Chris@211 169 QString lastPath = fallbackLocation;
Chris@211 170
Chris@211 171 QString title = tr("Select file");
Chris@211 172 QString filter = tr("All files (*.*)");
Chris@211 173
Chris@211 174 switch (type) {
Chris@211 175
Chris@211 176 case SessionFile:
Chris@211 177 settingsKey = "savesessionpath";
Chris@211 178 title = tr("Select a session file");
Chris@211 179 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@211 180 break;
Chris@211 181
Chris@211 182 case AudioFile:
Chris@211 183 settingsKey = "saveaudiopath";
Chris@211 184 title = "Select an audio file";
Chris@211 185 title = tr("Select a file to export to");
Chris@211 186 filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
Chris@211 187 break;
Chris@211 188
Chris@211 189 case LayerFile:
Chris@211 190 settingsKey = "savelayerpath";
Chris@211 191 title = tr("Select a file to export to");
Chris@211 192 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
Chris@211 193 break;
Chris@211 194
Chris@211 195 case SessionOrAudioFile:
Chris@211 196 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl;
Chris@211 197 abort();
Chris@211 198
Chris@211 199 case AnyFile:
Chris@211 200 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl;
Chris@211 201 abort();
Chris@211 202 };
Chris@211 203
Chris@211 204 if (lastPath == "") {
Chris@211 205 char *home = getenv("HOME");
Chris@211 206 if (home) lastPath = home;
Chris@211 207 else lastPath = ".";
Chris@211 208 } else if (QFileInfo(lastPath).isDir()) {
Chris@211 209 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@211 210 } else {
Chris@211 211 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@211 212 }
Chris@211 213
Chris@211 214 QSettings settings;
Chris@211 215 settings.beginGroup("FileFinder");
Chris@211 216 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@211 217
Chris@211 218 QString path = "";
Chris@211 219
Chris@211 220 // Use our own QFileDialog instead of static functions, as we may
Chris@211 221 // need to adjust the file extension based on the selected filter
Chris@211 222
Chris@211 223 QFileDialog dialog;
Chris@211 224 dialog.setFilters(filter.split('\n'));
Chris@211 225 dialog.setWindowTitle(title);
Chris@211 226 dialog.setDirectory(lastPath);
Chris@211 227
Chris@211 228 dialog.setAcceptMode(QFileDialog::AcceptSave);
Chris@211 229 dialog.setFileMode(QFileDialog::AnyFile);
Chris@211 230 dialog.setConfirmOverwrite(false); // we'll do that
Chris@211 231
Chris@211 232 if (type == SessionFile) {
Chris@211 233 dialog.setDefaultSuffix("sv");
Chris@211 234 } else if (type == AudioFile) {
Chris@211 235 dialog.setDefaultSuffix("wav");
Chris@211 236 }
Chris@211 237
Chris@211 238 bool good = false;
Chris@211 239
Chris@211 240 while (!good) {
Chris@211 241
Chris@211 242 path = "";
Chris@211 243
Chris@211 244 if (!dialog.exec()) break;
Chris@211 245
Chris@211 246 QStringList files = dialog.selectedFiles();
Chris@211 247 if (files.empty()) break;
Chris@211 248 path = *files.begin();
Chris@211 249
Chris@211 250 QFileInfo fi(path);
Chris@211 251
Chris@211 252 if (type == LayerFile && fi.suffix() == "") {
Chris@211 253 QString expectedExtension;
Chris@211 254 QString selectedFilter = dialog.selectedFilter();
Chris@211 255 if (selectedFilter.contains(".svl")) {
Chris@211 256 expectedExtension = "svl";
Chris@211 257 } else if (selectedFilter.contains(".txt")) {
Chris@211 258 expectedExtension = "txt";
Chris@211 259 } else if (selectedFilter.contains(".csv")) {
Chris@211 260 expectedExtension = "csv";
Chris@211 261 }
Chris@211 262 if (expectedExtension != "") {
Chris@211 263 path = QString("%1.%2").arg(path).arg(expectedExtension);
Chris@211 264 fi = QFileInfo(path);
Chris@211 265 }
Chris@211 266 }
Chris@211 267
Chris@211 268 if (fi.isDir()) {
Chris@211 269 QMessageBox::critical(0, tr("Directory selected"),
Chris@211 270 tr("File \"%1\" is a directory").arg(path));
Chris@211 271 continue;
Chris@211 272 }
Chris@211 273
Chris@211 274 if (fi.exists()) {
Chris@211 275 if (QMessageBox::question(0, tr("File exists"),
Chris@211 276 tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
Chris@211 277 QMessageBox::Ok,
Chris@211 278 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@211 279 continue;
Chris@211 280 }
Chris@211 281 }
Chris@211 282
Chris@211 283 good = true;
Chris@211 284 }
Chris@211 285
Chris@211 286 if (path != "") {
Chris@211 287 settings.setValue(settingsKey,
Chris@211 288 QFileInfo(path).absoluteDir().canonicalPath());
Chris@211 289 }
Chris@211 290
Chris@211 291 return path;
Chris@211 292 }
Chris@211 293
Chris@211 294 void
Chris@211 295 FileFinder::registerLastOpenedFilePath(FileType type, QString path)
Chris@211 296 {
Chris@211 297 QString settingsKey;
Chris@211 298
Chris@211 299 switch (type) {
Chris@211 300 case SessionFile:
Chris@211 301 settingsKey = "sessionpath";
Chris@211 302 break;
Chris@211 303
Chris@211 304 case AudioFile:
Chris@211 305 settingsKey = "audiopath";
Chris@211 306 break;
Chris@211 307
Chris@211 308 case LayerFile:
Chris@211 309 settingsKey = "layerpath";
Chris@211 310 break;
Chris@211 311
Chris@211 312 case SessionOrAudioFile:
Chris@211 313 settingsKey = "lastpath";
Chris@211 314 break;
Chris@211 315
Chris@211 316 case AnyFile:
Chris@211 317 settingsKey = "lastpath";
Chris@211 318 break;
Chris@211 319 }
Chris@211 320
Chris@211 321 if (path != "") {
Chris@211 322 QSettings settings;
Chris@211 323 settings.beginGroup("FileFinder");
Chris@211 324 path = QFileInfo(path).absoluteDir().canonicalPath();
Chris@211 325 settings.setValue(settingsKey, path);
Chris@211 326 settings.setValue("lastpath", path);
Chris@211 327 }
Chris@211 328 }
Chris@211 329
Chris@211 330 QString
Chris@211 331 FileFinder::find(FileType type, QString location, QString lastKnownLocation)
Chris@211 332 {
Chris@211 333 if (QFileInfo(location).exists()) return location;
Chris@211 334
Chris@211 335 if (RemoteFile::canHandleScheme(QUrl(location))) {
Chris@211 336 RemoteFile rf(location);
Chris@211 337 bool available = rf.isAvailable();
Chris@211 338 rf.deleteLocalFile();
Chris@211 339 if (available) return location;
Chris@211 340 }
Chris@211 341
Chris@211 342 QString foundAt = "";
Chris@211 343
Chris@211 344 if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
Chris@211 345 return foundAt;
Chris@211 346 }
Chris@211 347
Chris@211 348 if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
Chris@211 349 return foundAt;
Chris@211 350 }
Chris@211 351
Chris@211 352 return locateInteractive(type, location);
Chris@211 353 }
Chris@211 354
Chris@211 355 QString
Chris@211 356 FileFinder::findRelative(QString location, QString relativeTo)
Chris@211 357 {
Chris@211 358 if (relativeTo == "") return "";
Chris@211 359
Chris@211 360 std::cerr << "Looking for \"" << location.toStdString() << "\" next to \""
Chris@211 361 << relativeTo.toStdString() << "\"..." << std::endl;
Chris@211 362
Chris@211 363 QString fileName;
Chris@211 364 QString resolved;
Chris@211 365
Chris@211 366 if (RemoteFile::canHandleScheme(QUrl(location))) {
Chris@211 367 fileName = QUrl(location).path().section('/', -1, -1,
Chris@211 368 QString::SectionSkipEmpty);
Chris@211 369 } else {
Chris@211 370 fileName = QFileInfo(location).fileName();
Chris@211 371 }
Chris@211 372
Chris@211 373 if (RemoteFile::canHandleScheme(QUrl(relativeTo))) {
Chris@211 374 resolved = QUrl(relativeTo).resolved(fileName).toString();
Chris@211 375 RemoteFile rf(resolved);
Chris@211 376 if (!rf.isAvailable()) resolved = "";
Chris@211 377 std::cerr << "resolved: " << resolved.toStdString() << std::endl;
Chris@211 378 rf.deleteLocalFile();
Chris@211 379 } else {
Chris@211 380 resolved = QFileInfo(relativeTo).dir().filePath(fileName);
Chris@211 381 if (!QFileInfo(resolved).exists() ||
Chris@211 382 !QFileInfo(resolved).isFile() ||
Chris@211 383 !QFileInfo(resolved).isReadable()) {
Chris@211 384 resolved = "";
Chris@211 385 }
Chris@211 386 }
Chris@211 387
Chris@211 388 return resolved;
Chris@211 389 }
Chris@211 390
Chris@211 391 QString
Chris@211 392 FileFinder::locateInteractive(FileType type, QString thing)
Chris@211 393 {
Chris@211 394 QString question;
Chris@211 395 if (type == AudioFile) {
Chris@211 396 question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?");
Chris@211 397 } else {
Chris@211 398 question = tr("File \"%1\" could not be opened.\nDo you want to locate it?");
Chris@211 399 }
Chris@211 400
Chris@211 401 QString path = "";
Chris@211 402 bool done = false;
Chris@211 403
Chris@211 404 while (!done) {
Chris@211 405
Chris@211 406 int rv = QMessageBox::question
Chris@211 407 (0,
Chris@211 408 tr("Failed to open file"),
Chris@211 409 question.arg(thing),
Chris@211 410 tr("Locate file..."),
Chris@211 411 tr("Use URL..."),
Chris@211 412 tr("Cancel"),
Chris@211 413 0, 2);
Chris@211 414
Chris@211 415 switch (rv) {
Chris@211 416
Chris@211 417 case 0: // Locate file
Chris@211 418
Chris@211 419 if (QFileInfo(thing).dir().exists()) {
Chris@211 420 path = QFileInfo(thing).dir().canonicalPath();
Chris@211 421 }
Chris@211 422
Chris@211 423 path = getOpenFileName(type, path);
Chris@211 424 done = (path != "");
Chris@211 425 break;
Chris@211 426
Chris@211 427 case 1: // Use URL
Chris@211 428 {
Chris@211 429 bool ok = false;
Chris@211 430 path = QInputDialog::getText
Chris@211 431 (0, tr("Use URL"),
Chris@211 432 tr("Please enter the URL to use for this file:"),
Chris@211 433 QLineEdit::Normal, "", &ok);
Chris@211 434
Chris@211 435 if (ok && path != "") {
Chris@211 436 RemoteFile rf(path);
Chris@211 437 if (rf.isAvailable()) {
Chris@211 438 done = true;
Chris@211 439 } else {
Chris@211 440 QMessageBox::critical
Chris@211 441 (0, tr("Failed to open location"),
Chris@211 442 tr("URL \"%1\" could not be opened").arg(path));
Chris@211 443 path = "";
Chris@211 444 }
Chris@211 445 rf.deleteLocalFile();
Chris@211 446 }
Chris@211 447 break;
Chris@211 448 }
Chris@211 449
Chris@211 450 case 2: // Cancel
Chris@211 451 path = "";
Chris@211 452 done = true;
Chris@211 453 break;
Chris@210 454 }
Chris@210 455 }
Chris@210 456
Chris@211 457 if (path != "") m_lastLocatedLocation = path;
Chris@211 458 return path;
Chris@210 459 }
Chris@210 460
Chris@210 461