annotate base/ResourceFinder.cpp @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents 57833933cc75
children
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@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@1480 38 #include "system/System.h"
Chris@1480 39
Chris@679 40 /**
Chris@679 41 Resource files may be found in three places:
Chris@679 42
Chris@679 43 * Bundled into the application as Qt4 resources. These may be
Chris@679 44 opened using Qt classes such as QFile, with "fake" file paths
Chris@679 45 starting with a colon. For example ":icons/fileopen.png".
Chris@679 46
Chris@679 47 * Installed with the package, or in the user's equivalent home
Chris@679 48 directory location. For example,
Chris@679 49
Chris@679 50 - on Linux, in /usr/share/<appname> or /usr/local/share/<appname>
Chris@679 51 - on Linux, in $HOME/.local/share/<appname>
Chris@679 52
Chris@679 53 - on OS/X, in /Library/Application Support/<appname>
Chris@679 54 - on OS/X, in $HOME/Library/Application Support/<appname>
Chris@679 55
Chris@679 56 - on Windows, in %ProgramFiles%/<company>/<appname>
Chris@680 57 - on Windows, in (where?) something from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx ?
Chris@679 58
Chris@679 59 These locations are searched in reverse order (user-installed
Chris@679 60 copies take priority over system-installed copies take priority
Chris@679 61 over bundled copies). Also, /usr/local takes priority over /usr.
Chris@679 62 */
Chris@679 63
Chris@679 64 QStringList
Chris@679 65 ResourceFinder::getSystemResourcePrefixList()
Chris@679 66 {
Chris@679 67 // returned in order of priority
Chris@679 68
Chris@679 69 QStringList list;
Chris@679 70
Chris@679 71 #ifdef Q_OS_WIN32
Chris@1480 72 std::string programFiles;
Chris@1480 73 (void)getEnvUtf8("ProgramFiles", programFiles);
Chris@1480 74 if (programFiles != "") {
Chris@679 75 list << QString("%1/%2/%3")
Chris@1480 76 .arg(QString::fromStdString(programFiles))
Chris@679 77 .arg(qApp->organizationName())
Chris@679 78 .arg(qApp->applicationName());
Chris@679 79 } else {
Chris@679 80 list << QString("C:/Program Files/%1/%2")
Chris@679 81 .arg(qApp->organizationName())
Chris@679 82 .arg(qApp->applicationName());
Chris@679 83 }
Chris@679 84 #else
Chris@679 85 #ifdef Q_OS_MAC
Chris@733 86 list << QString("/Library/Application Support/%1")
Chris@679 87 .arg(qApp->applicationName());
Chris@679 88 #else
Chris@679 89 list << QString("/usr/local/share/%1")
Chris@679 90 .arg(qApp->applicationName());
Chris@679 91 list << QString("/usr/share/%1")
Chris@679 92 .arg(qApp->applicationName());
Chris@679 93 #endif
Chris@679 94 #endif
Chris@679 95
Chris@679 96 return list;
Chris@679 97 }
Chris@679 98
Chris@983 99 static QString
Chris@983 100 getOldStyleUserResourcePrefix()
Chris@679 101 {
Chris@680 102 #ifdef Q_OS_WIN32
Chris@983 103 // This is awkward and does not work correctly for non-ASCII home
Chris@983 104 // directory names, hence getNewStyleUserResourcePrefix() below
Chris@680 105 char *homedrive = getenv("HOMEDRIVE");
Chris@680 106 char *homepath = getenv("HOMEPATH");
Chris@680 107 QString home;
Chris@680 108 if (homedrive && homepath) {
Chris@680 109 home = QString("%1%2").arg(homedrive).arg(homepath);
Chris@680 110 } else {
Chris@680 111 home = QDir::home().absolutePath();
Chris@680 112 }
Chris@680 113 if (home == "") return "";
Chris@708 114 return QString("%1/.%2").arg(home).arg(qApp->applicationName()); //!!! wrong
Chris@680 115 #else
Chris@679 116 char *home = getenv("HOME");
Chris@679 117 if (!home || !home[0]) return "";
Chris@679 118 #ifdef Q_OS_MAC
Chris@733 119 return QString("%1/Library/Application Support/%2")
Chris@679 120 .arg(home)
Chris@679 121 .arg(qApp->applicationName());
Chris@679 122 #else
Chris@679 123 return QString("%1/.local/share/%2")
Chris@679 124 .arg(home)
Chris@679 125 .arg(qApp->applicationName());
Chris@679 126 #endif
Chris@982 127 #endif
Chris@983 128 }
Chris@983 129
Chris@983 130 static QString
Chris@983 131 getNewStyleUserResourcePrefix()
Chris@983 132 {
Chris@1772 133 if (qApp->applicationName() == "" || qApp->organizationName() == "") {
Chris@1772 134 cerr << "ERROR: Can't use ResourceFinder before setting application and organization name" << endl;
Chris@1772 135 throw std::logic_error("Can't use ResourceFinder before setting application and organization name");
Chris@1247 136 }
Chris@1772 137
Chris@983 138 #if QT_VERSION >= 0x050000
Chris@1398 139
Chris@983 140 // This is expected to be much more reliable than
Chris@983 141 // getOldStyleUserResourcePrefix(), but it returns a different
Chris@983 142 // directory because it includes the organisation name (which is
Chris@983 143 // fair enough). Hence migrateOldStyleResources() which moves
Chris@983 144 // across any resources found in the old-style path the first time
Chris@983 145 // we look for the new-style one
Chris@1398 146 #if QT_VERSION >= 0x050400
Chris@1247 147 return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
Chris@983 148 #else
Chris@1398 149 cerr << "WARNING: ResourceFinder::getOldStyleUserResourcePrefix: Building with older version of Qt (pre 5.4), resource location may be incompatible with future versions" << endl;
Chris@1398 150 return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
Chris@1398 151 #endif
Chris@1398 152
Chris@1398 153 #else
Chris@1398 154 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 155 return getOldStyleUserResourcePrefix();
Chris@982 156 #endif
Chris@679 157 }
Chris@679 158
Chris@983 159 static void
Chris@983 160 migrateOldStyleResources()
Chris@983 161 {
Chris@983 162 QString oldPath = getOldStyleUserResourcePrefix();
Chris@983 163 QString newPath = getNewStyleUserResourcePrefix();
Chris@983 164
Chris@983 165 if (oldPath != newPath &&
Chris@983 166 QDir(oldPath).exists() &&
Chris@983 167 !QDir(newPath).exists()) {
Chris@983 168
Chris@983 169 QDir d(oldPath);
Chris@983 170
Chris@983 171 if (!d.mkpath(newPath)) {
Chris@983 172 cerr << "WARNING: Failed to create new-style resource path \""
Chris@983 173 << newPath << "\" to migrate old resources to" << endl;
Chris@983 174 return;
Chris@983 175 }
Chris@983 176
Chris@983 177 QDir target(newPath);
Chris@983 178
Chris@983 179 bool success = true;
Chris@983 180
Chris@983 181 QStringList entries
Chris@983 182 (d.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot));
Chris@983 183
Chris@983 184 foreach (QString entry, entries) {
Chris@983 185 if (d.rename(entry, target.filePath(entry))) {
Chris@983 186 cerr << "NOTE: Successfully moved resource \""
Chris@983 187 << entry << "\" from old resource path to new" << endl;
Chris@983 188 } else {
Chris@983 189 cerr << "WARNING: Failed to move old resource \""
Chris@983 190 << entry << "\" from old location \""
Chris@983 191 << oldPath << "\" to new location \""
Chris@983 192 << newPath << "\"" << endl;
Chris@983 193 success = false;
Chris@983 194 }
Chris@983 195 }
Chris@983 196
Chris@983 197 if (success) {
Chris@983 198 if (!d.rmdir(oldPath)) {
Chris@983 199 cerr << "WARNING: Failed to remove old resource path \""
Chris@983 200 << oldPath << "\" after migrating " << entries.size()
Chris@983 201 << " resource(s) to new path \"" << newPath
Chris@983 202 << "\" (directory not empty?)" << endl;
Chris@983 203 } else {
Chris@983 204 cerr << "NOTE: Successfully moved " << entries.size()
Chris@983 205 << " resource(s) from old resource "
Chris@983 206 << "path \"" << oldPath << "\" to new path \""
Chris@983 207 << newPath << "\"" << endl;
Chris@983 208 }
Chris@983 209 }
Chris@983 210 }
Chris@983 211 }
Chris@983 212
Chris@983 213 QString
Chris@983 214 ResourceFinder::getUserResourcePrefix()
Chris@983 215 {
Chris@983 216 migrateOldStyleResources();
Chris@983 217 return getNewStyleUserResourcePrefix();
Chris@983 218 }
Chris@983 219
Chris@679 220 QStringList
Chris@679 221 ResourceFinder::getResourcePrefixList()
Chris@679 222 {
Chris@679 223 // returned in order of priority
Chris@679 224
Chris@679 225 QStringList list;
Chris@679 226
Chris@679 227 QString user = getUserResourcePrefix();
Chris@679 228 if (user != "") list << user;
Chris@679 229
Chris@679 230 list << getSystemResourcePrefixList();
Chris@679 231
Chris@679 232 list << ":"; // bundled resource location
Chris@679 233
Chris@679 234 return list;
Chris@679 235 }
Chris@679 236
Chris@679 237 QString
Chris@679 238 ResourceFinder::getResourcePath(QString resourceCat, QString fileName)
Chris@679 239 {
Chris@679 240 // We don't simply call getResourceDir here, because that returns
Chris@679 241 // only the "installed file" location. We also want to search the
Chris@679 242 // bundled resources and user-saved files.
Chris@679 243
Chris@679 244 QStringList prefixes = getResourcePrefixList();
Chris@679 245
Chris@679 246 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 247
Chris@679 248 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 249 i != prefixes.end(); ++i) {
Chris@679 250
Chris@679 251 QString prefix = *i;
Chris@679 252
Chris@1398 253 // cerr << "ResourceFinder::getResourcePath: Looking up file \"" << fileName << "\" for category \"" << resourceCat << "\" in prefix \"" << prefix << "\"" << endl;
Chris@679 254
Chris@679 255 QString path =
Chris@679 256 QString("%1%2/%3").arg(prefix).arg(resourceCat).arg(fileName);
Chris@679 257 if (QFileInfo(path).exists() && QFileInfo(path).isReadable()) {
Chris@1398 258 // cerr << "Found it!" << endl;
Chris@679 259 return path;
Chris@679 260 }
Chris@679 261 }
Chris@679 262
Chris@679 263 return "";
Chris@679 264 }
Chris@679 265
Chris@679 266 QString
Chris@679 267 ResourceFinder::getResourceDir(QString resourceCat)
Chris@679 268 {
Chris@679 269 // Returns only the "installed file" location
Chris@679 270
Chris@679 271 QStringList prefixes = getSystemResourcePrefixList();
Chris@679 272
Chris@679 273 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 274
Chris@679 275 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 276 i != prefixes.end(); ++i) {
Chris@679 277
Chris@679 278 QString prefix = *i;
Chris@679 279 QString path = QString("%1%2").arg(prefix).arg(resourceCat);
Chris@679 280 if (QFileInfo(path).exists() &&
Chris@679 281 QFileInfo(path).isDir() &&
Chris@679 282 QFileInfo(path).isReadable()) {
Chris@679 283 return path;
Chris@679 284 }
Chris@679 285 }
Chris@679 286
Chris@679 287 return "";
Chris@679 288 }
Chris@679 289
Chris@679 290 QString
Chris@679 291 ResourceFinder::getResourceSavePath(QString resourceCat, QString fileName)
Chris@679 292 {
Chris@679 293 QString dir = getResourceSaveDir(resourceCat);
Chris@679 294 if (dir == "") return "";
Chris@679 295
Chris@679 296 return dir + "/" + fileName;
Chris@679 297 }
Chris@679 298
Chris@679 299 QString
Chris@679 300 ResourceFinder::getResourceSaveDir(QString resourceCat)
Chris@679 301 {
Chris@679 302 // Returns the "user" location
Chris@679 303
Chris@679 304 QString user = getUserResourcePrefix();
Chris@679 305 if (user == "") return "";
Chris@679 306
Chris@679 307 if (resourceCat != "") resourceCat = "/" + resourceCat;
Chris@679 308
Chris@679 309 QDir userDir(user);
Chris@679 310 if (!userDir.exists()) {
Chris@679 311 if (!userDir.mkpath(user)) {
Chris@843 312 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << user << "\"" << endl;
Chris@679 313 return "";
Chris@679 314 }
Chris@679 315 }
Chris@679 316
Chris@679 317 if (resourceCat != "") {
Chris@679 318 QString save = QString("%1%2").arg(user).arg(resourceCat);
Chris@679 319 QDir saveDir(save);
Chris@679 320 if (!saveDir.exists()) {
Chris@959 321 if (!saveDir.mkpath(save)) {
Chris@843 322 cerr << "ResourceFinder::getResourceSaveDir: ERROR: Failed to create user resource path \"" << save << "\"" << endl;
Chris@679 323 return "";
Chris@679 324 }
Chris@679 325 }
Chris@679 326 return save;
Chris@679 327 } else {
Chris@679 328 return user;
Chris@679 329 }
Chris@679 330 }
Chris@679 331
Chris@679 332 QStringList
Chris@679 333 ResourceFinder::getResourceFiles(QString resourceCat, QString fileExt)
Chris@679 334 {
Chris@679 335 QStringList results;
Chris@679 336 QStringList prefixes = getResourcePrefixList();
Chris@679 337
Chris@679 338 QStringList filters;
Chris@679 339 filters << QString("*.%1").arg(fileExt);
Chris@679 340
Chris@679 341 for (QStringList::const_iterator i = prefixes.begin();
Chris@679 342 i != prefixes.end(); ++i) {
Chris@679 343
Chris@679 344 QString prefix = *i;
Chris@679 345 QString path;
Chris@679 346
Chris@679 347 if (resourceCat != "") {
Chris@679 348 path = QString("%1/%2").arg(prefix).arg(resourceCat);
Chris@679 349 } else {
Chris@679 350 path = prefix;
Chris@679 351 }
Chris@679 352
Chris@679 353 QDir dir(path);
Chris@679 354 if (!dir.exists()) continue;
Chris@679 355
Chris@679 356 dir.setNameFilters(filters);
Chris@679 357 QStringList entries = dir.entryList
Chris@679 358 (QDir::Files | QDir::Readable, QDir::Name);
Chris@679 359
Chris@679 360 for (QStringList::const_iterator j = entries.begin();
Chris@679 361 j != entries.end(); ++j) {
Chris@679 362 results << QString("%1/%2").arg(path).arg(*j);
Chris@679 363 }
Chris@679 364 }
Chris@679 365
Chris@679 366 return results;
Chris@679 367 }
Chris@679 368
Chris@679 369 bool
Chris@679 370 ResourceFinder::unbundleResource(QString resourceCat, QString fileName)
Chris@679 371 {
Chris@679 372 QString path = getResourcePath(resourceCat, fileName);
Chris@679 373
Chris@679 374 if (!path.startsWith(':')) return true;
Chris@679 375
Chris@679 376 // This is the lowest-priority alternative path for this
Chris@679 377 // resource, so we know that there must be no installed copy.
Chris@679 378 // Install one to the user location.
Chris@690 379 SVDEBUG << "ResourceFinder::unbundleResource: File " << fileName << " is bundled, un-bundling it" << endl;
Chris@679 380 QString target = getResourceSavePath(resourceCat, fileName);
Chris@679 381 QFile file(path);
Chris@679 382 if (!file.copy(target)) {
Chris@843 383 cerr << "ResourceFinder::unbundleResource: ERROR: Failed to un-bundle resource file \"" << fileName << "\" to user location \"" << target << "\"" << endl;
Chris@679 384 return false;
Chris@679 385 }
Chris@679 386
Chris@679 387 QFile chmod(target);
Chris@679 388 chmod.setPermissions(QFile::ReadOwner |
Chris@679 389 QFile::ReadUser | /* for potential platform-independence */
Chris@679 390 QFile::ReadGroup |
Chris@679 391 QFile::ReadOther |
Chris@679 392 QFile::WriteOwner|
Chris@679 393 QFile::WriteUser); /* for potential platform-independence */
Chris@679 394
Chris@679 395 return true;
Chris@679 396 }
Chris@679 397