diff --git a/CMakeLists.txt b/CMakeLists.txt index d1c69e4f6b..e5807146ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,6 +202,8 @@ if (NOT ANDROID) set_target_properties(ice-server PROPERTIES FOLDER "Apps") add_subdirectory(interface) set_target_properties(interface PROPERTIES FOLDER "Apps") + add_subdirectory(stack-manager) + set_target_properties(stack-manager PROPERTIES FOLER "Apps") add_subdirectory(tests) add_subdirectory(plugins) add_subdirectory(tools) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 830164eb60..58f200b0fe 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -10,5 +10,4 @@ link_hifi_libraries( ) include_application_version() - -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt new file mode 100644 index 0000000000..7c30721143 --- /dev/null +++ b/cmake/externals/quazip/CMakeLists.txt @@ -0,0 +1,43 @@ +set(EXTERNAL_NAME quazip) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +cmake_policy(SET CMP0046 OLD) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://s3-us-west-1.amazonaws.com/hifi-production/dependencies/quazip-0.6.2.zip + URL_MD5 514851970f1a14d815bdc3ad6267af4d + BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=$ENV{QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=/lib -DZLIB_ROOT=${ZLIB_ROOT} + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +add_dependencies(quazip zlib) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES + FOLDER "hidden/externals" + INSTALL_NAME_DIR ${INSTALL_DIR}/lib + BUILD_WITH_INSTALL_RPATH True) + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories") +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories") +set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL") + +if (APPLE) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library") +elseif (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip.lib CACHE FILEPATH "Location of QuaZip release library") +else () + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip.so CACHE FILEPATH "Location of QuaZip release library") +endif () + +include(SelectLibraryConfigurations) +select_library_configurations(${EXTERNAL_NAME_UPPER}) + +# Force selected libraries into the cache +set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries") +set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries") \ No newline at end of file diff --git a/cmake/externals/zlib/CMakeLists.txt b/cmake/externals/zlib/CMakeLists.txt index 0d279cc469..06b6b564ba 100644 --- a/cmake/externals/zlib/CMakeLists.txt +++ b/cmake/externals/zlib/CMakeLists.txt @@ -4,19 +4,20 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) include(ExternalProject) ExternalProject_Add( -${EXTERNAL_NAME} -URL http://zlib.net/zlib128.zip -CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build -LOG_DOWNLOAD 1 -LOG_CONFIGURE 1 -LOG_BUILD 1 + ${EXTERNAL_NAME} + URL http://zlib.net/zlib128.zip + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 ) # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(${EXTERNAL_NAME_UPPER}_ROOT ${INSTALL_DIR} CACHE PATH "Path for Zlib install root") set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of zlib include directories") set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of zlib include directories") set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/bin CACHE FILEPATH "Location of ZLib DLL") diff --git a/cmake/macros/CopyDllsBesideWindowsExecutable.cmake b/cmake/macros/CopyDllsBesideWindowsExecutable.cmake index 69fd20a57b..9330515a62 100644 --- a/cmake/macros/CopyDllsBesideWindowsExecutable.cmake +++ b/cmake/macros/CopyDllsBesideWindowsExecutable.cmake @@ -9,7 +9,7 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # -macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE) +macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT) if (WIN32) configure_file( @@ -18,11 +18,7 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE) @ONLY ) - if (APPLE) - set(PLUGIN_PATH "interface.app/Contents/MacOS/plugins") - else() - set(PLUGIN_PATH "plugins") - endif() + set(PLUGIN_PATH "plugins") # add a post-build command to copy DLLs beside the executable add_custom_command( @@ -46,5 +42,18 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE) POST_BUILD COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$,$,$>:--release> $" ) + elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) + find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) + + if (NOT MACDEPLOYQT_COMMAND) + message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin. macdeployqt is required.") + endif () + + # add a post-build command to call macdeployqt to copy Qt plugins + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ${MACDEPLOYQT_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/\${CONFIGURATION}/${TARGET_NAME}.app -verbose 0 + ) endif () -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 166a3fd4b9..79ca0122c9 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -22,8 +22,11 @@ macro(SETUP_HIFI_PROJECT) endif () endforeach() - # add the executable, include additional optional sources - add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) + if (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) + add_executable(${TARGET_NAME} MACOSX_BUNDLE ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) + else () + add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) + endif() set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN}) list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core) diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake new file mode 100644 index 0000000000..0536a1a9d8 --- /dev/null +++ b/cmake/macros/TargetQuazip.cmake @@ -0,0 +1,16 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Leonardo Murillo on 2015/11/20 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_QUAZIP) + add_dependency_external_projects(quazip) + find_package(QuaZip REQUIRED) + target_include_directories(${TARGET_NAME} PUBLIC ${QUAZIP_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + if (WIN32) + add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) + endif () +endmacro() diff --git a/cmake/modules/FindQuaZip.cmake b/cmake/modules/FindQuaZip.cmake new file mode 100644 index 0000000000..110f239c68 --- /dev/null +++ b/cmake/modules/FindQuaZip.cmake @@ -0,0 +1,29 @@ +# +# FindQuaZip.h +# StackManagerQt/cmake/modules +# +# Created by Mohammed Nafees. +# Copyright (c) 2014 High Fidelity. All rights reserved. +# + +# QUAZIP_FOUND - QuaZip library was found +# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir +# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR) +# QUAZIP_LIBRARIES - List of QuaZip libraries +# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers + +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("quazip") + +if (WIN32) + find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS}) +elseif (APPLE) + find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS}) +else () + find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES quazip HINTS ${QUAZIP_SEARCH_DIRS}) +endif () + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_INCLUDE_DIRS) + +mark_as_advanced(QUAZIP_INCLUDE_DIRS QUAZIP_SEARCH_DIRS) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index ce683df698..1f9280a899 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -37,4 +37,4 @@ if (UNIX) endif (UNIX) include_application_version() -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index b056d79efd..cfec3c966c 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -5,5 +5,4 @@ setup_hifi_project(Network) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) - -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 98a9dad909..3357b57858 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -201,4 +201,4 @@ else (APPLE) endif() endif (APPLE) -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/stack-manager/CMakeLists.txt b/stack-manager/CMakeLists.txt new file mode 100644 index 0000000000..e70fefc6e0 --- /dev/null +++ b/stack-manager/CMakeLists.txt @@ -0,0 +1,48 @@ +set(TARGET_NAME "stack-manager") +set(BUILD_BUNDLE YES) +setup_hifi_project(Widgets Gui Svg Core Network WebKitWidgets) + +if (WIN32) + target_zlib() +endif () +target_quazip() + +set_target_properties( + ${TARGET_NAME} PROPERTIES + EXCLUDE_FROM_ALL TRUE +) + +if (DEFINED ENV{JOB_ID}) + set(PR_BUILD "false") + set(BUILD_SEQ $ENV{JOB_ID}) + set(BASE_URL "http://s3.amazonaws.com/hifi-public") +else () + set(BUILD_SEQ "dev") + if (DEFINED ENV{PR_NUMBER}) + set(PR_BUILD "true") + set(BASE_URL "http://s3.amazonaws.com/hifi-public/pr-builds/$ENV{PR_NUMBER}") + else () + set(PR_BUILD "false") + set(BASE_URL "http://s3.amazonaws.com/hifi-public") + endif () +endif () + +configure_file(src/StackManagerVersion.h.in "${PROJECT_BINARY_DIR}/includes/StackManagerVersion.h") +include_directories( + ${PROJECT_BINARY_DIR}/includes + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/ui + ${QUAZIP_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} +) + +if (APPLE) + set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8) + set(MACOSX_BUNDLE_BUNDLE_NAME "Stack Manager") + set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.StackManager) + set(MACOSX_BUNDLE_ICON_FILE icon.icns) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/assets/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set(SM_SRCS ${SM_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon.icns") +endif () + +package_libraries_for_deployment() \ No newline at end of file diff --git a/stack-manager/assets/assignment-run.svg b/stack-manager/assets/assignment-run.svg new file mode 100644 index 0000000000..4005d58fbf --- /dev/null +++ b/stack-manager/assets/assignment-run.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/stack-manager/assets/assignment-stop.svg b/stack-manager/assets/assignment-stop.svg new file mode 100644 index 0000000000..ecc1b190c4 --- /dev/null +++ b/stack-manager/assets/assignment-stop.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/stack-manager/assets/icon.icns b/stack-manager/assets/icon.icns new file mode 100644 index 0000000000..711c7f6380 Binary files /dev/null and b/stack-manager/assets/icon.icns differ diff --git a/stack-manager/assets/icon.ico b/stack-manager/assets/icon.ico new file mode 100644 index 0000000000..0c0c023bdb Binary files /dev/null and b/stack-manager/assets/icon.ico differ diff --git a/stack-manager/assets/icon.png b/stack-manager/assets/icon.png new file mode 100644 index 0000000000..3889ed729e Binary files /dev/null and b/stack-manager/assets/icon.png differ diff --git a/stack-manager/assets/logo-larger.png b/stack-manager/assets/logo-larger.png new file mode 100644 index 0000000000..7872e44b5a Binary files /dev/null and b/stack-manager/assets/logo-larger.png differ diff --git a/stack-manager/assets/server-start.svg b/stack-manager/assets/server-start.svg new file mode 100644 index 0000000000..e95d2f3a25 --- /dev/null +++ b/stack-manager/assets/server-start.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stack-manager/assets/server-stop.svg b/stack-manager/assets/server-stop.svg new file mode 100644 index 0000000000..13bf8f3067 --- /dev/null +++ b/stack-manager/assets/server-stop.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stack-manager/content-sets/content-sets.html b/stack-manager/content-sets/content-sets.html new file mode 100644 index 0000000000..05c5f4d6f7 --- /dev/null +++ b/stack-manager/content-sets/content-sets.html @@ -0,0 +1,64 @@ + + + + High Fidelity Stack Manager Content Sets + + + + + + + + + + + + + + +
+
+
+
Click on the name of one of the content sets below to replace your local content with that set.
+
Note that the content set you choose may change the index path ('/') in your domain-server settings.
+
+
+
+ +
+
+ + + + diff --git a/stack-manager/content-sets/content-sets.json b/stack-manager/content-sets/content-sets.json new file mode 100644 index 0000000000..250c40adba --- /dev/null +++ b/stack-manager/content-sets/content-sets.json @@ -0,0 +1,27 @@ +{ + "floating-island": { + "name": "Floating Island", + "description": "Start your galactic empire with this floating island and small oasis. Build it up and share it with your friends.", + "path": "/1064.2,75.6,915.1/0.0000127922,0.71653,0.0000684642,0.697556" + }, + "low-poly-floating-island": { + "name": "Low-poly Floating Island", + "description": "Impressionism with polygons. If you want your virtual island to be nothing but a beautiful painting, this is the aesthetic for you.", + "path": "/8216.88,580.568,8264.03/-0.000192036,-0.838296,-0.000124955,0.545216" + }, + "mid-century-modern-living-room": { + "name": "Mid-century Modern Living Room", + "description": "Timeless, mid-century modern beauty. Notice the classic Eames Recliner and the beautiful built-in shelving.", + "path": "/8206.22,22.8716,8210.47/1.61213e-06,0.814919,1.44589e-06,0.579575" + }, + "bar" : { + "name": "The Bar", + "description": "A sexy club scene to plan your parties and live shows.", + "path": "/1048.52,9.5386,1005.7/-0.0000565125,-0.395713,-0.000131155,0.918374" + }, + "space": { + "name": "Space", + "description": "Vast, empty, nothingness. A completely clean slate for you to start building anything you desire.", + "path": "/1000,100,100" + } +} diff --git a/stack-manager/src/AppDelegate.cpp b/stack-manager/src/AppDelegate.cpp new file mode 100644 index 0000000000..ea9310a2d8 --- /dev/null +++ b/stack-manager/src/AppDelegate.cpp @@ -0,0 +1,776 @@ +// +// AppDelegate.cpp +// StackManagerQt/src +// +// Created by Mohammed Nafees on 06/27/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include + +#include "AppDelegate.h" +#include "BackgroundProcess.h" +#include "GlobalData.h" +#include "DownloadManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const QString HIGH_FIDELITY_API_URL = "https://metaverse.highfidelity.com/api/v1"; + +const QString CHECK_BUILDS_URL = "https://highfidelity.com/builds.xml"; + +// Use a custom User-Agent to avoid ModSecurity filtering, e.g. by hosting providers. +const QByteArray HIGH_FIDELITY_USER_AGENT = "Mozilla/5.0 (HighFidelity)"; + +const int VERSION_CHECK_INTERVAL_MS = 86400000; // a day + +const int WAIT_FOR_CHILD_MSECS = 5000; + +void signalHandler(int param) { + AppDelegate* app = AppDelegate::getInstance(); + + app->quit(); +} + +static QTextStream* outStream = NULL; + +void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + Q_UNUSED(context); + + QString dateTime = QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss"); + QString txt = QString("[%1] ").arg(dateTime); + + //in this function, you can write the message to any stream! + switch (type) { + case QtDebugMsg: + fprintf(stdout, "Debug: %s\n", qPrintable(msg)); + txt += msg; + break; + case QtWarningMsg: + fprintf(stdout, "Warning: %s\n", qPrintable(msg)); + txt += msg; + break; + case QtCriticalMsg: + fprintf(stdout, "Critical: %s\n", qPrintable(msg)); + txt += msg; + break; + case QtFatalMsg: + fprintf(stdout, "Fatal: %s\n", qPrintable(msg)); + txt += msg; + } + + if (outStream) { + *outStream << txt << endl; + } +} + +AppDelegate::AppDelegate(int argc, char* argv[]) : + QApplication(argc, argv), + _qtReady(false), + _dsReady(false), + _dsResourcesReady(false), + _acReady(false), + _domainServerProcess(NULL), + _acMonitorProcess(NULL), + _domainServerName("localhost") +{ + // be a signal handler for SIGTERM so we can stop child processes if we get it + signal(SIGTERM, signalHandler); + + // look for command-line options + parseCommandLine(); + + setApplicationName("Stack Manager"); + setOrganizationName("High Fidelity"); + setOrganizationDomain("io.highfidelity.StackManager"); + + QFile* logFile = new QFile("last_run_log", this); + if (!logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) { + qDebug() << "Failed to open log file. Will not be able to write STDOUT/STDERR to file."; + } else { + outStream = new QTextStream(logFile); + } + + + qInstallMessageHandler(myMessageHandler); + _domainServerProcess = new BackgroundProcess(GlobalData::getInstance().getDomainServerExecutablePath(), this); + _acMonitorProcess = new BackgroundProcess(GlobalData::getInstance().getAssignmentClientExecutablePath(), this); + + _manager = new QNetworkAccessManager(this); + + _window = new MainWindow(); + + createExecutablePath(); + downloadLatestExecutablesAndRequirements(); + + _checkVersionTimer.setInterval(0); + connect(&_checkVersionTimer, SIGNAL(timeout()), this, SLOT(checkVersion())); + _checkVersionTimer.start(); + + connect(this, &QApplication::aboutToQuit, this, &AppDelegate::stopStack); +} + +AppDelegate::~AppDelegate() { + QHash::iterator it = _scriptProcesses.begin(); + + qDebug() << "Stopping scripted assignment-client processes prior to quit."; + while (it != _scriptProcesses.end()) { + BackgroundProcess* backgroundProcess = it.value(); + + // remove from the script processes hash + it = _scriptProcesses.erase(it); + + // make sure the process is dead + backgroundProcess->terminate(); + backgroundProcess->waitForFinished(); + backgroundProcess->deleteLater(); + } + + qDebug() << "Stopping domain-server process prior to quit."; + _domainServerProcess->terminate(); + _domainServerProcess->waitForFinished(); + + qDebug() << "Stopping assignment-client process prior to quit."; + _acMonitorProcess->terminate(); + _acMonitorProcess->waitForFinished(); + + _domainServerProcess->deleteLater(); + _acMonitorProcess->deleteLater(); + + _window->deleteLater(); + + delete outStream; + outStream = NULL; +} + +void AppDelegate::parseCommandLine() { + QCommandLineParser parser; + parser.setApplicationDescription("High Fidelity Stack Manager"); + parser.addHelpOption(); + + const QCommandLineOption helpOption = parser.addHelpOption(); + + const QCommandLineOption hifiBuildDirectoryOption("b", "Path to build of hifi", "build-directory"); + parser.addOption(hifiBuildDirectoryOption); + + if (!parser.parse(QCoreApplication::arguments())) { + qCritical() << parser.errorText() << endl; + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(helpOption)) { + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(hifiBuildDirectoryOption)) { + const QString hifiBuildDirectory = parser.value(hifiBuildDirectoryOption); + qDebug() << "hifiBuildDirectory=" << hifiBuildDirectory << "\n"; + GlobalData::getInstance().setHifiBuildDirectory(hifiBuildDirectory); + } +} + +void AppDelegate::toggleStack(bool start) { + toggleDomainServer(start); + toggleAssignmentClientMonitor(start); + toggleScriptedAssignmentClients(start); + emit stackStateChanged(start); +} + +void AppDelegate::toggleDomainServer(bool start) { + + if (start) { + _domainServerProcess->start(QStringList()); + + _window->getLogsWidget()->addTab(_domainServerProcess->getLogViewer(), "Domain Server"); + + if (_domainServerID.isEmpty()) { + // after giving the domain server some time to set up, ask for its ID + QTimer::singleShot(1000, this, SLOT(requestDomainServerID())); + } + } else { + _domainServerProcess->terminate(); + _domainServerProcess->waitForFinished(WAIT_FOR_CHILD_MSECS); + _domainServerProcess->kill(); + } +} + +void AppDelegate::toggleAssignmentClientMonitor(bool start) { + if (start) { + _acMonitorProcess->start(QStringList() << "--min" << "5"); + _window->getLogsWidget()->addTab(_acMonitorProcess->getLogViewer(), "Assignment Clients"); + } else { + _acMonitorProcess->terminate(); + _acMonitorProcess->waitForFinished(WAIT_FOR_CHILD_MSECS); + _acMonitorProcess->kill(); + } +} + +void AppDelegate::toggleScriptedAssignmentClients(bool start) { + foreach(BackgroundProcess* scriptProcess, _scriptProcesses) { + if (start) { + scriptProcess->start(scriptProcess->getLastArgList()); + } else { + scriptProcess->terminate(); + scriptProcess->waitForFinished(WAIT_FOR_CHILD_MSECS); + scriptProcess->kill(); + } + } +} + +int AppDelegate::startScriptedAssignment(const QUuid& scriptID, const QString& pool) { + + BackgroundProcess* scriptProcess = _scriptProcesses.value(scriptID); + + if (!scriptProcess) { + QStringList argList = QStringList() << "-t" << "2"; + if (!pool.isEmpty()) { + argList << "--pool" << pool; + } + + scriptProcess = new BackgroundProcess(GlobalData::getInstance().getAssignmentClientExecutablePath(), + this); + + scriptProcess->start(argList); + + qint64 processID = scriptProcess->processId(); + _scriptProcesses.insert(scriptID, scriptProcess); + + _window->getLogsWidget()->addTab(scriptProcess->getLogViewer(), "Scripted Assignment " + + QString::number(processID)); + } else { + scriptProcess->QProcess::start(); + } + + return scriptProcess->processId(); +} + +void AppDelegate::stopScriptedAssignment(BackgroundProcess* backgroundProcess) { + _window->getLogsWidget()->removeTab(_window->getLogsWidget()->indexOf(backgroundProcess->getLogViewer())); + backgroundProcess->terminate(); + backgroundProcess->waitForFinished(WAIT_FOR_CHILD_MSECS); + backgroundProcess->kill(); +} + +void AppDelegate::stopScriptedAssignment(const QUuid& scriptID) { + BackgroundProcess* processValue = _scriptProcesses.take(scriptID); + if (processValue) { + stopScriptedAssignment(processValue); + } +} + + +void AppDelegate::requestDomainServerID() { + // ask the domain-server for its ID so we can update the accessible name + emit domainAddressChanged(); + QUrl domainIDURL = GlobalData::getInstance().getDomainServerBaseUrl() + "/id"; + + qDebug() << "Requesting domain server ID from" << domainIDURL.toString(); + + QNetworkReply* idReply = _manager->get(QNetworkRequest(domainIDURL)); + + connect(idReply, &QNetworkReply::finished, this, &AppDelegate::handleDomainIDReply); +} + +const QString AppDelegate::getServerAddress() const { + return "hifi://" + _domainServerName; +} + +void AppDelegate::handleDomainIDReply() { + QNetworkReply* reply = qobject_cast(sender()); + + if (reply->error() == QNetworkReply::NoError + && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { + _domainServerID = QString(reply->readAll()); + + if (!_domainServerID.isEmpty()) { + + if (!QUuid(_domainServerID).isNull()) { + qDebug() << "The domain server ID is" << _domainServerID; + qDebug() << "Asking High Fidelity API for associated domain name."; + + // fire off a request to high fidelity API to see if this domain exists with them + QUrl domainGetURL = HIGH_FIDELITY_API_URL + "/domains/" + _domainServerID; + QNetworkReply* domainGetReply = _manager->get(QNetworkRequest(domainGetURL)); + connect(domainGetReply, &QNetworkReply::finished, this, &AppDelegate::handleDomainGetReply); + } else { + emit domainServerIDMissing(); + } + } + } else { + qDebug() << "Error getting domain ID from domain-server - " + << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() + << reply->errorString(); + } +} + +void AppDelegate::handleDomainGetReply() { + QNetworkReply* reply = qobject_cast(sender()); + + if (reply->error() == QNetworkReply::NoError + && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { + QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll()); + + QJsonObject domainObject = responseDocument.object()["domain"].toObject(); + + const QString DOMAIN_NAME_KEY = "name"; + const QString DOMAIN_OWNER_PLACES_KEY = "owner_places"; + + if (domainObject.contains(DOMAIN_NAME_KEY)) { + _domainServerName = domainObject[DOMAIN_NAME_KEY].toString(); + } else if (domainObject.contains(DOMAIN_OWNER_PLACES_KEY)) { + QJsonArray ownerPlaces = domainObject[DOMAIN_OWNER_PLACES_KEY].toArray(); + if (ownerPlaces.size() > 0) { + _domainServerName = ownerPlaces[0].toObject()[DOMAIN_NAME_KEY].toString(); + } + } + + qDebug() << "This domain server's name is" << _domainServerName << "- updating address link."; + + emit domainAddressChanged(); + } +} + +void AppDelegate::changeDomainServerIndexPath(const QString& newPath) { + if (!newPath.isEmpty()) { + QString pathsJSON = "{\"paths\": { \"/\": { \"viewpoint\": \"%1\" }}}"; + + QNetworkRequest settingsRequest(GlobalData::getInstance().getDomainServerBaseUrl() + "/settings.json"); + settingsRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QNetworkReply* settingsReply = _manager->post(settingsRequest, pathsJSON.arg(newPath).toLocal8Bit()); + connect(settingsReply, &QNetworkReply::finished, this, &AppDelegate::handleChangeIndexPathResponse); + } +} + +void AppDelegate::handleChangeIndexPathResponse() { + QNetworkReply* reply = qobject_cast(sender()); + + if (reply->error() == QNetworkReply::NoError + && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { + + qDebug() << "Successfully changed index path in domain-server."; + emit indexPathChangeResponse(true); + } else { + qDebug() << "Error changing domain-server index path-" << reply->errorString(); + emit indexPathChangeResponse(false); + } +} + +void AppDelegate::downloadContentSet(const QUrl& contentSetURL) { + // make sure this link was an svo + if (contentSetURL.path().endsWith(".svo")) { + // setup a request for this content set + QNetworkRequest contentRequest(contentSetURL); + QNetworkReply* contentReply = _manager->get(contentRequest); + connect(contentReply, &QNetworkReply::finished, this, &AppDelegate::handleContentSetDownloadFinished); + } +} + +void AppDelegate::handleContentSetDownloadFinished() { + QNetworkReply* reply = qobject_cast(sender()); + + if (reply->error() == QNetworkReply::NoError + && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { + + QString modelFilename = GlobalData::getInstance().getClientsResourcesPath() + "models.svo"; + + // write the model file + QFile modelFile(modelFilename); + modelFile.open(QIODevice::WriteOnly); + + // stop the base assignment clients before we try to write the new content + toggleAssignmentClientMonitor(false); + + if (modelFile.write(reply->readAll()) == -1) { + qDebug() << "Error writing content set to" << modelFilename; + modelFile.close(); + toggleAssignmentClientMonitor(true); + } else { + qDebug() << "Wrote new content set to" << modelFilename; + modelFile.close(); + + // restart the assignment-client + toggleAssignmentClientMonitor(true); + + emit contentSetDownloadResponse(true); + + // did we have a path in the query? + // if so when we need to set the DS index path to that path + QUrlQuery svoQuery(reply->url().query()); + changeDomainServerIndexPath(svoQuery.queryItemValue("path")); + + emit domainAddressChanged(); + + return; + } + } + + // if we failed we need to emit our signal with a fail + emit contentSetDownloadResponse(false); + emit domainAddressChanged(); +} + +void AppDelegate::onFileSuccessfullyInstalled(const QUrl& url) { + if (url == GlobalData::getInstance().getRequirementsURL()) { + _qtReady = true; + } else if (url == GlobalData::getInstance().getAssignmentClientURL()) { + _acReady = true; + } else if (url == GlobalData::getInstance().getDomainServerURL()) { + _dsReady = true; + } else if (url == GlobalData::getInstance().getDomainServerResourcesURL()) { + _dsResourcesReady = true; + } + + if (_qtReady && _acReady && _dsReady && _dsResourcesReady) { + _window->setRequirementsLastChecked(QDateTime::currentDateTime().toString()); + _window->show(); + toggleStack(true); + } +} + +void AppDelegate::createExecutablePath() { + QDir launchDir(GlobalData::getInstance().getClientsLaunchPath()); + QDir resourcesDir(GlobalData::getInstance().getClientsResourcesPath()); + QDir logsDir(GlobalData::getInstance().getLogsPath()); + if (!launchDir.exists()) { + if (QDir().mkpath(launchDir.absolutePath())) { + qDebug() << "Successfully created directory: " + << launchDir.absolutePath(); + } else { + qCritical() << "Failed to create directory: " + << launchDir.absolutePath(); + } + } + if (!resourcesDir.exists()) { + if (QDir().mkpath(resourcesDir.absolutePath())) { + qDebug() << "Successfully created directory: " + << resourcesDir.absolutePath(); + } else { + qCritical() << "Failed to create directory: " + << resourcesDir.absolutePath(); + } + } + if (!logsDir.exists()) { + if (QDir().mkpath(logsDir.absolutePath())) { + qDebug() << "Successfully created directory: " + << logsDir.absolutePath(); + } else { + qCritical() << "Failed to create directory: " + << logsDir.absolutePath(); + } + } +} + +void AppDelegate::downloadLatestExecutablesAndRequirements() { + // Check if Qt is already installed + if (GlobalData::getInstance().getPlatform() == "mac") { + if (QDir(GlobalData::getInstance().getClientsLaunchPath() + "QtCore.framework").exists()) { + _qtReady = true; + } + } else if (GlobalData::getInstance().getPlatform() == "win") { + if (QFileInfo(GlobalData::getInstance().getClientsLaunchPath() + "Qt5Core.dll").exists()) { + _qtReady = true; + } + } else { // linux + if (QFileInfo(GlobalData::getInstance().getClientsLaunchPath() + "libQt5Core.so.5").exists()) { + _qtReady = true; + } + } + + + QFile reqZipFile(GlobalData::getInstance().getRequirementsZipPath()); + QByteArray reqZipData; + if (reqZipFile.open(QIODevice::ReadOnly)) { + reqZipData = reqZipFile.readAll(); + reqZipFile.close(); + } + QFile resZipFile(GlobalData::getInstance().getDomainServerResourcesZipPath()); + QByteArray resZipData; + if (resZipFile.open(QIODevice::ReadOnly)) { + resZipData = resZipFile.readAll(); + resZipFile.close(); + } + + QDir resourcesDir(GlobalData::getInstance().getClientsResourcesPath()); + if (!(resourcesDir.entryInfoList(QDir::AllEntries).size() < 3)) { + _dsResourcesReady = true; + } + + // if the user has set hifiBuildDirectory, don't attempt to download the domain-server or assignement-client + if (GlobalData::getInstance().isGetHifiBuildDirectorySet()) { + _dsReady = true; + _acReady = true; + } else { + QByteArray dsData; + QFile dsFile(GlobalData::getInstance().getDomainServerExecutablePath()); + if (dsFile.open(QIODevice::ReadOnly)) { + dsData = dsFile.readAll(); + dsFile.close(); + } + QByteArray acData; + QFile acFile(GlobalData::getInstance().getAssignmentClientExecutablePath()); + if (acFile.open(QIODevice::ReadOnly)) { + acData = acFile.readAll(); + acFile.close(); + } + + QNetworkRequest acReq(QUrl(GlobalData::getInstance().getAssignmentClientMD5URL())); + QNetworkReply* acReply = _manager->get(acReq); + QEventLoop acLoop; + connect(acReply, SIGNAL(finished()), &acLoop, SLOT(quit())); + acLoop.exec(); + QByteArray acMd5Data = acReply->readAll().trimmed(); + if (GlobalData::getInstance().getPlatform() == "win") { + // fix for reading the MD5 hash from Windows-generated + // binary data of the MD5 hash + QTextStream stream(acMd5Data); + stream >> acMd5Data; + } + + // fix for Mac and Linux network accessibility + if (acMd5Data.size() == 0) { + // network is not accessible + qDebug() << "Could not connect to the internet."; + _window->show(); + return; + } + + qDebug() << "AC MD5: " << acMd5Data; + if (acMd5Data.toLower() == QCryptographicHash::hash(acData, QCryptographicHash::Md5).toHex()) { + _acReady = true; + } + + + QNetworkRequest dsReq(QUrl(GlobalData::getInstance().getDomainServerMD5URL())); + QNetworkReply* dsReply = _manager->get(dsReq); + QEventLoop dsLoop; + connect(dsReply, SIGNAL(finished()), &dsLoop, SLOT(quit())); + dsLoop.exec(); + QByteArray dsMd5Data = dsReply->readAll().trimmed(); + if (GlobalData::getInstance().getPlatform() == "win") { + // fix for reading the MD5 hash from Windows generated + // binary data of the MD5 hash + QTextStream stream(dsMd5Data); + stream >> dsMd5Data; + } + qDebug() << "DS MD5: " << dsMd5Data; + if (dsMd5Data.toLower() == QCryptographicHash::hash(dsData, QCryptographicHash::Md5).toHex()) { + _dsReady = true; + } + } + + if (_qtReady) { + // check MD5 of requirements.zip only if Qt is found + QNetworkRequest reqZipReq(QUrl(GlobalData::getInstance().getRequirementsMD5URL())); + QNetworkReply* reqZipReply = _manager->get(reqZipReq); + QEventLoop reqZipLoop; + connect(reqZipReply, SIGNAL(finished()), &reqZipLoop, SLOT(quit())); + reqZipLoop.exec(); + QByteArray reqZipMd5Data = reqZipReply->readAll().trimmed(); + if (GlobalData::getInstance().getPlatform() == "win") { + // fix for reading the MD5 hash from Windows generated + // binary data of the MD5 hash + QTextStream stream(reqZipMd5Data); + stream >> reqZipMd5Data; + } + qDebug() << "Requirements ZIP MD5: " << reqZipMd5Data; + if (reqZipMd5Data.toLower() != QCryptographicHash::hash(reqZipData, QCryptographicHash::Md5).toHex()) { + _qtReady = false; + } + } + + if (_dsResourcesReady) { + // check MD5 of resources.zip only if Domain Server + // resources are installed + QNetworkRequest resZipReq(QUrl(GlobalData::getInstance().getDomainServerResourcesMD5URL())); + QNetworkReply* resZipReply = _manager->get(resZipReq); + QEventLoop resZipLoop; + connect(resZipReply, SIGNAL(finished()), &resZipLoop, SLOT(quit())); + resZipLoop.exec(); + QByteArray resZipMd5Data = resZipReply->readAll().trimmed(); + if (GlobalData::getInstance().getPlatform() == "win") { + // fix for reading the MD5 hash from Windows generated + // binary data of the MD5 hash + QTextStream stream(resZipMd5Data); + stream >> resZipMd5Data; + } + qDebug() << "Domain Server Resources ZIP MD5: " << resZipMd5Data; + if (resZipMd5Data.toLower() != QCryptographicHash::hash(resZipData, QCryptographicHash::Md5).toHex()) { + _dsResourcesReady = false; + } + } + + DownloadManager* downloadManager = 0; + if (!_qtReady || !_acReady || !_dsReady || !_dsResourcesReady) { + // initialise DownloadManager + downloadManager = new DownloadManager(_manager); + downloadManager->setWindowModality(Qt::ApplicationModal); + connect(downloadManager, SIGNAL(fileSuccessfullyInstalled(QUrl)), + SLOT(onFileSuccessfullyInstalled(QUrl))); + downloadManager->show(); + } else { + _window->setRequirementsLastChecked(QDateTime::currentDateTime().toString()); + _window->show(); + toggleStack(true); + } + + if (!_qtReady) { + downloadManager->downloadFile(GlobalData::getInstance().getRequirementsURL()); + } + + if (!_acReady) { + downloadManager->downloadFile(GlobalData::getInstance().getAssignmentClientURL()); + } + + if (!_dsReady) { + downloadManager->downloadFile(GlobalData::getInstance().getDomainServerURL()); + } + + if (!_dsResourcesReady) { + downloadManager->downloadFile(GlobalData::getInstance().getDomainServerResourcesURL()); + } +} + +void AppDelegate::checkVersion() { + QNetworkRequest latestVersionRequest((QUrl(CHECK_BUILDS_URL))); + latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + QNetworkReply* reply = _manager->get(latestVersionRequest); + connect(reply, &QNetworkReply::finished, this, &AppDelegate::parseVersionXml); + + _checkVersionTimer.setInterval(VERSION_CHECK_INTERVAL_MS); + _checkVersionTimer.start(); +} + +struct VersionInformation { + QString version; + QUrl downloadUrl; + QString timeStamp; + QString releaseNotes; +}; + +void AppDelegate::parseVersionXml() { + +#ifdef Q_OS_WIN32 + QString operatingSystem("windows"); +#endif + +#ifdef Q_OS_MAC + QString operatingSystem("mac"); +#endif + +#ifdef Q_OS_LINUX + QString operatingSystem("ubuntu"); +#endif + + QNetworkReply* sender = qobject_cast(QObject::sender()); + QXmlStreamReader xml(sender); + + QHash projectVersions; + + while (!xml.atEnd() && !xml.hasError()) { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "project") { + QString projectName = ""; + foreach(const QXmlStreamAttribute &attr, xml.attributes()) { + if (attr.name().toString() == "name") { + projectName = attr.value().toString(); + break; + } + } + while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name().toString() == "project")) { + if (projectName != "") { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "platform") { + QString platformName = ""; + foreach(const QXmlStreamAttribute &attr, xml.attributes()) { + if (attr.name().toString() == "name") { + platformName = attr.value().toString(); + break; + } + } + int latestVersion = 0; + VersionInformation latestVersionInformation; + while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name().toString() == "platform")) { + if (platformName == operatingSystem) { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "build") { + VersionInformation buildVersionInformation; + while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name().toString() == "build")) { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "version") { + xml.readNext(); + buildVersionInformation.version = xml.text().toString(); + } + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "url") { + xml.readNext(); + buildVersionInformation.downloadUrl = QUrl(xml.text().toString()); + } + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "timestamp") { + xml.readNext(); + buildVersionInformation.timeStamp = xml.text().toString(); + } + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "note") { + xml.readNext(); + if (buildVersionInformation.releaseNotes != "") { + buildVersionInformation.releaseNotes += "\n"; + } + buildVersionInformation.releaseNotes += xml.text().toString(); + } + xml.readNext(); + } + if (latestVersion < buildVersionInformation.version.toInt()) { + latestVersionInformation = buildVersionInformation; + latestVersion = buildVersionInformation.version.toInt(); + } + } + } + xml.readNext(); + } + if (latestVersion>0) { + projectVersions[projectName] = latestVersionInformation; + } + } + } + xml.readNext(); + } + } + xml.readNext(); + } + +#ifdef WANT_DEBUG + qDebug() << "parsed projects for OS" << operatingSystem; + QHashIterator projectVersion(projectVersions); + while (projectVersion.hasNext()) { + projectVersion.next(); + qDebug() << "project:" << projectVersion.key(); + qDebug() << "version:" << projectVersion.value().version; + qDebug() << "downloadUrl:" << projectVersion.value().downloadUrl.toString(); + qDebug() << "timeStamp:" << projectVersion.value().timeStamp; + qDebug() << "releaseNotes:" << projectVersion.value().releaseNotes; + } +#endif + + if (projectVersions.contains("stackmanager")) { + VersionInformation latestVersion = projectVersions["stackmanager"]; + if (QCoreApplication::applicationVersion() != latestVersion.version && QCoreApplication::applicationVersion() != "dev") { + _window->setUpdateNotification("There is an update available. Please download and install version " + latestVersion.version + "."); + _window->update(); + } + } + + sender->deleteLater(); +} diff --git a/stack-manager/src/AppDelegate.h b/stack-manager/src/AppDelegate.h new file mode 100644 index 0000000000..1d4728b7ce --- /dev/null +++ b/stack-manager/src/AppDelegate.h @@ -0,0 +1,89 @@ +// +// AppDelegate.h +// StackManagerQt/src +// +// Created by Mohammed Nafees on 06/27/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_AppDelegate_h +#define hifi_AppDelegate_h + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MainWindow.h" + +class BackgroundProcess; + +class AppDelegate : public QApplication +{ + Q_OBJECT +public: + static AppDelegate* getInstance() { return static_cast(QCoreApplication::instance()); } + + AppDelegate(int argc, char* argv[]); + ~AppDelegate(); + + void toggleStack(bool start); + void toggleDomainServer(bool start); + void toggleAssignmentClientMonitor(bool start); + void toggleScriptedAssignmentClients(bool start); + + int startScriptedAssignment(const QUuid& scriptID, const QString& pool = QString()); + void stopScriptedAssignment(BackgroundProcess* backgroundProcess); + void stopScriptedAssignment(const QUuid& scriptID); + + void stopStack() { toggleStack(false); } + + const QString getServerAddress() const; +public slots: + void downloadContentSet(const QUrl& contentSetURL); +signals: + void domainServerIDMissing(); + void domainAddressChanged(); + void contentSetDownloadResponse(bool wasSuccessful); + void indexPathChangeResponse(bool wasSuccessful); + void stackStateChanged(bool isOn); +private slots: + void onFileSuccessfullyInstalled(const QUrl& url); + void requestDomainServerID(); + void handleDomainIDReply(); + void handleDomainGetReply(); + void handleChangeIndexPathResponse(); + void handleContentSetDownloadFinished(); + void checkVersion(); + void parseVersionXml(); + +private: + void parseCommandLine(); + void createExecutablePath(); + void downloadLatestExecutablesAndRequirements(); + + void changeDomainServerIndexPath(const QString& newPath); + + QNetworkAccessManager* _manager; + bool _qtReady; + bool _dsReady; + bool _dsResourcesReady; + bool _acReady; + BackgroundProcess* _domainServerProcess; + BackgroundProcess* _acMonitorProcess; + QHash _scriptProcesses; + + QString _domainServerID; + QString _domainServerName; + + QTimer _checkVersionTimer; + + MainWindow* _window; +}; + +#endif diff --git a/stack-manager/src/BackgroundProcess.cpp b/stack-manager/src/BackgroundProcess.cpp new file mode 100644 index 0000000000..e5d0452a83 --- /dev/null +++ b/stack-manager/src/BackgroundProcess.cpp @@ -0,0 +1,125 @@ +// +// BackgroundProcess.cpp +// StackManagerQt/src +// +// Created by Mohammed Nafees on 07/03/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "BackgroundProcess.h" +#include "GlobalData.h" + +#include +#include +#include +#include +#include +#include +#include + +const int LOG_CHECK_INTERVAL_MS = 500; + +const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; +const QString LOGS_DIRECTORY = "/Logs/"; + +BackgroundProcess::BackgroundProcess(const QString& program, QObject *parent) : + QProcess(parent), + _program(program), + _stdoutFilePos(0), + _stderrFilePos(0) +{ + _logViewer = new LogViewer; + + connect(this, SIGNAL(started()), SLOT(processStarted())); + connect(this, SIGNAL(error(QProcess::ProcessError)), SLOT(processError())); + + _logFilePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + _logFilePath.append(LOGS_DIRECTORY); + QDir logDir(_logFilePath); + if (!logDir.exists(_logFilePath)) { + logDir.mkpath(_logFilePath); + } + + _logTimer.setInterval(LOG_CHECK_INTERVAL_MS); + _logTimer.setSingleShot(false); + connect(&_logTimer, SIGNAL(timeout()), this, SLOT(receivedStandardError())); + connect(&_logTimer, SIGNAL(timeout()), this, SLOT(receivedStandardOutput())); + connect(this, SIGNAL(started()), &_logTimer, SLOT(start())); + + setWorkingDirectory(GlobalData::getInstance().getClientsLaunchPath()); +} + +void BackgroundProcess::start(const QStringList& arguments) { + QDateTime now = QDateTime::currentDateTime(); + QString nowString = now.toString(DATETIME_FORMAT); + QFileInfo programFile(_program); + QString baseFilename = _logFilePath + programFile.completeBaseName(); + _stdoutFilename = QString("%1_stdout_%2.txt").arg(baseFilename, nowString); + _stderrFilename = QString("%1_stderr_%2.txt").arg(baseFilename, nowString); + + qDebug() << "stdout for " << _program << " being written to: " << _stdoutFilename; + qDebug() << "stderr for " << _program << " being written to: " << _stderrFilename; + + // reset the stdout and stderr file positions + _stdoutFilePos = 0; + _stderrFilePos = 0; + + // clear our LogViewer + _logViewer->clear(); + + // reset our output and error files + setStandardOutputFile(_stdoutFilename); + setStandardErrorFile(_stderrFilename); + + _lastArgList = arguments; + + QProcess::start(_program, arguments); +} + +void BackgroundProcess::processStarted() { + qDebug() << "process " << _program << " started."; +} + +void BackgroundProcess::processError() { + qDebug() << "process error for" << _program << "-" << errorString(); +} + +void BackgroundProcess::receivedStandardOutput() { + QString output; + + QFile file(_stdoutFilename); + + if (!file.open(QIODevice::ReadOnly)) return; + + if (file.size() > _stdoutFilePos) { + file.seek(_stdoutFilePos); + output = file.readAll(); + _stdoutFilePos = file.pos(); + } + + file.close(); + + if (!output.isEmpty() && !output.isNull()) { + _logViewer->appendStandardOutput(output); + } +} + +void BackgroundProcess::receivedStandardError() { + QString output; + + QFile file(_stderrFilename); + + if (!file.open(QIODevice::ReadOnly)) return; + + if (file.size() > _stderrFilePos) { + file.seek(_stderrFilePos); + output = file.readAll(); + _stderrFilePos = file.pos(); + } + + file.close(); + + if (!output.isEmpty() && !output.isNull()) { + _logViewer->appendStandardError(output); + } +} diff --git a/stack-manager/src/BackgroundProcess.h b/stack-manager/src/BackgroundProcess.h new file mode 100644 index 0000000000..fd652ba73e --- /dev/null +++ b/stack-manager/src/BackgroundProcess.h @@ -0,0 +1,48 @@ +// +// BackgroundProcess.h +// StackManagerQt/src +// +// Created by Mohammed Nafees on 07/03/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_BackgroundProcess_h +#define hifi_BackgroundProcess_h + +#include + +#include +#include +#include + +class BackgroundProcess : public QProcess +{ + Q_OBJECT +public: + BackgroundProcess(const QString& program, QObject* parent = 0); + + LogViewer* getLogViewer() { return _logViewer; } + + const QStringList& getLastArgList() const { return _lastArgList; } + + void start(const QStringList& arguments); + +private slots: + void processStarted(); + void processError(); + void receivedStandardOutput(); + void receivedStandardError(); + +private: + QString _program; + QStringList _lastArgList; + QString _logFilePath; + LogViewer* _logViewer; + QTimer _logTimer; + QString _stdoutFilename; + QString _stderrFilename; + qint64 _stdoutFilePos; + qint64 _stderrFilePos; +}; + +#endif diff --git a/stack-manager/src/DownloadManager.cpp b/stack-manager/src/DownloadManager.cpp new file mode 100644 index 0000000000..f97ba1f680 --- /dev/null +++ b/stack-manager/src/DownloadManager.cpp @@ -0,0 +1,164 @@ +// +// DownloadManager.cpp +// StackManagerQt/src +// +// Created by Mohammed Nafees on 07/09/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "DownloadManager.h" +#include "GlobalData.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DownloadManager::DownloadManager(QNetworkAccessManager* manager, QWidget* parent) : + QWidget(parent), + _manager(manager) +{ + setBaseSize(500, 250); + + QVBoxLayout* layout = new QVBoxLayout; + layout->setContentsMargins(10, 10, 10, 10); + QLabel* label = new QLabel; + label->setText("Download Manager"); + label->setStyleSheet("font-size: 19px;"); + label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + label->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + + _table = new QTableWidget; + _table->setEditTriggers(QTableWidget::NoEditTriggers); + _table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _table->setColumnCount(3); + _table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + _table->setHorizontalHeaderLabels(QStringList() << "Name" << "Progress" << "Status"); + layout->addWidget(_table); + + setLayout(layout); +} + +DownloadManager::~DownloadManager() { + _downloaderHash.clear(); +} + +void DownloadManager::downloadFile(const QUrl& url) { + for (int i = 0; i < _downloaderHash.size(); ++i) { + if (_downloaderHash.keys().at(i)->getUrl() == url) { + qDebug() << "Downloader for URL " << url << " already initialised."; + return; + } + } + + Downloader* downloader = new Downloader(url); + connect(downloader, SIGNAL(downloadCompleted(QUrl)), SLOT(onDownloadCompleted(QUrl))); + connect(downloader, SIGNAL(downloadStarted(Downloader*,QUrl)), + SLOT(onDownloadStarted(Downloader*,QUrl))); + connect(downloader, SIGNAL(downloadFailed(QUrl)), SLOT(onDownloadFailed(QUrl))); + connect(downloader, SIGNAL(downloadProgress(QUrl,int)), SLOT(onDownloadProgress(QUrl,int))); + connect(downloader, SIGNAL(installingFiles(QUrl)), SLOT(onInstallingFiles(QUrl))); + connect(downloader, SIGNAL(filesSuccessfullyInstalled(QUrl)), SLOT(onFilesSuccessfullyInstalled(QUrl))); + connect(downloader, SIGNAL(filesInstallationFailed(QUrl)), SLOT(onFilesInstallationFailed(QUrl))); + downloader->start(_manager); +} + +void DownloadManager::onDownloadStarted(Downloader* downloader, const QUrl& url) { + int rowIndex = _table->rowCount(); + _table->setRowCount(rowIndex + 1); + QTableWidgetItem* nameItem = new QTableWidgetItem(QFileInfo(url.toString()).fileName()); + _table->setItem(rowIndex, 0, nameItem); + QProgressBar* progressBar = new QProgressBar; + _table->setCellWidget(rowIndex, 1, progressBar); + QTableWidgetItem* statusItem = new QTableWidgetItem; + if (QFile(QDir::toNativeSeparators(GlobalData::getInstance().getClientsLaunchPath() + "/" + QFileInfo(url.toString()).fileName())).exists()) { + statusItem->setText("Updating"); + } else { + statusItem->setText("Downloading"); + } + _table->setItem(rowIndex, 2, statusItem); + _downloaderHash.insert(downloader, rowIndex); +} + +void DownloadManager::onDownloadCompleted(const QUrl& url) { + _table->item(downloaderRowIndexForUrl(url), 2)->setText("Download Complete"); +} + +void DownloadManager::onDownloadProgress(const QUrl& url, int percentage) { + qobject_cast(_table->cellWidget(downloaderRowIndexForUrl(url), 1))->setValue(percentage); +} + +void DownloadManager::onDownloadFailed(const QUrl& url) { + _table->item(downloaderRowIndexForUrl(url), 2)->setText("Download Failed"); + _downloaderHash.remove(downloaderForUrl(url)); +} + +void DownloadManager::onInstallingFiles(const QUrl& url) { + _table->item(downloaderRowIndexForUrl(url), 2)->setText("Installing"); +} + +void DownloadManager::onFilesSuccessfullyInstalled(const QUrl& url) { + _table->item(downloaderRowIndexForUrl(url), 2)->setText("Successfully Installed"); + _downloaderHash.remove(downloaderForUrl(url)); + emit fileSuccessfullyInstalled(url); + if (_downloaderHash.size() == 0) { + close(); + } +} + +void DownloadManager::onFilesInstallationFailed(const QUrl& url) { + _table->item(downloaderRowIndexForUrl(url), 2)->setText("Installation Failed"); + _downloaderHash.remove(downloaderForUrl(url)); +} + +void DownloadManager::closeEvent(QCloseEvent*) { + if (_downloaderHash.size() > 0) { + QMessageBox msgBox; + msgBox.setText("There are active downloads that need to be installed for the proper functioning of Stack Manager. Do you want to stop the downloads and exit?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + int ret = msgBox.exec(); + switch (ret) { + case QMessageBox::Yes: + qApp->quit(); + break; + case QMessageBox::No: + msgBox.close(); + break; + } + } +} + +int DownloadManager::downloaderRowIndexForUrl(const QUrl& url) { + QHash::const_iterator i = _downloaderHash.constBegin(); + while (i != _downloaderHash.constEnd()) { + if (i.key()->getUrl() == url) { + return i.value(); + } else { + ++i; + } + } + + return -1; +} + +Downloader* DownloadManager::downloaderForUrl(const QUrl& url) { + QHash::const_iterator i = _downloaderHash.constBegin(); + while (i != _downloaderHash.constEnd()) { + if (i.key()->getUrl() == url) { + return i.key(); + } else { + ++i; + } + } + + return NULL; +} diff --git a/stack-manager/src/DownloadManager.h b/stack-manager/src/DownloadManager.h new file mode 100644 index 0000000000..1cef0ae118 --- /dev/null +++ b/stack-manager/src/DownloadManager.h @@ -0,0 +1,52 @@ +// +// DownloadManager.h +// StackManagerQt/src +// +// Created by Mohammed Nafees on 07/09/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_DownloadManager_h +#define hifi_DownloadManager_h + +#include +#include +#include +#include +#include + +#include "Downloader.h" + +class DownloadManager : public QWidget { + Q_OBJECT +public: + DownloadManager(QNetworkAccessManager* manager, QWidget* parent = 0); + ~DownloadManager(); + + void downloadFile(const QUrl& url); + +private slots: + void onDownloadStarted(Downloader* downloader, const QUrl& url); + void onDownloadCompleted(const QUrl& url); + void onDownloadProgress(const QUrl& url, int percentage); + void onDownloadFailed(const QUrl& url); + void onInstallingFiles(const QUrl& url); + void onFilesSuccessfullyInstalled(const QUrl& url); + void onFilesInstallationFailed(const QUrl& url); + +protected: + void closeEvent(QCloseEvent*); + +signals: + void fileSuccessfullyInstalled(const QUrl& url); + +private: + QTableWidget* _table; + QNetworkAccessManager* _manager; + QHash _downloaderHash; + + int downloaderRowIndexForUrl(const QUrl& url); + Downloader* downloaderForUrl(const QUrl& url); +}; + +#endif diff --git a/stack-manager/src/Downloader.cpp b/stack-manager/src/Downloader.cpp new file mode 100644 index 0000000000..db3b397b96 --- /dev/null +++ b/stack-manager/src/Downloader.cpp @@ -0,0 +1,133 @@ +// +// Downloader.cpp +// StackManagerQt/src +// +// Created by Mohammed Nafees on 07/09/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "Downloader.h" +#include "GlobalData.h" + +#include +#include + +#include +#include +#include +#include +#include + +Downloader::Downloader(const QUrl& url, QObject* parent) : + QObject(parent) +{ + _url = url; +} + +void Downloader::start(QNetworkAccessManager* manager) { + qDebug() << "Downloader::start() for URL - " << _url; + QNetworkRequest req(_url); + QNetworkReply* reply = manager->get(req); + emit downloadStarted(this, _url); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(error(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(downloadProgress(qint64,qint64))); + connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); +} + +void Downloader::error(QNetworkReply::NetworkError error) { + QNetworkReply* reply = qobject_cast(sender()); + qDebug() << reply->errorString(); + reply->deleteLater(); +} + +void Downloader::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + int percentage = bytesReceived*100/bytesTotal; + emit downloadProgress(_url, percentage); +} + +void Downloader::downloadFinished() { + qDebug() << "Downloader::downloadFinished() for URL - " << _url; + QNetworkReply* reply = qobject_cast(sender()); + if (reply->error() != QNetworkReply::NoError) { + qDebug() << reply->errorString(); + emit downloadFailed(_url); + return; + } + emit downloadCompleted(_url); + + QString fileName = QFileInfo(_url.toString()).fileName(); + QString fileDir = GlobalData::getInstance().getClientsLaunchPath(); + QString filePath = fileDir + fileName; + + QFile file(filePath); + + // remove file if already exists + if (file.exists()) { + file.remove(); + } + + if (file.open(QIODevice::WriteOnly)) { + if (fileName == "assignment-client" || fileName == "assignment-client.exe" || + fileName == "domain-server" || fileName == "domain-server.exe") { + file.setPermissions(QFile::ExeOwner | QFile::ReadOwner | QFile::WriteOwner); + } else { + file.setPermissions(QFile::ReadOwner | QFile::WriteOwner); + } + emit installingFiles(_url); + file.write(reply->readAll()); + bool error = false; + file.close(); + + if (fileName.endsWith(".zip")) { // we need to unzip the file now + QuaZip zip(QFileInfo(file).absoluteFilePath()); + if (zip.open(QuaZip::mdUnzip)) { + QuaZipFile zipFile(&zip); + for(bool f = zip.goToFirstFile(); f; f = zip.goToNextFile()) { + if (zipFile.open(QIODevice::ReadOnly)) { + QFile newFile(QDir::toNativeSeparators(fileDir + "/" + zipFile.getActualFileName())); + if (zipFile.getActualFileName().endsWith("/")) { + QDir().mkpath(QFileInfo(newFile).absolutePath()); + zipFile.close(); + continue; + } + + // remove file if already exists + if (newFile.exists()) { + newFile.remove(); + } + + if (newFile.open(QIODevice::WriteOnly)) { + newFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner); + newFile.write(zipFile.readAll()); + newFile.close(); + } else { + error = true; + qDebug() << "Could not open archive file for writing: " << zip.getCurrentFileName(); + emit filesInstallationFailed(_url); + break; + } + } else { + error = true; + qDebug() << "Could not open archive file: " << zip.getCurrentFileName(); + emit filesInstallationFailed(_url); + break; + } + zipFile.close(); + } + zip.close(); + } else { + error = true; + emit filesInstallationFailed(_url); + qDebug() << "Could not open zip file for extraction."; + } + } + if (!error) + emit filesSuccessfullyInstalled(_url); + } else { + emit filesInstallationFailed(_url); + qDebug() << "Could not open file: " << filePath; + } + reply->deleteLater(); +} + + diff --git a/stack-manager/src/Downloader.h b/stack-manager/src/Downloader.h new file mode 100644 index 0000000000..f5b02214e0 --- /dev/null +++ b/stack-manager/src/Downloader.h @@ -0,0 +1,45 @@ +// +// Downloader.h +// StackManagerQt/src +// +// Created by Mohammed Nafees on 07/09/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_Downloader_h +#define hifi_Downloader_h + +#include +#include +#include +#include + +class Downloader : public QObject +{ + Q_OBJECT +public: + explicit Downloader(const QUrl& url, QObject* parent = 0); + + const QUrl& getUrl() { return _url; } + + void start(QNetworkAccessManager* manager); + +private slots: + void error(QNetworkReply::NetworkError error); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadFinished(); + +signals: + void downloadStarted(Downloader* downloader, const QUrl& url); + void downloadCompleted(const QUrl& url); + void downloadProgress(const QUrl& url, int percentage); + void downloadFailed(const QUrl& url); + void installingFiles(const QUrl& url); + void filesSuccessfullyInstalled(const QUrl& url); + void filesInstallationFailed(const QUrl& url); + +private: + QUrl _url; +}; + +#endif diff --git a/stack-manager/src/GlobalData.cpp b/stack-manager/src/GlobalData.cpp new file mode 100644 index 0000000000..ecc5ed520d --- /dev/null +++ b/stack-manager/src/GlobalData.cpp @@ -0,0 +1,84 @@ +// +// GlobalData.cpp +// StackManagerQt/src +// +// Created by Mohammed Nafees on 6/25/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "GlobalData.h" +#include "StackManagerVersion.h" + +#include +#include +#include +#include + +GlobalData& GlobalData::getInstance() { + static GlobalData staticInstance; + return staticInstance; +} + +GlobalData::GlobalData() { + QString urlBase = URL_BASE; +#if defined Q_OS_OSX + _platform = "mac"; +#elif defined Q_OS_WIN32 + _platform = "win"; +#elif defined Q_OS_LINUX + _platform = "linux"; +#endif + + _resourcePath = "resources/"; + _assignmentClientExecutable = "assignment-client"; + _domainServerExecutable = "domain-server"; + QString applicationSupportDirectory = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + if (PR_BUILD) { + applicationSupportDirectory += "/pr-binaries"; + } + + _clientsLaunchPath = QDir::toNativeSeparators(applicationSupportDirectory + "/"); + _clientsResourcePath = QDir::toNativeSeparators(applicationSupportDirectory + "/" + _resourcePath); + + _assignmentClientExecutablePath = QDir::toNativeSeparators(_clientsLaunchPath + _assignmentClientExecutable); + if (_platform == "win") { + _assignmentClientExecutablePath.append(".exe"); + } + _domainServerExecutablePath = QDir::toNativeSeparators(_clientsLaunchPath + _domainServerExecutable); + if (_platform == "win") { + _domainServerExecutablePath.append(".exe"); + } + + _requirementsURL = urlBase + "/binaries/" + _platform + "/requirements/requirements.zip"; + _requirementsZipPath = _clientsLaunchPath + "requirements.zip"; + _requirementsMD5URL = urlBase + "/binaries/" + _platform + "/requirements/requirements.md5"; + _assignmentClientURL = urlBase + "/binaries/" + _platform + "/assignment-client" + (_platform == "win" ? "/assignment-client.exe" : "/assignment-client"); + _domainServerResourcesURL = urlBase + "/binaries/" + _platform + "/domain-server/resources.zip"; + _domainServerResourcesZipPath = _clientsLaunchPath + "resources.zip"; + _domainServerResourcesMD5URL = urlBase + "/binaries/" + _platform + "/domain-server/resources.md5"; + _domainServerURL = urlBase + "/binaries/" + _platform + "/domain-server" + (_platform == "win" ? "/domain-server.exe" : "/domain-server"); + + _assignmentClientMD5URL = urlBase + "/binaries/" + _platform + "/assignment-client/assignment-client.md5"; + _domainServerMD5URL = urlBase + "/binaries/" + _platform + "/domain-server/domain-server.md5"; + + _defaultDomain = "localhost"; + _logsPath = QDir::toNativeSeparators(_clientsLaunchPath + "logs/"); + _availableAssignmentTypes.insert("audio-mixer", 0); + _availableAssignmentTypes.insert("avatar-mixer", 1); + _availableAssignmentTypes.insert("entity-server", 6); + + // allow user to override path to binaries so that they can run their own builds + _hifiBuildDirectory = ""; + + _domainServerBaseUrl = "http://localhost:40100"; +} + + +void GlobalData::setHifiBuildDirectory(const QString hifiBuildDirectory) { + _hifiBuildDirectory = hifiBuildDirectory; + _clientsLaunchPath = QDir::toNativeSeparators(_hifiBuildDirectory + "/assignment-client/"); + _clientsResourcePath = QDir::toNativeSeparators(_clientsLaunchPath + "/" + _resourcePath); + _logsPath = QDir::toNativeSeparators(_clientsLaunchPath + "logs/"); + _assignmentClientExecutablePath = QDir::toNativeSeparators(_clientsLaunchPath + _assignmentClientExecutable); + _domainServerExecutablePath = QDir::toNativeSeparators(_hifiBuildDirectory + "/domain-server/" + _domainServerExecutable); +} diff --git a/stack-manager/src/GlobalData.h b/stack-manager/src/GlobalData.h new file mode 100644 index 0000000000..58c9a93526 --- /dev/null +++ b/stack-manager/src/GlobalData.h @@ -0,0 +1,75 @@ +// +// GlobalData.h +// StackManagerQt/src +// +// Created by Mohammed Nafees on 6/25/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_GlobalData_h +#define hifi_GlobalData_h + +#include +#include + +class GlobalData { +public: + static GlobalData& getInstance(); + + QString getPlatform() { return _platform; } + QString getClientsLaunchPath() { return _clientsLaunchPath; } + QString getClientsResourcesPath() { return _clientsResourcePath; } + QString getAssignmentClientExecutablePath() { return _assignmentClientExecutablePath; } + QString getDomainServerExecutablePath() { return _domainServerExecutablePath; } + QString getRequirementsURL() { return _requirementsURL; } + QString getRequirementsZipPath() { return _requirementsZipPath; } + QString getRequirementsMD5URL() { return _requirementsMD5URL; } + QString getAssignmentClientURL() { return _assignmentClientURL; } + QString getAssignmentClientMD5URL() { return _assignmentClientMD5URL; } + QString getDomainServerURL() { return _domainServerURL; } + QString getDomainServerResourcesURL() { return _domainServerResourcesURL; } + QString getDomainServerResourcesZipPath() { return _domainServerResourcesZipPath; } + QString getDomainServerResourcesMD5URL() { return _domainServerResourcesMD5URL; } + QString getDomainServerMD5URL() { return _domainServerMD5URL; } + QString getDefaultDomain() { return _defaultDomain; } + QString getLogsPath() { return _logsPath; } + QHash getAvailableAssignmentTypes() { return _availableAssignmentTypes; } + + void setHifiBuildDirectory(const QString hifiBuildDirectory); + bool isGetHifiBuildDirectorySet() { return _hifiBuildDirectory != ""; } + + void setDomainServerBaseUrl(const QString domainServerBaseUrl) { _domainServerBaseUrl = domainServerBaseUrl; } + QString getDomainServerBaseUrl() { return _domainServerBaseUrl; } + +private: + GlobalData(); + + QString _platform; + QString _clientsLaunchPath; + QString _clientsResourcePath; + QString _assignmentClientExecutablePath; + QString _domainServerExecutablePath; + QString _requirementsURL; + QString _requirementsZipPath; + QString _requirementsMD5URL; + QString _assignmentClientURL; + QString _assignmentClientMD5URL; + QString _domainServerURL; + QString _domainServerResourcesURL; + QString _domainServerResourcesZipPath; + QString _domainServerResourcesMD5URL; + QString _domainServerMD5URL; + QString _defaultDomain; + QString _logsPath; + QString _hifiBuildDirectory; + + QString _resourcePath; + QString _assignmentClientExecutable; + QString _domainServerExecutable; + + QHash _availableAssignmentTypes; + + QString _domainServerBaseUrl; +}; + +#endif diff --git a/stack-manager/src/StackManagerVersion.h.in b/stack-manager/src/StackManagerVersion.h.in new file mode 100644 index 0000000000..402e19a056 --- /dev/null +++ b/stack-manager/src/StackManagerVersion.h.in @@ -0,0 +1,16 @@ +// +// StackManagerVersion.h +// StackManagerQt +// +// Created by Kai Ludwig on 02/16/15. +// Copyright 2015 High Fidelity, Inc. +// +// Declaration of version and build data +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +const QString BUILD_VERSION = "@BUILD_SEQ@"; +const QString URL_BASE = "@BASE_URL@"; +const bool PR_BUILD = @PR_BUILD@; diff --git a/stack-manager/src/main.cpp b/stack-manager/src/main.cpp new file mode 100644 index 0000000000..b5b715c75a --- /dev/null +++ b/stack-manager/src/main.cpp @@ -0,0 +1,15 @@ +// +// main.cpp +// StackManagerQt/src +// +// Created by Mohammed Nafees on 06/27/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "AppDelegate.h" + +int main(int argc, char* argv[]) +{ + AppDelegate app(argc, argv); + return app.exec(); +} diff --git a/stack-manager/src/resources.qrc b/stack-manager/src/resources.qrc new file mode 100644 index 0000000000..508edcc728 --- /dev/null +++ b/stack-manager/src/resources.qrc @@ -0,0 +1,9 @@ + + + ../assets/logo-larger.png + ../assets/assignment-run.svg + ../assets/assignment-stop.svg + ../assets/server-start.svg + ../assets/server-stop.svg + + diff --git a/stack-manager/src/ui/AssignmentWidget.cpp b/stack-manager/src/ui/AssignmentWidget.cpp new file mode 100644 index 0000000000..51fe067eb3 --- /dev/null +++ b/stack-manager/src/ui/AssignmentWidget.cpp @@ -0,0 +1,61 @@ +// +// AssignmentWidget.cpp +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 10/18/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "AssignmentWidget.h" + +#include +#include + +#include "AppDelegate.h" + +AssignmentWidget::AssignmentWidget(QWidget* parent) : + QWidget(parent), + _processID(0), + _isRunning(false), + _scriptID(QUuid::createUuid()) +{ + setFont(QFont("sans-serif")); + + QHBoxLayout* layout = new QHBoxLayout; + + _runButton = new SvgButton(this); + _runButton->setFixedSize(59, 32); + _runButton->setSvgImage(":/assignment-run.svg"); + _runButton->setCheckable(true); + _runButton->setChecked(false); + + QLabel* label = new QLabel; + label->setText("Pool ID"); + + _poolIDLineEdit = new QLineEdit; + _poolIDLineEdit->setPlaceholderText("Optional"); + + layout->addWidget(_runButton, 5); + layout->addWidget(label); + layout->addWidget(_poolIDLineEdit); + + setLayout(layout); + + connect(_runButton, &QPushButton::clicked, this, &AssignmentWidget::toggleRunningState); +} + +void AssignmentWidget::toggleRunningState() { + if (_isRunning && _processID > 0) { + AppDelegate::getInstance()->stopScriptedAssignment(_scriptID); + _runButton->setSvgImage(":/assignment-run.svg"); + update(); + _poolIDLineEdit->setEnabled(true); + _isRunning = false; + } else { + _processID = AppDelegate::getInstance()->startScriptedAssignment(_scriptID, _poolIDLineEdit->text()); + _runButton->setSvgImage(":/assignment-stop.svg"); + update(); + _poolIDLineEdit->setEnabled(false); + _isRunning = true; + } +} diff --git a/stack-manager/src/ui/AssignmentWidget.h b/stack-manager/src/ui/AssignmentWidget.h new file mode 100644 index 0000000000..3e52d7f1af --- /dev/null +++ b/stack-manager/src/ui/AssignmentWidget.h @@ -0,0 +1,37 @@ +// +// AssignmentWidget.h +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 10/18/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_AssignmentWidget_h +#define hifi_AssignmentWidget_h + +#include +#include +#include + +#include "SvgButton.h" + +class AssignmentWidget : public QWidget +{ + Q_OBJECT +public: + AssignmentWidget(QWidget* parent = 0); + + bool isRunning() { return _isRunning; } + +public slots: + void toggleRunningState(); + +private: + int _processID; + bool _isRunning; + SvgButton* _runButton; + QLineEdit* _poolIDLineEdit; + QUuid _scriptID; +}; + +#endif diff --git a/stack-manager/src/ui/LogViewer.cpp b/stack-manager/src/ui/LogViewer.cpp new file mode 100644 index 0000000000..12b6f33f88 --- /dev/null +++ b/stack-manager/src/ui/LogViewer.cpp @@ -0,0 +1,63 @@ +// +// LogViewer.cpp +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 07/10/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "LogViewer.h" +#include "GlobalData.h" + +#include +#include +#include + +LogViewer::LogViewer(QWidget* parent) : + QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout; + QLabel* outputLabel = new QLabel; + outputLabel->setText("Standard Output:"); + outputLabel->setStyleSheet("font-size: 13pt;"); + + layout->addWidget(outputLabel); + + _outputView = new QTextEdit; + _outputView->setUndoRedoEnabled(false); + _outputView->setReadOnly(true); + + layout->addWidget(_outputView); + + QLabel* errorLabel = new QLabel; + errorLabel->setText("Standard Error:"); + errorLabel->setStyleSheet("font-size: 13pt;"); + + layout->addWidget(errorLabel); + + _errorView = new QTextEdit; + _errorView->setUndoRedoEnabled(false); + _errorView->setReadOnly(true); + + layout->addWidget(_errorView); + setLayout(layout); +} + +void LogViewer::clear() { + _outputView->clear(); + _errorView->clear(); +} + +void LogViewer::appendStandardOutput(const QString& output) { + QTextCursor cursor = _outputView->textCursor(); + cursor.movePosition(QTextCursor::End); + cursor.insertText(output); + _outputView->ensureCursorVisible(); +} + +void LogViewer::appendStandardError(const QString& error) { + QTextCursor cursor = _errorView->textCursor(); + cursor.movePosition(QTextCursor::End); + cursor.insertText(error); + _errorView->ensureCursorVisible(); +} diff --git a/stack-manager/src/ui/LogViewer.h b/stack-manager/src/ui/LogViewer.h new file mode 100644 index 0000000000..b4321cc886 --- /dev/null +++ b/stack-manager/src/ui/LogViewer.h @@ -0,0 +1,31 @@ +// +// LogViewer.h +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 07/10/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_LogViewer_h +#define hifi_LogViewer_h + +#include +#include + +class LogViewer : public QWidget +{ + Q_OBJECT +public: + explicit LogViewer(QWidget* parent = 0); + + void clear(); + + void appendStandardOutput(const QString& output); + void appendStandardError(const QString& error); + +private: + QTextEdit* _outputView; + QTextEdit* _errorView; +}; + +#endif diff --git a/stack-manager/src/ui/MainWindow.cpp b/stack-manager/src/ui/MainWindow.cpp new file mode 100644 index 0000000000..59551933f4 --- /dev/null +++ b/stack-manager/src/ui/MainWindow.cpp @@ -0,0 +1,329 @@ +// +// MainWindow.cpp +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 10/17/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "MainWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AppDelegate.h" +#include "AssignmentWidget.h" +#include "GlobalData.h" +#include "StackManagerVersion.h" + +const int GLOBAL_X_PADDING = 55; +const int TOP_Y_PADDING = 25; +const int REQUIREMENTS_TEXT_TOP_MARGIN = 19; +//const int HORIZONTAL_RULE_TOP_MARGIN = 25; + +const int BUTTON_PADDING_FIX = -5; + +//const int ASSIGNMENT_LAYOUT_RESIZE_FACTOR = 56; +//const int ASSIGNMENT_LAYOUT_WIDGET_STRETCH = 0; + +const QColor lightGrayColor = QColor(205, 205, 205); +const QColor darkGrayColor = QColor(84, 84, 84); +const QColor redColor = QColor(189, 54, 78); +const QColor greenColor = QColor(3, 150, 126); + +const QString SHARE_BUTTON_COPY_LINK_TEXT = "Copy link"; + +MainWindow::MainWindow() : + QWidget(), + _domainServerRunning(false), + _startServerButton(NULL), + _stopServerButton(NULL), + _serverAddressLabel(NULL), + _viewLogsButton(NULL), + _settingsButton(NULL), + _copyLinkButton(NULL), + _contentSetButton(NULL), + _logsWidget(NULL), + _localHttpPortSharedMem(NULL) +{ + // Set build version + QCoreApplication::setApplicationVersion(BUILD_VERSION); + + setWindowTitle("High Fidelity Stack Manager (build " + QCoreApplication::applicationVersion() + ")"); + const int WINDOW_FIXED_WIDTH = 640; + const int WINDOW_INITIAL_HEIGHT = 170; + + if (GlobalData::getInstance().getPlatform() == "win") { + const int windowsYCoord = 30; + setGeometry(qApp->desktop()->availableGeometry().width() / 2 - WINDOW_FIXED_WIDTH / 2, windowsYCoord, + WINDOW_FIXED_WIDTH, WINDOW_INITIAL_HEIGHT); + } else if (GlobalData::getInstance().getPlatform() == "linux") { + const int linuxYCoord = 30; + setGeometry(qApp->desktop()->availableGeometry().width() / 2 - WINDOW_FIXED_WIDTH / 2, linuxYCoord, + WINDOW_FIXED_WIDTH, WINDOW_INITIAL_HEIGHT + 40); + } else { + const int unixYCoord = 0; + setGeometry(qApp->desktop()->availableGeometry().width() / 2 - WINDOW_FIXED_WIDTH / 2, unixYCoord, + WINDOW_FIXED_WIDTH, WINDOW_INITIAL_HEIGHT); + } + setFixedWidth(WINDOW_FIXED_WIDTH); + setMaximumHeight(qApp->desktop()->availableGeometry().height()); + setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint); + setMouseTracking(true); + setStyleSheet("font-family: 'Helvetica', 'Arial', 'sans-serif';"); + + const int SERVER_BUTTON_HEIGHT = 47; + + _startServerButton = new SvgButton(this); + + QPixmap scaledStart(":/server-start.svg"); + scaledStart.scaledToHeight(SERVER_BUTTON_HEIGHT); + + _startServerButton->setGeometry((width() / 2.0f) - (scaledStart.width() / 2.0f), + TOP_Y_PADDING, + scaledStart.width(), + scaledStart.height()); + _startServerButton->setSvgImage(":/server-start.svg"); + + _stopServerButton = new SvgButton(this); + _stopServerButton->setSvgImage(":/server-stop.svg"); + _stopServerButton->setGeometry(GLOBAL_X_PADDING, TOP_Y_PADDING, + scaledStart.width(), scaledStart.height()); + + const int SERVER_ADDRESS_LABEL_LEFT_MARGIN = 20; + const int SERVER_ADDRESS_LABEL_TOP_MARGIN = 17; + _serverAddressLabel = new QLabel(this); + _serverAddressLabel->move(_stopServerButton->geometry().right() + SERVER_ADDRESS_LABEL_LEFT_MARGIN, + TOP_Y_PADDING + SERVER_ADDRESS_LABEL_TOP_MARGIN); + _serverAddressLabel->setOpenExternalLinks(true); + + const int SECONDARY_BUTTON_ROW_TOP_MARGIN = 10; + + int secondaryButtonY = _stopServerButton->geometry().bottom() + SECONDARY_BUTTON_ROW_TOP_MARGIN; + + _viewLogsButton = new QPushButton("View logs", this); + _viewLogsButton->adjustSize(); + _viewLogsButton->setGeometry(GLOBAL_X_PADDING + BUTTON_PADDING_FIX, secondaryButtonY, + _viewLogsButton->width(), _viewLogsButton->height()); + + _settingsButton = new QPushButton("Settings", this); + _settingsButton->adjustSize(); + _settingsButton->setGeometry(_viewLogsButton->geometry().right(), secondaryButtonY, + _settingsButton->width(), _settingsButton->height()); + + _copyLinkButton = new QPushButton(SHARE_BUTTON_COPY_LINK_TEXT, this); + _copyLinkButton->adjustSize(); + _copyLinkButton->setGeometry(_settingsButton->geometry().right(), secondaryButtonY, + _copyLinkButton->width(), _copyLinkButton->height()); + + // add the drop down for content sets + _contentSetButton = new QPushButton("Get content set", this); + _contentSetButton->adjustSize(); + _contentSetButton->setGeometry(_copyLinkButton->geometry().right(), secondaryButtonY, + _contentSetButton->width(), _contentSetButton->height()); + + const QSize logsWidgetSize = QSize(500, 500); + _logsWidget = new QTabWidget; + _logsWidget->setUsesScrollButtons(true); + _logsWidget->setElideMode(Qt::ElideMiddle); + _logsWidget->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); + _logsWidget->resize(logsWidgetSize); + + connect(_startServerButton, &QPushButton::clicked, this, &MainWindow::toggleDomainServerButton); + connect(_stopServerButton, &QPushButton::clicked, this, &MainWindow::toggleDomainServerButton); + connect(_copyLinkButton, &QPushButton::clicked, this, &MainWindow::handleCopyLinkButton); + connect(_contentSetButton, &QPushButton::clicked, this, &MainWindow::showContentSetPage); + connect(_viewLogsButton, &QPushButton::clicked, _logsWidget, &QTabWidget::show); + connect(_settingsButton, &QPushButton::clicked, this, &MainWindow::openSettings); + + AppDelegate* app = AppDelegate::getInstance(); + // update the current server address label and change it if the AppDelegate says the address has changed + updateServerAddressLabel(); + connect(app, &AppDelegate::domainAddressChanged, this, &MainWindow::updateServerAddressLabel); + connect(app, &AppDelegate::domainAddressChanged, this, &MainWindow::updateServerBaseUrl); + + // handle response for content set download + connect(app, &AppDelegate::contentSetDownloadResponse, this, &MainWindow::handleContentSetDownloadResponse); + + // handle response for index path change + connect(app, &AppDelegate::indexPathChangeResponse, this, &MainWindow::handleIndexPathChangeResponse); + + // handle stack state change + connect(app, &AppDelegate::stackStateChanged, this, &MainWindow::toggleContent); + + toggleContent(false); + +} + +void MainWindow::updateServerAddressLabel() { + AppDelegate* app = AppDelegate::getInstance(); + + _serverAddressLabel->setText("

