changeset 679:c8badbd4c005

* Introduce ResourceFinder
author Chris Cannam
date Wed, 04 May 2011 14:05:08 +0100
parents 948271d124ac
children 27cdabba2d3e
files base/ResourceFinder.cpp base/ResourceFinder.h svcore.pro transform/FeatureExtractionModelTransformer.cpp
diffstat 4 files changed, 420 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/ResourceFinder.cpp	Wed May 04 14:05:08 2011 +0100
@@ -0,0 +1,300 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2005-2011 Chris Cannam and the Rosegarden
+   development team.
+*/
+
+#include "ResourceFinder.h"
+
+#include <QDir>
+#include <QFileInfo>
+#include <QStringList>
+#include <QProcess>
+#include <QCoreApplication>
+
+#include <cstdlib>
+#include <iostream>
+
+/**
+   Resource files may be found in three places:
+
+   * Bundled into the application as Qt4 resources.  These may be
+     opened using Qt classes such as QFile, with "fake" file paths
+     starting with a colon.  For example ":icons/fileopen.png".
+
+   * Installed with the package, or in the user's equivalent home
+     directory location.  For example,
+
+     - on Linux, in /usr/share/<appname> or /usr/local/share/<appname>
+     - on Linux, in $HOME/.local/share/<appname>
+
+     - on OS/X, in /Library/Application Support/<appname>
+     - on OS/X, in $HOME/Library/Application Support/<appname>
+
+     - on Windows, in %ProgramFiles%/<company>/<appname>
+     - on Windows, in (where?)
+
+   These locations are searched in reverse order (user-installed
+   copies take priority over system-installed copies take priority
+   over bundled copies).  Also, /usr/local takes priority over /usr.
+*/
+
+QStringList
+ResourceFinder::getSystemResourcePrefixList()
+{
+    // returned in order of priority
+
+    QStringList list;
+
+#ifdef Q_OS_WIN32
+    char *programFiles = getenv("ProgramFiles");
+    if (programFiles && programFiles[0]) {
+        list << QString("%1/%2/%3")
+            .arg(programFiles)
+            .arg(qApp->organizationName())
+            .arg(qApp->applicationName());
+    } else {
+        list << QString("C:/Program Files/%1/%2")
+            .arg(qApp->organizationName())
+            .arg(qApp->applicationName());
+    }
+#else
+#ifdef Q_OS_MAC
+    list << QString("/Library/Application Support/%1/%2")
+        .arg(qApp->organizationName())
+        .arg(qApp->applicationName());
+#else
+    list << QString("/usr/local/share/%1")
+        .arg(qApp->applicationName());
+    list << QString("/usr/share/%1")
+        .arg(qApp->applicationName());
+#endif
+#endif    
+
+    return list;
+}
+
+QString
+ResourceFinder::getUserResourcePrefix()
+{
+    char *home = getenv("HOME");
+    if (!home || !home[0]) return "";
+
+#ifdef Q_OS_WIN32
+    return QString(); //!!!???
+#else
+#ifdef Q_OS_MAC
+    return QString("%1/Library/Application Support/%2/%3")
+        .arg(home)
+        .arg(qApp->organizationName())
+        .arg(qApp->applicationName());
+#else
+    return QString("%1/.local/share/%2")
+        .arg(home)
+        .arg(qApp->applicationName());
+#endif
+#endif    
+}
+
+QStringList
+ResourceFinder::getResourcePrefixList()
+{
+    // returned in order of priority
+
+    QStringList list;
+
+    QString user = getUserResourcePrefix();
+    if (user != "") list << user;
+
+    list << getSystemResourcePrefixList();
+
+    list << ":"; // bundled resource location
+
+    return list;
+}
+
+QString
+ResourceFinder::getResourcePath(QString resourceCat, QString fileName)
+{
+    // We don't simply call getResourceDir here, because that returns
+    // only the "installed file" location.  We also want to search the
+    // bundled resources and user-saved files.
+
+    QStringList prefixes = getResourcePrefixList();
+    
+    if (resourceCat != "") resourceCat = "/" + resourceCat;
+
+    for (QStringList::const_iterator i = prefixes.begin();
+         i != prefixes.end(); ++i) {
+        
+        QString prefix = *i;
+
+        std::cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName.toStdString() << "\" for category \"" << resourceCat.toStdString() << "\" in prefix \"" << prefix.toStdString() << "\"" << std::endl;
+
+        QString path =
+            QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName);
+        if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) {
+            std::cerr << "Found it!" << std::endl;
+            return path;
+        }
+    }
+
+    return "";
+}
+
+QString
+ResourceFinder::getResourceDir(QString resourceCat)
+{
+    // Returns only the "installed file" location
+
+    QStringList prefixes = getSystemResourcePrefixList();
+    
+    if (resourceCat != "") resourceCat = "/" + resourceCat;
+
+    for (QStringList::const_iterator i = prefixes.begin();
+         i != prefixes.end(); ++i) {
+        
+        QString prefix = *i;
+        QString path = QString("%1%2").arg(prefix).arg(resourceCat);
+        if (QFileInfo(path).exists() &&
+            QFileInfo(path).isDir() &&
+            QFileInfo(path).isReadable()) {
+            return path;
+        }
+    }
+
+    return "";
+}
+
+QString
+ResourceFinder::getResourceSavePath(QString resourceCat, QString fileName)
+{
+    QString dir = getResourceSaveDir(resourceCat);
+    if (dir == "") return "";
+
+    return dir + "/" + fileName;
+}
+
+QString
+ResourceFinder::getResourceSaveDir(QString resourceCat)
+{
+    // Returns the "user" location
+
+    QString user = getUserResourcePrefix();
+    if (user == "") return "";
+
+    if (resourceCat != "") resourceCat = "/" + resourceCat;
+
+    QDir userDir(user);
+    if (!userDir.exists()) {
+        if (!userDir.mkpath(user)) {
+            std::cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user.toStdString() << "\"" << std::endl;
+            return "";
+        }
+    }
+
+    if (resourceCat != "") {
+        QString save = QString("%1%2").arg(user).arg(resourceCat);
+        QDir saveDir(save);
+        if (!saveDir.exists()) {
+            if (!userDir.mkpath(save)) {
+                std::cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save.toStdString() << "\"" << std::endl;
+                return "";
+            }
+        }
+        return save;
+    } else {
+        return user;
+    }
+}
+
+QStringList
+ResourceFinder::getResourceFiles(QString resourceCat, QString fileExt)
+{
+    QStringList results;
+    QStringList prefixes = getResourcePrefixList();
+
+    QStringList filters;
+    filters << QString("*.%1").arg(fileExt);
+
+    for (QStringList::const_iterator i = prefixes.begin();
+         i != prefixes.end(); ++i) {
+        
+        QString prefix = *i;
+        QString path;
+
+        if (resourceCat != "") {
+            path = QString("%1/%2").arg(prefix).arg(resourceCat);
+        } else {
+            path = prefix;
+        }
+        
+        QDir dir(path);
+        if (!dir.exists()) continue;
+
+        dir.setNameFilters(filters);
+        QStringList entries = dir.entryList
+            (QDir::Files | QDir::Readable, QDir::Name);
+        
+        for (QStringList::const_iterator j = entries.begin();
+             j != entries.end(); ++j) {
+            results << QString("%1/%2").arg(path).arg(*j);
+        }
+    }
+
+    return results;
+}
+
+bool
+ResourceFinder::unbundleResource(QString resourceCat, QString fileName)
+{
+    QString path = getResourcePath(resourceCat, fileName);
+    
+    if (!path.startsWith(':')) return true;
+
+    // This is the lowest-priority alternative path for this
+    // resource, so we know that there must be no installed copy.
+    // Install one to the user location.
+    std::cerr << "ResourceFinder::unbundleResource: File " << fileName.toStdString() << " is bundled, un-bundling it" << std::endl;
+    QString target = getResourceSavePath(resourceCat, fileName);
+    QFile file(path);
+    if (!file.copy(target)) {
+        std::cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName.toStdString() << "\" to user location \"" << target.toStdString() << "\"" << std::endl;
+        return false;
+    }
+
+    // Now since the file is in the user's editable space, the user should get
+    // to edit it.  The chords.xml file I unbundled came out 444 instead of 644
+    // which won't do.  Rather than put the chmod code there, I decided to put
+    // it here, because I think it will always be appropriate to make unbundled
+    // files editable.  That's rather the point in many cases, and for the rest,
+    // nobody will likely notice they could have edited their font files or what
+    // have you that were unbundled to improve performance.  (Dissenting
+    // opinions welcome.  We can always shuffle this somewhere else if
+    // necessary.  There are many possibilities.)
+    QFile chmod(target);
+    chmod.setPermissions(QFile::ReadOwner |
+                         QFile::ReadUser  | /* for potential platform-independence */
+                         QFile::ReadGroup |
+                         QFile::ReadOther |
+                         QFile::WriteOwner|
+                         QFile::WriteUser); /* for potential platform-independence */
+
+    return true;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/ResourceFinder.h	Wed May 04 14:05:08 2011 +0100
@@ -0,0 +1,117 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2005-2011 Chris Cannam and the Rosegarden
+   development team.
+*/
+
+#ifndef _RESOURCE_FINDER_H_
+#define _RESOURCE_FINDER_H_
+
+#include <QString>
+	
+class ResourceFinder
+{
+public:
+    ResourceFinder() { }
+    virtual ~ResourceFinder() { }
+
+    /**
+     * Return the location (as a true file path, or a Qt4 ":"-prefixed
+     * resource path) of the file best matching the given resource
+     * filename in the given resource category.
+     * 
+     * Category should be a relative directory path without leading or
+     * trailing slashes, for example "chords".  The fileName is the
+     * remainder of the file name without any path content, for
+     * example "user_chords.xml".
+     *
+     * Returns an empty string if no matching resource is found.
+     *
+     * Use this when you know that a particular resource is required
+     * and just need to locate it.
+     */
+    QString getResourcePath(QString resourceCat, QString fileName);
+
+    /**
+     * Return a list of full file paths for files with the given file
+     * extension, found in the given resource category.
+     * 
+     * Category should be a relative directory path without leading or
+     * trailing slashes, for example "chords".  File extension should
+     * be the extension without the dot, for example "xml".  Returned
+     * list may mix true file paths in both installed and user
+     * locations with Qt4 ":"-prefixed resource paths.
+     *
+     * Use this when you need to enumerate the options available for
+     * use directly in the program (rather than e.g. offering the user
+     * a file-open dialog).
+     */
+    QStringList getResourceFiles(QString resourceCat, QString fileExt);
+
+    /**
+     * Return the true file path for installed resource files in the
+     * given resource category.  Category should be a relative
+     * directory path without leading or trailing slashes, for example
+     * "chords".  Note that resources may also exist in the Qt4
+     * resource bundle; this method only returns the external
+     * (installed) resource location.  Use getResourceFiles instead to
+     * return an authoritative list of available resources of a given
+     * type.
+     *
+     * Use this when you need a file path, e.g. for use in a file
+     * finder dialog.
+     */
+    QString getResourceDir(QString resourceCat);
+
+    /**
+     * Return the true file path for the location in which the named
+     * resource file in the given resource category should be saved.
+     */
+    QString getResourceSavePath(QString resourceCat, QString fileName);
+
+    /**
+     * Return the true file path for the location in which resource
+     * files in the given resource category should be saved.
+     */
+    QString getResourceSaveDir(QString resourceCat);
+
+    /**
+     * If the named resource file in the given resource category is
+     * available only as a bundled resource, copy it out into the user
+     * location returned by getResourceSavePath so that it can be read
+     * by non-Qt code.  Any subsequent call to getResourcePath for
+     * this resource should return a true file path (if the resource
+     * exists) in either user or system location, or an empty string
+     * (if the resource does not exist), but never a ":"-prefixed
+     * resource path.  This function does not overwrite any existing
+     * unbundled copy of the resource.
+     *
+     * Return false if a system error occurs during unbundling
+     * (e.g. disk full).
+     */
+    bool unbundleResource(QString resourceCat, QString fileName);
+
+protected:
+    QString getUserResourcePrefix();
+    QStringList getSystemResourcePrefixList();
+    QStringList getResourcePrefixList();
+};
+
+#endif
+
+    
--- a/svcore.pro	Thu Apr 14 15:20:27 2011 +0100
+++ b/svcore.pro	Wed May 04 14:05:08 2011 +0100
@@ -43,6 +43,7 @@
            base/RecentFiles.h \
            base/Resampler.h \
            base/ResizeableBitset.h \
+           base/ResourceFinder.h \
            base/RingBuffer.h \
            base/Scavenger.h \
            base/Selection.h \
@@ -75,6 +76,7 @@
            base/RealTime.cpp \
            base/RecentFiles.cpp \
            base/Resampler.cpp \
+           base/ResourceFinder.cpp \
            base/Selection.cpp \
            base/Serialiser.cpp \
            base/StorageAdviser.cpp \
--- a/transform/FeatureExtractionModelTransformer.cpp	Thu Apr 14 15:20:27 2011 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Wed May 04 14:05:08 2011 +0100
@@ -519,7 +519,7 @@
                 }
                 error = fftModels[ch]->getError();
                 if (error != "") {
-                    std::cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << std::endl;
+                    std::cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error.toStdString() << std::endl;
                     m_abandoned = true;
                     m_message = error;
                 }