Mercurial > hg > svcore
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 |