From f3dd97cf6b827c2be7ca4e8aad004b551db0b572 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 2 Dec 2015 13:57:02 -0800 Subject: [PATCH 01/19] add a packaging command for darwin, ignore results --- console/.gitignore | 1 + console/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 console/.gitignore diff --git a/console/.gitignore b/console/.gitignore new file mode 100644 index 0000000000..6b85415b31 --- /dev/null +++ b/console/.gitignore @@ -0,0 +1 @@ +Console-darwin-x64/ diff --git a/console/package.json b/console/package.json index 270c9d26de..abca322f82 100755 --- a/console/package.json +++ b/console/package.json @@ -17,6 +17,7 @@ }, "main": "main.js", "scripts": { - "start": "electron ." + "start": "electron .", + "package-darwin": "electron-packager . Console --platform=darwin --arch=x64 --version=0.35.2" } } From fa4153d2e06d06cbc811fbcdb63310c581875f7f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 2 Dec 2015 14:15:04 -0800 Subject: [PATCH 02/19] add a custom target to package up the console --- CMakeLists.txt | 31 ++++++++++++++++--------------- console/CMakeLists.txt | 5 +++++ 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 console/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e95974383..c84c422ad6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ endif () if (POLICY CMP0043) cmake_policy(SET CMP0043 OLD) -endif () +endif () if (POLICY CMP0042) cmake_policy(SET CMP0042 OLD) @@ -26,7 +26,7 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets") project(hifi) add_definitions(-DGLM_FORCE_RADIANS) -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") if (WIN32) add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) @@ -70,7 +70,7 @@ if ((NOT MSVC12) AND (NOT MSVC14)) endif() endif () -if (APPLE) +if (APPLE) set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++0x") set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") @@ -80,16 +80,16 @@ if (NOT ANDROID_LIB_DIR) set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR}) endif () -if (ANDROID) +if (ANDROID) if (NOT ANDROID_QT_CMAKE_PREFIX_PATH) set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.4/android_armv7/lib/cmake) else () set(QT_CMAKE_PREFIX_PATH ${ANDROID_QT_CMAKE_PREFIX_PATH}) endif () - + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) - + if (ANDROID_LIB_DIR) list(APPEND CMAKE_FIND_ROOT_PATH ${ANDROID_LIB_DIR}) endif () @@ -114,34 +114,34 @@ get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH}) if (APPLE) - + exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION) string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION}) message(STATUS "Detected OS X version = ${OSX_VERSION}") - + set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") - + # set our OS X deployment target set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8) - + # find the SDK path for the desired SDK find_path( - _OSX_DESIRED_SDK_PATH - NAME MacOSX${OSX_SDK}.sdk + _OSX_DESIRED_SDK_PATH + NAME MacOSX${OSX_SDK}.sdk HINTS ${OSX_SDK_PATH} PATHS /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ ) - + if (NOT _OSX_DESIRED_SDK_PATH) message(STATUS "Could not find OS X ${OSX_SDK} SDK. Will fall back to default. If you want a specific SDK, please pass OSX_SDK and optionally OSX_SDK_PATH to CMake.") else () message(STATUS "Found OS X ${OSX_SDK} SDK at ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk") - + # set that as the SDK to use set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk) endif () - + endif () # Hide automoc folders (for IDEs) @@ -204,6 +204,7 @@ if (NOT ANDROID) set_target_properties(interface PROPERTIES FOLDER "Apps") add_subdirectory(stack-manager) set_target_properties(stack-manager PROPERTIES FOLDER "Apps") + add_subdirectory(console) add_subdirectory(tests) add_subdirectory(plugins) add_subdirectory(tools) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt new file mode 100644 index 0000000000..d4eb339dd4 --- /dev/null +++ b/console/CMakeLists.txt @@ -0,0 +1,5 @@ +# add a command to produce an executable for this platform +add_custom_target(package-console + COMMAND npm run-script package-darwin + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) From 370fc35eaf849e82ffea9fc1f074ae94d773b631 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 2 Dec 2015 15:41:17 -0800 Subject: [PATCH 03/19] add PACKAGING_TARGET_NAME variable --- console/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index d4eb339dd4..4cf96f2716 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -1,5 +1,7 @@ -# add a command to produce an executable for this platform -add_custom_target(package-console +set(PACKAGING_TARGET_NAME package-console) + +# add a target that when built will produce an executable of console for this platform +add_custom_target(${PACKAGING_TARGET_NAME} COMMAND npm run-script package-darwin WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) From 8423f2c9790266d5e9e8aad61b8527552fd0d7c5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 11:17:59 -0800 Subject: [PATCH 04/19] add commands for local debug and local release --- console/.gitignore | 1 + console/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/console/.gitignore b/console/.gitignore index 6b85415b31..c47c519a73 100644 --- a/console/.gitignore +++ b/console/.gitignore @@ -1 +1,2 @@ Console-darwin-x64/ +npm-debug.log diff --git a/console/package.json b/console/package.json index abca322f82..af9d962423 100755 --- a/console/package.json +++ b/console/package.json @@ -17,7 +17,8 @@ }, "main": "main.js", "scripts": { - "start": "electron .", + "start": "electron . --local-debug-builds", + "local-release": "electron . --local-release-builds", "package-darwin": "electron-packager . Console --platform=darwin --arch=x64 --version=0.35.2" } } From ea1c81ca0fd03809c9fdc0d1a92e694c3fb94974 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 11:40:28 -0800 Subject: [PATCH 05/19] add README and local binary cl options --- console/README.md | 25 +++++++++++++++++++++++++ console/main.js | 12 ++++++++++++ console/package.json | 3 +++ 3 files changed, 40 insertions(+) create mode 100644 console/README.md diff --git a/console/README.md b/console/README.md new file mode 100644 index 0000000000..528f9037ac --- /dev/null +++ b/console/README.md @@ -0,0 +1,25 @@ +### Console + +The High Fidelity Desktop Console, made with [Electron](http://electron.atom.io/). + +### Running Locally + +Make sure you have [Node.js](https://nodejs.org/en/) installed. Use the latest stable version. + +``` +npm install +npm start +``` + +To run, the console needs a build of Interface, domain-server, and assignment-client. + +The default, `npm start` tells the console to look for debug builds of those binaries in a build folder beside the console folder. + +If you want to use release builds use `npm run local-release`. + +### Packaging + +CMake produces a target `package-console` that will bundle up everything you need for the console on your platform. +It ensures that there are available builds for the domain-server, assignment-client, and Interface. Then it produces an executable for the console. + +Finally it copies all of the produced executables to a directory, ready for testing or packaging for deployment. diff --git a/console/main.js b/console/main.js index 5922f78a1e..0188bb11ab 100644 --- a/console/main.js +++ b/console/main.js @@ -31,6 +31,18 @@ app.on('window-all-closed', function() { } }); +// Check command line arguments to see how to find binaries +var argv = require('yargs'); + +if (argv.localDebugBuilds) { + // check in a dev folder structure for debug binaries +} else if (argv.localReleaseBuilds) { + // check in a dev folder structure for release binaries +} else { + // check beside the console application for the binaries +} + + // This method will be called when Electron has finished // initialization and is ready to create browser windows. app.on('ready', function() { diff --git a/console/package.json b/console/package.json index af9d962423..a6b4e9c9c2 100755 --- a/console/package.json +++ b/console/package.json @@ -20,5 +20,8 @@ "start": "electron . --local-debug-builds", "local-release": "electron . --local-release-builds", "package-darwin": "electron-packager . Console --platform=darwin --arch=x64 --version=0.35.2" + }, + "dependencies": { + "yargs": "^3.30.0" } } From a741208688540c5bccff5e600a7407bb05cfd968 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 11:42:52 -0800 Subject: [PATCH 06/19] some extra clarity in the README --- console/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/console/README.md b/console/README.md index 528f9037ac..1fda3583bf 100644 --- a/console/README.md +++ b/console/README.md @@ -11,15 +11,15 @@ npm install npm start ``` -To run, the console needs a build of Interface, domain-server, and assignment-client. +To run, the console needs to find a build of Interface, domain-server, and assignment-client. -The default, `npm start` tells the console to look for debug builds of those binaries in a build folder beside the console folder. +The command `npm start` tells the console to look for debug builds of those binaries in a build folder beside this console folder. -If you want to use release builds use `npm run local-release`. +To look for release builds in the build folder, you'll want `npm run local-release`. ### Packaging CMake produces a target `package-console` that will bundle up everything you need for the console on your platform. It ensures that there are available builds for the domain-server, assignment-client, and Interface. Then it produces an executable for the console. -Finally it copies all of the produced executables to a directory, ready for testing or packaging for deployment. +Finally it copies all of the produced executables to a directory, ready for testing or packaging for deployment. From c80e6f7da45795700eae846b27e71afae163ede7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 12:22:39 -0800 Subject: [PATCH 07/19] add initial local process search --- console/README.md | 4 +-- console/main.js | 68 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/console/README.md b/console/README.md index 1fda3583bf..129c784c74 100644 --- a/console/README.md +++ b/console/README.md @@ -13,9 +13,9 @@ npm start To run, the console needs to find a build of Interface, domain-server, and assignment-client. -The command `npm start` tells the console to look for debug builds of those binaries in a build folder beside this console folder. +The command `npm start` tells the console to look for builds of those binaries in a build folder beside this console folder. -To look for release builds in the build folder, you'll want `npm run local-release`. +On platforms with separate build folders for release and debug libraries `npm start` will choose the debug binaries. On those platforms if you prefer to use local release builds you'll want `npm run local-release`. ### Packaging diff --git a/console/main.js b/console/main.js index 0188bb11ab..deedc4af4f 100644 --- a/console/main.js +++ b/console/main.js @@ -24,25 +24,79 @@ var APP_ICON = 'resources/tray-icon.png'; // Quit when all windows are closed. app.on('window-all-closed', function() { +<<<<<<< HEAD // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform != 'darwin') { app.quit(); } +======= + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform != 'darwin') { + app.quit(); + } +>>>>>>> add initial local process search }); // Check command line arguments to see how to find binaries -var argv = require('yargs'); +var argv = require('yargs').argv; +var fs = require('fs'); -if (argv.localDebugBuilds) { - // check in a dev folder structure for debug binaries -} else if (argv.localReleaseBuilds) { - // check in a dev folder structure for release binaries -} else { - // check beside the console application for the binaries +function localProcessForBinary(name, preferRelease) { + var path = "../build/" + name + "/"; + + function processFromPath(name, path) { + function platformExtension() { + return process.platform == 'win32' ? ".exe" : "" + } + + try { + var fullPath = path + name + platformExtension(); + + var stats = fs.lstatSync(path + name + platformExtension()) + + if (stats.isFile()) { + console.log("Found " + name + " at " + fullPath); + return fullPath; + } + } catch (e) { + console.log("Executable with name " + name + " not found at path " + path); + } + + return null; + } + + // does the executable exist at this path already? + // if so assume we're on a platform that doesn't have Debug/Release + // folders and just use the discovered executable + var matchingProcess = processFromPath(name, path); + + if (matchingProcess == null) { + if (preferRelease) { + // check if we can find the executable in a Release folder below this path + matchingProcess = processFromPath(name, path + "Release/"); + } else { + // check if we can find the executable in a Debug folder below this path + matchingProcess = processFromPath(name, path + "Debug/"); + } + } + + return matchingProcess; } +if (argv.localDebugBuilds) { + // check in a dev folder structure for debug binaries + var interfaceProcess = localProcessForBinary("Interface"); + var dsProcess = localProcessForBinary("domain-server"); + var acProcess = localProcessForBinary("assignment-client"); +} else if (argv.localReleaseBuilds) { + // check in a dev folder structure for release binaries +} else { + // check beside the console application for the binaries +} + // This method will be called when Electron has finished // initialization and is ready to create browser windows. app.on('ready', function() { From 819562fd669c40b5f26a8168a35defd850669855 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:18:12 -0800 Subject: [PATCH 08/19] move the path finder to a separate file --- console/main.js | 60 +++++++----------------------------------- console/path-finder.js | 43 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 51 deletions(-) create mode 100644 console/path-finder.js diff --git a/console/main.js b/console/main.js index deedc4af4f..749c86954c 100644 --- a/console/main.js +++ b/console/main.js @@ -41,61 +41,19 @@ app.on('window-all-closed', function() { // Check command line arguments to see how to find binaries var argv = require('yargs').argv; -var fs = require('fs'); +var pathFinder = require('./path-finder.js'); -function localProcessForBinary(name, preferRelease) { - var path = "../build/" + name + "/"; +var interfacePath = null; +var dsPath = null; +var acPath = null; - function processFromPath(name, path) { - function platformExtension() { - return process.platform == 'win32' ? ".exe" : "" - } - - try { - var fullPath = path + name + platformExtension(); - - var stats = fs.lstatSync(path + name + platformExtension()) - - if (stats.isFile()) { - console.log("Found " + name + " at " + fullPath); - return fullPath; - } - } catch (e) { - console.log("Executable with name " + name + " not found at path " + path); - } - - return null; - } - - // does the executable exist at this path already? - // if so assume we're on a platform that doesn't have Debug/Release - // folders and just use the discovered executable - var matchingProcess = processFromPath(name, path); - - if (matchingProcess == null) { - if (preferRelease) { - // check if we can find the executable in a Release folder below this path - matchingProcess = processFromPath(name, path + "Release/"); - } else { - // check if we can find the executable in a Debug folder below this path - matchingProcess = processFromPath(name, path + "Debug/"); - } - } - - return matchingProcess; +if (argv.localDebugBuilds || argv.localReleaseBuilds) { + interfacePath = pathFinder.discoveredPath("Interface", argv.localReleaseBuilds); + dsPath = pathFinder.discoveredPath("domain-server", argv.localReleaseBuilds); + acPath = pathFinder.discoveredPath("assignment-client", argv.localReleaseBuilds); } - -if (argv.localDebugBuilds) { - // check in a dev folder structure for debug binaries - var interfaceProcess = localProcessForBinary("Interface"); - var dsProcess = localProcessForBinary("domain-server"); - var acProcess = localProcessForBinary("assignment-client"); -} else if (argv.localReleaseBuilds) { - // check in a dev folder structure for release binaries -} else { - // check beside the console application for the binaries -} +// if at this point any of the paths are null, we're missing something we wanted to find // This method will be called when Electron has finished // initialization and is ready to create browser windows. diff --git a/console/path-finder.js b/console/path-finder.js new file mode 100644 index 0000000000..504240dbd4 --- /dev/null +++ b/console/path-finder.js @@ -0,0 +1,43 @@ +var fs = require('fs'); + +exports.discoveredPath = function (name, preferRelease) { + var path = "../build/" + name + "/"; + + function binaryFromPath(name, path) { + function platformExtension() { + return process.platform == 'win32' ? ".exe" : "" + } + + try { + var fullPath = path + name + platformExtension(); + + var stats = fs.lstatSync(path + name + platformExtension()) + + if (stats.isFile()) { + console.log("Found " + name + " at " + fullPath); + return fullPath; + } + } catch (e) { + console.log("Executable with name " + name + " not found at path " + path); + } + + return null; + } + + // does the executable exist at this path already? + // if so assume we're on a platform that doesn't have Debug/Release + // folders and just use the discovered executable + var matchingBinary = binaryFromPath(name, path); + + if (matchingBinary == null) { + if (preferRelease) { + // check if we can find the executable in a Release folder below this path + matchingBinary = binaryFromPath(name, path + "Release/"); + } else { + // check if we can find the executable in a Debug folder below this path + matchingBinary = binaryFromPath(name, path + "Debug/"); + } + } + + return matchingBinary; +} From a0857fedda34fbe36f7d1c9e618d19dddcb4521e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:18:47 -0800 Subject: [PATCH 09/19] add a modules directory --- console/{ => modules}/path-finder.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename console/{ => modules}/path-finder.js (100%) diff --git a/console/path-finder.js b/console/modules/path-finder.js similarity index 100% rename from console/path-finder.js rename to console/modules/path-finder.js From 0c450d5515098d38d6902735ef77539ace80f93e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:19:18 -0800 Subject: [PATCH 10/19] fix path for path finder module --- console/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/main.js b/console/main.js index 749c86954c..b0c6412e15 100644 --- a/console/main.js +++ b/console/main.js @@ -41,7 +41,7 @@ app.on('window-all-closed', function() { // Check command line arguments to see how to find binaries var argv = require('yargs').argv; -var pathFinder = require('./path-finder.js'); +var pathFinder = require('./modules/path-finder.js'); var interfacePath = null; var dsPath = null; From 526082df95524465d6e2800c97880519a7375ffd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:37:27 -0800 Subject: [PATCH 11/19] handle finding interface.app on OS X --- console/modules/path-finder.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/console/modules/path-finder.js b/console/modules/path-finder.js index 504240dbd4..f336317a38 100644 --- a/console/modules/path-finder.js +++ b/console/modules/path-finder.js @@ -4,21 +4,32 @@ exports.discoveredPath = function (name, preferRelease) { var path = "../build/" + name + "/"; function binaryFromPath(name, path) { - function platformExtension() { - return process.platform == 'win32' ? ".exe" : "" + function platformExtension(name) { + if (name == "Interface") { + if (process.platform == "darwin") { + return ".app" + } else if (process.platform == "win32") { + return ".exe" + } else { + return "" + } + } else { + return process.platform == "win32" ? ".exe" : "" + } } + var extension = platformExtension(name); + var fullPath = path + name + extension; + try { - var fullPath = path + name + platformExtension(); + var stats = fs.lstatSync(fullPath); - var stats = fs.lstatSync(path + name + platformExtension()) - - if (stats.isFile()) { + if (stats.isFile() || (stats.isDirectory() && extension == ".app")) { console.log("Found " + name + " at " + fullPath); return fullPath; } } catch (e) { - console.log("Executable with name " + name + " not found at path " + path); + console.log("Executable with name " + name + " not found at path " + fullPath); } return null; From 14c3373a44a1d71e726728882acb46395e613549 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:46:28 -0800 Subject: [PATCH 12/19] make binary find fail a warning --- console/modules/path-finder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/modules/path-finder.js b/console/modules/path-finder.js index f336317a38..ffe8235ea1 100644 --- a/console/modules/path-finder.js +++ b/console/modules/path-finder.js @@ -29,7 +29,7 @@ exports.discoveredPath = function (name, preferRelease) { return fullPath; } } catch (e) { - console.log("Executable with name " + name + " not found at path " + fullPath); + console.warn("Executable with name " + name + " not found at path " + fullPath); } return null; From c2445f9f6eb58e555d76bc029dcb69c1b4a95d31 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:51:03 -0800 Subject: [PATCH 13/19] remove conflict added during rebase --- console/main.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/console/main.js b/console/main.js index b0c6412e15..788863f467 100644 --- a/console/main.js +++ b/console/main.js @@ -24,19 +24,11 @@ var APP_ICON = 'resources/tray-icon.png'; // Quit when all windows are closed. app.on('window-all-closed', function() { -<<<<<<< HEAD // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform != 'darwin') { app.quit(); } -======= - // On OS X it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform != 'darwin') { - app.quit(); - } ->>>>>>> add initial local process search }); // Check command line arguments to see how to find binaries @@ -54,6 +46,7 @@ if (argv.localDebugBuilds || argv.localReleaseBuilds) { } // if at this point any of the paths are null, we're missing something we wanted to find +// TODO: show an error for the binaries that couldn't be found // This method will be called when Electron has finished // initialization and is ready to create browser windows. From c01566bc04673f0fef37e42443c03978a04a22e1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:53:19 -0800 Subject: [PATCH 14/19] use discovered paths for processes --- console/main.js | 67 ++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/console/main.js b/console/main.js index 788863f467..163ea9e27b 100644 --- a/console/main.js +++ b/console/main.js @@ -16,7 +16,7 @@ const ipcMain = electron.ipcMain; require('crash-reporter').start(); // Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garggbage collected. +// be closed automatically when the JavaScript object is garbage collected. var mainWindow = null; var appIcon = null; var TRAY_ICON = 'resources/tray-icon.png'; @@ -78,44 +78,43 @@ app.on('ready', function() { mainWindow = null; }); - var pInterface = new Process('interface', 'C:\\Interface\\interface.exe'); + if (interfacePath && dsPath && acPath) { + var pInterface = new Process('interface', interfacePath); - var domainServerPath = 'C:\\Users\\Ryan\\AppData\\Local\\High Fidelity\\Stack Manager\\domain-server.exe'; - var acPath = 'C:\\Users\\Ryan\\AppData\\Local\\High Fidelity\\Stack Manager\\assignment-client.exe'; + var homeServer = new ProcessGroup('home', [ + new Process('Domain Server', dsPath), + new Process('AC - Audio', acPath, ['-t0']), + new Process('AC - Avatar', acPath, ['-t1']), + new Process('AC - Asset', acPath, ['-t3']), + new Process('AC - Messages', acPath, ['-t4']), + new Process('AC - Entity', acPath, ['-t6']) + ]); + homeServer.start(); - var homeServer = new ProcessGroup('home', [ - new Process('Domain Server', domainServerPath), - new Process('AC - Audio', acPath, ['-t0']), - new Process('AC - Avatar', acPath, ['-t1']), - new Process('AC - Asset', acPath, ['-t3']), - new Process('AC - Messages', acPath, ['-t4']), - new Process('AC - Entity', acPath, ['-t6']) - ]); - homeServer.start(); + var processes = { + interface: pInterface, + home: homeServer + }; - var processes = { - interface: pInterface, - home: homeServer - }; + function sendProcessUpdate() { + console.log("Sending process update to web view"); + mainWindow.webContents.send('process-update', processes); + }; - function sendProcessUpdate() { - console.log("Sending process update to web view"); - mainWindow.webContents.send('process-update', processes); - }; + pInterface.on('state-update', sendProcessUpdate); - pInterface.on('state-update', sendProcessUpdate); + ipcMain.on('start-process', function(event, arg) { + pInterface.start(); + sendProcessUpdate(); + }); + ipcMain.on('stop-process', function(event, arg) { + pInterface.stop(); + sendProcessUpdate(); + }); + ipcMain.on('update', function(event, arg) { + sendProcessUpdate(); + }); - ipcMain.on('start-process', function(event, arg) { - pInterface.start(); sendProcessUpdate(); - }); - ipcMain.on('stop-process', function(event, arg) { - pInterface.stop(); - sendProcessUpdate(); - }); - ipcMain.on('update', function(event, arg) { - sendProcessUpdate(); - }); - - sendProcessUpdate(); + } }); From 4bb187f77c4357d7270867f8394535a9e6f07898 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 13:59:26 -0800 Subject: [PATCH 15/19] use full path to executable on OS X --- console/modules/path-finder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/modules/path-finder.js b/console/modules/path-finder.js index ffe8235ea1..aed09b3fd3 100644 --- a/console/modules/path-finder.js +++ b/console/modules/path-finder.js @@ -7,7 +7,7 @@ exports.discoveredPath = function (name, preferRelease) { function platformExtension(name) { if (name == "Interface") { if (process.platform == "darwin") { - return ".app" + return ".app/Contents/MacOS/" + name } else if (process.platform == "win32") { return ".exe" } else { From 645a5319c33e4136d4adf59e3a6caf7584f4bbeb Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 14:06:51 -0800 Subject: [PATCH 16/19] remove the stack-manager --- CMakeLists.txt | 2 - stack-manager/CMakeLists.txt | 48 -- stack-manager/assets/assignment-run.svg | 22 - stack-manager/assets/assignment-stop.svg | 27 - stack-manager/assets/icon.icns | Bin 72888 -> 0 bytes stack-manager/assets/icon.ico | Bin 370070 -> 0 bytes stack-manager/assets/icon.png | Bin 14209 -> 0 bytes stack-manager/assets/logo-larger.png | Bin 2536 -> 0 bytes stack-manager/assets/server-start.svg | 57 -- stack-manager/assets/server-stop.svg | 53 -- stack-manager/content-sets/content-sets.html | 64 -- stack-manager/content-sets/content-sets.json | 27 - stack-manager/src/AppDelegate.cpp | 776 ------------------- stack-manager/src/AppDelegate.h | 89 --- stack-manager/src/BackgroundProcess.cpp | 125 --- stack-manager/src/BackgroundProcess.h | 48 -- stack-manager/src/DownloadManager.cpp | 164 ---- stack-manager/src/DownloadManager.h | 52 -- stack-manager/src/Downloader.cpp | 133 ---- stack-manager/src/Downloader.h | 45 -- stack-manager/src/GlobalData.cpp | 84 -- stack-manager/src/GlobalData.h | 75 -- stack-manager/src/StackManagerVersion.h.in | 16 - stack-manager/src/main.cpp | 15 - stack-manager/src/resources.qrc | 9 - stack-manager/src/ui/AssignmentWidget.cpp | 61 -- stack-manager/src/ui/AssignmentWidget.h | 37 - stack-manager/src/ui/LogViewer.cpp | 63 -- stack-manager/src/ui/LogViewer.h | 31 - stack-manager/src/ui/MainWindow.cpp | 329 -------- stack-manager/src/ui/MainWindow.h | 67 -- stack-manager/src/ui/SvgButton.cpp | 32 - stack-manager/src/ui/SvgButton.h | 33 - stack-manager/windows_icon.rc | 1 - 34 files changed, 2585 deletions(-) delete mode 100644 stack-manager/CMakeLists.txt delete mode 100644 stack-manager/assets/assignment-run.svg delete mode 100644 stack-manager/assets/assignment-stop.svg delete mode 100644 stack-manager/assets/icon.icns delete mode 100644 stack-manager/assets/icon.ico delete mode 100644 stack-manager/assets/icon.png delete mode 100644 stack-manager/assets/logo-larger.png delete mode 100644 stack-manager/assets/server-start.svg delete mode 100644 stack-manager/assets/server-stop.svg delete mode 100644 stack-manager/content-sets/content-sets.html delete mode 100644 stack-manager/content-sets/content-sets.json delete mode 100644 stack-manager/src/AppDelegate.cpp delete mode 100644 stack-manager/src/AppDelegate.h delete mode 100644 stack-manager/src/BackgroundProcess.cpp delete mode 100644 stack-manager/src/BackgroundProcess.h delete mode 100644 stack-manager/src/DownloadManager.cpp delete mode 100644 stack-manager/src/DownloadManager.h delete mode 100644 stack-manager/src/Downloader.cpp delete mode 100644 stack-manager/src/Downloader.h delete mode 100644 stack-manager/src/GlobalData.cpp delete mode 100644 stack-manager/src/GlobalData.h delete mode 100644 stack-manager/src/StackManagerVersion.h.in delete mode 100644 stack-manager/src/main.cpp delete mode 100644 stack-manager/src/resources.qrc delete mode 100644 stack-manager/src/ui/AssignmentWidget.cpp delete mode 100644 stack-manager/src/ui/AssignmentWidget.h delete mode 100644 stack-manager/src/ui/LogViewer.cpp delete mode 100644 stack-manager/src/ui/LogViewer.h delete mode 100644 stack-manager/src/ui/MainWindow.cpp delete mode 100644 stack-manager/src/ui/MainWindow.h delete mode 100644 stack-manager/src/ui/SvgButton.cpp delete mode 100644 stack-manager/src/ui/SvgButton.h delete mode 100644 stack-manager/windows_icon.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index c84c422ad6..2574dd7b14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,8 +202,6 @@ 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 FOLDER "Apps") add_subdirectory(console) add_subdirectory(tests) add_subdirectory(plugins) diff --git a/stack-manager/CMakeLists.txt b/stack-manager/CMakeLists.txt deleted file mode 100644 index e70fefc6e0..0000000000 --- a/stack-manager/CMakeLists.txt +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index 4005d58fbf..0000000000 --- a/stack-manager/assets/assignment-run.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - diff --git a/stack-manager/assets/assignment-stop.svg b/stack-manager/assets/assignment-stop.svg deleted file mode 100644 index ecc1b190c4..0000000000 --- a/stack-manager/assets/assignment-stop.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - diff --git a/stack-manager/assets/icon.icns b/stack-manager/assets/icon.icns deleted file mode 100644 index 711c7f63809c42ed7b9ac196513ffe9275d337e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72888 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 diff --git a/stack-manager/assets/server-start.svg b/stack-manager/assets/server-start.svg deleted file mode 100644 index e95d2f3a25..0000000000 --- a/stack-manager/assets/server-start.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/stack-manager/assets/server-stop.svg b/stack-manager/assets/server-stop.svg deleted file mode 100644 index 13bf8f3067..0000000000 --- a/stack-manager/assets/server-stop.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/stack-manager/content-sets/content-sets.html b/stack-manager/content-sets/content-sets.html deleted file mode 100644 index 05c5f4d6f7..0000000000 --- a/stack-manager/content-sets/content-sets.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - 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 deleted file mode 100644 index 250c40adba..0000000000 --- a/stack-manager/content-sets/content-sets.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "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 deleted file mode 100644 index ea9310a2d8..0000000000 --- a/stack-manager/src/AppDelegate.cpp +++ /dev/null @@ -1,776 +0,0 @@ -// -// 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 deleted file mode 100644 index 1d4728b7ce..0000000000 --- a/stack-manager/src/AppDelegate.h +++ /dev/null @@ -1,89 +0,0 @@ -// -// 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 deleted file mode 100644 index e5d0452a83..0000000000 --- a/stack-manager/src/BackgroundProcess.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// -// 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 deleted file mode 100644 index fd652ba73e..0000000000 --- a/stack-manager/src/BackgroundProcess.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// 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 deleted file mode 100644 index f97ba1f680..0000000000 --- a/stack-manager/src/DownloadManager.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// -// 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 deleted file mode 100644 index 1cef0ae118..0000000000 --- a/stack-manager/src/DownloadManager.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 deleted file mode 100644 index db3b397b96..0000000000 --- a/stack-manager/src/Downloader.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// -// 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 deleted file mode 100644 index f5b02214e0..0000000000 --- a/stack-manager/src/Downloader.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// 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 deleted file mode 100644 index ecc5ed520d..0000000000 --- a/stack-manager/src/GlobalData.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// -// 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 deleted file mode 100644 index 58c9a93526..0000000000 --- a/stack-manager/src/GlobalData.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// 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 deleted file mode 100644 index 402e19a056..0000000000 --- a/stack-manager/src/StackManagerVersion.h.in +++ /dev/null @@ -1,16 +0,0 @@ -// -// 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 deleted file mode 100644 index b5b715c75a..0000000000 --- a/stack-manager/src/main.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// -// 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 deleted file mode 100644 index 508edcc728..0000000000 --- a/stack-manager/src/resources.qrc +++ /dev/null @@ -1,9 +0,0 @@ - - - ../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 deleted file mode 100644 index 51fe067eb3..0000000000 --- a/stack-manager/src/ui/AssignmentWidget.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// 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 deleted file mode 100644 index 3e52d7f1af..0000000000 --- a/stack-manager/src/ui/AssignmentWidget.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// 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 deleted file mode 100644 index 12b6f33f88..0000000000 --- a/stack-manager/src/ui/LogViewer.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// -// 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 deleted file mode 100644 index b4321cc886..0000000000 --- a/stack-manager/src/ui/LogViewer.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// 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 deleted file mode 100644 index 59551933f4..0000000000 --- a/stack-manager/src/ui/MainWindow.cpp +++ /dev/null @@ -1,329 +0,0 @@ -// -// 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 deleted file mode 100644 index 6b6669ce3a..0000000000 --- a/stack-manager/src/ui/MainWindow.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// 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 deleted file mode 100644 index 0d646ff0d1..0000000000 --- a/stack-manager/src/ui/SvgButton.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// -// 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 deleted file mode 100644 index b4d44631ec..0000000000 --- a/stack-manager/src/ui/SvgButton.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// 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 deleted file mode 100644 index 125e4c19db..0000000000 --- a/stack-manager/windows_icon.rc +++ /dev/null @@ -1 +0,0 @@ -IDI_ICON1 ICON DISCARDABLE "assets/icon.ico" \ No newline at end of file From bddb8d5b68e247cd49753101bb25a7665b4c8d79 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 14:08:10 -0800 Subject: [PATCH 17/19] remove Quazip files used by CMake --- cmake/externals/quazip/CMakeLists.txt | 52 --------------------------- cmake/macros/TargetQuazip.cmake | 16 --------- cmake/modules/FindQuaZip.cmake | 29 --------------- 3 files changed, 97 deletions(-) delete mode 100644 cmake/externals/quazip/CMakeLists.txt delete mode 100644 cmake/macros/TargetQuazip.cmake delete mode 100644 cmake/modules/FindQuaZip.cmake diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt deleted file mode 100644 index ac19e7d680..0000000000 --- a/cmake/externals/quazip/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -set(EXTERNAL_NAME quazip) -string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -cmake_policy(SET CMP0046 OLD) - -include(ExternalProject) - -if (WIN32) - # windows shell does not like backslashes expanded on the command line, - # so convert all backslashes in the QT path to forward slashes - string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -elseif ($ENV{QT_CMAKE_PREFIX_PATH}) - set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -endif () - -ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://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=${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") diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake deleted file mode 100644 index 0536a1a9d8..0000000000 --- a/cmake/macros/TargetQuazip.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# -# 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 deleted file mode 100644 index 110f239c68..0000000000 --- a/cmake/modules/FindQuaZip.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# -# 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) From 66b3aeeebaef0150335d90443010be8d79414ac9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 14:20:29 -0800 Subject: [PATCH 18/19] ignore stdio from child process --- console/modules/hf-process.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/console/modules/hf-process.js b/console/modules/hf-process.js index bc018ed982..d8ab0abfd1 100755 --- a/console/modules/hf-process.js +++ b/console/modules/hf-process.js @@ -54,7 +54,8 @@ Process.prototype = extend(Process.prototype, { console.log("Starting " + this.command); try { this.child = childProcess.spawn(this.command, this.commandArgs, { - detached: false + detached: false, + stdio: 'ignore' }); //console.log("started ", this.child); this.child.on('error', this.onChildStartError.bind(this)); From d3d59b365fd791ec9232c8f4a42f40221a763dc1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 3 Dec 2015 14:24:01 -0800 Subject: [PATCH 19/19] stop processes on application quit --- console/main.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/console/main.js b/console/main.js index 163ea9e27b..ef76835db0 100644 --- a/console/main.js +++ b/console/main.js @@ -91,6 +91,12 @@ app.on('ready', function() { ]); homeServer.start(); + // make sure we stop child processes on app quit + app.on('quit', function(){ + pInterface.stop(); + homeServer.stop(); + }) + var processes = { interface: pInterface, home: homeServer