'use strict'; const electron = require('electron');const app = electron.app; // Module to control application life.const BrowserWindow = electron.BrowserWindow;const nativeImage = electron.nativeImage; const notifier = require('node-notifier');const util = require('util');const dialog = electron.dialog;const Menu = electron.Menu;const Tray = electron.Tray;const shell = electron.shell;const os = require('os');const childProcess = require('child_process');const path = require('path');const fs = require('fs-extra');const Tail = require('always-tail');const http = require('http');const zlib = require('zlib');const tar = require('tar-fs'); const request = require('request');const progress = require('request-progress');const osHomeDir = require('os-homedir'); const updater = require('./modules/hf-updater.js'); const Config = require('./modules/config').Config; const hfprocess = require('./modules/hf-process.js'); global.log = require('electron-log'); const Process = hfprocess.Process;const ACMonitorProcess = hfprocess.ACMonitorProcess;const ProcessStates = hfprocess.ProcessStates;const ProcessGroup = hfprocess.ProcessGroup;const ProcessGroupStates = hfprocess.ProcessGroupStates; const hfApp = require('./modules/hf-app.js');const GetBuildInfo = hfApp.getBuildInfo;const StartInterface = hfApp.startInterface;const getRootHifiDataDirectory = hfApp.getRootHifiDataDirectory;const getDomainServerClientResourcesDirectory = hfApp.getDomainServerClientResourcesDirectory;const getAssignmentClientResourcesDirectory = hfApp.getAssignmentClientResourcesDirectory;const getApplicationDataDirectory = hfApp.getApplicationDataDirectory; const osType = os.type(); const appIcon = path.join(__dirname, '../resources/console.png'); const menuNotificationIcon = path.join(__dirname, '../resources/tray-menu-notification.png'); const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Daysconst LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/; const HOME_CONTENT_URL = "https://content.overte.org/DomainContent/Sandbox/Rearranged_Basic_Sandbox.tar.gz"; const buildInfo = GetBuildInfo(); const NotificationState = { UNNOTIFIED: 'unnotified', NOTIFIED: 'notified'}; // Update lock filepathconst UPDATER_LOCK_FILENAME = ".updating";const UPDATER_LOCK_FULL_PATH = getRootHifiDataDirectory() + "/" + UPDATER_LOCK_FILENAME; // Configure logconst oldLogFile = path.join(getApplicationDataDirectory(), '/log.txt');const logFile = path.join(getApplicationDataDirectory(true), '/log.txt');if (oldLogFile != logFile && fs.existsSync(oldLogFile)) { if (!fs.existsSync(oldLogFile)) { fs.moveSync(oldLogFile, logFile); } else { fs.remove(oldLogFile); }}fs.ensureFileSync(logFile); // Ensure file existslog.transports.file.maxSize = 5 * 1024 * 1024;log.transports.file.file = logFile; log.debug("build info", buildInfo);log.debug("Root hifi directory is: ", getRootHifiDataDirectory());log.debug("App Data directory:", getApplicationDataDirectory());fs.ensureDirSync(getApplicationDataDirectory()); var oldLogPath = path.join(getApplicationDataDirectory(), '/logs');var logPath = path.join(getApplicationDataDirectory(true), '/logs');if (oldLogPath != logPath && fs.existsSync(oldLogPath)) { if (!fs.existsSync(oldLogPath)) { fs.moveSync(oldLogPath, logPath); } else { fs.remove(oldLogPath); }}fs.ensureDirSync(logPath);log.debug("Log directory:", logPath); const configPath = path.join(getApplicationDataDirectory(), 'config.json');var userConfig = new Config();userConfig.load(configPath); const ipcMain = electron.ipcMain; function isInterfaceInstalled () { if (osType == "Darwin") { // In OSX Sierra, the app translocation process moves // the executable to a random location before starting it // which makes finding the interface near impossible using // relative paths. For now, as there are no server-only // installs, we just assume the interface is installed here return true; } else { return interfacePath; }} function isServerInstalled () { return dsPath && acPath;} var isShuttingDown = false;function shutdown() { log.debug("Normal shutdown (isShuttingDown: " + isShuttingDown + ")"); if (!isShuttingDown) { // if the home server is running, show a prompt before quit to ask if the user is sure if (isServerInstalled() && homeServer.state == ProcessGroupStates.STARTED) { log.debug("Showing shutdown dialog."); dialog.showMessageBox({ type: 'question', buttons: ['Yes', 'No'], title: 'Stopping Overte Sandbox', message: 'Quitting will stop your Sandbox and your Home domain will no longer be running.\nDo you wish to continue?' }, shutdownCallback); } else { shutdownCallback(0); } }} function forcedShutdown() { log.debug("Forced shutdown (isShuttingDown: " + isShuttingDown + ")"); if (!isShuttingDown) { shutdownCallback(0); }} function shutdownCallback(idx) { log.debug("Entering shutdown callback."); if (idx == 0 && !isShuttingDown) { isShuttingDown = true; log.debug("Stop tray polling."); trayNotifications.stopPolling(); log.debug("Saving user config"); userConfig.save(configPath); if (logWindow) { log.debug("Closing log window"); logWindow.close(); } if (isServerInstalled()) { if (homeServer) { log.debug("Stoping home server"); homeServer.stop(); updateTrayMenu(homeServer.state); if (homeServer.state == ProcessGroupStates.STOPPED) { // if the home server is already down, take down the server console now log.debug("Quitting."); app.exit(0); } else { // if the home server is still running, wait until we get a state change or timeout // before quitting the app log.debug("Server still shutting down. Waiting"); var timeoutID = setTimeout(function() { app.exit(0); }, 5000); homeServer.on('state-update', function(processGroup) { if (processGroup.state == ProcessGroupStates.STOPPED) { clearTimeout(timeoutID); log.debug("Quitting."); app.exit(0); } }); } } } else { app.exit(0); } }} function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { log.debug("Deleting old log files in " + directoryPath); var filenames = []; try { filenames = fs.readdirSync(directoryPath); } catch (e) { log.warn("Error reading contents of log file directory", e); return; } for (const filename of filenames) { log.debug("Checking", filename); const absolutePath = path.join(directoryPath, filename); var stat = null; try { stat = fs.statSync(absolutePath); } catch (e) { log.debug("Error stat'ing file", absolutePath, e); continue; } const curTime = Date.now(); if (stat.isFile() && filename.search(filenameRegex) >= 0) { const ageInSeconds = (curTime - stat.mtime.getTime()) / 1000.0; if (ageInSeconds >= maxAgeInSeconds) { log.debug("\tDeleting:", filename, ageInSeconds); try { fs.unlinkSync(absolutePath); } catch (e) { if (e.code != 'EBUSY') { log.warn("\tError deleting:", e); } } } } }} app.setAppUserModelId(buildInfo.appUserModelId); // print out uncaught exceptions in the consoleprocess.on('uncaughtException', function(err) { log.error(err); log.error(err.stack);}); const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { log.warn("Another instance of the Sandbox is already running - this instance will quit."); app.exit(0); return;} // Check command line arguments to see how to find binariesvar argv = require('yargs').argv; var pathFinder = require('./modules/path-finder.js'); var interfacePath = null;var dsPath = null;var acPath = null; var debug = argv.debug; var binaryType = argv.binaryType; interfacePath = pathFinder.discoveredPath("interface", binaryType, buildInfo.releaseType);dsPath = pathFinder.discoveredPath("domain-server", binaryType, buildInfo.releaseType);acPath = pathFinder.discoveredPath("assignment-client", binaryType, buildInfo.releaseType); console.log("Domain Server Path: " + dsPath);console.log("Assignment Client Path: " + acPath);console.log("Interface Path: " + interfacePath); function binaryMissingMessage(displayName, executableName, required) { var message = "The " + displayName + " executable was not found.\n"; if (required) { message += "It is required for the Overte Sandbox to run.\n\n"; } else { message += "\n"; } if (debug) { message += "Please ensure there is a compiled " + displayName + " in a folder named build in this checkout.\n\n"; message += "It was expected to be found at one of the following paths:\n"; var paths = pathFinder.searchPaths(executableName, argv.localReleaseBuilds); message += paths.join("\n"); } else { message += "It is expected to be found beside this executable.\n"; message += "You may need to re-install the Overte Sandbox."; } return message;} function openFileBrowser(path) { // Add quotes around path path = '"' + path + '"'; if (osType == "Windows_NT") { childProcess.exec('start "" ' + path); } else if (osType == "Darwin") { childProcess.exec('open ' + path); } else if (osType == "Linux") { childProcess.exec('xdg-open ' + path); }} function openLogDirectory() { openFileBrowser(logPath);} // NOTE: this looks like it does nothing, but it's very important.// Without it the default behaviour is to quit the app once all windows closed// which is absolutely not what we want for a taskbar application.app.on('window-all-closed', function() {}); var tray = null;global.homeServer = null;global.domainServer = null;global.acMonitor = null;global.userConfig = userConfig;global.openLogDirectory = openLogDirectory; const hfNotifications = require('./modules/hf-notifications.js');const HifiNotifications = hfNotifications.HifiNotifications;const HifiNotificationType = hfNotifications.NotificationType; var pendingNotifications = {}var notificationState = NotificationState.UNNOTIFIED; function setNotificationState (notificationType, pending = undefined) { if (pending !== undefined) { if ((notificationType === HifiNotificationType.TRANSACTIONS || notificationType === HifiNotificationType.ITEMS)) { // special case, because we want to clear the indicator light // on INVENTORY when either Transactions or Items are // clicked on in the notification popup, we detect that case // here and force both to be unnotified. pendingNotifications[HifiNotificationType.TRANSACTIONS] = pending; pendingNotifications[HifiNotificationType.ITEMS] = pending; } else { pendingNotifications[notificationType] = pending; } notificationState = NotificationState.UNNOTIFIED; for (var key in pendingNotifications) { if (pendingNotifications[key]) { notificationState = NotificationState.NOTIFIED; break; } } } updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED);} var trayNotifications = new HifiNotifications(userConfig, setNotificationState); var LogWindow = function(ac, ds) { this.ac = ac; this.ds = ds; this.window = null; this.acMonitor = null; this.dsMonitor = null;}LogWindow.prototype = { open: function() { if (this.window) { this.window.show(); this.window.restore(); return; } // Create the browser window. this.window = new BrowserWindow({ width: 700, height: 500, icon: appIcon }); this.window.loadURL('file://' + __dirname + '/log.html'); if (!debug) { this.window.setMenu(null); } this.window.on('closed', function() { this.window = null; }.bind(this)); }, close: function() { if (this.window) { this.window.close(); } }}; function visitSandboxClicked() { if (isInterfaceInstalled()) { StartInterface('hifi://localhost'); } else { // show an error to say that we can't go home without an interface instance dialog.showErrorBox("Client Not Found", binaryMissingMessage("Overte client", "Interface", false)); }} var logWindow = null; var labels = { serverState: { label: 'Server - Stopped', enabled: false }, version: { label: 'Version - ' + buildInfo.buildIdentifier, enabled: false }, showNotifications: { label: 'Show Notifications', type: 'checkbox', checked: true, click: function () { trayNotifications.enable(!trayNotifications.enabled(), setNotificationState); userConfig.save(configPath); updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } }, goto: { label: 'GoTo', click: function () { StartInterface("hifiapp:GOTO"); setNotificationState(HifiNotificationType.GOTO, false); } }, people: { label: 'People', click: function () { StartInterface("hifiapp:PEOPLE"); setNotificationState(HifiNotificationType.PEOPLE, false); } }, inventory: { label: 'Inventory', click: function () { StartInterface("hifiapp:INVENTORY"); setNotificationState(HifiNotificationType.ITEMS, false); setNotificationState(HifiNotificationType.TRANSACTIONS, false); } }, restart: { label: 'Start Server', click: function() { homeServer.restart(); } }, stopServer: { label: 'Stop Server', visible: false, click: function() { homeServer.stop(); } }, goHome: { label: 'Visit Local Server', click: visitSandboxClicked, enabled: false }, quit: { label: 'Quit', accelerator: 'Command+Q', click: function() { shutdown(); } }, settings: { label: 'Settings', click: function() { shell.openExternal('http://localhost:40100/settings'); }, enabled: false }, viewLogs: { label: 'View Logs', click: function() { logWindow.open(); } }, share: { label: 'Share', click: function() { shell.openExternal('http://localhost:40100/settings/?action=share') } }, shuttingDown: { label: "Shutting down...", enabled: false },} var separator = { type: 'separator'Lines 1–500 of 958. Load more
'use strict'; const electron = require('electron'); const app = electron.app; // Module to control application life. const BrowserWindow = electron.BrowserWindow; const nativeImage = electron.nativeImage; const notifier = require('node-notifier'); const util = require('util'); const dialog = electron.dialog; const Menu = electron.Menu; const Tray = electron.Tray; const shell = electron.shell; const os = require('os'); const childProcess = require('child_process'); const path = require('path'); const fs = require('fs-extra'); const Tail = require('always-tail'); const http = require('http'); const zlib = require('zlib'); const tar = require('tar-fs'); const request = require('request'); const progress = require('request-progress'); const osHomeDir = require('os-homedir'); const updater = require('./modules/hf-updater.js'); const Config = require('./modules/config').Config; const hfprocess = require('./modules/hf-process.js'); global.log = require('electron-log'); const Process = hfprocess.Process; const ACMonitorProcess = hfprocess.ACMonitorProcess; const ProcessStates = hfprocess.ProcessStates; const ProcessGroup = hfprocess.ProcessGroup; const ProcessGroupStates = hfprocess.ProcessGroupStates; const hfApp = require('./modules/hf-app.js'); const GetBuildInfo = hfApp.getBuildInfo; const StartInterface = hfApp.startInterface; const getRootHifiDataDirectory = hfApp.getRootHifiDataDirectory; const getDomainServerClientResourcesDirectory = hfApp.getDomainServerClientResourcesDirectory; const getAssignmentClientResourcesDirectory = hfApp.getAssignmentClientResourcesDirectory; const getApplicationDataDirectory = hfApp.getApplicationDataDirectory; const osType = os.type(); const appIcon = path.join(__dirname, '../resources/console.png'); const menuNotificationIcon = path.join(__dirname, '../resources/tray-menu-notification.png'); const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Days const LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/; const HOME_CONTENT_URL = "https://content.overte.org/DomainContent/Sandbox/Rearranged_Basic_Sandbox.tar.gz"; const buildInfo = GetBuildInfo(); const NotificationState = { UNNOTIFIED: 'unnotified', NOTIFIED: 'notified' }; // Update lock filepath const UPDATER_LOCK_FILENAME = ".updating"; const UPDATER_LOCK_FULL_PATH = getRootHifiDataDirectory() + "/" + UPDATER_LOCK_FILENAME; // Configure log const oldLogFile = path.join(getApplicationDataDirectory(), '/log.txt'); const logFile = path.join(getApplicationDataDirectory(true), '/log.txt'); if (oldLogFile != logFile && fs.existsSync(oldLogFile)) { if (!fs.existsSync(oldLogFile)) { fs.moveSync(oldLogFile, logFile); } else { fs.remove(oldLogFile); } } fs.ensureFileSync(logFile); // Ensure file exists log.transports.file.maxSize = 5 * 1024 * 1024; log.transports.file.file = logFile; log.debug("build info", buildInfo); log.debug("Root hifi directory is: ", getRootHifiDataDirectory()); log.debug("App Data directory:", getApplicationDataDirectory()); fs.ensureDirSync(getApplicationDataDirectory()); var oldLogPath = path.join(getApplicationDataDirectory(), '/logs'); var logPath = path.join(getApplicationDataDirectory(true), '/logs'); if (oldLogPath != logPath && fs.existsSync(oldLogPath)) { if (!fs.existsSync(oldLogPath)) { fs.moveSync(oldLogPath, logPath); } else { fs.remove(oldLogPath); } } fs.ensureDirSync(logPath); log.debug("Log directory:", logPath); const configPath = path.join(getApplicationDataDirectory(), 'config.json'); var userConfig = new Config(); userConfig.load(configPath); const ipcMain = electron.ipcMain; function isInterfaceInstalled () { if (osType == "Darwin") { // In OSX Sierra, the app translocation process moves // the executable to a random location before starting it // which makes finding the interface near impossible using // relative paths. For now, as there are no server-only // installs, we just assume the interface is installed here return true; } else { return interfacePath; } } function isServerInstalled () { return dsPath && acPath; } var isShuttingDown = false; function shutdown() { log.debug("Normal shutdown (isShuttingDown: " + isShuttingDown + ")"); if (!isShuttingDown) { // if the home server is running, show a prompt before quit to ask if the user is sure if (isServerInstalled() && homeServer.state == ProcessGroupStates.STARTED) { log.debug("Showing shutdown dialog."); dialog.showMessageBox({ type: 'question', buttons: ['Yes', 'No'], title: 'Stopping Overte Sandbox', message: 'Quitting will stop your Sandbox and your Home domain will no longer be running.\nDo you wish to continue?' }, shutdownCallback); } else { shutdownCallback(0); } } } function forcedShutdown() { log.debug("Forced shutdown (isShuttingDown: " + isShuttingDown + ")"); if (!isShuttingDown) { shutdownCallback(0); } } function shutdownCallback(idx) { log.debug("Entering shutdown callback."); if (idx == 0 && !isShuttingDown) { isShuttingDown = true; log.debug("Stop tray polling."); trayNotifications.stopPolling(); log.debug("Saving user config"); userConfig.save(configPath); if (logWindow) { log.debug("Closing log window"); logWindow.close(); } if (isServerInstalled()) { if (homeServer) { log.debug("Stoping home server"); homeServer.stop(); updateTrayMenu(homeServer.state); if (homeServer.state == ProcessGroupStates.STOPPED) { // if the home server is already down, take down the server console now log.debug("Quitting."); app.exit(0); } else { // if the home server is still running, wait until we get a state change or timeout // before quitting the app log.debug("Server still shutting down. Waiting"); var timeoutID = setTimeout(function() { app.exit(0); }, 5000); homeServer.on('state-update', function(processGroup) { if (processGroup.state == ProcessGroupStates.STOPPED) { clearTimeout(timeoutID); log.debug("Quitting."); app.exit(0); } }); } } } else { app.exit(0); } } } function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { log.debug("Deleting old log files in " + directoryPath); var filenames = []; try { filenames = fs.readdirSync(directoryPath); } catch (e) { log.warn("Error reading contents of log file directory", e); return; } for (const filename of filenames) { log.debug("Checking", filename); const absolutePath = path.join(directoryPath, filename); var stat = null; try { stat = fs.statSync(absolutePath); } catch (e) { log.debug("Error stat'ing file", absolutePath, e); continue; } const curTime = Date.now(); if (stat.isFile() && filename.search(filenameRegex) >= 0) { const ageInSeconds = (curTime - stat.mtime.getTime()) / 1000.0; if (ageInSeconds >= maxAgeInSeconds) { log.debug("\tDeleting:", filename, ageInSeconds); try { fs.unlinkSync(absolutePath); } catch (e) { if (e.code != 'EBUSY') { log.warn("\tError deleting:", e); } } } } } } app.setAppUserModelId(buildInfo.appUserModelId); // print out uncaught exceptions in the console process.on('uncaughtException', function(err) { log.error(err); log.error(err.stack); }); const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { log.warn("Another instance of the Sandbox is already running - this instance will quit."); app.exit(0); return; } // Check command line arguments to see how to find binaries var argv = require('yargs').argv; var pathFinder = require('./modules/path-finder.js'); var interfacePath = null; var dsPath = null; var acPath = null; var debug = argv.debug; var binaryType = argv.binaryType; interfacePath = pathFinder.discoveredPath("interface", binaryType, buildInfo.releaseType); dsPath = pathFinder.discoveredPath("domain-server", binaryType, buildInfo.releaseType); acPath = pathFinder.discoveredPath("assignment-client", binaryType, buildInfo.releaseType); console.log("Domain Server Path: " + dsPath); console.log("Assignment Client Path: " + acPath); console.log("Interface Path: " + interfacePath); function binaryMissingMessage(displayName, executableName, required) { var message = "The " + displayName + " executable was not found.\n"; if (required) { message += "It is required for the Overte Sandbox to run.\n\n"; } else { message += "\n"; } if (debug) { message += "Please ensure there is a compiled " + displayName + " in a folder named build in this checkout.\n\n"; message += "It was expected to be found at one of the following paths:\n"; var paths = pathFinder.searchPaths(executableName, argv.localReleaseBuilds); message += paths.join("\n"); } else { message += "It is expected to be found beside this executable.\n"; message += "You may need to re-install the Overte Sandbox."; } return message; } function openFileBrowser(path) { // Add quotes around path path = '"' + path + '"'; if (osType == "Windows_NT") { childProcess.exec('start "" ' + path); } else if (osType == "Darwin") { childProcess.exec('open ' + path); } else if (osType == "Linux") { childProcess.exec('xdg-open ' + path); } } function openLogDirectory() { openFileBrowser(logPath); } // NOTE: this looks like it does nothing, but it's very important. // Without it the default behaviour is to quit the app once all windows closed // which is absolutely not what we want for a taskbar application. app.on('window-all-closed', function() { }); var tray = null; global.homeServer = null; global.domainServer = null; global.acMonitor = null; global.userConfig = userConfig; global.openLogDirectory = openLogDirectory; const hfNotifications = require('./modules/hf-notifications.js'); const HifiNotifications = hfNotifications.HifiNotifications; const HifiNotificationType = hfNotifications.NotificationType; var pendingNotifications = {} var notificationState = NotificationState.UNNOTIFIED; function setNotificationState (notificationType, pending = undefined) { if (pending !== undefined) { if ((notificationType === HifiNotificationType.TRANSACTIONS || notificationType === HifiNotificationType.ITEMS)) { // special case, because we want to clear the indicator light // on INVENTORY when either Transactions or Items are // clicked on in the notification popup, we detect that case // here and force both to be unnotified. pendingNotifications[HifiNotificationType.TRANSACTIONS] = pending; pendingNotifications[HifiNotificationType.ITEMS] = pending; } else { pendingNotifications[notificationType] = pending; } notificationState = NotificationState.UNNOTIFIED; for (var key in pendingNotifications) { if (pendingNotifications[key]) { notificationState = NotificationState.NOTIFIED; break; } } } updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } var trayNotifications = new HifiNotifications(userConfig, setNotificationState); var LogWindow = function(ac, ds) { this.ac = ac; this.ds = ds; this.window = null; this.acMonitor = null; this.dsMonitor = null; } LogWindow.prototype = { open: function() { if (this.window) { this.window.show(); this.window.restore(); return; } // Create the browser window. this.window = new BrowserWindow({ width: 700, height: 500, icon: appIcon }); this.window.loadURL('file://' + __dirname + '/log.html'); if (!debug) { this.window.setMenu(null); } this.window.on('closed', function() { this.window = null; }.bind(this)); }, close: function() { if (this.window) { this.window.close(); } } }; function visitSandboxClicked() { if (isInterfaceInstalled()) { StartInterface('hifi://localhost'); } else { // show an error to say that we can't go home without an interface instance dialog.showErrorBox("Client Not Found", binaryMissingMessage("Overte client", "Interface", false)); } } var logWindow = null; var labels = { serverState: { label: 'Server - Stopped', enabled: false }, version: { label: 'Version - ' + buildInfo.buildIdentifier, enabled: false }, showNotifications: { label: 'Show Notifications', type: 'checkbox', checked: true, click: function () { trayNotifications.enable(!trayNotifications.enabled(), setNotificationState); userConfig.save(configPath); updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } }, goto: { label: 'GoTo', click: function () { StartInterface("hifiapp:GOTO"); setNotificationState(HifiNotificationType.GOTO, false); } }, people: { label: 'People', click: function () { StartInterface("hifiapp:PEOPLE"); setNotificationState(HifiNotificationType.PEOPLE, false); } }, inventory: { label: 'Inventory', click: function () { StartInterface("hifiapp:INVENTORY"); setNotificationState(HifiNotificationType.ITEMS, false); setNotificationState(HifiNotificationType.TRANSACTIONS, false); } }, restart: { label: 'Start Server', click: function() { homeServer.restart(); } }, stopServer: { label: 'Stop Server', visible: false, click: function() { homeServer.stop(); } }, goHome: { label: 'Visit Local Server', click: visitSandboxClicked, enabled: false }, quit: { label: 'Quit', accelerator: 'Command+Q', click: function() { shutdown(); } }, settings: { label: 'Settings', click: function() { shell.openExternal('http://localhost:40100/settings'); }, enabled: false }, viewLogs: { label: 'View Logs', click: function() { logWindow.open(); } }, share: { label: 'Share', click: function() { shell.openExternal('http://localhost:40100/settings/?action=share') } }, shuttingDown: { label: "Shutting down...", enabled: false }, } var separator = { type: 'separator'