annotate base/ResourceFinder.cpp @ 983:a8f91db36e9d

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