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@679: #include Chris@679: #include Chris@679: 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@679: char *programFiles = getenv("ProgramFiles"); Chris@679: if (programFiles && programFiles[0]) { Chris@679: list << QString("%1/%2/%3") Chris@679: .arg(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@679: list << QString("/Library/Application Support/%1/%2") Chris@679: .arg(qApp->organizationName()) 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@679: QString Chris@679: ResourceFinder::getUserResourcePrefix() Chris@679: { Chris@680: #ifdef Q_OS_WIN32 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@680: return QString("%1/.%2").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@679: return QString("%1/Library/Application Support/%2/%3") Chris@679: .arg(home) Chris@679: .arg(qApp->organizationName()) 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@679: #endif Chris@679: } Chris@679: 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@679: std::cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName.toStdString() << "\" for category \"" << resourceCat.toStdString() << "\" in prefix \"" << prefix.toStdString() << "\"" << std::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@679: std::cerr << "Found it!" << std::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@679: std::cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user.toStdString() << "\"" << std::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@679: if (!userDir.mkpath(save)) { Chris@679: std::cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save.toStdString() << "\"" << std::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@679: std::cerr << "ResourceFinder::unbundleResource: File " << fileName.toStdString() << " is bundled, un-bundling it" << std::endl; Chris@679: QString target = getResourceSavePath(resourceCat, fileName); Chris@679: QFile file(path); Chris@679: if (!file.copy(target)) { Chris@679: std::cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName.toStdString() << "\" to user location \"" << target.toStdString() << "\"" << std::endl; Chris@679: return false; Chris@679: } Chris@679: Chris@679: // Now since the file is in the user's editable space, the user should get Chris@679: // to edit it. The chords.xml file I unbundled came out 444 instead of 644 Chris@679: // which won't do. Rather than put the chmod code there, I decided to put Chris@679: // it here, because I think it will always be appropriate to make unbundled Chris@679: // files editable. That's rather the point in many cases, and for the rest, Chris@679: // nobody will likely notice they could have edited their font files or what Chris@679: // have you that were unbundled to improve performance. (Dissenting Chris@679: // opinions welcome. We can always shuffle this somewhere else if Chris@679: // necessary. There are many possibilities.) 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: