Chris@679: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@679: Chris@679: /* Chris@679: Sonic Visualiser Chris@679: An audio file viewer and annotation editor. Chris@679: Centre for Digital Music, Queen Mary, University of London. Chris@679: Chris@679: This program is free software; you can redistribute it and/or Chris@679: modify it under the terms of the GNU General Public License as Chris@679: published by the Free Software Foundation; either version 2 of the Chris@679: License, or (at your option) any later version. See the file Chris@679: COPYING included with this distribution for more information. Chris@679: */ Chris@679: Chris@679: /* Chris@679: This is a modified version of a source file from the Chris@679: Rosegarden MIDI and audio sequencer and notation editor. Chris@679: This file copyright 2005-2011 Chris Cannam and the Rosegarden Chris@679: development team. Chris@679: */ Chris@679: Chris@679: #include "ResourceFinder.h" Chris@679: Chris@679: #include Chris@679: #include Chris@679: #include Chris@679: #include Chris@679: #include Chris@679: Chris@982: #if QT_VERSION >= 0x050000 Chris@982: #include Chris@982: #endif Chris@982: Chris@679: #include Chris@679: #include Chris@1247: #include Chris@679: Chris@1480: #include "system/System.h" Chris@1480: Chris@679: /** Chris@679: Resource files may be found in three places: Chris@679: Chris@679: * Bundled into the application as Qt4 resources. These may be Chris@679: opened using Qt classes such as QFile, with "fake" file paths Chris@679: starting with a colon. For example ":icons/fileopen.png". Chris@679: Chris@679: * Installed with the package, or in the user's equivalent home Chris@679: directory location. For example, Chris@679: Chris@679: - on Linux, in /usr/share/ or /usr/local/share/ Chris@679: - on Linux, in $HOME/.local/share/ Chris@679: Chris@679: - on OS/X, in /Library/Application Support/ Chris@679: - on OS/X, in $HOME/Library/Application Support/ Chris@679: Chris@679: - on Windows, in %ProgramFiles%// Chris@680: - on Windows, in (where?) something from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx ? Chris@679: Chris@679: These locations are searched in reverse order (user-installed Chris@679: copies take priority over system-installed copies take priority Chris@679: over bundled copies). Also, /usr/local takes priority over /usr. Chris@679: */ Chris@679: Chris@679: QStringList Chris@679: ResourceFinder::getSystemResourcePrefixList() Chris@679: { Chris@679: // returned in order of priority Chris@679: Chris@679: QStringList list; Chris@679: Chris@679: #ifdef Q_OS_WIN32 Chris@1480: std::string programFiles; Chris@1480: (void)getEnvUtf8("ProgramFiles", programFiles); Chris@1480: if (programFiles != "") { Chris@679: list << QString("%1/%2/%3") Chris@1480: .arg(QString::fromStdString(programFiles)) Chris@679: .arg(qApp->organizationName()) Chris@679: .arg(qApp->applicationName()); Chris@679: } else { Chris@679: list << QString("C:/Program Files/%1/%2") Chris@679: .arg(qApp->organizationName()) Chris@679: .arg(qApp->applicationName()); Chris@679: } Chris@679: #else Chris@679: #ifdef Q_OS_MAC Chris@733: list << QString("/Library/Application Support/%1") Chris@679: .arg(qApp->applicationName()); Chris@679: #else Chris@679: list << QString("/usr/local/share/%1") Chris@679: .arg(qApp->applicationName()); Chris@679: list << QString("/usr/share/%1") Chris@679: .arg(qApp->applicationName()); Chris@679: #endif Chris@679: #endif Chris@679: Chris@679: return list; Chris@679: } Chris@679: Chris@983: static QString Chris@983: getOldStyleUserResourcePrefix() Chris@679: { Chris@680: #ifdef Q_OS_WIN32 Chris@983: // This is awkward and does not work correctly for non-ASCII home Chris@983: // directory names, hence getNewStyleUserResourcePrefix() below Chris@680: char *homedrive = getenv("HOMEDRIVE"); Chris@680: char *homepath = getenv("HOMEPATH"); Chris@680: QString home; Chris@680: if (homedrive && homepath) { Chris@680: home = QString("%1%2").arg(homedrive).arg(homepath); Chris@680: } else { Chris@680: home = QDir::home().absolutePath(); Chris@680: } Chris@680: if (home == "") return ""; Chris@708: return QString("%1/.%2").arg(home).arg(qApp->applicationName()); //!!! wrong Chris@680: #else Chris@679: char *home = getenv("HOME"); Chris@679: if (!home || !home[0]) return ""; Chris@679: #ifdef Q_OS_MAC Chris@733: return QString("%1/Library/Application Support/%2") Chris@679: .arg(home) Chris@679: .arg(qApp->applicationName()); Chris@679: #else Chris@679: return QString("%1/.local/share/%2") Chris@679: .arg(home) Chris@679: .arg(qApp->applicationName()); Chris@679: #endif Chris@982: #endif Chris@983: } Chris@983: Chris@983: static QString Chris@983: getNewStyleUserResourcePrefix() Chris@983: { Chris@1247: if (qApp->applicationName() == "") { Chris@1247: cerr << "ERROR: Can't use ResourceFinder before setting application name" << endl; Chris@1247: throw std::logic_error("Can't use ResourceFinder before setting application name"); Chris@1247: } Chris@1247: Chris@983: #if QT_VERSION >= 0x050000 Chris@1398: Chris@983: // This is expected to be much more reliable than Chris@983: // getOldStyleUserResourcePrefix(), but it returns a different Chris@983: // directory because it includes the organisation name (which is Chris@983: // fair enough). Hence migrateOldStyleResources() which moves Chris@983: // across any resources found in the old-style path the first time Chris@983: // we look for the new-style one Chris@1398: #if QT_VERSION >= 0x050400 Chris@1247: return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); Chris@983: #else Chris@1398: cerr << "WARNING: ResourceFinder::getOldStyleUserResourcePrefix: Building with older version of Qt (pre 5.4), resource location may be incompatible with future versions" << endl; Chris@1398: return QStandardPaths::writableLocation(QStandardPaths::DataLocation); Chris@1398: #endif Chris@1398: Chris@1398: #else Chris@1398: 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: return getOldStyleUserResourcePrefix(); Chris@982: #endif Chris@679: } Chris@679: Chris@983: static void Chris@983: migrateOldStyleResources() Chris@983: { Chris@983: QString oldPath = getOldStyleUserResourcePrefix(); Chris@983: QString newPath = getNewStyleUserResourcePrefix(); Chris@983: Chris@983: if (oldPath != newPath && Chris@983: QDir(oldPath).exists() && Chris@983: !QDir(newPath).exists()) { Chris@983: Chris@983: QDir d(oldPath); Chris@983: Chris@983: if (!d.mkpath(newPath)) { Chris@983: cerr << "WARNING: Failed to create new-style resource path \"" Chris@983: << newPath << "\" to migrate old resources to" << endl; Chris@983: return; Chris@983: } Chris@983: Chris@983: QDir target(newPath); Chris@983: Chris@983: bool success = true; Chris@983: Chris@983: QStringList entries Chris@983: (d.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)); Chris@983: Chris@983: foreach (QString entry, entries) { Chris@983: if (d.rename(entry, target.filePath(entry))) { Chris@983: cerr << "NOTE: Successfully moved resource \"" Chris@983: << entry << "\" from old resource path to new" << endl; Chris@983: } else { Chris@983: cerr << "WARNING: Failed to move old resource \"" Chris@983: << entry << "\" from old location \"" Chris@983: << oldPath << "\" to new location \"" Chris@983: << newPath << "\"" << endl; Chris@983: success = false; Chris@983: } Chris@983: } Chris@983: Chris@983: if (success) { Chris@983: if (!d.rmdir(oldPath)) { Chris@983: cerr << "WARNING: Failed to remove old resource path \"" Chris@983: << oldPath << "\" after migrating " << entries.size() Chris@983: << " resource(s) to new path \"" << newPath Chris@983: << "\" (directory not empty?)" << endl; Chris@983: } else { Chris@983: cerr << "NOTE: Successfully moved " << entries.size() Chris@983: << " resource(s) from old resource " Chris@983: << "path \"" << oldPath << "\" to new path \"" Chris@983: << newPath << "\"" << endl; Chris@983: } Chris@983: } Chris@983: } Chris@983: } Chris@983: Chris@983: QString Chris@983: ResourceFinder::getUserResourcePrefix() Chris@983: { Chris@983: migrateOldStyleResources(); Chris@983: return getNewStyleUserResourcePrefix(); Chris@983: } Chris@983: Chris@679: QStringList Chris@679: ResourceFinder::getResourcePrefixList() Chris@679: { Chris@679: // returned in order of priority Chris@679: Chris@679: QStringList list; Chris@679: Chris@679: QString user = getUserResourcePrefix(); Chris@679: if (user != "") list << user; Chris@679: Chris@679: list << getSystemResourcePrefixList(); Chris@679: Chris@679: list << ":"; // bundled resource location Chris@679: Chris@679: return list; Chris@679: } Chris@679: Chris@679: QString Chris@679: ResourceFinder::getResourcePath(QString resourceCat, QString fileName) Chris@679: { Chris@679: // We don't simply call getResourceDir here, because that returns Chris@679: // only the "installed file" location. We also want to search the Chris@679: // bundled resources and user-saved files. Chris@679: Chris@679: QStringList prefixes = getResourcePrefixList(); Chris@679: Chris@679: if (resourceCat != "") resourceCat = "/" + resourceCat; Chris@679: Chris@679: for (QStringList::const_iterator i = prefixes.begin(); Chris@679: i != prefixes.end(); ++i) { Chris@679: Chris@679: QString prefix = *i; Chris@679: Chris@1398: // cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName << "\" for category \"" << resourceCat << "\" in prefix \"" << prefix << "\"" << endl; Chris@679: Chris@679: QString path = Chris@679: QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName); Chris@679: if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) { Chris@1398: // cerr << "Found it!" << endl; Chris@679: return path; Chris@679: } Chris@679: } Chris@679: Chris@679: return ""; Chris@679: } Chris@679: Chris@679: QString Chris@679: ResourceFinder::getResourceDir(QString resourceCat) Chris@679: { Chris@679: // Returns only the "installed file" location Chris@679: Chris@679: QStringList prefixes = getSystemResourcePrefixList(); Chris@679: Chris@679: if (resourceCat != "") resourceCat = "/" + resourceCat; Chris@679: Chris@679: for (QStringList::const_iterator i = prefixes.begin(); Chris@679: i != prefixes.end(); ++i) { Chris@679: Chris@679: QString prefix = *i; Chris@679: QString path = QString("%1%2").arg(prefix).arg(resourceCat); Chris@679: if (QFileInfo(path).exists() && Chris@679: QFileInfo(path).isDir() && Chris@679: QFileInfo(path).isReadable()) { Chris@679: return path; Chris@679: } Chris@679: } Chris@679: Chris@679: return ""; Chris@679: } Chris@679: Chris@679: QString Chris@679: ResourceFinder::getResourceSavePath(QString resourceCat, QString fileName) Chris@679: { Chris@679: QString dir = getResourceSaveDir(resourceCat); Chris@679: if (dir == "") return ""; Chris@679: Chris@679: return dir + "/" + fileName; Chris@679: } Chris@679: Chris@679: QString Chris@679: ResourceFinder::getResourceSaveDir(QString resourceCat) Chris@679: { Chris@679: // Returns the "user" location Chris@679: Chris@679: QString user = getUserResourcePrefix(); Chris@679: if (user == "") return ""; Chris@679: Chris@679: if (resourceCat != "") resourceCat = "/" + resourceCat; Chris@679: Chris@679: QDir userDir(user); Chris@679: if (!userDir.exists()) { Chris@679: if (!userDir.mkpath(user)) { Chris@843: cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user << "\"" << endl; Chris@679: return ""; Chris@679: } Chris@679: } Chris@679: Chris@679: if (resourceCat != "") { Chris@679: QString save = QString("%1%2").arg(user).arg(resourceCat); Chris@679: QDir saveDir(save); Chris@679: if (!saveDir.exists()) { Chris@959: if (!saveDir.mkpath(save)) { Chris@843: cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save << "\"" << endl; Chris@679: return ""; Chris@679: } Chris@679: } Chris@679: return save; Chris@679: } else { Chris@679: return user; Chris@679: } Chris@679: } Chris@679: Chris@679: QStringList Chris@679: ResourceFinder::getResourceFiles(QString resourceCat, QString fileExt) Chris@679: { Chris@679: QStringList results; Chris@679: QStringList prefixes = getResourcePrefixList(); Chris@679: Chris@679: QStringList filters; Chris@679: filters << QString("*.%1").arg(fileExt); Chris@679: Chris@679: for (QStringList::const_iterator i = prefixes.begin(); Chris@679: i != prefixes.end(); ++i) { Chris@679: Chris@679: QString prefix = *i; Chris@679: QString path; Chris@679: Chris@679: if (resourceCat != "") { Chris@679: path = QString("%1/%2").arg(prefix).arg(resourceCat); Chris@679: } else { Chris@679: path = prefix; Chris@679: } Chris@679: Chris@679: QDir dir(path); Chris@679: if (!dir.exists()) continue; Chris@679: Chris@679: dir.setNameFilters(filters); Chris@679: QStringList entries = dir.entryList Chris@679: (QDir::Files | QDir::Readable, QDir::Name); Chris@679: Chris@679: for (QStringList::const_iterator j = entries.begin(); Chris@679: j != entries.end(); ++j) { Chris@679: results << QString("%1/%2").arg(path).arg(*j); Chris@679: } Chris@679: } Chris@679: Chris@679: return results; Chris@679: } Chris@679: Chris@679: bool Chris@679: ResourceFinder::unbundleResource(QString resourceCat, QString fileName) Chris@679: { Chris@679: QString path = getResourcePath(resourceCat, fileName); Chris@679: Chris@679: if (!path.startsWith(':')) return true; Chris@679: Chris@679: // This is the lowest-priority alternative path for this Chris@679: // resource, so we know that there must be no installed copy. Chris@679: // Install one to the user location. Chris@690: SVDEBUG << "ResourceFinder::unbundleResource: File " << fileName << " is bundled, un-bundling it" << endl; Chris@679: QString target = getResourceSavePath(resourceCat, fileName); Chris@679: QFile file(path); Chris@679: if (!file.copy(target)) { Chris@843: cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName << "\" to user location \"" << target << "\"" << endl; Chris@679: return false; Chris@679: } Chris@679: Chris@679: QFile chmod(target); Chris@679: chmod.setPermissions(QFile::ReadOwner | Chris@679: QFile::ReadUser | /* for potential platform-independence */ Chris@679: QFile::ReadGroup | Chris@679: QFile::ReadOther | Chris@679: QFile::WriteOwner| Chris@679: QFile::WriteUser); /* for potential platform-independence */ Chris@679: Chris@679: return true; Chris@679: } Chris@679: