Chris@679
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@679
|
2
|
Chris@679
|
3 /*
|
Chris@679
|
4 Sonic Visualiser
|
Chris@679
|
5 An audio file viewer and annotation editor.
|
Chris@679
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@679
|
7
|
Chris@679
|
8 This program is free software; you can redistribute it and/or
|
Chris@679
|
9 modify it under the terms of the GNU General Public License as
|
Chris@679
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@679
|
11 License, or (at your option) any later version. See the file
|
Chris@679
|
12 COPYING included with this distribution for more information.
|
Chris@679
|
13 */
|
Chris@679
|
14
|
Chris@679
|
15 /*
|
Chris@679
|
16 This is a modified version of a source file from the
|
Chris@679
|
17 Rosegarden MIDI and audio sequencer and notation editor.
|
Chris@679
|
18 This file copyright 2005-2011 Chris Cannam and the Rosegarden
|
Chris@679
|
19 development team.
|
Chris@679
|
20 */
|
Chris@679
|
21
|
Chris@679
|
22 #include "ResourceFinder.h"
|
Chris@679
|
23
|
Chris@679
|
24 #include <QDir>
|
Chris@679
|
25 #include <QFileInfo>
|
Chris@679
|
26 #include <QStringList>
|
Chris@679
|
27 #include <QProcess>
|
Chris@679
|
28 #include <QCoreApplication>
|
Chris@679
|
29
|
Chris@982
|
30 #if QT_VERSION >= 0x050000
|
Chris@982
|
31 #include <QStandardPaths>
|
Chris@982
|
32 #endif
|
Chris@982
|
33
|
Chris@679
|
34 #include <cstdlib>
|
Chris@679
|
35 #include <iostream>
|
Chris@1247
|
36 #include <stdexcept>
|
Chris@679
|
37
|
Chris@1480
|
38 #include "system/System.h"
|
Chris@1480
|
39
|
Chris@679
|
40 /**
|
Chris@679
|
41 Resource files may be found in three places:
|
Chris@679
|
42
|
Chris@679
|
43 * Bundled into the application as Qt4 resources. These may be
|
Chris@679
|
44 opened using Qt classes such as QFile, with "fake" file paths
|
Chris@679
|
45 starting with a colon. For example ":icons/fileopen.png".
|
Chris@679
|
46
|
Chris@679
|
47 * Installed with the package, or in the user's equivalent home
|
Chris@679
|
48 directory location. For example,
|
Chris@679
|
49
|
Chris@679
|
50 - on Linux, in /usr/share/<appname> or /usr/local/share/<appname>
|
Chris@679
|
51 - on Linux, in $HOME/.local/share/<appname>
|
Chris@679
|
52
|
Chris@679
|
53 - on OS/X, in /Library/Application Support/<appname>
|
Chris@679
|
54 - on OS/X, in $HOME/Library/Application Support/<appname>
|
Chris@679
|
55
|
Chris@679
|
56 - on Windows, in %ProgramFiles%/<company>/<appname>
|
Chris@680
|
57 - on Windows, in (where?) something from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx ?
|
Chris@679
|
58
|
Chris@679
|
59 These locations are searched in reverse order (user-installed
|
Chris@679
|
60 copies take priority over system-installed copies take priority
|
Chris@679
|
61 over bundled copies). Also, /usr/local takes priority over /usr.
|
Chris@679
|
62 */
|
Chris@679
|
63
|
Chris@679
|
64 QStringList
|
Chris@679
|
65 ResourceFinder::getSystemResourcePrefixList()
|
Chris@679
|
66 {
|
Chris@679
|
67 // returned in order of priority
|
Chris@679
|
68
|
Chris@679
|
69 QStringList list;
|
Chris@679
|
70
|
Chris@679
|
71 #ifdef Q_OS_WIN32
|
Chris@1480
|
72 std::string programFiles;
|
Chris@1480
|
73 (void)getEnvUtf8("ProgramFiles", programFiles);
|
Chris@1480
|
74 if (programFiles != "") {
|
Chris@679
|
75 list << QString("%1/%2/%3")
|
Chris@1480
|
76 .arg(QString::fromStdString(programFiles))
|
Chris@679
|
77 .arg(qApp->organizationName())
|
Chris@679
|
78 .arg(qApp->applicationName());
|
Chris@679
|
79 } else {
|
Chris@679
|
80 list << QString("C:/Program Files/%1/%2")
|
Chris@679
|
81 .arg(qApp->organizationName())
|
Chris@679
|
82 .arg(qApp->applicationName());
|
Chris@679
|
83 }
|
Chris@679
|
84 #else
|
Chris@679
|
85 #ifdef Q_OS_MAC
|
Chris@733
|
86 list << QString("/Library/Application Support/%1")
|
Chris@679
|
87 .arg(qApp->applicationName());
|
Chris@679
|
88 #else
|
Chris@679
|
89 list << QString("/usr/local/share/%1")
|
Chris@679
|
90 .arg(qApp->applicationName());
|
Chris@679
|
91 list << QString("/usr/share/%1")
|
Chris@679
|
92 .arg(qApp->applicationName());
|
Chris@679
|
93 #endif
|
Chris@679
|
94 #endif
|
Chris@679
|
95
|
Chris@679
|
96 return list;
|
Chris@679
|
97 }
|
Chris@679
|
98
|
Chris@983
|
99 static QString
|
Chris@983
|
100 getOldStyleUserResourcePrefix()
|
Chris@679
|
101 {
|
Chris@680
|
102 #ifdef Q_OS_WIN32
|
Chris@983
|
103 // This is awkward and does not work correctly for non-ASCII home
|
Chris@983
|
104 // directory names, hence getNewStyleUserResourcePrefix() below
|
Chris@680
|
105 char *homedrive = getenv("HOMEDRIVE");
|
Chris@680
|
106 char *homepath = getenv("HOMEPATH");
|
Chris@680
|
107 QString home;
|
Chris@680
|
108 if (homedrive && homepath) {
|
Chris@680
|
109 home = QString("%1%2").arg(homedrive).arg(homepath);
|
Chris@680
|
110 } else {
|
Chris@680
|
111 home = QDir::home().absolutePath();
|
Chris@680
|
112 }
|
Chris@680
|
113 if (home == "") return "";
|
Chris@708
|
114 return QString("%1/.%2").arg(home).arg(qApp->applicationName()); //!!! wrong
|
Chris@680
|
115 #else
|
Chris@679
|
116 char *home = getenv("HOME");
|
Chris@679
|
117 if (!home || !home[0]) return "";
|
Chris@679
|
118 #ifdef Q_OS_MAC
|
Chris@733
|
119 return QString("%1/Library/Application Support/%2")
|
Chris@679
|
120 .arg(home)
|
Chris@679
|
121 .arg(qApp->applicationName());
|
Chris@679
|
122 #else
|
Chris@679
|
123 return QString("%1/.local/share/%2")
|
Chris@679
|
124 .arg(home)
|
Chris@679
|
125 .arg(qApp->applicationName());
|
Chris@679
|
126 #endif
|
Chris@982
|
127 #endif
|
Chris@983
|
128 }
|
Chris@983
|
129
|
Chris@983
|
130 static QString
|
Chris@983
|
131 getNewStyleUserResourcePrefix()
|
Chris@983
|
132 {
|
Chris@1247
|
133 if (qApp->applicationName() == "") {
|
Chris@1247
|
134 cerr << "ERROR: Can't use ResourceFinder before setting application name" << endl;
|
Chris@1247
|
135 throw std::logic_error("Can't use ResourceFinder before setting application name");
|
Chris@1247
|
136 }
|
Chris@1247
|
137
|
Chris@983
|
138 #if QT_VERSION >= 0x050000
|
Chris@1398
|
139
|
Chris@983
|
140 // This is expected to be much more reliable than
|
Chris@983
|
141 // getOldStyleUserResourcePrefix(), but it returns a different
|
Chris@983
|
142 // directory because it includes the organisation name (which is
|
Chris@983
|
143 // fair enough). Hence migrateOldStyleResources() which moves
|
Chris@983
|
144 // across any resources found in the old-style path the first time
|
Chris@983
|
145 // we look for the new-style one
|
Chris@1398
|
146 #if QT_VERSION >= 0x050400
|
Chris@1247
|
147 return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
Chris@983
|
148 #else
|
Chris@1398
|
149 cerr << "WARNING: ResourceFinder::getOldStyleUserResourcePrefix: Building with older version of Qt (pre 5.4), resource location may be incompatible with future versions" << endl;
|
Chris@1398
|
150 return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
Chris@1398
|
151 #endif
|
Chris@1398
|
152
|
Chris@1398
|
153 #else
|
Chris@1398
|
154 cerr << "WARNING: ResourceFinder::getOldStyleUserResourcePrefix: Building with very old version of Qt (pre 5.0?), resource location may be incompatible with future versions" << endl;
|
Chris@983
|
155 return getOldStyleUserResourcePrefix();
|
Chris@982
|
156 #endif
|
Chris@679
|
157 }
|
Chris@679
|
158
|
Chris@983
|
159 static void
|
Chris@983
|
160 migrateOldStyleResources()
|
Chris@983
|
161 {
|
Chris@983
|
162 QString oldPath = getOldStyleUserResourcePrefix();
|
Chris@983
|
163 QString newPath = getNewStyleUserResourcePrefix();
|
Chris@983
|
164
|
Chris@983
|
165 if (oldPath != newPath &&
|
Chris@983
|
166 QDir(oldPath).exists() &&
|
Chris@983
|
167 !QDir(newPath).exists()) {
|
Chris@983
|
168
|
Chris@983
|
169 QDir d(oldPath);
|
Chris@983
|
170
|
Chris@983
|
171 if (!d.mkpath(newPath)) {
|
Chris@983
|
172 cerr << "WARNING: Failed to create new-style resource path \""
|
Chris@983
|
173 << newPath << "\" to migrate old resources to" << endl;
|
Chris@983
|
174 return;
|
Chris@983
|
175 }
|
Chris@983
|
176
|
Chris@983
|
177 QDir target(newPath);
|
Chris@983
|
178
|
Chris@983
|
179 bool success = true;
|
Chris@983
|
180
|
Chris@983
|
181 QStringList entries
|
Chris@983
|
182 (d.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot));
|
Chris@983
|
183
|
Chris@983
|
184 foreach (QString entry, entries) {
|
Chris@983
|
185 if (d.rename(entry, target.filePath(entry))) {
|
Chris@983
|
186 cerr << "NOTE: Successfully moved resource \""
|
Chris@983
|
187 << entry << "\" from old resource path to new" << endl;
|
Chris@983
|
188 } else {
|
Chris@983
|
189 cerr << "WARNING: Failed to move old resource \""
|
Chris@983
|
190 << entry << "\" from old location \""
|
Chris@983
|
191 << oldPath << "\" to new location \""
|
Chris@983
|
192 << newPath << "\"" << endl;
|
Chris@983
|
193 success = false;
|
Chris@983
|
194 }
|
Chris@983
|
195 }
|
Chris@983
|
196
|
Chris@983
|
197 if (success) {
|
Chris@983
|
198 if (!d.rmdir(oldPath)) {
|
Chris@983
|
199 cerr << "WARNING: Failed to remove old resource path \""
|
Chris@983
|
200 << oldPath << "\" after migrating " << entries.size()
|
Chris@983
|
201 << " resource(s) to new path \"" << newPath
|
Chris@983
|
202 << "\" (directory not empty?)" << endl;
|
Chris@983
|
203 } else {
|
Chris@983
|
204 cerr << "NOTE: Successfully moved " << entries.size()
|
Chris@983
|
205 << " resource(s) from old resource "
|
Chris@983
|
206 << "path \"" << oldPath << "\" to new path \""
|
Chris@983
|
207 << newPath << "\"" << endl;
|
Chris@983
|
208 }
|
Chris@983
|
209 }
|
Chris@983
|
210 }
|
Chris@983
|
211 }
|
Chris@983
|
212
|
Chris@983
|
213 QString
|
Chris@983
|
214 ResourceFinder::getUserResourcePrefix()
|
Chris@983
|
215 {
|
Chris@983
|
216 migrateOldStyleResources();
|
Chris@983
|
217 return getNewStyleUserResourcePrefix();
|
Chris@983
|
218 }
|
Chris@983
|
219
|
Chris@679
|
220 QStringList
|
Chris@679
|
221 ResourceFinder::getResourcePrefixList()
|
Chris@679
|
222 {
|
Chris@679
|
223 // returned in order of priority
|
Chris@679
|
224
|
Chris@679
|
225 QStringList list;
|
Chris@679
|
226
|
Chris@679
|
227 QString user = getUserResourcePrefix();
|
Chris@679
|
228 if (user != "") list << user;
|
Chris@679
|
229
|
Chris@679
|
230 list << getSystemResourcePrefixList();
|
Chris@679
|
231
|
Chris@679
|
232 list << ":"; // bundled resource location
|
Chris@679
|
233
|
Chris@679
|
234 return list;
|
Chris@679
|
235 }
|
Chris@679
|
236
|
Chris@679
|
237 QString
|
Chris@679
|
238 ResourceFinder::getResourcePath(QString resourceCat, QString fileName)
|
Chris@679
|
239 {
|
Chris@679
|
240 // We don't simply call getResourceDir here, because that returns
|
Chris@679
|
241 // only the "installed file" location. We also want to search the
|
Chris@679
|
242 // bundled resources and user-saved files.
|
Chris@679
|
243
|
Chris@679
|
244 QStringList prefixes = getResourcePrefixList();
|
Chris@679
|
245
|
Chris@679
|
246 if (resourceCat != "") resourceCat = "/" + resourceCat;
|
Chris@679
|
247
|
Chris@679
|
248 for (QStringList::const_iterator i = prefixes.begin();
|
Chris@679
|
249 i != prefixes.end(); ++i) {
|
Chris@679
|
250
|
Chris@679
|
251 QString prefix = *i;
|
Chris@679
|
252
|
Chris@1398
|
253 // cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName << "\" for category \"" << resourceCat << "\" in prefix \"" << prefix << "\"" << endl;
|
Chris@679
|
254
|
Chris@679
|
255 QString path =
|
Chris@679
|
256 QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName);
|
Chris@679
|
257 if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) {
|
Chris@1398
|
258 // cerr << "Found it!" << endl;
|
Chris@679
|
259 return path;
|
Chris@679
|
260 }
|
Chris@679
|
261 }
|
Chris@679
|
262
|
Chris@679
|
263 return "";
|
Chris@679
|
264 }
|
Chris@679
|
265
|
Chris@679
|
266 QString
|
Chris@679
|
267 ResourceFinder::getResourceDir(QString resourceCat)
|
Chris@679
|
268 {
|
Chris@679
|
269 // Returns only the "installed file" location
|
Chris@679
|
270
|
Chris@679
|
271 QStringList prefixes = getSystemResourcePrefixList();
|
Chris@679
|
272
|
Chris@679
|
273 if (resourceCat != "") resourceCat = "/" + resourceCat;
|
Chris@679
|
274
|
Chris@679
|
275 for (QStringList::const_iterator i = prefixes.begin();
|
Chris@679
|
276 i != prefixes.end(); ++i) {
|
Chris@679
|
277
|
Chris@679
|
278 QString prefix = *i;
|
Chris@679
|
279 QString path = QString("%1%2").arg(prefix).arg(resourceCat);
|
Chris@679
|
280 if (QFileInfo(path).exists() &&
|
Chris@679
|
281 QFileInfo(path).isDir() &&
|
Chris@679
|
282 QFileInfo(path).isReadable()) {
|
Chris@679
|
283 return path;
|
Chris@679
|
284 }
|
Chris@679
|
285 }
|
Chris@679
|
286
|
Chris@679
|
287 return "";
|
Chris@679
|
288 }
|
Chris@679
|
289
|
Chris@679
|
290 QString
|
Chris@679
|
291 ResourceFinder::getResourceSavePath(QString resourceCat, QString fileName)
|
Chris@679
|
292 {
|
Chris@679
|
293 QString dir = getResourceSaveDir(resourceCat);
|
Chris@679
|
294 if (dir == "") return "";
|
Chris@679
|
295
|
Chris@679
|
296 return dir + "/" + fileName;
|
Chris@679
|
297 }
|
Chris@679
|
298
|
Chris@679
|
299 QString
|
Chris@679
|
300 ResourceFinder::getResourceSaveDir(QString resourceCat)
|
Chris@679
|
301 {
|
Chris@679
|
302 // Returns the "user" location
|
Chris@679
|
303
|
Chris@679
|
304 QString user = getUserResourcePrefix();
|
Chris@679
|
305 if (user == "") return "";
|
Chris@679
|
306
|
Chris@679
|
307 if (resourceCat != "") resourceCat = "/" + resourceCat;
|
Chris@679
|
308
|
Chris@679
|
309 QDir userDir(user);
|
Chris@679
|
310 if (!userDir.exists()) {
|
Chris@679
|
311 if (!userDir.mkpath(user)) {
|
Chris@843
|
312 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user << "\"" << endl;
|
Chris@679
|
313 return "";
|
Chris@679
|
314 }
|
Chris@679
|
315 }
|
Chris@679
|
316
|
Chris@679
|
317 if (resourceCat != "") {
|
Chris@679
|
318 QString save = QString("%1%2").arg(user).arg(resourceCat);
|
Chris@679
|
319 QDir saveDir(save);
|
Chris@679
|
320 if (!saveDir.exists()) {
|
Chris@959
|
321 if (!saveDir.mkpath(save)) {
|
Chris@843
|
322 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save << "\"" << endl;
|
Chris@679
|
323 return "";
|
Chris@679
|
324 }
|
Chris@679
|
325 }
|
Chris@679
|
326 return save;
|
Chris@679
|
327 } else {
|
Chris@679
|
328 return user;
|
Chris@679
|
329 }
|
Chris@679
|
330 }
|
Chris@679
|
331
|
Chris@679
|
332 QStringList
|
Chris@679
|
333 ResourceFinder::getResourceFiles(QString resourceCat, QString fileExt)
|
Chris@679
|
334 {
|
Chris@679
|
335 QStringList results;
|
Chris@679
|
336 QStringList prefixes = getResourcePrefixList();
|
Chris@679
|
337
|
Chris@679
|
338 QStringList filters;
|
Chris@679
|
339 filters << QString("*.%1").arg(fileExt);
|
Chris@679
|
340
|
Chris@679
|
341 for (QStringList::const_iterator i = prefixes.begin();
|
Chris@679
|
342 i != prefixes.end(); ++i) {
|
Chris@679
|
343
|
Chris@679
|
344 QString prefix = *i;
|
Chris@679
|
345 QString path;
|
Chris@679
|
346
|
Chris@679
|
347 if (resourceCat != "") {
|
Chris@679
|
348 path = QString("%1/%2").arg(prefix).arg(resourceCat);
|
Chris@679
|
349 } else {
|
Chris@679
|
350 path = prefix;
|
Chris@679
|
351 }
|
Chris@679
|
352
|
Chris@679
|
353 QDir dir(path);
|
Chris@679
|
354 if (!dir.exists()) continue;
|
Chris@679
|
355
|
Chris@679
|
356 dir.setNameFilters(filters);
|
Chris@679
|
357 QStringList entries = dir.entryList
|
Chris@679
|
358 (QDir::Files | QDir::Readable, QDir::Name);
|
Chris@679
|
359
|
Chris@679
|
360 for (QStringList::const_iterator j = entries.begin();
|
Chris@679
|
361 j != entries.end(); ++j) {
|
Chris@679
|
362 results << QString("%1/%2").arg(path).arg(*j);
|
Chris@679
|
363 }
|
Chris@679
|
364 }
|
Chris@679
|
365
|
Chris@679
|
366 return results;
|
Chris@679
|
367 }
|
Chris@679
|
368
|
Chris@679
|
369 bool
|
Chris@679
|
370 ResourceFinder::unbundleResource(QString resourceCat, QString fileName)
|
Chris@679
|
371 {
|
Chris@679
|
372 QString path = getResourcePath(resourceCat, fileName);
|
Chris@679
|
373
|
Chris@679
|
374 if (!path.startsWith(':')) return true;
|
Chris@679
|
375
|
Chris@679
|
376 // This is the lowest-priority alternative path for this
|
Chris@679
|
377 // resource, so we know that there must be no installed copy.
|
Chris@679
|
378 // Install one to the user location.
|
Chris@690
|
379 SVDEBUG << "ResourceFinder::unbundleResource: File " << fileName << " is bundled, un-bundling it" << endl;
|
Chris@679
|
380 QString target = getResourceSavePath(resourceCat, fileName);
|
Chris@679
|
381 QFile file(path);
|
Chris@679
|
382 if (!file.copy(target)) {
|
Chris@843
|
383 cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName << "\" to user location \"" << target << "\"" << endl;
|
Chris@679
|
384 return false;
|
Chris@679
|
385 }
|
Chris@679
|
386
|
Chris@679
|
387 QFile chmod(target);
|
Chris@679
|
388 chmod.setPermissions(QFile::ReadOwner |
|
Chris@679
|
389 QFile::ReadUser | /* for potential platform-independence */
|
Chris@679
|
390 QFile::ReadGroup |
|
Chris@679
|
391 QFile::ReadOther |
|
Chris@679
|
392 QFile::WriteOwner|
|
Chris@679
|
393 QFile::WriteUser); /* for potential platform-independence */
|
Chris@679
|
394
|
Chris@679
|
395 return true;
|
Chris@679
|
396 }
|
Chris@679
|
397
|