annotate base/ResourceFinder.cpp @ 1346:75ad55315db4 3.0-integration

More work on getting tests (especially file encoding ones) running on Windows. Various problems here to do with interaction with test filenames in Hg repos
author Chris Cannam
date Fri, 06 Jan 2017 15:44:55 +0000
parents 8f076d02569a
children e1926cba940c
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@983 136 // This is expected to be much more reliable than
Chris@983 137 // getOldStyleUserResourcePrefix(), but it returns a different
Chris@983 138 // directory because it includes the organisation name (which is
Chris@983 139 // fair enough). Hence migrateOldStyleResources() which moves
Chris@983 140 // across any resources found in the old-style path the first time
Chris@983 141 // we look for the new-style one
Chris@1247 142 return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
Chris@983 143 #else
Chris@983 144 return getOldStyleUserResourcePrefix();
Chris@982 145 #endif
Chris@679 146 }
Chris@679 147
Chris@983 148 static void
Chris@983 149 migrateOldStyleResources()
Chris@983 150 {
Chris@983 151 QString oldPath = getOldStyleUserResourcePrefix();
Chris@983 152 QString newPath = getNewStyleUserResourcePrefix();
Chris@983 153
Chris@983 154 if (oldPath != newPath &&
Chris@983 155 QDir(oldPath).exists() &&
Chris@983 156 !QDir(newPath).exists()) {
Chris@983 157
Chris@983 158 QDir d(oldPath);
Chris@983 159
Chris@983 160 if (!d.mkpath(newPath)) {
Chris@983 161 cerr << "WARNING: Failed to create new-style resource path \""
Chris@983 162 << newPath << "\" to migrate old resources to" << endl;
Chris@983 163 return;
Chris@983 164 }
Chris@983 165
Chris@983 166 QDir target(newPath);
Chris@983 167
Chris@983 168 bool success = true;
Chris@983 169
Chris@983 170 QStringList entries
Chris@983 171 (d.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot));
Chris@983 172
Chris@983 173 foreach (QString entry, entries) {
Chris@983 174 if (d.rename(entry, target.filePath(entry))) {
Chris@983 175 cerr << "NOTE: Successfully moved resource \""
Chris@983 176 << entry << "\" from old resource path to new" << endl;
Chris@983 177 } else {
Chris@983 178 cerr << "WARNING: Failed to move old resource \""
Chris@983 179 << entry << "\" from old location \""
Chris@983 180 << oldPath << "\" to new location \""
Chris@983 181 << newPath << "\"" << endl;
Chris@983 182 success = false;
Chris@983 183 }
Chris@983 184 }
Chris@983 185
Chris@983 186 if (success) {
Chris@983 187 if (!d.rmdir(oldPath)) {
Chris@983 188 cerr << "WARNING: Failed to remove old resource path \""
Chris@983 189 << oldPath << "\" after migrating " << entries.size()
Chris@983 190 << " resource(s) to new path \"" << newPath
Chris@983 191 << "\" (directory not empty?)" << endl;
Chris@983 192 } else {
Chris@983 193 cerr << "NOTE: Successfully moved " << entries.size()
Chris@983 194 << " resource(s) from old resource "
Chris@983 195 << "path \"" << oldPath << "\" to new path \""
Chris@983 196 << newPath << "\"" << endl;
Chris@983 197 }
Chris@983 198 }
Chris@983 199 }
Chris@983 200 }
Chris@983 201
Chris@983 202 QString
Chris@983 203 ResourceFinder::getUserResourcePrefix()
Chris@983 204 {
Chris@983 205 migrateOldStyleResources();
Chris@983 206 return getNewStyleUserResourcePrefix();
Chris@983 207 }
Chris@983 208
Chris@679 209 QStringList
Chris@679 210 ResourceFinder::getResourcePrefixList()
Chris@679 211 {
Chris@679 212 // returned in order of priority
Chris@679 213
Chris@679 214 QStringList list;
Chris@679 215
Chris@679 216 QString user = getUserResourcePrefix();
Chris@679 217 if (user != "") list << user;
Chris@679 218
Chris@679 219 list << getSystemResourcePrefixList();
Chris@679 220
Chris@679 221 list << ":"; // bundled resource location
Chris@679 222
Chris@679 223 return list;
Chris@679 224 }
Chris@679 225
Chris@679 226 QString
Chris@679 227 ResourceFinder::getResourcePath(QString resourceCat, QString fileName)
Chris@679 228 {
Chris@679 229 // We don't simply call getResourceDir here, because that returns
Chris@679 230 // only the "installed file" location. We also want to search the
Chris@679 231 // bundled resources and user-saved files.
Chris@679 232
Chris@679 233 QStringList prefixes = getResourcePrefixList();
Chris@679 234
Chris@679 235 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 236
Chris@679 237 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 238 i != prefixes.end(); ++i) {
Chris@679 239
Chris@679 240 QString prefix = *i;
Chris@679 241
Chris@690 242 SVDEBUG << "ResourceFinder::getResourcePath: Looking up file \"" << fileName << "\" for category \"" << resourceCat << "\" in prefix \"" << prefix << "\"" << endl;
Chris@679 243
Chris@679 244 QString path =
Chris@679 245 QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName);
Chris@679 246 if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) {
Chris@843 247 cerr << "Found it!" << endl;
Chris@679 248 return path;
Chris@679 249 }
Chris@679 250 }
Chris@679 251
Chris@679 252 return "";
Chris@679 253 }
Chris@679 254
Chris@679 255 QString
Chris@679 256 ResourceFinder::getResourceDir(QString resourceCat)
Chris@679 257 {
Chris@679 258 // Returns only the "installed file" location
Chris@679 259
Chris@679 260 QStringList prefixes = getSystemResourcePrefixList();
Chris@679 261
Chris@679 262 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 263
Chris@679 264 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 265 i != prefixes.end(); ++i) {
Chris@679 266
Chris@679 267 QString prefix = *i;
Chris@679 268 QString path = QString("%1%2").arg(prefix).arg(resourceCat);
Chris@679 269 if (QFileInfo(path).exists() &&
Chris@679 270 QFileInfo(path).isDir() &&
Chris@679 271 QFileInfo(path).isReadable()) {
Chris@679 272 return path;
Chris@679 273 }
Chris@679 274 }
Chris@679 275
Chris@679 276 return "";
Chris@679 277 }
Chris@679 278
Chris@679 279 QString
Chris@679 280 ResourceFinder::getResourceSavePath(QString resourceCat, QString fileName)
Chris@679 281 {
Chris@679 282 QString dir = getResourceSaveDir(resourceCat);
Chris@679 283 if (dir == "") return "";
Chris@679 284
Chris@679 285 return dir + "/" + fileName;
Chris@679 286 }
Chris@679 287
Chris@679 288 QString
Chris@679 289 ResourceFinder::getResourceSaveDir(QString resourceCat)
Chris@679 290 {
Chris@679 291 // Returns the "user" location
Chris@679 292
Chris@679 293 QString user = getUserResourcePrefix();
Chris@679 294 if (user == "") return "";
Chris@679 295
Chris@679 296 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 297
Chris@679 298 QDir userDir(user);
Chris@679 299 if (!userDir.exists()) {
Chris@679 300 if (!userDir.mkpath(user)) {
Chris@843 301 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user << "\"" << endl;
Chris@679 302 return "";
Chris@679 303 }
Chris@679 304 }
Chris@679 305
Chris@679 306 if (resourceCat != "") {
Chris@679 307 QString save = QString("%1%2").arg(user).arg(resourceCat);
Chris@679 308 QDir saveDir(save);
Chris@679 309 if (!saveDir.exists()) {
Chris@959 310 if (!saveDir.mkpath(save)) {
Chris@843 311 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save << "\"" << endl;
Chris@679 312 return "";
Chris@679 313 }
Chris@679 314 }
Chris@679 315 return save;
Chris@679 316 } else {
Chris@679 317 return user;
Chris@679 318 }
Chris@679 319 }
Chris@679 320
Chris@679 321 QStringList
Chris@679 322 ResourceFinder::getResourceFiles(QString resourceCat, QString fileExt)
Chris@679 323 {
Chris@679 324 QStringList results;
Chris@679 325 QStringList prefixes = getResourcePrefixList();
Chris@679 326
Chris@679 327 QStringList filters;
Chris@679 328 filters << QString("*.%1").arg(fileExt);
Chris@679 329
Chris@679 330 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 331 i != prefixes.end(); ++i) {
Chris@679 332
Chris@679 333 QString prefix = *i;
Chris@679 334 QString path;
Chris@679 335
Chris@679 336 if (resourceCat != "") {
Chris@679 337 path = QString("%1/%2").arg(prefix).arg(resourceCat);
Chris@679 338 } else {
Chris@679 339 path = prefix;
Chris@679 340 }
Chris@679 341
Chris@679 342 QDir dir(path);
Chris@679 343 if (!dir.exists()) continue;
Chris@679 344
Chris@679 345 dir.setNameFilters(filters);
Chris@679 346 QStringList entries = dir.entryList
Chris@679 347 (QDir::Files | QDir::Readable, QDir::Name);
Chris@679 348
Chris@679 349 for (QStringList::const_iterator j = entries.begin();
Chris@679 350 j != entries.end(); ++j) {
Chris@679 351 results << QString("%1/%2").arg(path).arg(*j);
Chris@679 352 }
Chris@679 353 }
Chris@679 354
Chris@679 355 return results;
Chris@679 356 }
Chris@679 357
Chris@679 358 bool
Chris@679 359 ResourceFinder::unbundleResource(QString resourceCat, QString fileName)
Chris@679 360 {
Chris@679 361 QString path = getResourcePath(resourceCat, fileName);
Chris@679 362
Chris@679 363 if (!path.startsWith(':')) return true;
Chris@679 364
Chris@679 365 // This is the lowest-priority alternative path for this
Chris@679 366 // resource, so we know that there must be no installed copy.
Chris@679 367 // Install one to the user location.
Chris@690 368 SVDEBUG << "ResourceFinder::unbundleResource: File " << fileName << " is bundled, un-bundling it" << endl;
Chris@679 369 QString target = getResourceSavePath(resourceCat, fileName);
Chris@679 370 QFile file(path);
Chris@679 371 if (!file.copy(target)) {
Chris@843 372 cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName << "\" to user location \"" << target << "\"" << endl;
Chris@679 373 return false;
Chris@679 374 }
Chris@679 375
Chris@679 376 QFile chmod(target);
Chris@679 377 chmod.setPermissions(QFile::ReadOwner |
Chris@679 378 QFile::ReadUser | /* for potential platform-independence */
Chris@679 379 QFile::ReadGroup |
Chris@679 380 QFile::ReadOther |
Chris@679 381 QFile::WriteOwner|
Chris@679 382 QFile::WriteUser); /* for potential platform-independence */
Chris@679 383
Chris@679 384 return true;
Chris@679 385 }
Chris@679 386