annotate base/ResourceFinder.cpp @ 679:c8badbd4c005

* Introduce ResourceFinder
author Chris Cannam
date Wed, 04 May 2011 14:05:08 +0100
parents
children 27cdabba2d3e
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@679 30 #include <cstdlib>
Chris@679 31 #include <iostream>
Chris@679 32
Chris@679 33 /**
Chris@679 34 Resource files may be found in three places:
Chris@679 35
Chris@679 36 * Bundled into the application as Qt4 resources. These may be
Chris@679 37 opened using Qt classes such as QFile, with "fake" file paths
Chris@679 38 starting with a colon. For example ":icons/fileopen.png".
Chris@679 39
Chris@679 40 * Installed with the package, or in the user's equivalent home
Chris@679 41 directory location. For example,
Chris@679 42
Chris@679 43 - on Linux, in /usr/share/<appname> or /usr/local/share/<appname>
Chris@679 44 - on Linux, in $HOME/.local/share/<appname>
Chris@679 45
Chris@679 46 - on OS/X, in /Library/Application Support/<appname>
Chris@679 47 - on OS/X, in $HOME/Library/Application Support/<appname>
Chris@679 48
Chris@679 49 - on Windows, in %ProgramFiles%/<company>/<appname>
Chris@679 50 - on Windows, in (where?)
Chris@679 51
Chris@679 52 These locations are searched in reverse order (user-installed
Chris@679 53 copies take priority over system-installed copies take priority
Chris@679 54 over bundled copies). Also, /usr/local takes priority over /usr.
Chris@679 55 */
Chris@679 56
Chris@679 57 QStringList
Chris@679 58 ResourceFinder::getSystemResourcePrefixList()
Chris@679 59 {
Chris@679 60 // returned in order of priority
Chris@679 61
Chris@679 62 QStringList list;
Chris@679 63
Chris@679 64 #ifdef Q_OS_WIN32
Chris@679 65 char *programFiles = getenv("ProgramFiles");
Chris@679 66 if (programFiles && programFiles[0]) {
Chris@679 67 list << QString("%1/%2/%3")
Chris@679 68 .arg(programFiles)
Chris@679 69 .arg(qApp->organizationName())
Chris@679 70 .arg(qApp->applicationName());
Chris@679 71 } else {
Chris@679 72 list << QString("C:/Program Files/%1/%2")
Chris@679 73 .arg(qApp->organizationName())
Chris@679 74 .arg(qApp->applicationName());
Chris@679 75 }
Chris@679 76 #else
Chris@679 77 #ifdef Q_OS_MAC
Chris@679 78 list << QString("/Library/Application Support/%1/%2")
Chris@679 79 .arg(qApp->organizationName())
Chris@679 80 .arg(qApp->applicationName());
Chris@679 81 #else
Chris@679 82 list << QString("/usr/local/share/%1")
Chris@679 83 .arg(qApp->applicationName());
Chris@679 84 list << QString("/usr/share/%1")
Chris@679 85 .arg(qApp->applicationName());
Chris@679 86 #endif
Chris@679 87 #endif
Chris@679 88
Chris@679 89 return list;
Chris@679 90 }
Chris@679 91
Chris@679 92 QString
Chris@679 93 ResourceFinder::getUserResourcePrefix()
Chris@679 94 {
Chris@679 95 char *home = getenv("HOME");
Chris@679 96 if (!home || !home[0]) return "";
Chris@679 97
Chris@679 98 #ifdef Q_OS_WIN32
Chris@679 99 return QString(); //!!!???
Chris@679 100 #else
Chris@679 101 #ifdef Q_OS_MAC
Chris@679 102 return QString("%1/Library/Application Support/%2/%3")
Chris@679 103 .arg(home)
Chris@679 104 .arg(qApp->organizationName())
Chris@679 105 .arg(qApp->applicationName());
Chris@679 106 #else
Chris@679 107 return QString("%1/.local/share/%2")
Chris@679 108 .arg(home)
Chris@679 109 .arg(qApp->applicationName());
Chris@679 110 #endif
Chris@679 111 #endif
Chris@679 112 }
Chris@679 113
Chris@679 114 QStringList
Chris@679 115 ResourceFinder::getResourcePrefixList()
Chris@679 116 {
Chris@679 117 // returned in order of priority
Chris@679 118
Chris@679 119 QStringList list;
Chris@679 120
Chris@679 121 QString user = getUserResourcePrefix();
Chris@679 122 if (user != "") list << user;
Chris@679 123
Chris@679 124 list << getSystemResourcePrefixList();
Chris@679 125
Chris@679 126 list << ":"; // bundled resource location
Chris@679 127
Chris@679 128 return list;
Chris@679 129 }
Chris@679 130
Chris@679 131 QString
Chris@679 132 ResourceFinder::getResourcePath(QString resourceCat, QString fileName)
Chris@679 133 {
Chris@679 134 // We don't simply call getResourceDir here, because that returns
Chris@679 135 // only the "installed file" location. We also want to search the
Chris@679 136 // bundled resources and user-saved files.
Chris@679 137
Chris@679 138 QStringList prefixes = getResourcePrefixList();
Chris@679 139
Chris@679 140 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 141
Chris@679 142 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 143 i != prefixes.end(); ++i) {
Chris@679 144
Chris@679 145 QString prefix = *i;
Chris@679 146
Chris@679 147 std::cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName.toStdString() << "\" for category \"" << resourceCat.toStdString() << "\" in prefix \"" << prefix.toStdString() << "\"" << std::endl;
Chris@679 148
Chris@679 149 QString path =
Chris@679 150 QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName);
Chris@679 151 if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) {
Chris@679 152 std::cerr << "Found it!" << std::endl;
Chris@679 153 return path;
Chris@679 154 }
Chris@679 155 }
Chris@679 156
Chris@679 157 return "";
Chris@679 158 }
Chris@679 159
Chris@679 160 QString
Chris@679 161 ResourceFinder::getResourceDir(QString resourceCat)
Chris@679 162 {
Chris@679 163 // Returns only the "installed file" location
Chris@679 164
Chris@679 165 QStringList prefixes = getSystemResourcePrefixList();
Chris@679 166
Chris@679 167 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 168
Chris@679 169 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 170 i != prefixes.end(); ++i) {
Chris@679 171
Chris@679 172 QString prefix = *i;
Chris@679 173 QString path = QString("%1%2").arg(prefix).arg(resourceCat);
Chris@679 174 if (QFileInfo(path).exists() &&
Chris@679 175 QFileInfo(path).isDir() &&
Chris@679 176 QFileInfo(path).isReadable()) {
Chris@679 177 return path;
Chris@679 178 }
Chris@679 179 }
Chris@679 180
Chris@679 181 return "";
Chris@679 182 }
Chris@679 183
Chris@679 184 QString
Chris@679 185 ResourceFinder::getResourceSavePath(QString resourceCat, QString fileName)
Chris@679 186 {
Chris@679 187 QString dir = getResourceSaveDir(resourceCat);
Chris@679 188 if (dir == "") return "";
Chris@679 189
Chris@679 190 return dir + "/" + fileName;
Chris@679 191 }
Chris@679 192
Chris@679 193 QString
Chris@679 194 ResourceFinder::getResourceSaveDir(QString resourceCat)
Chris@679 195 {
Chris@679 196 // Returns the "user" location
Chris@679 197
Chris@679 198 QString user = getUserResourcePrefix();
Chris@679 199 if (user == "") return "";
Chris@679 200
Chris@679 201 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 202
Chris@679 203 QDir userDir(user);
Chris@679 204 if (!userDir.exists()) {
Chris@679 205 if (!userDir.mkpath(user)) {
Chris@679 206 std::cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user.toStdString() << "\"" << std::endl;
Chris@679 207 return "";
Chris@679 208 }
Chris@679 209 }
Chris@679 210
Chris@679 211 if (resourceCat != "") {
Chris@679 212 QString save = QString("%1%2").arg(user).arg(resourceCat);
Chris@679 213 QDir saveDir(save);
Chris@679 214 if (!saveDir.exists()) {
Chris@679 215 if (!userDir.mkpath(save)) {
Chris@679 216 std::cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save.toStdString() << "\"" << std::endl;
Chris@679 217 return "";
Chris@679 218 }
Chris@679 219 }
Chris@679 220 return save;
Chris@679 221 } else {
Chris@679 222 return user;
Chris@679 223 }
Chris@679 224 }
Chris@679 225
Chris@679 226 QStringList
Chris@679 227 ResourceFinder::getResourceFiles(QString resourceCat, QString fileExt)
Chris@679 228 {
Chris@679 229 QStringList results;
Chris@679 230 QStringList prefixes = getResourcePrefixList();
Chris@679 231
Chris@679 232 QStringList filters;
Chris@679 233 filters << QString("*.%1").arg(fileExt);
Chris@679 234
Chris@679 235 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 236 i != prefixes.end(); ++i) {
Chris@679 237
Chris@679 238 QString prefix = *i;
Chris@679 239 QString path;
Chris@679 240
Chris@679 241 if (resourceCat != "") {
Chris@679 242 path = QString("%1/%2").arg(prefix).arg(resourceCat);
Chris@679 243 } else {
Chris@679 244 path = prefix;
Chris@679 245 }
Chris@679 246
Chris@679 247 QDir dir(path);
Chris@679 248 if (!dir.exists()) continue;
Chris@679 249
Chris@679 250 dir.setNameFilters(filters);
Chris@679 251 QStringList entries = dir.entryList
Chris@679 252 (QDir::Files | QDir::Readable, QDir::Name);
Chris@679 253
Chris@679 254 for (QStringList::const_iterator j = entries.begin();
Chris@679 255 j != entries.end(); ++j) {
Chris@679 256 results << QString("%1/%2").arg(path).arg(*j);
Chris@679 257 }
Chris@679 258 }
Chris@679 259
Chris@679 260 return results;
Chris@679 261 }
Chris@679 262
Chris@679 263 bool
Chris@679 264 ResourceFinder::unbundleResource(QString resourceCat, QString fileName)
Chris@679 265 {
Chris@679 266 QString path = getResourcePath(resourceCat, fileName);
Chris@679 267
Chris@679 268 if (!path.startsWith(':')) return true;
Chris@679 269
Chris@679 270 // This is the lowest-priority alternative path for this
Chris@679 271 // resource, so we know that there must be no installed copy.
Chris@679 272 // Install one to the user location.
Chris@679 273 std::cerr << "ResourceFinder::unbundleResource: File " << fileName.toStdString() << " is bundled, un-bundling it" << std::endl;
Chris@679 274 QString target = getResourceSavePath(resourceCat, fileName);
Chris@679 275 QFile file(path);
Chris@679 276 if (!file.copy(target)) {
Chris@679 277 std::cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName.toStdString() << "\" to user location \"" << target.toStdString() << "\"" << std::endl;
Chris@679 278 return false;
Chris@679 279 }
Chris@679 280
Chris@679 281 // Now since the file is in the user's editable space, the user should get
Chris@679 282 // to edit it. The chords.xml file I unbundled came out 444 instead of 644
Chris@679 283 // which won't do. Rather than put the chmod code there, I decided to put
Chris@679 284 // it here, because I think it will always be appropriate to make unbundled
Chris@679 285 // files editable. That's rather the point in many cases, and for the rest,
Chris@679 286 // nobody will likely notice they could have edited their font files or what
Chris@679 287 // have you that were unbundled to improve performance. (Dissenting
Chris@679 288 // opinions welcome. We can always shuffle this somewhere else if
Chris@679 289 // necessary. There are many possibilities.)
Chris@679 290 QFile chmod(target);
Chris@679 291 chmod.setPermissions(QFile::ReadOwner |
Chris@679 292 QFile::ReadUser | /* for potential platform-independence */
Chris@679 293 QFile::ReadGroup |
Chris@679 294 QFile::ReadOther |
Chris@679 295 QFile::WriteOwner|
Chris@679 296 QFile::WriteUser); /* for potential platform-independence */
Chris@679 297
Chris@679 298 return true;
Chris@679 299 }
Chris@679 300