annotate base/ResourceFinder.cpp @ 1412:b7a9edee85e0 scale-ticks

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