Support for custom data folders, disable single instance when used.

This commit is contained in:
Martin Rotter 2020-11-14 09:47:15 +01:00
parent c5822369cc
commit 31f010290d
6 changed files with 112 additions and 52 deletions

View file

@ -83,6 +83,8 @@
#define CLI_LOG_SHORT "l" #define CLI_LOG_SHORT "l"
#define CLI_LOG_LONG "log" #define CLI_LOG_LONG "log"
#define CLI_DAT_SHORT "d"
#define CLI_DAT_LONG "data"
#define HTTP_HEADERS_ACCEPT "Accept" #define HTTP_HEADERS_ACCEPT "Accept"
#define HTTP_HEADERS_CONTENT_TYPE "Content-Type" #define HTTP_HEADERS_CONTENT_TYPE "Content-Type"

View file

@ -26,6 +26,9 @@ void FormAbout::loadSettingsAndPaths() {
if (qApp->settings()->type() == SettingsProperties::SettingsType::Portable) { if (qApp->settings()->type() == SettingsProperties::SettingsType::Portable) {
m_ui.m_txtPathsSettingsType->setText(tr("FULLY portable")); m_ui.m_txtPathsSettingsType->setText(tr("FULLY portable"));
} }
else if (qApp->settings()->type() == SettingsProperties::SettingsType::Custom) {
m_ui.m_txtPathsSettingsType->setText(tr("CUSTOM"));
}
else { else {
m_ui.m_txtPathsSettingsType->setText(tr("NOT portable")); m_ui.m_txtPathsSettingsType->setText(tr("NOT portable"));
} }

View file

@ -41,27 +41,26 @@
#endif #endif
Application::Application(const QString& id, int& argc, char** argv) Application::Application(const QString& id, int& argc, char** argv)
: QtSingleApplication(id, argc, argv), : QtSingleApplication(id, argc, argv), m_updateFeedsLock(new Mutex()) {
parseCmdArguments();
#if defined(USE_WEBENGINE) #if defined(USE_WEBENGINE)
m_urlInterceptor(new NetworkUrlInterceptor(this)), m_urlInterceptor = new NetworkUrlInterceptor(this);
#endif #endif
m_feedReader(nullptr), m_feedReader = nullptr;
m_quitLogicDone(false), m_quitLogicDone = false;
m_updateFeedsLock(new Mutex()), m_mainForm = nullptr;
m_mainForm(nullptr), m_trayIcon = nullptr;
m_trayIcon(nullptr), m_settings = Settings::setupSettings(this);
m_settings(Settings::setupSettings(this)), m_webFactory = new WebFactory(this);
m_webFactory(new WebFactory(this)), m_system = new SystemFactory(this);
m_system(new SystemFactory(this)), m_skins = new SkinFactory(this);
m_skins(new SkinFactory(this)), m_localization = new Localization(this);
m_localization(new Localization(this)), m_icons = new IconFactory(this);
m_icons(new IconFactory(this)), m_database = new DatabaseFactory(this);
m_database(new DatabaseFactory(this)), m_downloadManager = nullptr;
m_downloadManager(nullptr), m_shouldRestart(false) { m_shouldRestart = false;
parseCmdArguments();
// Setup debug output system. // Setup debug output system.
qInstallMessageHandler(performLogging); qInstallMessageHandler(performLogging);
@ -176,7 +175,9 @@ void Application::offerChanges() const {
} }
bool Application::isAlreadyRunning() { bool Application::isAlreadyRunning() {
return sendMessage((QStringList() << APP_IS_RUNNING << Application::arguments().mid(1)).join(ARGUMENTS_LIST_SEPARATOR)); return m_allowMultipleInstances
? false
: sendMessage((QStringList() << APP_IS_RUNNING << Application::arguments().mid(1)).join(ARGUMENTS_LIST_SEPARATOR));
} }
FeedReader* Application::feedReader() { FeedReader* Application::feedReader() {
@ -271,18 +272,21 @@ void Application::setMainForm(FormMain* main_form) {
m_mainForm = main_form; m_mainForm = main_form;
} }
QString Application::configFolder() { QString Application::configFolder() const {
return IOFactory::getSystemFolder(QStandardPaths::GenericConfigLocation); return IOFactory::getSystemFolder(QStandardPaths::GenericConfigLocation);
} }
QString Application::userDataAppFolder() { QString Application::userDataAppFolder() const {
// In "app" folder, we would like to separate all user data into own subfolder, // In "app" folder, we would like to separate all user data into own subfolder,
// therefore stick to "data" folder in this mode. // therefore stick to "data" folder in this mode.
return applicationDirPath() + QDir::separator() + QSL("data"); return applicationDirPath() + QDir::separator() + QSL("data");
} }
QString Application::userDataFolder() { QString Application::userDataFolder() {
if (settings()->type() == SettingsProperties::SettingsType::Portable) { if (settings()->type() == SettingsProperties::SettingsType::Custom) {
return customDataFolder();
}
else if (settings()->type() == SettingsProperties::SettingsType::Portable) {
return userDataAppFolder(); return userDataAppFolder();
} }
else { else {
@ -290,7 +294,7 @@ QString Application::userDataFolder() {
} }
} }
QString Application::userDataHomeFolder() { QString Application::userDataHomeFolder() const {
// Fallback folder. // Fallback folder.
const QString home_folder = homeFolder() + QDir::separator() + QSL(APP_LOW_H_NAME) + QDir::separator() + QSL("data"); const QString home_folder = homeFolder() + QDir::separator() + QSL(APP_LOW_H_NAME) + QDir::separator() + QSL("data");
@ -306,15 +310,15 @@ QString Application::userDataHomeFolder() {
} }
} }
QString Application::tempFolder() { QString Application::tempFolder() const {
return IOFactory::getSystemFolder(QStandardPaths::TempLocation); return IOFactory::getSystemFolder(QStandardPaths::TempLocation);
} }
QString Application::documentsFolder() { QString Application::documentsFolder() const {
return IOFactory::getSystemFolder(QStandardPaths::DocumentsLocation); return IOFactory::getSystemFolder(QStandardPaths::DocumentsLocation);
} }
QString Application::homeFolder() { QString Application::homeFolder() const {
#if defined (Q_OS_ANDROID) #if defined (Q_OS_ANDROID)
return IOFactory::getSystemFolder(QStandardPaths::GenericDataLocation); return IOFactory::getSystemFolder(QStandardPaths::GenericDataLocation);
#else #else
@ -551,6 +555,20 @@ void Application::onFeedUpdatesFinished(const FeedDownloadResults& results) {
} }
} }
void Application::setupCustomDataFolder(const QString& data_folder) {
if (!QDir().mkpath(data_folder)) {
qCriticalNN << "Failed to create custom data path" << QUOTE_W_SPACE(data_folder) << "thus falling back to standard setup.";
m_customDataFolder = QString();
return;
}
// Disable single instance mode.
m_allowMultipleInstances = true;
// Save custom data folder.
m_customDataFolder = data_folder;
}
void Application::determineFirstRuns() { void Application::determineFirstRuns() {
m_firstRunEver = settings()->value(GROUP(General), m_firstRunEver = settings()->value(GROUP(General),
SETTING(General::FirstRun)).toBool(); SETTING(General::FirstRun)).toBool();
@ -564,8 +582,11 @@ void Application::determineFirstRuns() {
void Application::parseCmdArguments() { void Application::parseCmdArguments() {
QCommandLineOption log_file(QStringList() << CLI_LOG_SHORT << CLI_LOG_LONG, QCommandLineOption log_file(QStringList() << CLI_LOG_SHORT << CLI_LOG_LONG,
"Write application debug log to file.", "log-file"); "Write application debug log to file.", "log-file");
QCommandLineOption custom_data_folder(QStringList() << CLI_DAT_SHORT << CLI_DAT_LONG,
"Use custom folder for user data and disable single instance application mode.",
"user-data-folder");
m_cmdParser.addOption(log_file); m_cmdParser.addOptions({ log_file, custom_data_folder });
m_cmdParser.addHelpOption(); m_cmdParser.addHelpOption();
m_cmdParser.addVersionOption(); m_cmdParser.addVersionOption();
m_cmdParser.setApplicationDescription(APP_NAME); m_cmdParser.setApplicationDescription(APP_NAME);
@ -573,4 +594,20 @@ void Application::parseCmdArguments() {
m_cmdParser.process(*this); m_cmdParser.process(*this);
s_customLogFile = m_cmdParser.value(CLI_LOG_SHORT); s_customLogFile = m_cmdParser.value(CLI_LOG_SHORT);
if (!m_cmdParser.value(CLI_DAT_SHORT).isEmpty()) {
auto data_folder = QDir::toNativeSeparators(m_cmdParser.value(CLI_DAT_SHORT));
qDebugNN << "User wants to use custom directory for user data (and disable single instance mode):"
<< QUOTE_W_SPACE_DOT(data_folder);
setupCustomDataFolder(data_folder);
}
else {
m_allowMultipleInstances = false;
}
}
QString Application::customDataFolder() const {
return m_customDataFolder;
} }

View file

@ -85,14 +85,15 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
NetworkUrlInterceptor* urlIinterceptor(); NetworkUrlInterceptor* urlIinterceptor();
#endif #endif
QString tempFolder(); QString tempFolder() const;
QString documentsFolder(); QString documentsFolder() const;
QString homeFolder(); QString homeFolder() const;
QString configFolder(); QString configFolder() const;
// These return user ready folders. // These return user ready folders.
QString userDataAppFolder(); QString userDataAppFolder() const;
QString userDataHomeFolder(); QString userDataHomeFolder() const;
QString customDataFolder() const;
// Returns the base folder to which store user data, the "data" folder. // Returns the base folder to which store user data, the "data" folder.
// NOTE: Use this to get correct path under which store user data. // NOTE: Use this to get correct path under which store user data.
@ -143,6 +144,7 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
void onFeedUpdatesFinished(const FeedDownloadResults& results); void onFeedUpdatesFinished(const FeedDownloadResults& results);
private: private:
void setupCustomDataFolder(const QString& data_folder);
void determineFirstRuns(); void determineFirstRuns();
void eliminateFirstRuns(); void eliminateFirstRuns();
void parseCmdArguments(); void parseCmdArguments();
@ -186,6 +188,8 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
bool m_shouldRestart; bool m_shouldRestart;
bool m_firstRunEver; bool m_firstRunEver;
bool m_firstRunCurrentVersion; bool m_firstRunCurrentVersion;
QString m_customDataFolder;
bool m_allowMultipleInstances;
}; };
inline Application* Application::instance() { inline Application* Application::instance() {

View file

@ -372,13 +372,18 @@ Settings* Settings::setupSettings(QObject* parent) {
// Portable settings are available, use them. // Portable settings are available, use them.
new_settings = new Settings(properties.m_absoluteSettingsFileName, QSettings::IniFormat, properties.m_type, parent); new_settings = new Settings(properties.m_absoluteSettingsFileName, QSettings::IniFormat, properties.m_type, parent);
// Check if portable settings are available.
if (properties.m_type == SettingsProperties::SettingsType::Portable) { if (properties.m_type == SettingsProperties::SettingsType::Portable) {
qDebugNN << LOGSEC_CORE qDebugNN << LOGSEC_CORE
<< "Initializing settings in" << "Initializing settings in"
<< QUOTE_W_SPACE(QDir::toNativeSeparators(properties.m_absoluteSettingsFileName)) << QUOTE_W_SPACE(QDir::toNativeSeparators(properties.m_absoluteSettingsFileName))
<< "(portable way)."; << "(portable way).";
} }
else if (properties.m_type == SettingsProperties::SettingsType::Custom) {
qDebugNN << LOGSEC_CORE
<< "Initializing settings in"
<< QUOTE_W_SPACE(QDir::toNativeSeparators(properties.m_absoluteSettingsFileName))
<< "(custom way).";
}
else { else {
qDebugNN << LOGSEC_CORE qDebugNN << LOGSEC_CORE
<< "Initializing settings in" << "Initializing settings in"
@ -395,27 +400,35 @@ SettingsProperties Settings::determineProperties() {
properties.m_settingsSuffix = QDir::separator() + QSL(APP_CFG_PATH) + QDir::separator() + QSL(APP_CFG_FILE); properties.m_settingsSuffix = QDir::separator() + QSL(APP_CFG_PATH) + QDir::separator() + QSL(APP_CFG_FILE);
const QString app_path = qApp->userDataAppFolder(); const QString app_path = qApp->userDataAppFolder();
const QString home_path = qApp->userDataHomeFolder(); const QString home_path = qApp->userDataHomeFolder();
const QString custom_path = qApp->customDataFolder();
// We will use PORTABLE settings only and only if it is available and NON-PORTABLE if (!custom_path.isEmpty()) {
// settings was not initialized before. // User wants to have his user data in custom folder, okay.
#if defined (Q_OS_LINUX) || defined (Q_OS_ANDROID) || defined (Q_OS_MACOSOS) properties.m_type = SettingsProperties::SettingsType::Custom;
// DO NOT use portable settings for Linux, it is really not used on that platform. properties.m_baseDirectory = custom_path;
const bool will_we_use_portable_settings = false;
#else
const QString exe_path = qApp->applicationDirPath();
const QString home_path_file = home_path + properties.m_settingsSuffix;
const bool portable_settings_available = IOFactory::isFolderWritable(exe_path);
const bool non_portable_settings_exist = QFile::exists(home_path_file);
const bool will_we_use_portable_settings = portable_settings_available && !non_portable_settings_exist;
#endif
if (will_we_use_portable_settings) {
properties.m_type = SettingsProperties::SettingsType::Portable;
properties.m_baseDirectory = app_path;
} }
else { else {
properties.m_type = SettingsProperties::SettingsType::NonPortable; // We will use PORTABLE settings only and only if it is available and NON-PORTABLE
properties.m_baseDirectory = home_path; // settings was not initialized before.
#if defined (Q_OS_LINUX) || defined (Q_OS_ANDROID) || defined (Q_OS_MACOSOS)
// DO NOT use portable settings for Linux, it is really not used on that platform.
const bool will_we_use_portable_settings = false;
#else
const QString exe_path = qApp->applicationDirPath();
const QString home_path_file = home_path + properties.m_settingsSuffix;
const bool portable_settings_available = IOFactory::isFolderWritable(exe_path);
const bool non_portable_settings_exist = QFile::exists(home_path_file);
const bool will_we_use_portable_settings = portable_settings_available && !non_portable_settings_exist;
#endif
if (will_we_use_portable_settings) {
properties.m_type = SettingsProperties::SettingsType::Portable;
properties.m_baseDirectory = QDir::toNativeSeparators(app_path);
}
else {
properties.m_type = SettingsProperties::SettingsType::NonPortable;
properties.m_baseDirectory = QDir::toNativeSeparators(home_path);
}
} }
properties.m_absoluteSettingsFileName = properties.m_baseDirectory + properties.m_settingsSuffix; properties.m_absoluteSettingsFileName = properties.m_baseDirectory + properties.m_settingsSuffix;

View file

@ -9,7 +9,8 @@
struct SettingsProperties { struct SettingsProperties {
enum class SettingsType { enum class SettingsType {
Portable, Portable,
NonPortable NonPortable,
Custom
}; };
SettingsType m_type; SettingsType m_type;