diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b13c432a14..26d1270293 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -94,6 +94,7 @@ #include #include #include +#include #include #include #include @@ -415,8 +416,6 @@ bool setupEssentials(int& argc, char** argv) { static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; bool suppressPrompt = cmdOptionExists(argc, const_cast(argv), SUPPRESS_SETTINGS_RESET); bool previousSessionCrashed = CrashHandler::checkForResetSettings(suppressPrompt); - CrashHandler::writeRunningMarkerFiler(); - qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); @@ -504,8 +503,11 @@ Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; -Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : +Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), + _shouldRunServer(runServer), + _runServerPath(runServerPathOption), + _runningMarker(this, RUNNING_MARKER_FILENAME), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), _previousSessionCrashed(setupEssentials(argc, argv)), @@ -529,7 +531,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0) { - + _runningMarker.startRunningMarker(); PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); @@ -575,6 +577,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services."; #endif + + bool wantsSandboxRunning = shouldRunServer(); + bool determinedSandboxState = false; + bool sandboxRunning = false; + SandboxUtils sandboxUtils; + sandboxUtils.ifLocalSandboxRunningElse([&]() { + qDebug() << "Home sandbox appears to be running....."; + determinedSandboxState = true; + sandboxRunning = true; + }, [&]() { + qDebug() << "Home sandbox does not appear to be running...."; + determinedSandboxState = true; + sandboxRunning = false; + if (wantsSandboxRunning) { + QString contentPath = getRunServerPath(); + SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME); + } + }); + + quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4; + auto startWaiting = usecTimestampNow(); + while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) { + QCoreApplication::processEvents(); + usleep(USECS_PER_MSEC * 50); // 20hz + } _bookmarks = new Bookmarks(); // Before setting up the menu @@ -1299,7 +1326,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : const QString TUTORIAL_PATH = "/tutorial_begin"; if (shouldGoToTutorial) { - DependencyManager::get()->ifLocalSandboxRunningElse([=]() { + sandboxUtils.ifLocalSandboxRunningElse([=]() { qDebug() << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(TUTORIAL_PATH); }, [=]() { @@ -1324,7 +1351,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // If this is a first run we short-circuit the address passed in if (isFirstRun) { if (hasHMDAndHandControllers) { - DependencyManager::get()->ifLocalSandboxRunningElse([=]() { + sandboxUtils.ifLocalSandboxRunningElse([=]() { qDebug() << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(); }, [=]() { diff --git a/interface/src/Application.h b/interface/src/Application.h index e6429f1ebe..5f78658254 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -46,6 +46,8 @@ #include #include +#include + #include "avatar/MyAvatar.h" #include "Bookmarks.h" #include "Camera.h" @@ -87,6 +89,8 @@ static const UINT UWM_SHOW_APPLICATION = RegisterWindowMessage("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}_" + qgetenv("USERNAME")); #endif +static const QString RUNNING_MARKER_FILENAME = "Interface.running"; + class Application; #if defined(qApp) #undef qApp @@ -103,7 +107,16 @@ class Application : public QApplication, // TODO? Get rid of those friend class OctreePacketProcessor; +private: + bool _shouldRunServer { false }; + QString _runServerPath; + RunningMarker _runningMarker; + public: + // startup related getter/setters + bool shouldRunServer() const { return _shouldRunServer; } + bool hasRunServerPath() const { return !_runServerPath.isEmpty(); } + QString getRunServerPath() const { return _runServerPath; } // virtual functions required for PluginContainer virtual ui::Menu* getPrimaryMenu() override; @@ -127,7 +140,7 @@ public: static void initPlugins(const QStringList& arguments); static void shutdownPlugins(); - Application(int& argc, char** argv, QElapsedTimer& startup_time); + Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runServer, QString runServerPathOption); ~Application(); void postLambdaEvent(std::function f) override; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index 3c5f03bef3..0c9698ffc3 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -23,9 +23,10 @@ #include #include +#include "Application.h" #include "Menu.h" -static const QString RUNNING_MARKER_FILENAME = "Interface.running"; +#include bool CrashHandler::checkForResetSettings(bool suppressPrompt) { QSettings::setDefaultFormat(QSettings::IniFormat); @@ -39,7 +40,7 @@ bool CrashHandler::checkForResetSettings(bool suppressPrompt) { // If option does not exist in Interface.ini so assume default behavior. bool displaySettingsResetOnCrash = !displayCrashOptions.isValid() || displayCrashOptions.toBool(); - QFile runningMarkerFile(runningMarkerFilePath()); + QFile runningMarkerFile(RunningMarker::getMarkerFilePath(RUNNING_MARKER_FILENAME)); bool wasLikelyCrash = runningMarkerFile.exists(); if (suppressPrompt) { @@ -161,20 +162,3 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { } } -void CrashHandler::writeRunningMarkerFiler() { - QFile runningMarkerFile(runningMarkerFilePath()); - if (!runningMarkerFile.exists()) { - runningMarkerFile.open(QIODevice::WriteOnly); - runningMarkerFile.close(); - } -} -void CrashHandler::deleteRunningMarkerFile() { - QFile runningMarkerFile(runningMarkerFilePath()); - if (runningMarkerFile.exists()) { - runningMarkerFile.remove(); - } -} - -const QString CrashHandler::runningMarkerFilePath() { - return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + RUNNING_MARKER_FILENAME; -} diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index a65fed677d..308cac3411 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -19,9 +19,6 @@ class CrashHandler { public: static bool checkForResetSettings(bool suppressPrompt = false); - static void writeRunningMarkerFiler(); - static void deleteRunningMarkerFile(); - private: enum Action { DELETE_INTERFACE_INI, @@ -31,8 +28,6 @@ private: static Action promptUserForAction(bool showCrashMessage); static void handleCrash(Action action); - - static const QString runningMarkerFilePath(); }; #endif // hifi_CrashHandler_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 3c4a3fd77a..5b0da4f578 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -37,6 +37,8 @@ #include #endif + + int main(int argc, const char* argv[]) { #if HAS_BUGSPLAT static QString BUG_SPLAT_DATABASE = "interface_alpha"; @@ -128,22 +130,9 @@ int main(int argc, const char* argv[]) { parser.addOption(runServerOption); parser.addOption(serverContentPathOption); parser.parse(arguments); - if (parser.isSet(runServerOption)) { - QString applicationDirPath = QFileInfo(arguments[0]).path(); - QString serverPath = applicationDirPath + "/server-console/server-console.exe"; - qDebug() << "Application dir path is: " << applicationDirPath; - qDebug() << "Server path is: " << serverPath; - QStringList args; - if (parser.isSet(serverContentPathOption)) { - QString serverContentPath = QFileInfo(arguments[0]).path() + "/" + parser.value(serverContentPathOption); - args << "--" << "--contentPath" << serverContentPath; - } - qDebug() << QFileInfo(arguments[0]).path(); - qDebug() << QProcess::startDetached(serverPath, args); - - // Sleep a short amount of time to give the server a chance to start - usleep(2000000); - } + bool runServer = parser.isSet(runServerOption); + bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); + QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); QElapsedTimer startupTime; startupTime.start(); @@ -166,10 +155,11 @@ int main(int argc, const char* argv[]) { SteamClient::init(); + int exitCode; { QSettings::setDefaultFormat(QSettings::IniFormat); - Application app(argc, const_cast(argv), startupTime); + Application app(argc, const_cast(argv), startupTime, runServer, serverContentPathOptionValue); // If we failed the OpenGLVersion check, log it. if (override) { @@ -223,7 +213,6 @@ int main(int argc, const char* argv[]) { QTranslator translator; translator.load("i18n/interface_en"); app.installTranslator(&translator); - qCDebug(interfaceapp, "Created QT Application."); exitCode = app.exec(); server.close(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index f7f305dcc8..6cba4873ac 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -831,32 +831,3 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { } } -void AddressManager::ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, - std::function localSandboxNotRunningDoThat) { - - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); - sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(sandboxStatus); - - connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() { - auto statusData = reply->readAll(); - auto statusJson = QJsonDocument::fromJson(statusData); - if (!statusJson.isEmpty()) { - auto statusObject = statusJson.object(); - auto serversValue = statusObject.value("servers"); - if (!serversValue.isUndefined() && serversValue.isObject()) { - auto serversObject = serversValue.toObject(); - auto serversCount = serversObject.size(); - const int MINIMUM_EXPECTED_SERVER_COUNT = 5; - if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { - localSandboxRunningDoThis(); - return; - } - } - } - localSandboxNotRunningDoThat(); - }); -} - diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index ab7b53a6d9..1707132c08 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -25,7 +25,6 @@ const QString HIFI_URL_SCHEME = "hifi"; const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome"; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; -const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; const QString INDEX_PATH = "/"; const QString GET_PLACE = "/api/v1/places/%1"; @@ -78,11 +77,6 @@ public: const QStack& getBackStack() const { return _backStack; } const QStack& getForwardStack() const { return _forwardStack; } - /// determines if the local sandbox is likely running. It does not account for custom setups, and is only - /// intended to detect the standard local sandbox install. - void ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, - std::function localSandboxNotRunningDoThat); - public slots: void handleLookupString(const QString& lookupString, bool fromSuggestions = false); diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp new file mode 100644 index 0000000000..98c963b793 --- /dev/null +++ b/libraries/networking/src/SandboxUtils.cpp @@ -0,0 +1,96 @@ +// +// SandboxUtils.cpp +// libraries/networking/src +// +// Created by Brad Hefta-Gaub on 2016-10-15. +// Copyright 2016 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "SandboxUtils.h" +#include "NetworkAccessManager.h" + + +void SandboxUtils::ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, + std::function localSandboxNotRunningDoThat) { + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); + sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(sandboxStatus); + + connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() { + if (reply->error() == QNetworkReply::NoError) { + auto statusData = reply->readAll(); + auto statusJson = QJsonDocument::fromJson(statusData); + if (!statusJson.isEmpty()) { + auto statusObject = statusJson.object(); + auto serversValue = statusObject.value("servers"); + if (!serversValue.isUndefined() && serversValue.isObject()) { + auto serversObject = serversValue.toObject(); + auto serversCount = serversObject.size(); + const int MINIMUM_EXPECTED_SERVER_COUNT = 5; + if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { + localSandboxRunningDoThis(); + return; + } + } + } + } + localSandboxNotRunningDoThat(); + }); +} + + +void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName) { + QString applicationDirPath = QFileInfo(QCoreApplication::applicationFilePath()).path(); + QString serverPath = applicationDirPath + "/server-console/server-console.exe"; + qDebug() << "Application dir path is: " << applicationDirPath; + qDebug() << "Server path is: " << serverPath; + qDebug() << "autoShutdown: " << autoShutdown; + + bool hasContentPath = !contentPath.isEmpty(); + bool passArgs = autoShutdown || hasContentPath; + + QStringList args; + + if (passArgs) { + args << "--"; + } + + if (hasContentPath) { + QString serverContentPath = applicationDirPath + "/" + contentPath; + args << "--contentPath" << serverContentPath; + } + + if (autoShutdown) { + QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName); + args << "--shutdownWatcher" << interfaceRunningStateFile; + } + + qDebug() << applicationDirPath; + qDebug() << "Launching sandbox with:" << args; + qDebug() << QProcess::startDetached(serverPath, args); + + // Sleep a short amount of time to give the server a chance to start + usleep(2000000); /// do we really need this?? +} diff --git a/libraries/networking/src/SandboxUtils.h b/libraries/networking/src/SandboxUtils.h new file mode 100644 index 0000000000..e40ff71a56 --- /dev/null +++ b/libraries/networking/src/SandboxUtils.h @@ -0,0 +1,32 @@ +// +// SandboxUtils.h +// libraries/networking/src +// +// Created by Brad Hefta-Gaub on 2016-10-15. +// Copyright 2016 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 +// + +#ifndef hifi_SandboxUtils_h +#define hifi_SandboxUtils_h + +#include +#include + + +const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; + +class SandboxUtils : public QObject { + Q_OBJECT +public: + /// determines if the local sandbox is likely running. It does not account for custom setups, and is only + /// intended to detect the standard local sandbox install. + void ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, + std::function localSandboxNotRunningDoThat); + + static void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName); +}; + +#endif // hifi_SandboxUtils_h diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp new file mode 100644 index 0000000000..269ade8a40 --- /dev/null +++ b/libraries/shared/src/RunningMarker.cpp @@ -0,0 +1,73 @@ +// +// RunningMarker.cpp +// libraries/shared/src +// +// Created by Brad Hefta-Gaub on 2016-10-16 +// Copyright 2016 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 "RunningMarker.h" + +#include +#include +#include +#include + +#include "NumericalConstants.h" +#include "PathUtils.h" + + +RunningMarker::RunningMarker(QObject* parent, QString name) : + _parent(parent), + _name(name) +{ +} + +void RunningMarker::startRunningMarker() { + static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND; + + // start the nodeThread so its event loop is running + QThread* runningMarkerThread = new QThread(_parent); + runningMarkerThread->setObjectName("Running Marker Thread"); + runningMarkerThread->start(); + + writeRunningMarkerFiler(); // write the first file, even before timer + + QTimer* runningMarkerTimer = new QTimer(_parent); + QObject::connect(runningMarkerTimer, &QTimer::timeout, [=](){ + writeRunningMarkerFiler(); + }); + runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS); +} + +RunningMarker::~RunningMarker() { + deleteRunningMarkerFile(); +} + +void RunningMarker::writeRunningMarkerFiler() { + QFile runningMarkerFile(getFilePath()); + + // always write, even it it exists, so that it touches the files + if (runningMarkerFile.open(QIODevice::WriteOnly)) { + runningMarkerFile.close(); + } +} + +void RunningMarker::deleteRunningMarkerFile() { + QFile runningMarkerFile(getFilePath()); + if (runningMarkerFile.exists()) { + runningMarkerFile.remove(); + } +} + +QString RunningMarker::getFilePath() { + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; +} + +QString RunningMarker::getMarkerFilePath(QString name) { + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name; +} + diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h new file mode 100644 index 0000000000..ad657d612c --- /dev/null +++ b/libraries/shared/src/RunningMarker.h @@ -0,0 +1,35 @@ +// +// RunningMarker.h +// interface/src +// +// Created by David Rowe on 24 Aug 2015. +// Copyright 2015 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 +// + +#ifndef hifi_RunningMarker_h +#define hifi_RunningMarker_h + +#include +#include + +class RunningMarker { +public: + RunningMarker(QObject* parent, QString name); + ~RunningMarker(); + + void startRunningMarker(); + + QString getFilePath(); + static QString getMarkerFilePath(QString name); +protected: + void writeRunningMarkerFiler(); + void deleteRunningMarkerFile(); + + QObject* _parent { nullptr }; + QString _name; +}; + +#endif // hifi_RunningMarker_h diff --git a/server-console/src/main.js b/server-console/src/main.js index 2d9320da15..19ef5acab2 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -128,6 +128,12 @@ function shutdown() { } } +function forcedShutdown() { + if (!isShuttingDown) { + shutdownCallback(0); + } +} + function shutdownCallback(idx) { if (idx == 0 && !isShuttingDown) { isShuttingDown = true; @@ -226,6 +232,7 @@ if (shouldQuit) { // Check command line arguments to see how to find binaries var argv = require('yargs').argv; + var pathFinder = require('./modules/path-finder.js'); var interfacePath = null; @@ -774,6 +781,7 @@ function maybeShowSplash() { } } + const trayIconOS = (osType == "Darwin") ? "osx" : "win"; var trayIcons = {}; trayIcons[ProcessGroupStates.STARTED] = "console-tray-" + trayIconOS + ".png"; @@ -842,6 +850,34 @@ function onContentLoaded() { // start the home server homeServer.start(); } + + // If we were launched with the shutdownWatcher option, then we need to watch for the interface app + // shutting down. The interface app will regularly update a running state file which we will check. + // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. + if (argv.shutdownWatcher) { + console.log("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher); + var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates + var firstAttemptToCheck = new Date().getTime(); + var shutdownWatchInterval = setInterval(function(){ + var stats = fs.stat(argv.shutdownWatcher,function(err, stats) { + if (err) { + var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck; + if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) { + console.log("Running state file is missing, assume interface has shutdown... shutting down snadbox."); + forcedShutdown(); + clearTimeout(shutdownWatchInterval); + } + } else { + var sinceEdit = new Date().getTime() - stats.mtime.getTime(); + if (sinceEdit > MAX_TIME_SINCE_EDIT) { + console.log("Running state of interface hasn't updated in MAX time... shutting down."); + forcedShutdown(); + clearTimeout(shutdownWatchInterval); + } + } + }); + }, 1000); + } }