1
0
Fork 0

Improve UI around read-only mode / lock-files

See ticket:
https://sourceforge.net/p/flightgear/codetickets/2133/

Give the user a chance to intervene, if we select read-only mode.
Offer a button to clear the lock file if it’s stale, and start in
read-write mode.

This is still evolving, but want to get some feedback on it.
This commit is contained in:
James Turner 2020-05-03 18:02:20 +01:00
parent 9d2e50fa56
commit ddc7baada2
5 changed files with 88 additions and 42 deletions

View file

@ -30,21 +30,15 @@
#include <QProgressDialog>
#include <QDir>
#include <QFileInfo>
#include <QPixmap>
#include <QTimer>
#include <QDebug>
#include <QCompleter>
#include <QListView>
#include <QSettings>
#include <QUrl>
#include <QAction>
#include <QDateTime>
#include <QApplication>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QThread>
#include <QProcess>
#include <QTranslator>
#include <QMessageBox>
// Simgear
#include <simgear/timing/timestamp.hxx>
@ -506,6 +500,26 @@ bool runInAppLauncherDialog()
return true;
}
LockFileDialogResult showLockFileDialog()
{
QMessageBox mb;
mb.setIconPixmap(QPixmap(":/app-icon-large"));
mb.setWindowTitle("Multiple copies of Flightgear running");
mb.setText("Flightgear has detected another copy is already running. "
"This copy will run in read-only mode, so downloads will not be possible, "
"and settings will not be saved.");
mb.setInformativeText("If you are sure another copy is not running on this computer, "
"you can choose to reset the lock file to prevent seeing this message again.");
mb.addButton(QMessageBox::Ok);
mb.setDefaultButton(QMessageBox::Ok);
mb.addButton(QMessageBox::Reset);
int r = mb.exec();
if (r == QMessageBox::Reset)
return LockFileReset;
return LockFileContinue;
}
} // of namespace flightgear
#include "QtLauncher.moc"

View file