Accessible at: " + "getServerAddress() + "\">" + "" + app->getServerAddress() + + "

"); + _serverAddressLabel->adjustSize(); +} + +void MainWindow::updateServerBaseUrl() { + quint16 localPort; + + if (getLocalServerPortFromSharedMemory("domain-server.local-http-port", _localHttpPortSharedMem, localPort)) { + GlobalData::getInstance().setDomainServerBaseUrl(QString("http://localhost:") + QString::number(localPort)); + } +} + + +void MainWindow::handleCopyLinkButton() { + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(AppDelegate::getInstance()->getServerAddress()); +} + +void MainWindow::showContentSetPage() { + const QString CONTENT_SET_HTML_URL = "http://hifi-public.s3.amazonaws.com/content-sets/content-sets.html"; + + // show a QWebView for the content set page + QWebView* contentSetWebView = new QWebView(); + contentSetWebView->setUrl(CONTENT_SET_HTML_URL); + + // have the widget delete on close + contentSetWebView->setAttribute(Qt::WA_DeleteOnClose); + + // setup the page viewport to be the right size + const QSize CONTENT_SET_VIEWPORT_SIZE = QSize(800, 480); + contentSetWebView->resize(CONTENT_SET_VIEWPORT_SIZE); + + // have our app delegate handle a click on one of the content sets + contentSetWebView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); + connect(contentSetWebView->page(), &QWebPage::linkClicked, AppDelegate::getInstance(), &AppDelegate::downloadContentSet); + connect(contentSetWebView->page(), &QWebPage::linkClicked, contentSetWebView, &QWebView::close); + + contentSetWebView->show(); +} + +void MainWindow::handleContentSetDownloadResponse(bool wasSuccessful) { + if (wasSuccessful) { + QMessageBox::information(this, "New content set", + "Your new content set has been downloaded and your assignment-clients have been restarted."); + } else { + QMessageBox::information(this, "Error", "There was a problem downloading that content set. Please try again!"); + } +} + +void MainWindow::handleIndexPathChangeResponse(bool wasSuccessful) { + if (!wasSuccessful) { + QString errorMessage = "The content set was downloaded successfully but there was a problem changing your \ + domain-server index path.\n\nIf you want users to jump to the new content set when they come to your domain \ + please try and re-download the content set."; + QMessageBox::information(this, "Error", errorMessage); + } +} + +void MainWindow::setRequirementsLastChecked(const QString& lastCheckedDateTime) { + _requirementsLastCheckedDateTime = lastCheckedDateTime; +} + +void MainWindow::setUpdateNotification(const QString& updateNotification) { + _updateNotification = updateNotification; +} + +void MainWindow::toggleContent(bool isRunning) { + _stopServerButton->setVisible(isRunning); + _startServerButton->setVisible(!isRunning); + _domainServerRunning = isRunning; + _serverAddressLabel->setVisible(isRunning); + _viewLogsButton->setVisible(isRunning); + _settingsButton->setVisible(isRunning); + _copyLinkButton->setVisible(isRunning); + _contentSetButton->setVisible(isRunning); + update(); +} + +void MainWindow::paintEvent(QPaintEvent *) { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QFont font("Helvetica"); + font.insertSubstitutions("Helvetica", QStringList() << "Arial" << "sans-serif"); + + int currentY = (_domainServerRunning ? _viewLogsButton->geometry().bottom() : _startServerButton->geometry().bottom()) + + REQUIREMENTS_TEXT_TOP_MARGIN; + + if (!_updateNotification.isEmpty()) { + font.setBold(true); + font.setUnderline(false); + if (GlobalData::getInstance().getPlatform() == "linux") { + font.setPointSize(14); + } + painter.setFont(font); + painter.setPen(redColor); + + QString updateNotificationString = ">>> " + _updateNotification + " <<<"; + float fontWidth = QFontMetrics(font).width(updateNotificationString) + GLOBAL_X_PADDING; + + painter.drawText(QRectF(_domainServerRunning ? ((width() - fontWidth) / 2.0f) : GLOBAL_X_PADDING, + currentY, + fontWidth, + QFontMetrics(font).height()), + updateNotificationString); + } + else if (!_requirementsLastCheckedDateTime.isEmpty()) { + font.setBold(false); + font.setUnderline(false); + if (GlobalData::getInstance().getPlatform() == "linux") { + font.setPointSize(14); + } + painter.setFont(font); + painter.setPen(darkGrayColor); + + QString requirementsString = "Requirements are up to date as of " + _requirementsLastCheckedDateTime; + float fontWidth = QFontMetrics(font).width(requirementsString); + + painter.drawText(QRectF(_domainServerRunning ? GLOBAL_X_PADDING : ((width() - fontWidth)/ 2.0f), + currentY, + fontWidth, + QFontMetrics(font).height()), + "Requirements are up to date as of " + _requirementsLastCheckedDateTime); + } +} + +void MainWindow::toggleDomainServerButton() { + AppDelegate::getInstance()->toggleStack(!_domainServerRunning); +} + +void MainWindow::openSettings() { + QDesktopServices::openUrl(QUrl(GlobalData::getInstance().getDomainServerBaseUrl() + "/settings/")); +} + + +// XXX this code is duplicate of LimitedNodeList::getLocalServerPortFromSharedMemory +bool MainWindow::getLocalServerPortFromSharedMemory(const QString key, QSharedMemory*& sharedMem, quint16& localPort) { + if (!sharedMem) { + sharedMem = new QSharedMemory(key, this); + + if (!sharedMem->attach(QSharedMemory::ReadOnly)) { + qWarning() << "Could not attach to shared memory at key" << key; + } + } + + if (sharedMem->isAttached()) { + sharedMem->lock(); + memcpy(&localPort, sharedMem->data(), sizeof(localPort)); + sharedMem->unlock(); + return true; + } + + return false; +} diff --git a/stack-manager/src/ui/MainWindow.h b/stack-manager/src/ui/MainWindow.h new file mode 100644 index 0000000000..6b6669ce3a --- /dev/null +++ b/stack-manager/src/ui/MainWindow.h @@ -0,0 +1,67 @@ +// +// MainWindow.h +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 10/17/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_MainWindow_h +#define hifi_MainWindow_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "SvgButton.h" + +class MainWindow : public QWidget { + Q_OBJECT +public: + MainWindow(); + + void setRequirementsLastChecked(const QString& lastCheckedDateTime); + void setUpdateNotification(const QString& updateNotification); + QTabWidget* getLogsWidget() { return _logsWidget; } + bool getLocalServerPortFromSharedMemory(const QString key, QSharedMemory*& sharedMem, quint16& localPort); + +protected: + virtual void paintEvent(QPaintEvent*); + +private slots: + void toggleDomainServerButton(); + void openSettings(); + void updateServerAddressLabel(); + void updateServerBaseUrl(); + void handleCopyLinkButton(); + void showContentSetPage(); + + void handleContentSetDownloadResponse(bool wasSuccessful); + void handleIndexPathChangeResponse(bool wasSuccessful); +private: + void toggleContent(bool isRunning); + + bool _domainServerRunning; + + QString _requirementsLastCheckedDateTime; + QString _updateNotification; + SvgButton* _startServerButton; + SvgButton* _stopServerButton; + QLabel* _serverAddressLabel; + QPushButton* _viewLogsButton; + QPushButton* _settingsButton; + QPushButton* _copyLinkButton; + QPushButton* _contentSetButton; + QTabWidget* _logsWidget; + + QSharedMemory* _localHttpPortSharedMem; // memory shared with domain server +}; + +#endif diff --git a/stack-manager/src/ui/SvgButton.cpp b/stack-manager/src/ui/SvgButton.cpp new file mode 100644 index 0000000000..0d646ff0d1 --- /dev/null +++ b/stack-manager/src/ui/SvgButton.cpp @@ -0,0 +1,32 @@ +// +// SvgButton.cpp +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 10/20/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#include "SvgButton.h" + +#include +#include +#include + +SvgButton::SvgButton(QWidget* parent) : + QAbstractButton(parent) +{ +} + +void SvgButton::enterEvent(QEvent*) { + setCursor(QCursor(Qt::PointingHandCursor)); +} + +void SvgButton::setSvgImage(const QString& svg) { + _svgImage = svg; +} + +void SvgButton::paintEvent(QPaintEvent*) { + QPainter painter(this); + QSvgRenderer renderer(_svgImage); + renderer.render(&painter); +} diff --git a/stack-manager/src/ui/SvgButton.h b/stack-manager/src/ui/SvgButton.h new file mode 100644 index 0000000000..b4d44631ec --- /dev/null +++ b/stack-manager/src/ui/SvgButton.h @@ -0,0 +1,33 @@ +// +// SvgButton.h +// StackManagerQt/src/ui +// +// Created by Mohammed Nafees on 10/20/14. +// Copyright (c) 2014 High Fidelity. All rights reserved. +// + +#ifndef hifi_SvgButton_h +#define hifi_SvgButton_h + +#include +#include +#include + +class SvgButton : public QAbstractButton +{ + Q_OBJECT +public: + explicit SvgButton(QWidget* parent = 0); + + void setSvgImage(const QString& svg); + +protected: + virtual void enterEvent(QEvent*); + virtual void paintEvent(QPaintEvent*); + +private: + QString _svgImage; + +}; + +#endif diff --git a/stack-manager/windows_icon.rc b/stack-manager/windows_icon.rc new file mode 100644 index 0000000000..125e4c19db --- /dev/null +++ b/stack-manager/windows_icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "assets/icon.ico" \ No newline at end of file diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index a66e391f69..2a07f6f429 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -3,7 +3,7 @@ macro (setup_testcase_dependencies) # link in the shared libraries link_hifi_libraries(shared animation gpu fbx model networking) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro () setup_hifi_testcase() diff --git a/tests/audio/CMakeLists.txt b/tests/audio/CMakeLists.txt index f7dd1d23b9..239bc41fd0 100644 --- a/tests/audio/CMakeLists.txt +++ b/tests/audio/CMakeLists.txt @@ -3,7 +3,7 @@ macro (SETUP_TESTCASE_DEPENDENCIES) # link in the shared libraries link_hifi_libraries(shared audio networking) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro () setup_hifi_testcase() diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index d1c8464dd5..cf1152da02 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -16,4 +16,4 @@ if (WIN32) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) endif() -copy_dlls_beside_windows_executable() \ No newline at end of file +package_libraries_for_deployment() \ No newline at end of file diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt index b2c7969386..81b8b86368 100644 --- a/tests/entities/CMakeLists.txt +++ b/tests/entities/CMakeLists.txt @@ -9,4 +9,4 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation environment) -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 2c9ee23c47..3d83c310cf 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -4,4 +4,4 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils) setup_hifi_project(Quick Gui OpenGL Script Widgets) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(networking gl gpu procedural shared fbx model model-networking animation script-engine render-utils ) -copy_dlls_beside_windows_executable() \ No newline at end of file +package_libraries_for_deployment() \ No newline at end of file diff --git a/tests/jitter/CMakeLists.txt b/tests/jitter/CMakeLists.txt index 98be42530c..c10961d687 100644 --- a/tests/jitter/CMakeLists.txt +++ b/tests/jitter/CMakeLists.txt @@ -4,7 +4,7 @@ macro (setup_testcase_dependencies) # link in the shared libraries link_hifi_libraries(shared networking) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro() setup_hifi_testcase() diff --git a/tests/networking/CMakeLists.txt b/tests/networking/CMakeLists.txt index efa744e4c9..03578ba01d 100644 --- a/tests/networking/CMakeLists.txt +++ b/tests/networking/CMakeLists.txt @@ -4,7 +4,7 @@ macro (setup_testcase_dependencies) # link in the shared libraries link_hifi_libraries(shared networking) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro () setup_hifi_testcase() diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt index e2a756105a..9f9315ba2b 100644 --- a/tests/octree/CMakeLists.txt +++ b/tests/octree/CMakeLists.txt @@ -4,7 +4,7 @@ macro (setup_testcase_dependencies) # link in the shared libraries link_hifi_libraries(shared octree gpu model fbx networking environment entities avatars audio animation script-engine physics) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro () setup_hifi_testcase(Script Network) diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index f789a7b2ba..cc3df5ea8e 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -3,7 +3,7 @@ macro (SETUP_TESTCASE_DEPENDENCIES) target_bullet() link_hifi_libraries(shared physics) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro () setup_hifi_testcase(Script) diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt index a523947f52..4e881fcbd9 100644 --- a/tests/recording/CMakeLists.txt +++ b/tests/recording/CMakeLists.txt @@ -3,7 +3,7 @@ set(TARGET_NAME recording-test) setup_hifi_project(Test) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(shared recording) -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() # FIXME convert to unit tests # Declare dependencies @@ -11,6 +11,6 @@ copy_dlls_beside_windows_executable() # # link in the shared libraries # link_hifi_libraries(shared recording) # -# copy_dlls_beside_windows_executable() +# package_libraries_for_deployment() #endmacro () #setup_hifi_testcase() diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt index 865db4dad5..25221a1e86 100644 --- a/tests/render-utils/CMakeLists.txt +++ b/tests/render-utils/CMakeLists.txt @@ -8,4 +8,4 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries(render-utils gl gpu shared) -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index fd58a5911f..3ba29c5626 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -17,4 +17,4 @@ include_directories("${PROJECT_BINARY_DIR}/../../libraries/render-utils/") include_directories("${PROJECT_BINARY_DIR}/../../libraries/entities-renderer/") include_directories("${PROJECT_BINARY_DIR}/../../libraries/model/") -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/tests/shared/CMakeLists.txt b/tests/shared/CMakeLists.txt index 7bddb4b2ed..740014ea75 100644 --- a/tests/shared/CMakeLists.txt +++ b/tests/shared/CMakeLists.txt @@ -5,7 +5,7 @@ macro (setup_testcase_dependencies) # link in the shared libraries link_hifi_libraries(shared) - copy_dlls_beside_windows_executable() + package_libraries_for_deployment() endmacro () setup_hifi_testcase() diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt index 82fc23e680..8fda001e14 100644 --- a/tests/ui/CMakeLists.txt +++ b/tests/ui/CMakeLists.txt @@ -13,4 +13,4 @@ endif() # link in the shared libraries link_hifi_libraries(shared networking gl gpu ui) -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/tools/mtc/CMakeLists.txt b/tools/mtc/CMakeLists.txt index 16c812673d..c7134a869f 100644 --- a/tools/mtc/CMakeLists.txt +++ b/tools/mtc/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME mtc) setup_hifi_project() -copy_dlls_beside_windows_executable() +package_libraries_for_deployment() diff --git a/tools/udt-test/CMakeLists.txt b/tools/udt-test/CMakeLists.txt index 648ef6f00c..f21e3e9aea 100644 --- a/tools/udt-test/CMakeLists.txt +++ b/tools/udt-test/CMakeLists.txt @@ -2,5 +2,4 @@ set(TARGET_NAME udt-test) setup_hifi_project() link_hifi_libraries(networking shared) - -copy_dlls_beside_windows_executable() \ No newline at end of file +package_libraries_for_deployment() \ No newline at end of file