diff --git a/installer/win/Symphony-x64.aip b/installer/win/Symphony-x64.aip index 7cad63ce..1efdb9a9 100644 --- a/installer/win/Symphony-x64.aip +++ b/installer/win/Symphony-x64.aip @@ -73,6 +73,9 @@ + + + @@ -130,13 +133,10 @@ - - - @@ -147,12 +147,15 @@ - + + + + @@ -243,20 +246,17 @@ - - + - - + - - + @@ -279,7 +279,7 @@ - + diff --git a/installer/win/Symphony-x86.aip b/installer/win/Symphony-x86.aip index 3662d897..74064d64 100644 --- a/installer/win/Symphony-x86.aip +++ b/installer/win/Symphony-x86.aip @@ -72,6 +72,9 @@ + + + @@ -127,14 +130,10 @@ - - - - @@ -145,12 +144,15 @@ - + + + + @@ -215,7 +217,7 @@ - + @@ -240,23 +242,17 @@ - - - + - - - + - - - + @@ -280,7 +276,7 @@ - + diff --git a/js/search/search.js b/js/search/search.js index 19156c26..be7f7d8c 100644 --- a/js/search/search.js +++ b/js/search/search.js @@ -10,6 +10,7 @@ const makeBoundTimedCollector = require('./queue'); const searchConfig = require('./searchConfig'); const log = require('../log.js'); const logLevels = require('../enums/logLevels.js'); +const { launchAgent, launchDaemon, taskScheduler } = require('./utils/search-launchd.js'); const libSymphonySearch = require('./searchLibrary'); const Crypto = require('../cryptoLib'); @@ -34,6 +35,7 @@ class Search { this.messageData = []; this.isRealTimeIndexing = false; this.crypto = new Crypto(userId, key); + initializeLaunchAgent(); this.decryptAndInit(); this.collector = makeBoundTimedCollector(this.checkIsRealTimeIndexing.bind(this), searchConfig.REAL_TIME_INDEXING_TIME, this.realTimeIndexing.bind(this)); @@ -594,6 +596,78 @@ function readFile(batch) { }); } +/** + * Creating launch agent for handling the deletion of + * index data folder when app crashed or on boot up + */ +function initializeLaunchAgent() { + let pidValue = process.pid; + if (isMac) { + createLaunchScript(pidValue, 'clear-data', searchConfig.LIBRARY_CONSTANTS.LAUNCH_AGENT_FILE, function (res) { + if (!res) { + log.send(logLevels.ERROR, `Launch Agent not created`); + } + createLaunchScript(null, 'clear-data-boot', searchConfig.LIBRARY_CONSTANTS.LAUNCH_DAEMON_FILE, function (result) { + if (!result) { + log.send(logLevels.ERROR, `Launch Agent not created`); + } + launchDaemon(`${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony/clear-data-boot.sh`, function (data) { + if (data) { + log.send(logLevels.INFO, 'Launch Daemon: Creating successful'); + } + }); + }); + + launchAgent(pidValue, `${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony/clear-data.sh`, function (response) { + if (response) { + log.send(logLevels.INFO, 'Launch Agent: Creating successful'); + } + }); + }); + } else { + let folderPath = isDevEnv ? path.join(__dirname, '..', '..', searchConfig.FOLDERS_CONSTANTS.INDEX_FOLDER_NAME) : + path.join(searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH, searchConfig.FOLDERS_CONSTANTS.INDEX_FOLDER_NAME); + taskScheduler(`${searchConfig.LIBRARY_CONSTANTS.WINDOWS_TASK_FILE}`, folderPath, pidValue, `${searchConfig.LIBRARY_CONSTANTS.WINDOWS_CLEAR_SCRIPT}`); + } +} + +/** + * Passing the pid of the application and creating the + * bash file in the userData folder + * @param pid + * @param name + * @param scriptPath + * @param cb + */ +function createLaunchScript(pid, name, scriptPath, cb) { + + if (!fs.existsSync(`${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony/`)) { + fs.mkdirSync(`${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony/`); + } + + fs.readFile(scriptPath, 'utf8', function (err, data) { + if (err) { + log.send(logLevels.ERROR, `Error reading sh file: ${err}`); + cb(false); + return; + } + let result = data; + result = result.replace(/dataPath/g, `"${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/${searchConfig.FOLDERS_CONSTANTS.INDEX_FOLDER_NAME}"`); + result = result.replace(/scriptPath/g, `${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony/${name}.sh`); + if (pid) { + result = result.replace(/SymphonyPID/g, `${pid}`); + } + + fs.writeFile(`${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony/${name}.sh`, result, 'utf8', function (error) { + if (error) { + log.send(logLevels.ERROR, `Error writing sh file: ${error}`); + return cb(false); + } + return cb(true); + }); + }); +} + /** * Exporting the search library * @type {{Search: Search}} diff --git a/js/search/searchConfig.js b/js/search/searchConfig.js index dd7f1b38..bab0a58d 100644 --- a/js/search/searchConfig.js +++ b/js/search/searchConfig.js @@ -26,12 +26,25 @@ const libraryPath = isMac ? path.join(macLibraryPath, 'libsymphonysearch.dylib') const userConfigFileName = 'search_users_config.json'; const userConfigFile = isDevEnv ? path.join(__dirname, '..', '..', userConfigFileName) : path.join(userData, userConfigFileName); +const libraryFolderPath = isMac ? macLibraryPath : winLibraryPath; + +const pathToUtils = isDevEnv ? path.join(__dirname, '../../node_modules/electron-utils') : winLibraryPath; +const launchAgentFile = path.join(libraryFolderPath, 'search-launch-agent.sh'); +const launchDaemonFile = path.join(libraryFolderPath, 'search-launch-daemon.sh'); +const windowsTaskFile = path.join(pathToUtils, isDevEnv ? 'ClearSchTasks/bin/Release/ClearSchTasks.exe' : 'ClearSchTasks.exe'); +const windowsClearScript = path.join(pathToUtils, isDevEnv ? 'ClearOnBoot/bin/Release/ClearOnBoot.exe' : 'ClearOnBoot.exe'); + const libraryPaths = { INDEX_VALIDATOR: indexValidatorPath, LZ4_PATH: lz4Path, MAC_LIBRARY_FOLDER: macLibraryPath, WIN_LIBRARY_FOLDER: winLibraryPath, - SEARCH_LIBRARY_PATH: libraryPath + SEARCH_LIBRARY_PATH: libraryPath, + LIBRARY_FOLDER_PATH: libraryFolderPath, + LAUNCH_AGENT_FILE: launchAgentFile, + LAUNCH_DAEMON_FILE: launchDaemonFile, + WINDOWS_TASK_FILE: windowsTaskFile, + WINDOWS_CLEAR_SCRIPT: windowsClearScript, }; const folderPaths = { diff --git a/js/search/utils/search-launchd.js b/js/search/utils/search-launchd.js new file mode 100644 index 00000000..de6aca4f --- /dev/null +++ b/js/search/utils/search-launchd.js @@ -0,0 +1,105 @@ +const { exec, execSync } = require('child_process'); +const os = require('os'); +const { randomString } = require('./randomString.js'); +const log = require('../../log.js'); +const logLevels = require('../../enums/logLevels.js'); +const Winreg = require('winreg'); + +/** + * Register for creating launch agent + * @type {Registry} + */ +const regKey = new Winreg({ + hive: Winreg.HKCU, + key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' +}); + +/** + * Clears the data folder on app crash + * @param pid + * @param script + * @param cb (callback) + */ +function launchAgent(pid, script, cb) { + exec(`sh "${script}" true ${pid}`, (error, stdout, stderr) => { + if (error) { + log.send(logLevels.ERROR, `Lanuchd: Error creating script ${error}`); + return cb(false); + } + if (stderr) { + log.send(logLevels.ERROR, `Lanuchd: Error creating script ${stderr}`); + } + return cb(true); + }); +} + +/** + * Clears the data folder on boot + * @param script + * @param cb (callback) + */ +function launchDaemon(script, cb) { + exec(`sh "${script}" true`, (error, stdout, stderr) => { + if (error) { + log.send(logLevels.ERROR, `Lanuchd: Error creating script ${error}`); + return cb(false); + } + if (stderr) { + log.send(logLevels.ERROR, `Lanuchd: Error creating script ${stderr}`); + } + return cb(true); + }); +} + +/** + * Windows clears the data folder on app crash + * @param script + * @param dataFolder + * @param pid + * @param clearScript + */ +function taskScheduler(script, dataFolder, pid, clearScript) { + let userName; + if (os.userInfo) { + userName = os.userInfo().username; + } else { + try { + userName = execSync('whoami').toString().replace(/^.*\\/, ''); + } catch (e) { + log.send(logLevels.WARN, `whoami failed (using randomString): ${e}`); + userName = randomString(); + } + } + exec(`SCHTASKS /Create /SC MINUTE /TN "SymphonyTask${userName}" /TR "'${script}' '${dataFolder}' 'SymphonyTask${userName}' '${pid}'" /F`, (error, stdout, stderr) => { + if (error) { + log.send(logLevels.ERROR, `Lanuchd: Error creating task ${error}`); + } + if (stderr) { + log.send(logLevels.WARN, `Lanuchd: Error creating task ${stderr}`); + } + log.send(logLevels.INFO, `Lanuchd: Creating task successful ${stdout}`); + }); + + winRegScript(userName, clearScript, dataFolder); +} + +/** + * Clear the data folder on user login for first time + * @param userName + * @param script + * @param dataFolder + */ +function winRegScript(userName, script, dataFolder) { + regKey.set(`SymphonyTask-${userName}`, Winreg.REG_SZ, `"${script}" "${dataFolder}"`, function(err) { + if (err !== null) { + log.send(logLevels.INFO, `winReg: Creating task failed ${err}`); + } + log.send(logLevels.INFO, 'winReg: Creating task successful'); + }); +} + +module.exports = { + launchAgent, + launchDaemon, + taskScheduler +}; \ No newline at end of file diff --git a/library/search-launch-agent.sh b/library/search-launch-agent.sh new file mode 100644 index 00000000..4dacf670 --- /dev/null +++ b/library/search-launch-agent.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +pid=$2 +launchDir=~/Library/LaunchAgents/ + +if $1; then + if [ ! -d "$launchDir" ]; then + mkdir "$launchDir" + fi + launchctl unload ~/Library/LaunchAgents/com.symphony-search.data.plist + cat > ~/Library/LaunchAgents/com.symphony-search.data.plist << EOT + + + + + Label + com.symphony-search.data.agent + ProgramArguments + + /bin/sh + scriptPath + false + SymphonyPID + + RunAtLoad + + StartInterval + 60 + StandardOutPath + /dev/null + StandardErrorPath + /dev/null + + +EOT +launchctl load ~/Library/LaunchAgents/com.symphony-search.data.plist +elif ps -p $pid > /dev/null +then + echo true +else + echo false + rm -rf dataPath + launchctl unload ~/Library/LaunchAgents/com.symphony-search.data.plist + rm -rf ~/Library/LaunchAgents/com.symphony-search.data.plist +fi \ No newline at end of file diff --git a/library/search-launch-daemon.sh b/library/search-launch-daemon.sh new file mode 100644 index 00000000..f325197f --- /dev/null +++ b/library/search-launch-daemon.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +launchDir=~/Library/LaunchAgents/ + +if $1; then + if [ ! -d "$launchDir" ]; then + mkdir "$launchDir" + fi + cat > ~/Library/LaunchAgents/com.symphony-search.clear.plist << EOT + + + + + Label + com.symphony-search.clear.daemon + ProgramArguments + + /bin/sh + scriptPath + false + + RunAtLoad + + KeepAlive + + StandardOutPath + /dev/null + StandardErrorPath + /dev/null + + +EOT +else + rm -rf dataPath +fi \ No newline at end of file diff --git a/package.json b/package.json index 6afcfb95..339f4653 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,9 @@ "config/Symphony.config", "library/libsymphonysearch.dylib", "library/indexvalidator.exec", - "library/lz4.exec" + "library/lz4.exec", + "library/search-launch-agent.sh", + "library/search-launch-daemon.sh" ], "appId": "symphony-electron-desktop", "mac": { @@ -124,6 +126,7 @@ "winreg": "1.2.4" }, "optionalDependencies": { - "screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1" + "screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1", + "electron-utils": "git+https://github.com/symphonyoss/electron-utils.git#v1.0.5" } } diff --git a/tests/Search.test.js b/tests/Search.test.js index f8947e82..d66fd90d 100644 --- a/tests/Search.test.js +++ b/tests/Search.test.js @@ -11,11 +11,16 @@ let SearchApi; jest.mock('electron', function() { return { app: { - getPath: mockedGetPath + getPath: mockedGetPath, + getName: mockedGetName } } }); +function mockedGetName() { + return 'Symphony'; +} + function mockedGetPath(type) { if (type === 'exe') { return executionPath; @@ -34,6 +39,7 @@ describe('Tests for Search', function() { let dataFolderPath; let realTimeIndexPath; let tempBatchPath; + let launchAgent; let currentDate = new Date().getTime(); jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; @@ -51,12 +57,12 @@ describe('Tests for Search', function() { searchConfig = require('../js/search/searchConfig.js'); const { Search } = require('../js/search/search.js'); SearchApi = new Search(userId, key); - + launchAgent = require('../js/search/utils/search-launchd.js'); realTimeIndexPath = path.join(userConfigDir, 'data', 'temp_realtime_index'); tempBatchPath = path.join(userConfigDir, 'data', 'temp_batch_indexes'); dataFolderPath = path.join(userConfigDir, 'data'); if (fs.existsSync(dataFolderPath)) { - deleteIndexFolders(dataFolderPath) + deleteIndexFolders(dataFolderPath); } done(); }); @@ -69,7 +75,10 @@ describe('Tests for Search', function() { if (fs.existsSync(root)) { fs.unlinkSync(root); } - + let script = `${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony`; + if (fs.existsSync(script)) { + deleteIndexFolders(script); + } done(); }, 3000); });