@ -2,7 +2,7 @@
//
// Written by James Turner, started December 2014.
//
// Copyright (C) 2014 James Turner <zakalawe@mac.com>
// Copyright (C) 2014 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
@ -18,8 +18,9 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_QTLAUNCHER_HXX
#define FG_QTLAUNCHER_HXX
#pragma once
class SGPath;
namespace flightgear
{
@ -42,6 +43,14 @@ namespace flightgear
bool runInAppLauncherDialog();
enum LockFileDialogResult
{
LockFileContinue,
LockFileReset
};
LockFileDialogResult showLockFileDialog();
/**
* @brief restartTheApp quit the application and relaunch it, passing the
* --launcher flag explicitly.
@ -50,5 +59,3 @@ namespace flightgear
void launcherSetSceneryPaths();
}
#endif // of FG_QTLAUNCHER_HXX

View file

@ -456,7 +456,7 @@ SGPath fgHomePath()
return SGPath::fromEnv("FG_HOME", platformDefaultDataPath());
}
bool fgInitHome()
InitHomeResult fgInitHome()
{
SGPath dataPath = fgHomePath();
globals->set_fg_home(dataPath);
@ -471,16 +471,16 @@ bool fgInitHome()
"Problem setting up user data",
"Unable to create the user-data storage folder at '" +
dataPath.utf8Str() + "'.");
return false;
return InitHomeAbort;
}
if (fgGetBool("/sim/fghome-readonly", false)) {
// user / config forced us into readonly mode, fine
SG_LOG(SG_GENERAL, SG_INFO, "Running with FG_HOME readonly");
return true;
return InitHomeExplicitReadOnly;
}
bool result = false;
InitHomeResult result = InitHomeOkay;
#if defined(SG_WINDOWS)
// don't use a PID file on Windows, because deleting on close is
// unreliable and causes false-positives. Instead, use a named
@ -490,14 +490,14 @@ bool fgInitHome()
if (static_fgHomeWriteMutex == nullptr) {
printf("CreateMutex error: %d\n", GetLastError());
SG_LOG(SG_GENERAL, SG_POPUP, "Failed to create mutex for multi-app protection");
return false;
return InitHomeAbort;
} else if (GetLastError() == ERROR_ALREADY_EXISTS) {
SG_LOG(SG_GENERAL, SG_ALERT, "flightgear instance already running, switching to FG_HOME read-only.");
fgSetBool("/sim/fghome-readonly", true);
return true;
return InitHomeReadOnly;
} else {
SG_LOG(SG_GENERAL, SG_INFO, "Created multi-app mutex, we are in writeable mode");
result = true;
result = InitHomeOkay;
}
#else
// write our PID, and check writeability
@ -509,7 +509,7 @@ bool fgInitHome()
if (fd < 0) {
SG_LOG(SG_GENERAL, SG_ALERT, "failed to open local file:" << pidPath
<< "\n\tdue to:" << simgear::strutils::error_string(errno));
return false;
return InitHomeAbort;
}
int err = ::flock(fd, LOCK_EX | LOCK_NB);
@ -521,16 +521,16 @@ bool fgInitHome()
// set a marker property so terrasync/navcache don't try to write
// from secondary instances
fgSetBool("/sim/fghome-readonly", true);
return true;
return InitHomeReadOnly;
} else {
SG_LOG(SG_GENERAL, SG_ALERT, "failed to lock file:" << pidPath
<< "\n\tdue to:" << simgear::strutils::error_string(errno));
return false;
return InitHomeAbort;
}
}
// we locked it!
result = true;
result = InitHomeOkay;
} else {
char buf[16];
std::string ps = pidPath.utf8Str();
@ -540,24 +540,24 @@ bool fgInitHome()
if (fd < 0) {
SG_LOG(SG_GENERAL, SG_ALERT, "failed to open local file:" << pidPath
<< "\n\tdue to:" << simgear::strutils::error_string(errno));
return false;
return InitHomeAbort;
}
int err = write(fd, buf, len);
if (err < 0) {
SG_LOG(SG_GENERAL, SG_ALERT, "failed to write to lock file:" << pidPath
<< "\n\tdue to:" << simgear::strutils::error_string(errno));
return false;
return InitHomeAbort;
}
err = flock(fd, LOCK_EX);
if (err != 0) {
SG_LOG(SG_GENERAL, SG_ALERT, "failed to lock file:" << pidPath
<< "\n\tdue to:" << simgear::strutils::error_string(errno));
return false;
return InitHomeAbort;
}
result = true;
result = InitHomeOkay;
}
#endif
fgSetBool("/sim/fghome-readonly", false);
@ -578,6 +578,16 @@ void fgShutdownHome()
#endif
}
void fgDeleteLockFile()
{
#if defined(SG_WINDOWS)
// there's no file here, so we can't actually delete anything
#else
SGPath pidPath = globals->get_fg_home() / "fgfs_lock.pid";
pidPath.remove();
#endif
}
static void createBaseStorageDirForAddons(const SGPath& exportDir)
{
SGPath addonStorageBasePath = exportDir / "Addons";

View file

@ -37,8 +37,17 @@ std::string fgBasePackageVersion(const SGPath& path);
SGPath fgHomePath();
bool fgInitHome();
enum InitHomeResult
{
InitHomeOkay,
InitHomeReadOnly,
InitHomeExplicitReadOnly,
InitHomeAbort
};
InitHomeResult fgInitHome();
void fgShutdownHome();
void fgDeleteLockFile();
// Read in configuration (file and command line)
int fgInitConfig ( int argc, char **argv, bool reinit );

View file

@ -530,14 +530,32 @@ int fgMainInit( int argc, char **argv )
sglog().setStartupLoggingEnabled(true);
globals = new FGGlobals;
if (!fgInitHome()) {
return EXIT_FAILURE;
auto initHomeResult = fgInitHome();
if (initHomeResult == InitHomeAbort) {
flightgear::fatalMessageBoxThenExit("Unable to create lock file",
"Flightgear was unable to create the lock file in FG_HOME");
}
#if defined(HAVE_QT)
flightgear::initApp(argc, argv);
#endif
// check if the launcher is requested, since it affects config file parsing
bool showLauncher = flightgear::Options::checkForArg(argc, argv, "launcher");
// an Info.plist bundle can't define command line arguments, but it can set
// environment variables. This avoids needed a wrapper shell-script on OS-X.
showLauncher |= (::getenv("FG_LAUNCHER") != nullptr);
if (showLauncher && (initHomeResult == InitHomeReadOnly)) {
// show this message early, if we can
auto r = flightgear::showLockFileDialog();
if (r == flightgear::LockFileReset) {
SG_LOG( SG_GENERAL, SG_ALERT, "Deleting lock file at user request");
fgDeleteLockFile();
fgSetBool("/sim/fghome-readonly", false);
}
}
const bool readOnlyFGHome = fgGetBool("/sim/fghome-readonly");
if (!readOnlyFGHome) {
// now home is initialised, we can log to a file inside it
@ -597,11 +615,6 @@ int fgMainInit( int argc, char **argv )
upper_case_property("/sim/tower/airport-id");
upper_case_property("/autopilot/route-manager/input");
// check if the launcher is requested, since it affects config file parsing
bool showLauncher = flightgear::Options::checkForArg(argc, argv, "launcher");
// an Info.plist bundle can't define command line arguments, but it can set
// environment variables. This avoids needed a wrapper shell-script on OS-X.
showLauncher |= (::getenv("FG_LAUNCHER") != nullptr);
if (showLauncher) {
// to minimise strange interactions when launcher and config files
// set overlaping options, we disable the default files. Users can
@ -609,13 +622,6 @@ int fgMainInit( int argc, char **argv )
flightgear::Options::sharedInstance()->setShouldLoadDefaultConfig(false);
}
if (showLauncher && readOnlyFGHome) {
// this is perhaps not what the user wanted, let's inform them
flightgear::modalMessageBox("Multiple copies of FlightGear",
"Another copy of FlightGear is already running on this computer, "
"so this copy will run in read-only mode.");
}
// Load the configuration parameters. (Command line options
// override config file options. Config file options override
// defaults.)