annotate data/fileio/FileFinder.cpp @ 271:822bd7fd526c

* Add support for reading mp3 and Ogg file title tags
author Chris Cannam
date Mon, 02 Jul 2007 13:53:38 +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