annotate data/fileio/FileFinder.cpp @ 263:71dfc6ab3b54

* Threaded mp3/ogg file reading. Not activated yet, as it doesn't work in context (SV needs to know the duration of its main model at the outset)
author Chris Cannam
date Thu, 24 May 2007 16:20:22 +0000
parents 40db5491bcf8
children 20028c634494
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@250 85 case ImageFile:
Chris@250 86 settingsKey = "imagepath";
Chris@250 87 filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
Chris@250 88 break;
Chris@250 89
Chris@211 90 case AnyFile:
Chris@211 91 settingsKey = "lastpath";
Chris@211 92 filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)")
Chris@211 93 .arg(AudioFileReaderFactory::getKnownExtensions())
Chris@211 94 .arg(DataFileReaderFactory::getKnownExtensions());
Chris@211 95 break;
Chris@211 96 };
Chris@211 97
Chris@211 98 if (lastPath == "") {
Chris@211 99 char *home = getenv("HOME");
Chris@211 100 if (home) lastPath = home;
Chris@211 101 else lastPath = ".";
Chris@211 102 } else if (QFileInfo(lastPath).isDir()) {
Chris@211 103 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@211 104 } else {
Chris@211 105 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@211 106 }
Chris@211 107
Chris@211 108 QSettings settings;
Chris@211 109 settings.beginGroup("FileFinder");
Chris@211 110 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@211 111
Chris@211 112 QString path = "";
Chris@211 113
Chris@211 114 // Use our own QFileDialog just for symmetry with getSaveFileName below
Chris@211 115
Chris@211 116 QFileDialog dialog;
Chris@211 117 dialog.setFilters(filter.split('\n'));
Chris@211 118 dialog.setWindowTitle(title);
Chris@211 119 dialog.setDirectory(lastPath);
Chris@211 120
Chris@211 121 dialog.setAcceptMode(QFileDialog::AcceptOpen);
Chris@211 122 dialog.setFileMode(QFileDialog::ExistingFile);
Chris@211 123
Chris@211 124 if (dialog.exec()) {
Chris@211 125 QStringList files = dialog.selectedFiles();
Chris@211 126 if (!files.empty()) path = *files.begin();
Chris@211 127
Chris@211 128 QFileInfo fi(path);
Chris@211 129
Chris@211 130 if (!fi.exists()) {
Chris@211 131
Chris@211 132 QMessageBox::critical(0, tr("File does not exist"),
Chris@211 133 tr("File \"%1\" does not exist").arg(path));
Chris@211 134 path = "";
Chris@211 135
Chris@211 136 } else if (!fi.isReadable()) {
Chris@211 137
Chris@211 138 QMessageBox::critical(0, tr("File is not readable"),
Chris@211 139 tr("File \"%1\" can not be read").arg(path));
Chris@211 140 path = "";
Chris@211 141
Chris@211 142 } else if (fi.isDir()) {
Chris@211 143
Chris@211 144 QMessageBox::critical(0, tr("Directory selected"),
Chris@211 145 tr("File \"%1\" is a directory").arg(path));
Chris@211 146 path = "";
Chris@211 147
Chris@211 148 } else if (!fi.isFile()) {
Chris@211 149
Chris@211 150 QMessageBox::critical(0, tr("Non-file selected"),
Chris@211 151 tr("Path \"%1\" is not a file").arg(path));
Chris@211 152 path = "";
Chris@211 153
Chris@211 154 } else if (fi.size() == 0) {
Chris@211 155
Chris@211 156 QMessageBox::critical(0, tr("File is empty"),
Chris@211 157 tr("File \"%1\" is empty").arg(path));
Chris@211 158 path = "";
Chris@211 159 }
Chris@211 160 }
Chris@211 161
Chris@211 162 if (path != "") {
Chris@211 163 settings.setValue(settingsKey,
Chris@211 164 QFileInfo(path).absoluteDir().canonicalPath());
Chris@211 165 }
Chris@211 166
Chris@211 167 return path;
Chris@211 168 }
Chris@211 169
Chris@211 170 QString
Chris@211 171 FileFinder::getSaveFileName(FileType type, QString fallbackLocation)
Chris@211 172 {
Chris@211 173 QString settingsKey;
Chris@211 174 QString lastPath = fallbackLocation;
Chris@211 175
Chris@211 176 QString title = tr("Select file");
Chris@211 177 QString filter = tr("All files (*.*)");
Chris@211 178
Chris@211 179 switch (type) {
Chris@211 180
Chris@211 181 case SessionFile:
Chris@211 182 settingsKey = "savesessionpath";
Chris@211 183 title = tr("Select a session file");
Chris@211 184 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@211 185 break;
Chris@211 186
Chris@211 187 case AudioFile:
Chris@211 188 settingsKey = "saveaudiopath";
Chris@211 189 title = "Select an audio file";
Chris@211 190 title = tr("Select a file to export to");
Chris@211 191 filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
Chris@211 192 break;
Chris@211 193
Chris@211 194 case LayerFile:
Chris@211 195 settingsKey = "savelayerpath";
Chris@211 196 title = tr("Select a file to export to");
Chris@211 197 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
Chris@211 198 break;
Chris@211 199
Chris@211 200 case SessionOrAudioFile:
Chris@211 201 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl;
Chris@211 202 abort();
Chris@211 203
Chris@250 204 case ImageFile:
Chris@250 205 settingsKey = "saveimagepath";
Chris@250 206 title = tr("Select a file to export to");
Chris@250 207 filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
Chris@250 208 break;
Chris@250 209
Chris@211 210 case AnyFile:
Chris@211 211 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl;
Chris@211 212 abort();
Chris@211 213 };
Chris@211 214
Chris@211 215 if (lastPath == "") {
Chris@211 216 char *home = getenv("HOME");
Chris@211 217 if (home) lastPath = home;
Chris@211 218 else lastPath = ".";
Chris@211 219 } else if (QFileInfo(lastPath).isDir()) {
Chris@211 220 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@211 221 } else {
Chris@211 222 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@211 223 }
Chris@211 224
Chris@211 225 QSettings settings;
Chris@211 226 settings.beginGroup("FileFinder");
Chris@211 227 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@211 228
Chris@211 229 QString path = "";
Chris@211 230
Chris@211 231 // Use our own QFileDialog instead of static functions, as we may
Chris@211 232 // need to adjust the file extension based on the selected filter
Chris@211 233
Chris@211 234 QFileDialog dialog;
Chris@211 235 dialog.setFilters(filter.split('\n'));
Chris@211 236 dialog.setWindowTitle(title);
Chris@211 237 dialog.setDirectory(lastPath);
Chris@211 238
Chris@211 239 dialog.setAcceptMode(QFileDialog::AcceptSave);
Chris@211 240 dialog.setFileMode(QFileDialog::AnyFile);
Chris@211 241 dialog.setConfirmOverwrite(false); // we'll do that
Chris@211 242
Chris@211 243 if (type == SessionFile) {
Chris@211 244 dialog.setDefaultSuffix("sv");
Chris@211 245 } else if (type == AudioFile) {
Chris@211 246 dialog.setDefaultSuffix("wav");
Chris@250 247 } else if (type == ImageFile) {
Chris@250 248 dialog.setDefaultSuffix("png");
Chris@211 249 }
Chris@211 250
Chris@211 251 bool good = false;
Chris@211 252
Chris@211 253 while (!good) {
Chris@211 254
Chris@211 255 path = "";
Chris@211 256
Chris@211 257 if (!dialog.exec()) break;
Chris@211 258
Chris@211 259 QStringList files = dialog.selectedFiles();
Chris@211 260 if (files.empty()) break;
Chris@211 261 path = *files.begin();
Chris@211 262
Chris@211 263 QFileInfo fi(path);
Chris@211 264
Chris@211 265 if (type == LayerFile && fi.suffix() == "") {
Chris@211 266 QString expectedExtension;
Chris@211 267 QString selectedFilter = dialog.selectedFilter();
Chris@211 268 if (selectedFilter.contains(".svl")) {
Chris@211 269 expectedExtension = "svl";
Chris@211 270 } else if (selectedFilter.contains(".txt")) {
Chris@211 271 expectedExtension = "txt";
Chris@211 272 } else if (selectedFilter.contains(".csv")) {
Chris@211 273 expectedExtension = "csv";
Chris@211 274 }
Chris@211 275 if (expectedExtension != "") {
Chris@211 276 path = QString("%1.%2").arg(path).arg(expectedExtension);
Chris@211 277 fi = QFileInfo(path);
Chris@211 278 }
Chris@211 279 }
Chris@211 280
Chris@211 281 if (fi.isDir()) {
Chris@211 282 QMessageBox::critical(0, tr("Directory selected"),
Chris@211 283 tr("File \"%1\" is a directory").arg(path));
Chris@211 284 continue;
Chris@211 285 }
Chris@211 286
Chris@211 287 if (fi.exists()) {
Chris@211 288 if (QMessageBox::question(0, tr("File exists"),
Chris@211 289 tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
Chris@211 290 QMessageBox::Ok,
Chris@211 291 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@211 292 continue;
Chris@211 293 }
Chris@211 294 }
Chris@211 295
Chris@211 296 good = true;
Chris@211 297 }
Chris@211 298
Chris@211 299 if (path != "") {
Chris@211 300 settings.setValue(settingsKey,
Chris@211 301 QFileInfo(path).absoluteDir().canonicalPath());
Chris@211 302 }
Chris@211 303
Chris@211 304 return path;
Chris@211 305 }
Chris@211 306
Chris@211 307 void
Chris@211 308 FileFinder::registerLastOpenedFilePath(FileType type, QString path)
Chris@211 309 {
Chris@211 310 QString settingsKey;
Chris@211 311
Chris@211 312 switch (type) {
Chris@211 313 case SessionFile:
Chris@211 314 settingsKey = "sessionpath";
Chris@211 315 break;
Chris@211 316
Chris@211 317 case AudioFile:
Chris@211 318 settingsKey = "audiopath";
Chris@211 319 break;
Chris@211 320
Chris@211 321 case LayerFile:
Chris@211 322 settingsKey = "layerpath";
Chris@211 323 break;
Chris@211 324
Chris@211 325 case SessionOrAudioFile:
Chris@211 326 settingsKey = "lastpath";
Chris@211 327 break;
Chris@211 328
Chris@250 329 case ImageFile:
Chris@250 330 settingsKey = "imagepath";
Chris@250 331 break;
Chris@250 332
Chris@211 333 case AnyFile:
Chris@211 334 settingsKey = "lastpath";
Chris@211 335 break;
Chris@211 336 }
Chris@211 337
Chris@211 338 if (path != "") {
Chris@211 339 QSettings settings;
Chris@211 340 settings.beginGroup("FileFinder");
Chris@211 341 path = QFileInfo(path).absoluteDir().canonicalPath();
Chris@211 342 settings.setValue(settingsKey, path);
Chris@211 343 settings.setValue("lastpath", path);
Chris@211 344 }
Chris@211 345 }
Chris@211 346
Chris@211 347 QString
Chris@211 348 FileFinder::find(FileType type, QString location, QString lastKnownLocation)
Chris@211 349 {
Chris@211 350 if (QFileInfo(location).exists()) return location;
Chris@211 351
Chris@211 352 if (RemoteFile::canHandleScheme(QUrl(location))) {
Chris@211 353 RemoteFile rf(location);
Chris@211 354 bool available = rf.isAvailable();
Chris@211 355 rf.deleteLocalFile();
Chris@211 356 if (available) return location;
Chris@211 357 }
Chris@211 358
Chris@211 359 QString foundAt = "";
Chris@211 360
Chris@211 361 if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
Chris@211 362 return foundAt;
Chris@211 363 }
Chris@211 364
Chris@211 365 if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
Chris@211 366 return foundAt;
Chris@211 367 }
Chris@211 368
Chris@211 369 return locateInteractive(type, location);
Chris@211 370 }
Chris@211 371
Chris@211 372 QString
Chris@211 373 FileFinder::findRelative(QString location, QString relativeTo)
Chris@211 374 {
Chris@211 375 if (relativeTo == "") return "";
Chris@211 376
Chris@211 377 std::cerr << "Looking for \"" << location.toStdString() << "\" next to \""
Chris@211 378 << relativeTo.toStdString() << "\"..." << std::endl;
Chris@211 379
Chris@211 380 QString fileName;
Chris@211 381 QString resolved;
Chris@211 382
Chris@211 383 if (RemoteFile::canHandleScheme(QUrl(location))) {
Chris@211 384 fileName = QUrl(location).path().section('/', -1, -1,
Chris@211 385 QString::SectionSkipEmpty);
Chris@211 386 } else {
Chris@211 387 fileName = QFileInfo(location).fileName();
Chris@211 388 }
Chris@211 389
Chris@211 390 if (RemoteFile::canHandleScheme(QUrl(relativeTo))) {
Chris@211 391 resolved = QUrl(relativeTo).resolved(fileName).toString();
Chris@211 392 RemoteFile rf(resolved);
Chris@211 393 if (!rf.isAvailable()) resolved = "";
Chris@211 394 std::cerr << "resolved: " << resolved.toStdString() << std::endl;
Chris@211 395 rf.deleteLocalFile();
Chris@211 396 } else {
Chris@211 397 resolved = QFileInfo(relativeTo).dir().filePath(fileName);
Chris@211 398 if (!QFileInfo(resolved).exists() ||
Chris@211 399 !QFileInfo(resolved).isFile() ||
Chris@211 400 !QFileInfo(resolved).isReadable()) {
Chris@211 401 resolved = "";
Chris@211 402 }
Chris@211 403 }
Chris@211 404
Chris@211 405 return resolved;
Chris@211 406 }
Chris@211 407
Chris@211 408 QString
Chris@211 409 FileFinder::locateInteractive(FileType type, QString thing)
Chris@211 410 {
Chris@211 411 QString question;
Chris@211 412 if (type == AudioFile) {
Chris@211 413 question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?");
Chris@211 414 } else {
Chris@211 415 question = tr("File \"%1\" could not be opened.\nDo you want to locate it?");
Chris@211 416 }
Chris@211 417
Chris@211 418 QString path = "";
Chris@211 419 bool done = false;
Chris@211 420
Chris@211 421 while (!done) {
Chris@211 422
Chris@211 423 int rv = QMessageBox::question
Chris@211 424 (0,
Chris@211 425 tr("Failed to open file"),
Chris@211 426 question.arg(thing),
Chris@211 427 tr("Locate file..."),
Chris@211 428 tr("Use URL..."),
Chris@211 429 tr("Cancel"),
Chris@211 430 0, 2);
Chris@211 431
Chris@211 432 switch (rv) {
Chris@211 433
Chris@211 434 case 0: // Locate file
Chris@211 435
Chris@211 436 if (QFileInfo(thing).dir().exists()) {
Chris@211 437 path = QFileInfo(thing).dir().canonicalPath();
Chris@211 438 }
Chris@211 439
Chris@211 440 path = getOpenFileName(type, path);
Chris@211 441 done = (path != "");
Chris@211 442 break;
Chris@211 443
Chris@211 444 case 1: // Use URL
Chris@211 445 {
Chris@211 446 bool ok = false;
Chris@211 447 path = QInputDialog::getText
Chris@211 448 (0, tr("Use URL"),
Chris@211 449 tr("Please enter the URL to use for this file:"),
Chris@211 450 QLineEdit::Normal, "", &ok);
Chris@211 451
Chris@211 452 if (ok && path != "") {
Chris@211 453 RemoteFile rf(path);
Chris@211 454 if (rf.isAvailable()) {
Chris@211 455 done = true;
Chris@211 456 } else {
Chris@211 457 QMessageBox::critical
Chris@211 458 (0, tr("Failed to open location"),
Chris@211 459 tr("URL \"%1\" could not be opened").arg(path));
Chris@211 460 path = "";
Chris@211 461 }
Chris@211 462 rf.deleteLocalFile();
Chris@211 463 }
Chris@211 464 break;
Chris@211 465 }
Chris@211 466
Chris@211 467 case 2: // Cancel
Chris@211 468 path = "";
Chris@211 469 done = true;
Chris@211 470 break;
Chris@210 471 }
Chris@210 472 }
Chris@210 473
Chris@211 474 if (path != "") m_lastLocatedLocation = path;
Chris@211 475 return path;
Chris@210 476 }
Chris@210 477
Chris@210 478