annotate base/ResourceFinder.cpp @ 1693:718ce5fb9fec single-point

Merge
author Chris Cannam
date Thu, 25 Apr 2019 11:30:51 +0100
parents 5ac102155409
children 57833933cc75
rev   line source
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