| 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 |