mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-20 09:29:19 +02:00
318 lines
11 KiB
C++
318 lines
11 KiB
C++
//
|
|
// PathUtils.cpp
|
|
// libraries/shared/src
|
|
//
|
|
// Created by Brad Hefta-Gaub on 12/15/14.
|
|
// Copyright 2014 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include "PathUtils.h"
|
|
|
|
#include <mutex> // std::once
|
|
|
|
#include <QtCore/QCoreApplication>
|
|
#include <QtCore/QDateTime>
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QDirIterator>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtCore/QProcessEnvironment>
|
|
#include <QtCore/QRegularExpression>
|
|
#include <QtCore/QStandardPaths>
|
|
#include <QtCore/QString>
|
|
#include <QtCore/QUrl>
|
|
#include <QtCore/QVector>
|
|
|
|
#if defined(Q_OS_OSX)
|
|
#include <mach-o/dyld.h>
|
|
#endif
|
|
|
|
#include "shared/GlobalAppProperties.h"
|
|
#include "SharedUtil.h"
|
|
|
|
|
|
// Format: AppName-PID-Timestamp
|
|
// Example: ...
|
|
QString TEMP_DIR_FORMAT { "%1-%2-%3" };
|
|
|
|
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
|
|
static bool USE_SOURCE_TREE_RESOURCES() {
|
|
static bool result = false;
|
|
static std::once_flag once;
|
|
std::call_once(once, [&] {
|
|
const QString USE_SOURCE_TREE_RESOURCES_FLAG("HIFI_USE_SOURCE_TREE_RESOURCES");
|
|
result = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG);
|
|
});
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
const QString& PathUtils::getRccPath() {
|
|
static QString rccLocation;
|
|
static std::once_flag once;
|
|
std::call_once(once, [&] {
|
|
static const QString rccName{ "/resources.rcc" };
|
|
#if defined(Q_OS_OSX)
|
|
char buffer[8192];
|
|
uint32_t bufferSize = sizeof(buffer);
|
|
_NSGetExecutablePath(buffer, &bufferSize);
|
|
rccLocation = QDir::cleanPath(QFileInfo(buffer).dir().absoluteFilePath("../Resources")) + rccName;
|
|
#elif defined(Q_OS_ANDROID)
|
|
rccLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + rccName;
|
|
#else
|
|
rccLocation = QCoreApplication::applicationDirPath() + rccName;
|
|
#endif
|
|
});
|
|
return rccLocation;
|
|
}
|
|
|
|
#ifdef DEV_BUILD
|
|
const QString& PathUtils::projectRootPath() {
|
|
static QString sourceFolder;
|
|
static std::once_flag once;
|
|
std::call_once(once, [&] {
|
|
QDir thisDir = QFileInfo(__FILE__).absoluteDir();
|
|
sourceFolder = QDir::cleanPath(thisDir.absoluteFilePath("../../../"));
|
|
});
|
|
return sourceFolder;
|
|
}
|
|
#endif
|
|
|
|
const QString& PathUtils::resourcesPath() {
|
|
static QString staticResourcePath{ ":/" };
|
|
static std::once_flag once;
|
|
std::call_once(once, [&]{
|
|
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
|
|
if (USE_SOURCE_TREE_RESOURCES()) {
|
|
// For dev builds, optionally load content from the Git source tree
|
|
staticResourcePath = projectRootPath() + "/interface/resources/";
|
|
}
|
|
#endif
|
|
});
|
|
return staticResourcePath;
|
|
}
|
|
|
|
const QString& PathUtils::resourcesUrl() {
|
|
static QString staticResourcePath{ "qrc:///" };
|
|
static std::once_flag once;
|
|
std::call_once(once, [&]{
|
|
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
|
|
if (USE_SOURCE_TREE_RESOURCES()) {
|
|
// For dev builds, optionally load content from the Git source tree
|
|
staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/").toString();
|
|
}
|
|
#endif
|
|
});
|
|
return staticResourcePath;
|
|
}
|
|
|
|
QUrl PathUtils::resourcesUrl(const QString& relativeUrl) {
|
|
return QUrl(resourcesUrl() + relativeUrl);
|
|
}
|
|
|
|
QUrl PathUtils::expandToLocalDataAbsolutePath(const QUrl& fileUrl) {
|
|
QString path = fileUrl.path();
|
|
|
|
if (path.startsWith("/~/")) {
|
|
// this results in a qrc:// url...
|
|
// return resourcesUrl(path.mid(3));
|
|
|
|
#ifdef Q_OS_MAC
|
|
static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
|
|
#elif defined (ANDROID)
|
|
static const QString staticResourcePath =
|
|
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources/";
|
|
#else
|
|
static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
|
|
#endif
|
|
path.replace(0, 3, staticResourcePath);
|
|
QUrl expandedURL = QUrl::fromLocalFile(path);
|
|
return expandedURL;
|
|
}
|
|
|
|
return fileUrl;
|
|
}
|
|
|
|
const QString& PathUtils::qmlBaseUrl() {
|
|
static const QString staticResourcePath = resourcesUrl() + "qml/";
|
|
return staticResourcePath;
|
|
}
|
|
|
|
QUrl PathUtils::qmlUrl(const QString& relativeUrl) {
|
|
return QUrl(qmlBaseUrl() + relativeUrl);
|
|
}
|
|
|
|
QString PathUtils::getAppDataPath() {
|
|
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/";
|
|
}
|
|
|
|
QString PathUtils::getAppLocalDataPath() {
|
|
QString overriddenPath = qApp->property(hifi::properties::APP_LOCAL_DATA_PATH).toString();
|
|
// return overridden path if set
|
|
if (!overriddenPath.isEmpty()) {
|
|
return overriddenPath;
|
|
}
|
|
|
|
// otherwise return standard path
|
|
#ifdef Q_OS_ANDROID
|
|
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/";
|
|
#else
|
|
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/";
|
|
#endif
|
|
}
|
|
|
|
QString PathUtils::getAppDataFilePath(const QString& filename) {
|
|
return QDir(getAppDataPath()).absoluteFilePath(filename);
|
|
}
|
|
|
|
QString PathUtils::getAppLocalDataFilePath(const QString& filename) {
|
|
return QDir(getAppLocalDataPath()).absoluteFilePath(filename);
|
|
}
|
|
|
|
QString PathUtils::generateTemporaryDir() {
|
|
QDir rootTempDir = QDir::tempPath();
|
|
QString appName = qApp->applicationName();
|
|
for (auto i = 0; i < 64; ++i) {
|
|
auto now = std::chrono::system_clock::now().time_since_epoch().count();
|
|
auto dirName = TEMP_DIR_FORMAT.arg(appName).arg(qApp->applicationPid()).arg(now);
|
|
QDir tempDir = rootTempDir.filePath(dirName);
|
|
if (tempDir.mkpath(".")) {
|
|
return tempDir.absolutePath();
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool PathUtils::deleteMyTemporaryDir(QString dirName) {
|
|
QDir rootTempDir = QDir::tempPath();
|
|
|
|
QString appName = qApp->applicationName();
|
|
QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };
|
|
|
|
auto match = re.match(dirName);
|
|
auto pid = match.capturedRef("pid").toLongLong();
|
|
|
|
if (match.hasMatch() && rootTempDir.exists(dirName) && pid == qApp->applicationPid()) {
|
|
auto absoluteDirPath = QDir(rootTempDir.absoluteFilePath(dirName));
|
|
|
|
bool success = absoluteDirPath.removeRecursively();
|
|
if (success) {
|
|
qDebug() << " Removing temporary directory: " << absoluteDirPath.absolutePath();
|
|
} else {
|
|
qDebug() << " Failed to remove temporary directory: " << absoluteDirPath.absolutePath();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Delete all temporary directories for an application
|
|
int PathUtils::removeTemporaryApplicationDirs(QString appName) {
|
|
if (appName.isNull()) {
|
|
appName = qApp->applicationName();
|
|
}
|
|
|
|
auto dirName = TEMP_DIR_FORMAT.arg(appName).arg("*").arg("*");
|
|
|
|
QDir rootTempDir = QDir::tempPath();
|
|
auto dirs = rootTempDir.entryInfoList({ dirName }, QDir::Dirs);
|
|
int removed = 0;
|
|
for (auto& dir : dirs) {
|
|
auto dirName = dir.fileName();
|
|
auto absoluteDirPath = QDir(dir.absoluteFilePath());
|
|
QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };
|
|
|
|
auto match = re.match(dirName);
|
|
if (match.hasMatch()) {
|
|
auto pid = match.capturedRef("pid").toLongLong();
|
|
auto timestamp = match.capturedRef("timestamp");
|
|
if (!processIsRunning(pid)) {
|
|
qDebug() << " Removing old temporary directory: " << dir.absoluteFilePath();
|
|
absoluteDirPath.removeRecursively();
|
|
removed++;
|
|
} else {
|
|
qDebug() << " Not removing (process is running): " << dir.absoluteFilePath();
|
|
}
|
|
}
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
QString fileNameWithoutExtension(const QString& fileName, const QVector<QString> possibleExtensions) {
|
|
QString fileNameLowered = fileName.toLower();
|
|
foreach (const QString possibleExtension, possibleExtensions) {
|
|
if (fileNameLowered.endsWith(possibleExtension.toLower())) {
|
|
return fileName.left(fileName.count() - possibleExtension.count() - 1);
|
|
}
|
|
}
|
|
return fileName;
|
|
}
|
|
|
|
QString findMostRecentFileExtension(const QString& originalFileName, QVector<QString> possibleExtensions) {
|
|
QString sansExt = fileNameWithoutExtension(originalFileName, possibleExtensions);
|
|
QString newestFileName = originalFileName;
|
|
QDateTime newestTime = QDateTime::fromMSecsSinceEpoch(0);
|
|
foreach (QString possibleExtension, possibleExtensions) {
|
|
QString fileName = sansExt + "." + possibleExtension;
|
|
QFileInfo fileInfo(fileName);
|
|
if (fileInfo.exists() && fileInfo.lastModified() > newestTime) {
|
|
newestFileName = fileName;
|
|
newestTime = fileInfo.lastModified();
|
|
}
|
|
}
|
|
return newestFileName;
|
|
}
|
|
|
|
QUrl PathUtils::defaultScriptsLocation(const QString& newDefaultPath) {
|
|
static QString overriddenDefaultScriptsLocation = "";
|
|
QString path;
|
|
|
|
// set overriddenDefaultScriptLocation if it was passed in
|
|
if (!newDefaultPath.isEmpty()) {
|
|
overriddenDefaultScriptsLocation = newDefaultPath;
|
|
}
|
|
|
|
// use the overridden location if it is set
|
|
if (!overriddenDefaultScriptsLocation.isEmpty()) {
|
|
path = overriddenDefaultScriptsLocation;
|
|
} else {
|
|
#if defined(Q_OS_OSX)
|
|
path = QCoreApplication::applicationDirPath() + "/../Resources/scripts";
|
|
#elif defined(Q_OS_ANDROID)
|
|
path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/scripts";
|
|
#else
|
|
path = QCoreApplication::applicationDirPath() + "/scripts";
|
|
#endif
|
|
}
|
|
|
|
// turn the string into a legit QUrl
|
|
return QUrl::fromLocalFile(QFileInfo(path).canonicalFilePath());
|
|
}
|
|
|
|
QString PathUtils::stripFilename(const QUrl& url) {
|
|
// Guard against meaningless query and fragment parts.
|
|
// Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation())
|
|
return url.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
|
}
|
|
|
|
Qt::CaseSensitivity PathUtils::getFSCaseSensitivity() {
|
|
static Qt::CaseSensitivity sensitivity { Qt::CaseSensitive };
|
|
static std::once_flag once;
|
|
std::call_once(once, [&] {
|
|
QString path = defaultScriptsLocation().toLocalFile();
|
|
QFileInfo upperFI(path.toUpper());
|
|
QFileInfo lowerFI(path.toLower());
|
|
sensitivity = (upperFI == lowerFI) ? Qt::CaseInsensitive : Qt::CaseSensitive;
|
|
});
|
|
return sensitivity;
|
|
}
|
|
|
|
bool PathUtils::isDescendantOf(const QUrl& childURL, const QUrl& parentURL) {
|
|
QString child = stripFilename(childURL);
|
|
QString parent = stripFilename(parentURL);
|
|
return child.startsWith(parent, PathUtils::getFSCaseSensitivity());
|
|
}
|