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