mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
SEARCH-840 Migrates SwiftSearch to a different project (#434)
* SEARCH-840 Migrates SwiftSearch to a different project https://github.com/symphonyoss/SwiftSearch * SEARCH-840 Changes swift-search require * SEARCH-840 refactor * SEARCH-840 Changes swift-search version * SEARCH-840 Changes warn message
This commit is contained in:
parent
08ca667c3c
commit
a71d8cb2c7
@ -1,9 +1,5 @@
|
||||
## Prerequisites
|
||||
|
||||
### General
|
||||
- You'll need a set of binaries for swift search to work. To get these binaries, please contact the project maintainers
|
||||
- Without these binaries, you'll not be able to build the app
|
||||
|
||||
### Windows
|
||||
- NodeJS version >= 8.9.4 (corresponds to electron 2.0.2)
|
||||
- Microsoft Visual Studio 2015 Community or Paid (C++ and .NET/C# development tools)
|
||||
@ -99,4 +95,4 @@
|
||||
## Misc notes
|
||||
- If desiring to run against server without proper cert use cmd line option: --ignore-certificate-errors
|
||||
- To start additional instance with custom data directory (if you want seperate user) use cmd line options: --multiInstance --userDataPath=`<path to data dir>`
|
||||
- If directory doesn't exist, it will be created
|
||||
- If directory doesn't exist, it will be created
|
||||
|
257
demo/search.html
257
demo/search.html
@ -1,257 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Search</title>
|
||||
<style>
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 5px;
|
||||
margin: 2px 0;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
label {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 64, 98, 0.7);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="padding: 20px;">
|
||||
<h1>Symphony Electron Search API Demo</h1>
|
||||
<div>
|
||||
<p>Search</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="query">Query:</label><input id="query" size=60>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="has">Has:</label><input id="has">
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="offset">Offset:</label><input id="offset" type="number" value="0" size=5>
|
||||
<label for="limit">Limit:</label><input id="limit" type="number" value="25" size=5>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="start">Start:</label><input id="start" type="date">
|
||||
<label for="end">End:</label><input id="end" type="date">
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="senderId">SenderId:</label><input id="senderId" placeholder='["abc", "123"]'>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="threadId">ThreadId:</label><input id="threadId" placeholder='["abc", "123"]'>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<button id='search'>Search</button>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="var1">var1:</label><input id="var1" type="number" value="0" size=5>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="realTimeIndexing">Real Time Indexing:</label><input placeholder="Pass array of messages:"
|
||||
id="realTimeIndexing">
|
||||
<button id='sendMessage'>Send</button>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label for="getLatestMessageTimestamp">Get Latest Message Timestamp</label>
|
||||
<button id='getLatestMessageTimestamp'>Click</button>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label>Check Free Space: </label>
|
||||
<button id='checkFreeSpace'>Click</button>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<label>View User Config</label>
|
||||
<button id='viewUserConfig'>Click</button>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<div>
|
||||
<label>Update User Config</label>
|
||||
</div>
|
||||
<br>
|
||||
<label for="rotationId">Rotation Id: </label><input placeholder="Ex: 0, 1" id="rotationId">
|
||||
<br>
|
||||
<label for="version">Version: </label><input placeholder="Ex: 1, 2" id="version">
|
||||
<br>
|
||||
<button id='updateUserConfig'>Save</button>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p>Results:</p>
|
||||
<p id="results"></p>
|
||||
<table id="table" class="hidden" style="width:100%">
|
||||
</table>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var search = new ssf.Search("12345678910112", "jjjehdnctsjyieoalskcjdhsnahsadndfnusdfsdfsd=");
|
||||
var searchUtils = new ssf.SearchUtils();
|
||||
var buttonEl = document.getElementById('search');
|
||||
var queryEl = document.getElementById('query');
|
||||
var offsetEl = document.getElementById('offset');
|
||||
var limitEl = document.getElementById('limit');
|
||||
var startEl = document.getElementById('start');
|
||||
var endEl = document.getElementById('end');
|
||||
var resultsEl = document.getElementById('results');
|
||||
var senderIdEl = document.getElementById('senderId');
|
||||
var threadIdEl = document.getElementById('threadId');
|
||||
var var1El = document.getElementById('var1');
|
||||
var table = document.getElementById('table');
|
||||
var sendMessage = document.getElementById('sendMessage');
|
||||
var realTimeIndexing = document.getElementById('realTimeIndexing');
|
||||
var timestamp = document.getElementById('getLatestMessageTimestamp');
|
||||
var has = document.getElementById('has');
|
||||
var checkFreeSpace = document.getElementById('checkFreeSpace');
|
||||
var viewUserConfig = document.getElementById('viewUserConfig');
|
||||
var version = document.getElementById('version');
|
||||
var rotationId = document.getElementById('rotationId');
|
||||
var updateUserConfig = document.getElementById('updateUserConfig');
|
||||
|
||||
buttonEl.addEventListener('click', function () {
|
||||
if (!search.isLibInit()) {
|
||||
search.init();
|
||||
}
|
||||
let out;
|
||||
table.innerHTML = '';
|
||||
table.classList.remove('hidden');
|
||||
let startDate = new Date(startEl.value);
|
||||
let endDate = new Date(endEl.value);
|
||||
let threadIdObj, senderIdObj;
|
||||
if (senderIdEl.value && senderIdEl.value !== "" && senderIdEl.value.replace(/ /g, "").length > 0) {
|
||||
senderIdObj = JSON.parse(senderIdEl.value);
|
||||
}
|
||||
if (threadIdEl.value && threadIdEl.value !== "" && threadIdEl.value.replace(/ /g, "").length > 0) {
|
||||
threadIdObj = JSON.parse(threadIdEl.value);
|
||||
}
|
||||
let _has = has.value || null;
|
||||
search.searchQuery(queryEl.value, senderIdObj, threadIdObj, _has, startDate, endDate, parseInt(limitEl.value, 10), parseInt(offsetEl.value, 10), 0).then(function (result) {
|
||||
if (result.messages.length < 1) {
|
||||
resultsEl.innerHTML = "No results found"
|
||||
}
|
||||
if (result.messages.length > 0) {
|
||||
out = result;
|
||||
var th = document.createElement('tr');
|
||||
var avatarTh = document.createElement('td');
|
||||
avatarTh.innerText = "Avatar";
|
||||
var senderNameTh = document.createElement('td');
|
||||
senderNameTh.innerText = "Sender Name";
|
||||
var th1 = document.createElement('td');
|
||||
th1.innerText = "ThreadId";
|
||||
var th2 = document.createElement('td');
|
||||
th2.innerText = 'SenderId';
|
||||
var th3 = document.createElement('td');
|
||||
th3.innerText = 'Text';
|
||||
th.appendChild(avatarTh);
|
||||
th.appendChild(senderNameTh);
|
||||
th.appendChild(th1);
|
||||
th.appendChild(th2);
|
||||
th.appendChild(th3);
|
||||
table.appendChild(th);
|
||||
out.messages.forEach(function (msg) {
|
||||
var tr = document.createElement('tr');
|
||||
var avatar = document.createElement('img');
|
||||
avatar.src = msg.senderAvatar;
|
||||
avatar.style.maxWidth = '75px';
|
||||
avatar.style.maxHeight = '75px';
|
||||
var senderName = document.createElement('td');
|
||||
senderName.innerText = msg.senderName;
|
||||
var t1 = document.createElement('td');
|
||||
t1.innerText = msg.threadId;
|
||||
var t2 = document.createElement('td');
|
||||
t2.innerText = msg.senderId;
|
||||
var t3 = document.createElement('td');
|
||||
t3.innerText = msg.text;
|
||||
tr.appendChild(avatar);
|
||||
tr.appendChild(senderName);
|
||||
tr.appendChild(t1);
|
||||
tr.appendChild(t2);
|
||||
tr.appendChild(t3);
|
||||
table.appendChild(tr);
|
||||
});
|
||||
}
|
||||
}).catch(function (err) {
|
||||
resultsEl.innerHTML = 'Error: ' + err;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
sendMessage.addEventListener('click', function () {
|
||||
if (realTimeIndexing.value !== "") {
|
||||
let message = realTimeIndexing.value;
|
||||
search.batchRealTimeIndexing(JSON.parse(message));
|
||||
resultsEl.innerHTML = 'success';
|
||||
} else {
|
||||
resultsEl.innerHTML = "Please check the entered value"
|
||||
}
|
||||
});
|
||||
|
||||
timestamp.addEventListener('click', function () {
|
||||
search.getLatestMessageTimestamp().then(function (res) {
|
||||
resultsEl.innerHTML = res;
|
||||
}).catch(function (err) {
|
||||
resultsEl.innerHTML = 'Error: ' + err;
|
||||
});
|
||||
});
|
||||
|
||||
checkFreeSpace.addEventListener('click', function () {
|
||||
searchUtils.checkFreeSpace().then(function (res) {
|
||||
resultsEl.innerHTML = res ? "Free Disk Space Available" : "Insufficient Disk Space";
|
||||
}).catch(function (err) {
|
||||
resultsEl.innerHTML = err;
|
||||
});
|
||||
});
|
||||
|
||||
viewUserConfig.addEventListener('click', function () {
|
||||
searchUtils.getSearchUserConfig(12345678910112).then(function (res) {
|
||||
resultsEl.innerHTML = JSON.stringify(res);
|
||||
}).catch(function (err) {
|
||||
resultsEl.innerHTML = JSON.stringify(err);
|
||||
});
|
||||
});
|
||||
|
||||
updateUserConfig.addEventListener('click', function () {
|
||||
var data = {
|
||||
rotationId: rotationId.value || 0,
|
||||
version: version.value || 1
|
||||
};
|
||||
searchUtils.updateUserConfig(12345678910112, data).then(function (res) {
|
||||
resultsEl.innerHTML = JSON.stringify(res);
|
||||
}).catch(function (err) {
|
||||
resultsEl.innerHTML = JSON.stringify(err);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,74 +0,0 @@
|
||||
const child = require('child_process');
|
||||
const path = require('path');
|
||||
const isMac = require('../utils/misc.js').isMac;
|
||||
const isDevEnv = require('../utils/misc.js').isDevEnv;
|
||||
const searchConfig = require('../search/searchConfig.js');
|
||||
const ROOT_PATH = isDevEnv ? path.join(__dirname, '..', '..') : searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH;
|
||||
|
||||
/**
|
||||
* Using the child process to execute the tar and lz4
|
||||
* compression and the final output of this function
|
||||
* will be compressed file with ext: .tar.lz4
|
||||
* @param pathToFolder
|
||||
* @param outputPath
|
||||
* @param callback
|
||||
*/
|
||||
function compression(pathToFolder, outputPath, callback) {
|
||||
if (isMac) {
|
||||
child.exec(`cd "${ROOT_PATH}" && tar cf - "${pathToFolder}" | "${searchConfig.LIBRARY_CONSTANTS.MAC_LIBRARY_FOLDER}/lz4.exec" > "${outputPath}.tar.lz4"`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return callback(new Error(error), null);
|
||||
}
|
||||
return callback(null, {
|
||||
stderr: stderr.toString().trim(),
|
||||
stdout: stdout.toString().trim()
|
||||
});
|
||||
})
|
||||
} else {
|
||||
child.exec(`cd "${ROOT_PATH}" && "${searchConfig.LIBRARY_CONSTANTS.WIN_LIBRARY_FOLDER}\\tar-win.exe" cf - "${pathToFolder}" | "${searchConfig.LIBRARY_CONSTANTS.LZ4_PATH}" > "${outputPath}.tar.lz4"`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return callback(new Error(error), null);
|
||||
}
|
||||
return callback(null, {
|
||||
stderr: stderr.toString().trim(),
|
||||
stdout: stdout.toString().trim()
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function decompress the file
|
||||
* and the ext should be .tar.lz4
|
||||
* the output will be the user index folder
|
||||
* @param pathName
|
||||
* @param callback
|
||||
*/
|
||||
function deCompression(pathName, callback) {
|
||||
if (isMac) {
|
||||
child.exec(`cd "${ROOT_PATH}" && "${searchConfig.LIBRARY_CONSTANTS.MAC_LIBRARY_FOLDER}/lz4.exec" -d "${pathName}" | tar -xf - `, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return callback(new Error(error), null);
|
||||
}
|
||||
return callback(null, {
|
||||
stderr: stderr.toString().trim(),
|
||||
stdout: stdout.toString().trim()
|
||||
});
|
||||
})
|
||||
} else {
|
||||
child.exec(`cd "${ROOT_PATH}" && "${searchConfig.LIBRARY_CONSTANTS.LZ4_PATH}" -d "${pathName}" | "${searchConfig.LIBRARY_CONSTANTS.WIN_LIBRARY_FOLDER}\\tar-win.exe" xf - `, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return callback(new Error(error), null);
|
||||
}
|
||||
return callback(null, {
|
||||
stderr: stderr.toString().trim(),
|
||||
stdout: stdout.toString().trim()
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compression,
|
||||
deCompression
|
||||
};
|
@ -1,197 +0,0 @@
|
||||
/**
|
||||
* AES GCM Stream
|
||||
* This module exports encrypt and decrypt stream constructors which can be
|
||||
* used to protect data with authenticated encryption.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
let stream = require('stream');
|
||||
let Transform = stream.Transform;
|
||||
let util = require('util');
|
||||
let crypto = require('crypto');
|
||||
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
|
||||
let KEY_LENGTH = 32; // bytes
|
||||
let GCM_NONCE_LENGTH = 12; //bytes
|
||||
let GCM_MAC_LENGTH = 16; //bytes
|
||||
|
||||
let keyEncoding = 'base64';
|
||||
|
||||
/**
|
||||
* Private helper method to validate a key passed into the Encrypt and Decrypt streams.
|
||||
* Strings are converted it into a buffer, buffers are returned as they are.
|
||||
* @param key
|
||||
* @throws Missing, Encoding, or Length errors
|
||||
* @returns Buffer
|
||||
*/
|
||||
let validateAndConvertKey = function(key) {
|
||||
if (key && key instanceof Buffer && key.length === KEY_LENGTH) {
|
||||
return key;
|
||||
} else if (key && typeof key === 'string') {
|
||||
let bufKey = new Buffer(key, keyEncoding);
|
||||
if (bufKey.length !== KEY_LENGTH) {
|
||||
let encodingErrorMessage = 'Provided key string is either of an unknown encoding (expected: ' +
|
||||
keyEncoding + ') or the wrong length.';
|
||||
throw new Error(encodingErrorMessage);
|
||||
}
|
||||
return bufKey;
|
||||
}
|
||||
let message = 'The key options property is required! Expected ' +
|
||||
keyEncoding + ' encoded string or a buffer.';
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
exports.encrypt = EncryptionStream;
|
||||
exports.decrypt = DecryptionStream;
|
||||
|
||||
/**
|
||||
* createSalt
|
||||
* Helper method that returns a salt
|
||||
* @returns string
|
||||
* @throws error
|
||||
*/
|
||||
exports.createSalt = function(length) {
|
||||
try {
|
||||
return crypto.randomBytes(length);
|
||||
} catch (ex) {
|
||||
throw ex;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EncryptionStream
|
||||
* A constructor which returns an encryption stream
|
||||
* The stream first outputs a 12 byte nonce then encrypted cipher text.
|
||||
* When the stream is flushed it outputs a 16 byte MAC.
|
||||
* @param options Object Object.key is the only required param
|
||||
* @returns {EncryptionStream}
|
||||
* @constructor
|
||||
*/
|
||||
function EncryptionStream(options) {
|
||||
if (!(this instanceof EncryptionStream)) {
|
||||
return new EncryptionStream(options);
|
||||
}
|
||||
|
||||
let nonce = options.nonce || exports.createSalt(12);
|
||||
|
||||
this._key = validateAndConvertKey(options.key);
|
||||
this._cipher = crypto.createCipheriv('aes-256-gcm', this._key, nonce);
|
||||
|
||||
Transform.call(this, options);
|
||||
this.push(nonce);
|
||||
}
|
||||
util.inherits(EncryptionStream, Transform);
|
||||
|
||||
EncryptionStream.prototype._transform = function(chunk, enc, cb) {
|
||||
this.push(this._cipher.update(chunk));
|
||||
cb();
|
||||
};
|
||||
|
||||
EncryptionStream.prototype._flush = function(cb) {
|
||||
// final must be called on the cipher before generating a MAC
|
||||
this._cipher.final(); // this will never output data
|
||||
this.push(this._cipher.getAuthTag()); // 16 bytes
|
||||
|
||||
cb();
|
||||
};
|
||||
|
||||
/**
|
||||
* DecryptionStream
|
||||
* A constructor which returns a decryption stream
|
||||
* The stream assumes the first 12 bytes of data are the nonce and the final
|
||||
* 16 bytes received is the MAC.
|
||||
* @param options Object Object.key is the only required param
|
||||
* @returns {DecryptionStream}
|
||||
* @constructor
|
||||
*/
|
||||
function DecryptionStream(options) {
|
||||
if (!(this instanceof DecryptionStream)) {
|
||||
return new DecryptionStream(options);
|
||||
}
|
||||
|
||||
this._started = false;
|
||||
this._nonce = new Buffer(GCM_NONCE_LENGTH);
|
||||
this._nonceBytesRead = 0;
|
||||
this._cipherTextChunks = [];
|
||||
this._key = validateAndConvertKey(options.key);
|
||||
|
||||
Transform.call(this, options);
|
||||
}
|
||||
util.inherits(DecryptionStream, Transform);
|
||||
|
||||
DecryptionStream.prototype._transform = function(chunk, enc, cb) {
|
||||
let chunkLength = chunk.length;
|
||||
let chunkOffset = 0;
|
||||
let _chunk = chunk;
|
||||
if (!this._started) {
|
||||
if (this._nonceBytesRead < GCM_NONCE_LENGTH) {
|
||||
let nonceRemaining = GCM_NONCE_LENGTH - this._nonceBytesRead;
|
||||
chunkOffset = chunkLength <= nonceRemaining ? chunkLength : nonceRemaining;
|
||||
_chunk.copy(this._nonce, this._nonceBytesRead, 0, chunkOffset);
|
||||
_chunk = _chunk.slice(chunkOffset);
|
||||
chunkLength = _chunk.length;
|
||||
this._nonceBytesRead += chunkOffset;
|
||||
}
|
||||
|
||||
|
||||
if (this._nonceBytesRead === GCM_NONCE_LENGTH) {
|
||||
this._decipher = crypto.createDecipheriv('aes-256-gcm', this._key, this._nonce);
|
||||
this._started = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't use an else because we have no idea how long our chunks will be
|
||||
// all we know is that once we've got a nonce we can start storing cipher text
|
||||
if (this._started) {
|
||||
this._cipherTextChunks.push(_chunk);
|
||||
}
|
||||
|
||||
cb();
|
||||
};
|
||||
|
||||
DecryptionStream.prototype._flush = function(cb) {
|
||||
let mac = pullOutMac(this._cipherTextChunks);
|
||||
if (!mac) {
|
||||
log.send(logLevels.ERROR, 'Crypto: Decryption failed: bad cipher text.');
|
||||
return cb();
|
||||
}
|
||||
this._decipher.setAuthTag(mac);
|
||||
let decrypted = this._cipherTextChunks.map(function(item) {
|
||||
return this._decipher.update(item);
|
||||
}, this);
|
||||
try {
|
||||
this._decipher.final();
|
||||
} catch (e) {
|
||||
log.send(logLevels.ERROR, 'Crypto: Decryption failed: ' + e);
|
||||
return cb();
|
||||
}
|
||||
decrypted.forEach(function(item) {
|
||||
this.push(item);
|
||||
}, this);
|
||||
return cb();
|
||||
};
|
||||
|
||||
function pullOutMac(array) {
|
||||
let macBits = [];
|
||||
let macByteCount = 0;
|
||||
let current, macStartIndex;
|
||||
while (macByteCount !== GCM_MAC_LENGTH && array.length) {
|
||||
current = array.pop();
|
||||
if (macByteCount + current.length <= GCM_MAC_LENGTH) {
|
||||
macBits.push(current);
|
||||
macByteCount += current.length;
|
||||
} else {
|
||||
macStartIndex = (macByteCount + current.length) - GCM_MAC_LENGTH;
|
||||
macBits.push(current.slice(macStartIndex));
|
||||
array.push(current.slice(0, macStartIndex));
|
||||
macByteCount += (current.length - macStartIndex);
|
||||
}
|
||||
}
|
||||
if (macByteCount !== GCM_MAC_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
macBits.reverse();
|
||||
return Buffer.concat(macBits, GCM_MAC_LENGTH);
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const lz4 = require('../compressionLib');
|
||||
const isDevEnv = require('../utils/misc.js').isDevEnv;
|
||||
const crypto = require('./crypto');
|
||||
const log = require('../log.js');
|
||||
const logLevels = require('../enums/logLevels.js');
|
||||
const searchConfig = require('../search/searchConfig.js');
|
||||
|
||||
const DUMP_PATH = isDevEnv ? path.join(__dirname, '..', '..') : searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH;
|
||||
|
||||
class Crypto {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param userId
|
||||
* @param key
|
||||
*/
|
||||
constructor(userId, key) {
|
||||
this.userId = userId;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compressing the user index folder and
|
||||
* encrypting it
|
||||
* @returns {Promise}
|
||||
*/
|
||||
encryption(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!fs.existsSync(`${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME_PATH}_${this.userId}`)){
|
||||
log.send(logLevels.ERROR, 'Crypto: User index folder not found');
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
lz4.compression(`${searchConfig.FOLDERS_CONSTANTS.INDEX_FOLDER_NAME}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}`,
|
||||
`${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}`, (error, response) => {
|
||||
if (error) {
|
||||
log.send(logLevels.ERROR, 'Crypto: Error while compressing to lz4: ' + error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && response.stderr) {
|
||||
log.send(logLevels.WARN, 'Crypto: Child process stderr while compression, ' + response.stderr);
|
||||
}
|
||||
const input = fs.createReadStream(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`);
|
||||
const outputEncryption = fs.createWriteStream(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}.enc`);
|
||||
let config = {
|
||||
key: key
|
||||
};
|
||||
let encrypt;
|
||||
try {
|
||||
encrypt = crypto.encrypt(config);
|
||||
} catch (e) {
|
||||
log.send(logLevels.ERROR, 'Error encrypting : ' + e);
|
||||
if (fs.existsSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`)) {
|
||||
fs.unlinkSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`);
|
||||
}
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
let encryptionProcess = input.pipe(encrypt).pipe(outputEncryption);
|
||||
|
||||
encryptionProcess.on('finish', (err) => {
|
||||
if (err) {
|
||||
log.send(logLevels.ERROR, 'Crypto: Error while encrypting the compressed file: ' + err);
|
||||
if (fs.existsSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`)) {
|
||||
fs.unlinkSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`);
|
||||
}
|
||||
reject(new Error(err));
|
||||
return;
|
||||
}
|
||||
if (fs.existsSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`)) {
|
||||
fs.unlinkSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}${searchConfig.TAR_LZ4_EXT}`);
|
||||
}
|
||||
resolve('Success');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypting the .enc file and unzipping
|
||||
* removing the .enc file and the dump files
|
||||
* @returns {Promise}
|
||||
*/
|
||||
decryption() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!fs.existsSync(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}.enc`)){
|
||||
log.send(logLevels.ERROR, 'Crypto: Encrypted file not found');
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
const input = fs.createReadStream(`${DUMP_PATH}/${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${this.userId}.enc`);
|
||||
const output = fs.createWriteStream(`${DUMP_PATH}/decrypted${searchConfig.TAR_LZ4_EXT}`);
|
||||
let config = {
|
||||
key: this.key
|
||||
};
|
||||
let decrypt;
|
||||
try {
|
||||
decrypt = crypto.decrypt(config);
|
||||
} catch (e) {
|
||||
log.send(logLevels.ERROR, 'Error decrypting : ' + e);
|
||||
if (fs.existsSync(`${DUMP_PATH}/decrypted${searchConfig.TAR_LZ4_EXT}`)) {
|
||||
fs.unlinkSync(`${DUMP_PATH}/decrypted${searchConfig.TAR_LZ4_EXT}`);
|
||||
}
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
let decryptionProcess = input.pipe(decrypt).pipe(output);
|
||||
|
||||
decryptionProcess.on('finish', () => {
|
||||
|
||||
if (!fs.existsSync(`${DUMP_PATH}/decrypted${searchConfig.TAR_LZ4_EXT}`)){
|
||||
log.send(logLevels.ERROR, 'decrypted.tar.lz4 file not found');
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
lz4.deCompression(`${DUMP_PATH}/decrypted${searchConfig.TAR_LZ4_EXT}`,(error, response) => {
|
||||
if (error) {
|
||||
log.send(logLevels.ERROR, 'Crypto: Error while deCompression, ' + error);
|
||||
// no return, need to unlink if error
|
||||
}
|
||||
|
||||
if (response && response.stderr) {
|
||||
log.send(logLevels.WARN, 'Crypto: Child process stderr while deCompression, ' + response.stderr);
|
||||
}
|
||||
fs.unlink(`${DUMP_PATH}/decrypted${searchConfig.TAR_LZ4_EXT}`, () => {
|
||||
resolve('success');
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Crypto;
|
21
js/main.js
21
js/main.js
@ -26,7 +26,6 @@ const protocolHandler = require('./protocolHandler');
|
||||
const getCmdLineArg = require('./utils/getCmdLineArg.js');
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
const { deleteIndexFolder } = require('./search/search.js');
|
||||
|
||||
require('electron-dl')();
|
||||
|
||||
@ -147,7 +146,7 @@ function setChromeFlags() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
app.commandLine.appendSwitch("disable-background-timer-throttling", true);
|
||||
}
|
||||
|
||||
@ -183,12 +182,6 @@ app.on('activate', function() {
|
||||
}
|
||||
});
|
||||
|
||||
app.on('will-quit', function (e) {
|
||||
e.preventDefault();
|
||||
deleteIndexFolder();
|
||||
app.exit();
|
||||
});
|
||||
|
||||
// adds 'symphony' as a protocol
|
||||
// in the system. plist file in macOS
|
||||
// and registry keys in windows
|
||||
@ -227,25 +220,25 @@ function setupThenOpenMainWindow() {
|
||||
|
||||
isAppAlreadyOpen = true;
|
||||
getUrlAndCreateMainWindow();
|
||||
|
||||
|
||||
// Allows a developer to set custom user data path from command line when
|
||||
// launching the app. Mostly used for running automation tests with
|
||||
// multiple instances
|
||||
let customDataArg = getCmdLineArg(process.argv, '--userDataPath=', false);
|
||||
let customDataFolder = customDataArg && customDataArg.substring(customDataArg.indexOf('=') + 1);
|
||||
|
||||
|
||||
if (customDataArg && customDataFolder) {
|
||||
app.setPath('userData', customDataFolder);
|
||||
}
|
||||
|
||||
|
||||
// Event that fixes the remote desktop issue in Windows
|
||||
// by repositioning the browser window
|
||||
electron.screen.on('display-removed', windowMgr.verifyDisplays);
|
||||
|
||||
|
||||
}
|
||||
|
||||
function checkFirstTimeLaunch() {
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
||||
getUserConfigField('version')
|
||||
@ -269,7 +262,7 @@ function checkFirstTimeLaunch() {
|
||||
});
|
||||
return resolve();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,6 +28,23 @@ const KeyCodes = {
|
||||
Esc: 27,
|
||||
};
|
||||
|
||||
let Search;
|
||||
let SearchUtils;
|
||||
|
||||
try {
|
||||
Search = remote.require('swift-search').Search;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Failed to initialize swift search. You'll need to include the search dependency. Contact the developers for more details");
|
||||
}
|
||||
|
||||
try {
|
||||
SearchUtils = remote.require('swift-search').SearchUtils;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Failed to initialize swift search (Utils). You'll need to include the search dependency. Contact the developers for more details");
|
||||
}
|
||||
|
||||
require('../downloadManager');
|
||||
let snackBar;
|
||||
|
||||
@ -173,19 +190,14 @@ function createAPI() {
|
||||
* using the SymphonySearchEngine library
|
||||
* details in ./search/search.js & ./search/searchLibrary.js
|
||||
*/
|
||||
Search: remote.require('./search/search.js').Search,
|
||||
Search: Search || null,
|
||||
|
||||
/**
|
||||
* Provides api for search module utils
|
||||
* like checking free space / search user config data to the client app
|
||||
* details in ./search/searchUtils.js & ./search/searchConfig.js
|
||||
*/
|
||||
SearchUtils: remote.require('./search/searchUtils.js').SearchUtils,
|
||||
|
||||
/**
|
||||
* Function to clear the user index data
|
||||
*/
|
||||
deleteIndexFolder: remote.require('./search/search.js').deleteIndexFolder,
|
||||
SearchUtils: SearchUtils || null,
|
||||
|
||||
/**
|
||||
* Brings window forward and gives focus.
|
||||
|
@ -1,41 +0,0 @@
|
||||
let messagesData = [];
|
||||
|
||||
let makeBoundTimedCollector = function(isIndexing, timeout, callback) {
|
||||
let timer;
|
||||
|
||||
return function (...args) {
|
||||
if (!timer){
|
||||
timer = setTimeout(function(){
|
||||
if (!isIndexing()) {
|
||||
flush(getQueue());
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
let queue = getQueue();
|
||||
queue.push(args[0]);
|
||||
|
||||
if (!isIndexing()) {
|
||||
flush(queue);
|
||||
}
|
||||
};
|
||||
|
||||
function flush(queue) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
resetQueue();
|
||||
if (queue) {
|
||||
callback(JSON.stringify(queue));
|
||||
}
|
||||
}
|
||||
|
||||
function getQueue(){
|
||||
return messagesData;
|
||||
}
|
||||
|
||||
function resetQueue(){
|
||||
messagesData = [];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = makeBoundTimedCollector;
|
@ -1,626 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const ref = require('ref');
|
||||
const childProcess = require('child_process');
|
||||
const path = require('path');
|
||||
const isDevEnv = require('../utils/misc.js').isDevEnv;
|
||||
const isMac = require('../utils/misc.js').isMac;
|
||||
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');
|
||||
|
||||
const INDEX_VALIDATOR = searchConfig.LIBRARY_CONSTANTS.INDEX_VALIDATOR;
|
||||
|
||||
/*eslint class-methods-use-this: ["error", { "exceptMethods": ["deleteRealTimeFolder"] }] */
|
||||
/**
|
||||
* This search class communicates with the SymphonySearchEngine C library via node-ffi.
|
||||
* There should be only 1 instance of this class in the Electron
|
||||
*/
|
||||
class Search {
|
||||
|
||||
/**
|
||||
* Constructor for the SymphonySearchEngine library
|
||||
* @param userId (for the index folder name)
|
||||
* @param key
|
||||
*/
|
||||
constructor(userId, key) {
|
||||
this.isInitialized = false;
|
||||
this.userId = userId;
|
||||
this.key = key;
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypting the existing user .enc file
|
||||
* and initialing the library
|
||||
*/
|
||||
decryptAndInit() {
|
||||
this.crypto.decryption().then(() => {
|
||||
this.init();
|
||||
}).catch(() => {
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* returns isInitialized boolean
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLibInit() {
|
||||
return this.isInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* This init function
|
||||
* initialise the SymphonySearchEngine library
|
||||
* and creates a folder in the userData
|
||||
*/
|
||||
init() {
|
||||
libSymphonySearch.symSEDestroy();
|
||||
libSymphonySearch.symSEInit();
|
||||
libSymphonySearch.symSEClearMainRAMIndex();
|
||||
libSymphonySearch.symSEClearRealtimeRAMIndex();
|
||||
libSymphonySearch.symSEEnsureFolderExists(searchConfig.FOLDERS_CONSTANTS.INDEX_PATH);
|
||||
Search.indexValidator(`${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME_PATH}_${this.userId}`);
|
||||
let indexDateStartFrom = new Date().getTime() - searchConfig.SEARCH_PERIOD_SUBTRACTOR;
|
||||
libSymphonySearch.symSEMainFSIndexToRAMIndex(`${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME_PATH}_${this.userId}`);
|
||||
// Deleting all the messages except 3 Months from now
|
||||
libSymphonySearch.symSEDeleteMessagesFromRAMIndex(null,
|
||||
searchConfig.MINIMUM_DATE, indexDateStartFrom.toString());
|
||||
Search.deleteIndexFolders();
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of messages is passed for indexing
|
||||
* it will be indexed in a temporary index folder
|
||||
* @param {Array} messages
|
||||
* @returns {Promise}
|
||||
*/
|
||||
indexBatch(messages) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!messages) {
|
||||
log.send(logLevels.ERROR, 'Batch Indexing: Messages not provided');
|
||||
reject(new Error('Batch Indexing: Messages are required'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let msg = JSON.parse(messages);
|
||||
if (!(msg instanceof Array)) {
|
||||
log.send(logLevels.ERROR, 'Batch Indexing: Messages must be an array');
|
||||
reject(new Error('Batch Indexing: Messages must be an array'));
|
||||
return;
|
||||
}
|
||||
} catch(e) {
|
||||
log.send(logLevels.ERROR, 'Batch Indexing: parse error -> ' + e);
|
||||
reject(new Error(e));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isInitialized) {
|
||||
log.send(logLevels.ERROR, 'Library not initialized');
|
||||
reject(new Error('Library not initialized'));
|
||||
return;
|
||||
}
|
||||
|
||||
libSymphonySearch.symSEIndexMainRAMAsync(messages, function (err, res) {
|
||||
if (err) {
|
||||
log.send(logLevels.ERROR, `IndexBatch: Error indexing messages to memory : ${err}`);
|
||||
reject(new Error('IndexBatch: Error indexing messages to memory '));
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merging the temporary
|
||||
* created from indexBatch()
|
||||
*/
|
||||
memoryIndexToFSIndex() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
Search.deleteIndexFolders();
|
||||
libSymphonySearch.symSEEnsureFolderExists(searchConfig.FOLDERS_CONSTANTS.INDEX_PATH);
|
||||
|
||||
if (!fs.existsSync(searchConfig.FOLDERS_CONSTANTS.INDEX_PATH)) {
|
||||
log.send(logLevels.ERROR, 'User index folder not found');
|
||||
reject(new Error('User index folder not found'));
|
||||
return;
|
||||
}
|
||||
|
||||
libSymphonySearch.symSEMainRAMIndexToFSIndexAsync(`${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME_PATH}_${this.userId}`, (err, res) => {
|
||||
if (err) {
|
||||
log.send(logLevels.ERROR, 'Error merging the index ->' + err);
|
||||
reject(new Error(err));
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Batching the real time
|
||||
* messages for queue and flush
|
||||
* @param {Object} message
|
||||
*/
|
||||
batchRealTimeIndexing(message) {
|
||||
this.collector(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the
|
||||
* real-time indexing
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkIsRealTimeIndexing() {
|
||||
return this.isRealTimeIndexing;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of messages to be indexed
|
||||
* in real time
|
||||
* @param message
|
||||
*/
|
||||
realTimeIndexing(message) {
|
||||
|
||||
try {
|
||||
let msg = JSON.parse(message);
|
||||
if (!(msg instanceof Array)) {
|
||||
log.send(logLevels.ERROR, 'RealTime Indexing: Messages must be an array real-time indexing');
|
||||
return (new Error('RealTime Indexing: Messages must be an array'));
|
||||
}
|
||||
} catch(e) {
|
||||
log.send(logLevels.ERROR, 'RealTime Indexing: parse error -> ' + e);
|
||||
throw (new Error(e));
|
||||
}
|
||||
|
||||
if (!this.isInitialized) {
|
||||
log.send(logLevels.ERROR, 'Library not initialized');
|
||||
throw new Error('Library not initialized');
|
||||
}
|
||||
|
||||
this.isRealTimeIndexing = true;
|
||||
return libSymphonySearch.symSEIndexRealtimeRAMAsync(message, (err, result) => {
|
||||
this.isRealTimeIndexing = false;
|
||||
if (err) {
|
||||
log.send(logLevels.ERROR, 'RealTime Indexing: error -> ' + err);
|
||||
throw new Error(err);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypting the index after the merging the index
|
||||
* to the main user index
|
||||
*/
|
||||
encryptIndex(key) {
|
||||
return this.crypto.encryption(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the search results
|
||||
* which returns a char *
|
||||
* @param {String} query
|
||||
* @param {Array} senderIds
|
||||
* @param {Array} threadIds
|
||||
* @param {String} fileType
|
||||
* @param {String} startDate
|
||||
* @param {String} endDate
|
||||
* @param {Number} limit
|
||||
* @param {Number} offset
|
||||
* @param {Number} sortOrder
|
||||
* @returns {Promise}
|
||||
*/
|
||||
searchQuery(query, senderIds, threadIds, fileType, startDate,
|
||||
endDate, limit, offset, sortOrder) {
|
||||
|
||||
let _limit = limit;
|
||||
let _offset = offset;
|
||||
let _sortOrder = sortOrder;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isInitialized) {
|
||||
log.send(logLevels.ERROR, 'Library not initialized');
|
||||
reject(new Error('Library not initialized'));
|
||||
return;
|
||||
}
|
||||
|
||||
let q = Search.constructQuery(query, senderIds, threadIds, fileType);
|
||||
|
||||
if (q === undefined) {
|
||||
reject(new Error('Search query error'));
|
||||
return;
|
||||
}
|
||||
|
||||
let searchPeriod = new Date().getTime() - searchConfig.SEARCH_PERIOD_SUBTRACTOR;
|
||||
let startDateTime = searchPeriod;
|
||||
if (startDate) {
|
||||
startDateTime = new Date(parseInt(startDate, 10)).getTime();
|
||||
if (!startDateTime || startDateTime < searchPeriod) {
|
||||
startDateTime = searchPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
let endDateTime = searchConfig.MAXIMUM_DATE;
|
||||
if (endDate) {
|
||||
let eTime = new Date(parseInt(endDate, 10)).getTime();
|
||||
if (eTime) {
|
||||
endDateTime = eTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_limit || _limit === "" || typeof _limit !== 'number' || Math.round(_limit) !== _limit) {
|
||||
_limit = 25;
|
||||
}
|
||||
|
||||
if (!_offset || _offset === "" || typeof _offset !== 'number' || Math.round(_offset) !== _offset) {
|
||||
_offset = 0
|
||||
}
|
||||
|
||||
if (!_sortOrder || _sortOrder === "" || typeof _sortOrder !== 'number' || Math.round(_sortOrder) !== _sortOrder) {
|
||||
_sortOrder = searchConfig.SORT_BY_SCORE;
|
||||
}
|
||||
|
||||
const returnedResult = libSymphonySearch.symSERAMIndexSearch(q, startDateTime.toString(), endDateTime.toString(), _offset, _limit, _sortOrder);
|
||||
try {
|
||||
let ret = ref.readCString(returnedResult);
|
||||
resolve(JSON.parse(ret));
|
||||
} finally {
|
||||
libSymphonySearch.symSEFreeResult(returnedResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the latest message timestamp
|
||||
* from the indexed data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getLatestMessageTimestamp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isInitialized) {
|
||||
log.send(logLevels.ERROR, 'Library not initialized');
|
||||
reject(new Error('Not initialized'));
|
||||
return;
|
||||
}
|
||||
|
||||
libSymphonySearch.symSEMainRAMIndexGetLastMessageTimestampAsync((err, res) => {
|
||||
if (err) {
|
||||
log.send(logLevels.ERROR, 'Error getting the index timestamp ->' + err);
|
||||
reject(new Error(err));
|
||||
}
|
||||
const returnedResult = res;
|
||||
try {
|
||||
let ret = ref.readCString(returnedResult);
|
||||
resolve(ret);
|
||||
} finally {
|
||||
libSymphonySearch.symSEFreeResult(returnedResult);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteRealTimeFolder() {
|
||||
libSymphonySearch.symSEClearRealtimeRAMIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* This the query constructor
|
||||
* for the search function
|
||||
* @param {String} searchQuery
|
||||
* @param {Array} senderId
|
||||
* @param {Array} threadId
|
||||
* @param {String} fileType
|
||||
* @returns {string}
|
||||
*/
|
||||
static constructQuery(searchQuery, senderId, threadId, fileType) {
|
||||
|
||||
let searchText = "";
|
||||
let textQuery = "";
|
||||
if(searchQuery !== undefined) {
|
||||
searchText = searchQuery.trim().toLowerCase(); //to prevent injection of AND and ORs
|
||||
textQuery = Search.getTextQuery(searchText);
|
||||
}
|
||||
let q = "";
|
||||
let hashTags = Search.getHashTags(searchText);
|
||||
let hashCashTagQuery = "";
|
||||
|
||||
if(hashTags.length > 0) {
|
||||
hashCashTagQuery = " OR tags:(";
|
||||
hashTags.forEach((item) => {
|
||||
hashCashTagQuery = hashCashTagQuery + "\"" + item + "\" "
|
||||
});
|
||||
hashCashTagQuery += ")";
|
||||
}
|
||||
|
||||
let hasAttachments = false;
|
||||
let additionalAttachmentQuery = "";
|
||||
if(fileType) {
|
||||
hasAttachments = true;
|
||||
if(fileType.toLowerCase() === "attachment") {
|
||||
additionalAttachmentQuery = "(hasfiles:true)";
|
||||
} else {
|
||||
additionalAttachmentQuery = "(filetype:(" + fileType +"))";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (searchText.length > 0 ) {
|
||||
q = "((text:(" + textQuery + "))" + hashCashTagQuery ;
|
||||
if(hasAttachments) {
|
||||
q += " OR (filename:(" + searchText + "))" ;
|
||||
}
|
||||
q = q + ")";
|
||||
}
|
||||
|
||||
q = Search.appendFilterQuery(q, "senderId", senderId);
|
||||
q = Search.appendFilterQuery(q, "threadId", threadId);
|
||||
|
||||
if(q === "") {
|
||||
if(hasAttachments) {
|
||||
q = additionalAttachmentQuery;
|
||||
} else {
|
||||
q = undefined; //will be handled in the search function
|
||||
}
|
||||
} else {
|
||||
if(hasAttachments){
|
||||
q = q + " AND " + additionalAttachmentQuery
|
||||
}
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
/**
|
||||
* appending the senderId and threadId for the query
|
||||
* @param {String} searchText
|
||||
* @param {String} fieldName
|
||||
* @param {Array} valueArray
|
||||
* @returns {string}
|
||||
*/
|
||||
static appendFilterQuery(searchText, fieldName, valueArray) {
|
||||
let q = "";
|
||||
if (valueArray && valueArray.length > 0 ) {
|
||||
|
||||
q += "(" + fieldName +":(";
|
||||
valueArray.forEach((item)=>{
|
||||
q+= "\"" + item + "\" ";
|
||||
});
|
||||
q += "))";
|
||||
if(searchText.length > 0 ) {
|
||||
q = searchText + " AND " + q;
|
||||
}
|
||||
|
||||
} else {
|
||||
q = searchText;
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
// hashtags can have any characters(before the latest release it was
|
||||
// not like this). So the only regex is splitting the search query based on
|
||||
// whitespaces
|
||||
/**
|
||||
* return the hash cash
|
||||
* tags from the query
|
||||
* @param {String} searchText
|
||||
* @returns {Array}
|
||||
*/
|
||||
static getHashTags(searchText) {
|
||||
let hashTags = [];
|
||||
let tokens = searchText.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s\s+/g, ' ')
|
||||
.split(' ').filter((el) => {return el.length !== 0});
|
||||
tokens.forEach((item) => {
|
||||
if (item.startsWith('#') || item.startsWith('$')) {
|
||||
hashTags.push(item);
|
||||
}
|
||||
});
|
||||
return hashTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the search query does not have double quotes (implying phrase search),
|
||||
* then create all tuples of the terms in the search query
|
||||
* @param {String} searchText
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
static getTextQuery(searchText) {
|
||||
let s1 = searchText.trim().toLowerCase();
|
||||
//if contains quotes we assume it will be a phrase search
|
||||
if(searchText.indexOf("\"") !== -1 ) {
|
||||
return s1;
|
||||
}
|
||||
//else we will create tuples
|
||||
let s2 = s1.replace(/\s{2,}/g," ").trim();
|
||||
let tokens = s2.split(" ");
|
||||
|
||||
let i,j = 0;
|
||||
let out = "";
|
||||
for(i = tokens.length; i > 0; i--) {// number of tokens in a tuple
|
||||
for(j = 0; j < tokens.length-i + 1 ; j++){ //start from index
|
||||
if(out !== ""){
|
||||
out += " ";
|
||||
}
|
||||
out += Search.putTokensInRange(tokens, j, i);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for getTextQuery()
|
||||
* Given a list of tokens create a tuple given the start index of the
|
||||
* token list and given the number of tokens to create.
|
||||
* @param {Array} tokens
|
||||
* @param {Number} start
|
||||
* @param {Number} numTokens
|
||||
* @returns {String}
|
||||
*/
|
||||
static putTokensInRange(tokens, start, numTokens) {
|
||||
let out = "\"";
|
||||
for(let i = 0; i < numTokens; i++) {
|
||||
if(i !== 0) {
|
||||
out += " ";
|
||||
}
|
||||
out+= tokens[start+i];
|
||||
}
|
||||
out += "\"";
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the index folder exist or not
|
||||
* @param {String} file
|
||||
* @returns {*}
|
||||
*/
|
||||
static indexValidator(file) {
|
||||
let data;
|
||||
let result = childProcess.execFileSync(INDEX_VALIDATOR, [file]).toString();
|
||||
try {
|
||||
data = JSON.parse(result);
|
||||
if (data.status === 'OK') {
|
||||
return data;
|
||||
}
|
||||
log.send(logLevels.ERROR, 'Unable validate index folder');
|
||||
return new Error('Unable validate index folder')
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removing all the folders and files inside the data folder
|
||||
*/
|
||||
static deleteIndexFolders() {
|
||||
function removeFiles(filePath) {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.readdirSync(filePath).forEach((file) => {
|
||||
let curPath = filePath + "/" + file;
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
removeFiles(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(filePath);
|
||||
}
|
||||
}
|
||||
removeFiles(searchConfig.FOLDERS_CONSTANTS.INDEX_PATH);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deleting the data index folder
|
||||
* when the app is closed/signed-out/navigates
|
||||
* isEncryption if that is true
|
||||
* will not clear the memory index
|
||||
*/
|
||||
function deleteIndexFolder(isEncryption) {
|
||||
if (!isEncryption) {
|
||||
libSymphonySearch.symSEDestroy();
|
||||
}
|
||||
Search.deleteIndexFolders();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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}}
|
||||
*/
|
||||
module.exports = {
|
||||
Search: Search,
|
||||
deleteIndexFolder: deleteIndexFolder
|
||||
};
|
@ -1,84 +0,0 @@
|
||||
const electron = require('electron');
|
||||
const app = electron.app;
|
||||
const path = require('path');
|
||||
const userData = path.join(app.getPath('userData'));
|
||||
const execPath = path.dirname(app.getPath('exe'));
|
||||
const { isDevEnv, isMac } = require('../utils/misc.js');
|
||||
|
||||
const INDEX_FOLDER_NAME = 'data';
|
||||
|
||||
const winLibraryPath = isDevEnv ? path.join(__dirname, '..', '..', 'library') : path.join(execPath, 'library');
|
||||
const macLibraryPath = isDevEnv ? path.join(__dirname, '..', '..', 'library') : path.join(execPath, '..', 'library');
|
||||
|
||||
const arch = process.arch === 'ia32';
|
||||
|
||||
const winIndexValidatorArch = arch ? 'indexvalidator-x86.exe' : 'indexvalidator-x64.exe';
|
||||
const indexValidatorPath = isMac ? path.join(macLibraryPath, 'indexvalidator.exec') : path.join(winLibraryPath, winIndexValidatorArch);
|
||||
|
||||
const winLZ4ArchPath = arch ? 'lz4-win-x86.exe' : 'lz4-win-x64.exe';
|
||||
const lz4Path = path.join(winLibraryPath, winLZ4ArchPath);
|
||||
|
||||
const indexFolderPath = isDevEnv ? `./${INDEX_FOLDER_NAME}` : path.join(userData, INDEX_FOLDER_NAME);
|
||||
|
||||
const winSearchLibArchPath = arch ? 'libsymphonysearch-x86.dll' : 'libsymphonysearch-x64.dll';
|
||||
const libraryPath = isMac ? path.join(macLibraryPath, 'libsymphonysearch.dylib') : path.join(winLibraryPath, winSearchLibArchPath);
|
||||
|
||||
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 freeDiskSpace = path.join(pathToUtils, isDevEnv ? 'FreeDiskSpace/bin/Release/FreeDiskSpace.exe' : 'FreeDiskSpace.exe');
|
||||
|
||||
|
||||
const libraryPaths = {
|
||||
INDEX_VALIDATOR: indexValidatorPath,
|
||||
LZ4_PATH: lz4Path,
|
||||
MAC_LIBRARY_FOLDER: macLibraryPath,
|
||||
WIN_LIBRARY_FOLDER: winLibraryPath,
|
||||
SEARCH_LIBRARY_PATH: libraryPath,
|
||||
LIBRARY_FOLDER_PATH: libraryFolderPath,
|
||||
LAUNCH_AGENT_FILE: launchAgentFile,
|
||||
LAUNCH_DAEMON_FILE: launchDaemonFile,
|
||||
WINDOWS_TASK_FILE: windowsTaskFile,
|
||||
WINDOWS_CLEAR_SCRIPT: windowsClearScript,
|
||||
FREE_DISK_SPACE: freeDiskSpace,
|
||||
};
|
||||
|
||||
const folderPaths = {
|
||||
INDEX_PATH: indexFolderPath,
|
||||
TEMP_BATCH_INDEX_FOLDER: indexFolderPath + '/temp_batch_indexes',
|
||||
PREFIX_NAME: 'search_index',
|
||||
PREFIX_NAME_PATH: indexFolderPath + '/search_index',
|
||||
EXEC_PATH: execPath,
|
||||
USER_DATA_PATH: userData,
|
||||
INDEX_FOLDER_NAME: INDEX_FOLDER_NAME,
|
||||
USER_CONFIG_FILE: userConfigFile
|
||||
};
|
||||
|
||||
const searchConfig = {
|
||||
SEARCH_PERIOD_SUBTRACTOR: 3 * 31 * 24 * 60 * 60 * 1000,
|
||||
REAL_TIME_INDEXING_TIME: 60000,
|
||||
MINIMUM_DATE: '0000000000000',
|
||||
MAXIMUM_DATE: '9999999999999',
|
||||
SORT_BY_SCORE: 0,
|
||||
INDEX_VERSION: 'v1',
|
||||
BATCH_RANDOM_INDEX_PATH_LENGTH: 20,
|
||||
LIBRARY_CONSTANTS: libraryPaths,
|
||||
FOLDERS_CONSTANTS: folderPaths,
|
||||
TAR_LZ4_EXT: '.tar.lz4',
|
||||
DISK_NOT_READY: 'NOT_READY',
|
||||
DISK_NOT_FOUND: 'DISK_NOT_FOUND',
|
||||
RANDOM_STRING: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
MINIMUM_DISK_SPACE: 300000000, // in bytes
|
||||
PERMISSION_ERROR: "The FSUTIL utility requires that you have administrative privileges.",
|
||||
WIN_PATH_ERROR: "Error: The system cannot find the path specified.",
|
||||
MAC_PATH_ERROR: "No such file or directory"
|
||||
};
|
||||
|
||||
module.exports = searchConfig;
|
@ -1,115 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const ffi = require('ffi');
|
||||
const ref = require('ref');
|
||||
|
||||
const searchConfig = require('../search/searchConfig.js');
|
||||
|
||||
const symLucyIndexer = ref.types.void;
|
||||
const symLucyIndexerPtr = ref.refType(symLucyIndexer);
|
||||
|
||||
/**
|
||||
* Initializing the C SymphonySearchEngine library
|
||||
* using the node-ffi
|
||||
*/
|
||||
let libSymphonySearch = ffi.Library(searchConfig.LIBRARY_CONSTANTS.SEARCH_LIBRARY_PATH, {
|
||||
|
||||
//New Memory Indexing API
|
||||
'symSE_index_main_RAM': ['int', [ 'string' ] ],
|
||||
'symSE_index_realtime_RAM': ['int', [ 'string' ] ],
|
||||
'symSE_main_RAM_index_to_FS_index': ['int', [ 'string' ] ],
|
||||
'symSE_realtime_RAM_index_to_FS_index': ['int', [ 'string' ] ],
|
||||
'symSE_main_RAM_index_get_last_message_timestamp': ['char *', [] ],
|
||||
'symSE_RAM_index_search': ['char *', [ 'string', 'string', 'string', 'int', 'int', 'int' ] ],
|
||||
'symSE_main_FS_index_to_RAM_index': ['int', [ 'string' ] ],
|
||||
'symSE_realtime_FS_index_to_RAM_index': ['int', [ 'string' ] ],
|
||||
'symSE_clear_realtime_RAM_index': ['int', [] ],
|
||||
'symSE_clear_main_RAM_index': ['int', [] ],
|
||||
'symSE_delete_messages_from_RAM_index': ['int', [ 'string', 'string', 'string' ] ],
|
||||
'symSE_destroy': ['int', [] ],
|
||||
|
||||
|
||||
//init
|
||||
'symSE_init': ['void', []],
|
||||
'symSE_remove_folder': ['int', ['string']],
|
||||
'symSE_ensure_index_exists': ['int', ['string']],
|
||||
'symSE_ensure_folder_exists': ['int', ['string']],
|
||||
//first time indexing and delta indexing
|
||||
'symSE_get_indexer': [symLucyIndexerPtr, ['string']], //will be removed
|
||||
'symSE_create_partial_index': ['int', ['string', 'string', 'string']],
|
||||
'symSE_merge_partial_index': ['int', ['string', 'string']],
|
||||
//real time indexing
|
||||
'symSE_index_realtime': ['int', ['string', 'string']],
|
||||
'symSE_merge_temp_index': ['int', ['string', 'string']],
|
||||
'symSE_clear_temp_index': ['int', ['string']],
|
||||
//Search,
|
||||
'symSE_search': ['char *', ['string', 'string', 'string', 'string', 'string', 'int', 'int', 'int']],
|
||||
//Deletion
|
||||
'symSE_delete_messages': ['int', ['string', 'string', 'string', 'string']],
|
||||
//Index commit/optimize
|
||||
'symSE_commit_index': ['int', [symLucyIndexerPtr, 'int']], //will be removed
|
||||
//freePointer
|
||||
'symSE_free_results': ['int', ['char *']],
|
||||
|
||||
//Latest messages timestamp
|
||||
'symSE_get_last_message_timestamp': ['char *', ['string']]
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
// New Memory Indexing API
|
||||
symSEIndexMainRAM: libSymphonySearch.symSE_index_main_RAM,
|
||||
symSEIndexRealtimeRAM: libSymphonySearch.symSE_index_realtime_RAM,
|
||||
symSEMainRAMIndexToFSIndex: libSymphonySearch.symSE_main_RAM_index_to_FS_index,
|
||||
symSERealtimeRAMIndexToFSIndex: libSymphonySearch.symSE_realtime_RAM_index_to_FS_index,
|
||||
symSEMainRAMIndexGetLastMessageTimestamp: libSymphonySearch.symSE_main_RAM_index_get_last_message_timestamp,
|
||||
symSERAMIndexSearch: libSymphonySearch.symSE_RAM_index_search,
|
||||
symSEMainFSIndexToRAMIndex: libSymphonySearch.symSE_main_FS_index_to_RAM_index,
|
||||
symSERealtimeFSIndexToRAMIndex: libSymphonySearch.symSE_realtime_FS_index_to_RAM_index,
|
||||
symSEClearRealtimeRAMIndex: libSymphonySearch.symSE_clear_realtime_RAM_index,
|
||||
symSEClearMainRAMIndex: libSymphonySearch.symSE_clear_main_RAM_index,
|
||||
symSEDeleteMessagesFromRAMIndex: libSymphonySearch.symSE_delete_messages_from_RAM_index,
|
||||
symSEDestroy: libSymphonySearch.symSE_destroy,
|
||||
symSEIndexMainRAMAsync: libSymphonySearch.symSE_index_main_RAM.async,
|
||||
symSEIndexRealtimeRAMAsync: libSymphonySearch.symSE_index_realtime_RAM.async,
|
||||
symSEMainRAMIndexToFSIndexAsync: libSymphonySearch.symSE_main_RAM_index_to_FS_index.async,
|
||||
symSERealtimeRAMIndexToFSIndexAsync: libSymphonySearch.symSE_realtime_RAM_index_to_FS_index.async,
|
||||
symSEMainRAMIndexGetLastMessageTimestampAsync: libSymphonySearch.symSE_main_RAM_index_get_last_message_timestamp.async,
|
||||
symSERAMIndexSearchAsync: libSymphonySearch.symSE_RAM_index_search.async,
|
||||
symSEMainFSIndexToRAMIndexAsync: libSymphonySearch.symSE_main_FS_index_to_RAM_index.async,
|
||||
symSERealtimeFSIndexToRAMIndexAsync: libSymphonySearch.symSE_realtime_FS_index_to_RAM_index.async,
|
||||
symSEClearRealtimeRAMIndexAsync: libSymphonySearch.symSE_clear_realtime_RAM_index.async,
|
||||
symSEClearMainRAMIndexAsync: libSymphonySearch.symSE_clear_main_RAM_index.async,
|
||||
symSEDeleteMessagesFromRAMIndexAsync: libSymphonySearch.symSE_delete_messages_from_RAM_index.async,
|
||||
symSEDestroyAsync: libSymphonySearch.symSE_destroy.async,
|
||||
|
||||
symSEInit: libSymphonySearch.symSE_init,
|
||||
symSERemoveFolder: libSymphonySearch.symSE_remove_folder,
|
||||
symSEEnsureIndexExists: libSymphonySearch.symSE_ensure_index_exists,
|
||||
symSEEnsureFolderExists: libSymphonySearch.symSE_ensure_folder_exists,
|
||||
symSEGetIndexer: libSymphonySearch.symSE_get_indexer,
|
||||
symSECreatePartialIndex: libSymphonySearch.symSE_create_partial_index,
|
||||
symSEMergePartialIndex: libSymphonySearch.symSE_merge_partial_index,
|
||||
symSEIndexRealTime: libSymphonySearch.symSE_index_realtime,
|
||||
symSEMergeTempIndex: libSymphonySearch.symSE_merge_temp_index,
|
||||
symSEClearTempIndex: libSymphonySearch.symSE_clear_temp_index,
|
||||
symSESearch: libSymphonySearch.symSE_search,
|
||||
symSEDeleteMessages: libSymphonySearch.symSE_delete_messages,
|
||||
symSECommitIndex: libSymphonySearch.symSE_commit_index,
|
||||
symSEFreeResult: libSymphonySearch.symSE_free_results,
|
||||
symSEGetLastMessageTimestamp: libSymphonySearch.symSE_get_last_message_timestamp,
|
||||
symSEInitAsync: libSymphonySearch.symSE_init.async,
|
||||
symSERemoveFolderAsync: libSymphonySearch.symSE_remove_folder.async,
|
||||
symSEEnsureIndexExistsAsync: libSymphonySearch.symSE_ensure_index_exists.async,
|
||||
symSEEnsureFolderExistsAsync: libSymphonySearch.symSE_ensure_folder_exists.async,
|
||||
symSEGetIndexerAsync: libSymphonySearch.symSE_get_indexer.async,
|
||||
symSECreatePartialIndexAsync: libSymphonySearch.symSE_create_partial_index.async,
|
||||
symSEMergePartialIndexAsync: libSymphonySearch.symSE_merge_partial_index.async,
|
||||
symSEIndexRealTimeAsync: libSymphonySearch.symSE_index_realtime.async,
|
||||
symSEMergeTempIndexAsync: libSymphonySearch.symSE_merge_temp_index.async,
|
||||
symSEClearTempIndexAsync: libSymphonySearch.symSE_clear_temp_index.async,
|
||||
symSESearchAsync: libSymphonySearch.symSE_search.async,
|
||||
symSEDeleteMessagesAsync: libSymphonySearch.symSE_delete_messages.async,
|
||||
symSECommitIndexAsync: libSymphonySearch.symSE_commit_index.async,
|
||||
symSEFreeResultAsync: libSymphonySearch.symSE_free_results.async,
|
||||
symSEGetLastMessageTimestampAsync: libSymphonySearch.symSE_get_last_message_timestamp.async,
|
||||
};
|
@ -1,180 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const { checkDiskSpace } = require('./utils/checkDiskSpace.js');
|
||||
const searchConfig = require('./searchConfig.js');
|
||||
const { isMac } = require('../utils/misc.js');
|
||||
|
||||
/**
|
||||
* Utils to validate users config data and
|
||||
* available disk space to enable electron search
|
||||
*/
|
||||
/*eslint class-methods-use-this: ["error", { "exceptMethods": ["checkFreeSpace"] }] */
|
||||
class SearchUtils {
|
||||
|
||||
constructor() {
|
||||
this.indexVersion = searchConfig.INDEX_VERSION;
|
||||
}
|
||||
/**
|
||||
* This function returns true if the available disk space
|
||||
* is more than the constant MINIMUM_DISK_SPACE
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
checkFreeSpace() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let userDataPath = searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH;
|
||||
if (!isMac) {
|
||||
try {
|
||||
userDataPath = userDataPath.substring(0, 1);
|
||||
} catch (e) {
|
||||
reject(new Error('Invalid Path : ' + e));
|
||||
}
|
||||
}
|
||||
checkDiskSpace(userDataPath, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function return the user search config
|
||||
* @param userId
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
getSearchUserConfig(userId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readFile.call(this, userId, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function updates the user config file
|
||||
* with the provided data
|
||||
* @param userId
|
||||
* @param data
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
updateUserConfig(userId, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
updateConfig.call(this, userId, data, resolve, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function reads the search user config file and
|
||||
* return the object
|
||||
* @param userId
|
||||
* @param resolve
|
||||
* @param reject
|
||||
*/
|
||||
function readFile(userId, resolve, reject) {
|
||||
if (fs.existsSync(`${searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE}`)) {
|
||||
fs.readFile(`${searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE}`, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return reject(new Error('Error reading the '))
|
||||
}
|
||||
let usersConfig = {};
|
||||
try {
|
||||
usersConfig = JSON.parse(data);
|
||||
} catch (e) {
|
||||
createUserConfigFile(userId);
|
||||
return reject('can not parse user config file data: ' + data + ', error: ' + e);
|
||||
}
|
||||
if (!usersConfig[userId]) {
|
||||
createUser(userId, usersConfig);
|
||||
return reject(null);
|
||||
}
|
||||
return resolve(usersConfig[userId]);
|
||||
})
|
||||
} else {
|
||||
createUserConfigFile(userId);
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the config has no object for the provided userId this function
|
||||
* creates an empty object with the key as the userId
|
||||
* @param userId
|
||||
* @param oldConfig
|
||||
*/
|
||||
function createUser(userId, oldConfig) {
|
||||
let configPath = searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE;
|
||||
let newConfig = Object.assign({}, oldConfig);
|
||||
newConfig[userId] = {};
|
||||
|
||||
let jsonNewConfig = JSON.stringify(newConfig, null, ' ');
|
||||
|
||||
fs.writeFile(configPath, jsonNewConfig, 'utf8', (err) => {
|
||||
if (err) {
|
||||
throw new err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates the config
|
||||
* file if not present
|
||||
* @param userId
|
||||
* @param data
|
||||
*/
|
||||
function createUserConfigFile(userId, data) {
|
||||
let userData = data;
|
||||
|
||||
let createStream = fs.createWriteStream(searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE);
|
||||
if (userData) {
|
||||
if (!userData.indexVersion) {
|
||||
userData.indexVersion = this.indexVersion;
|
||||
}
|
||||
try {
|
||||
userData = JSON.stringify(userData);
|
||||
createStream.write(`{"${userId}": ${userData}}`);
|
||||
} catch (e) {
|
||||
createStream.write(`{"${userId}": {}}`);
|
||||
}
|
||||
} else {
|
||||
createStream.write(`{"${userId}": {}}`);
|
||||
}
|
||||
createStream.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to update user config data
|
||||
* @param userId
|
||||
* @param data
|
||||
* @param resolve
|
||||
* @param reject
|
||||
* @returns {*}
|
||||
*/
|
||||
function updateConfig(userId, data, resolve, reject) {
|
||||
let userData = data;
|
||||
|
||||
if (userData && !userData.indexVersion) {
|
||||
userData.indexVersion = this.indexVersion;
|
||||
}
|
||||
|
||||
let configPath = searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE;
|
||||
if (!fs.existsSync(configPath)) {
|
||||
createUserConfigFile(userId, userData);
|
||||
return reject(null);
|
||||
}
|
||||
|
||||
let oldConfig;
|
||||
let oldData = fs.readFileSync(configPath, 'utf8');
|
||||
|
||||
try {
|
||||
oldConfig = JSON.parse(oldData);
|
||||
} catch (e) {
|
||||
createUserConfigFile(userId, data);
|
||||
return reject(new Error('can not parse user config file data: ' + e));
|
||||
}
|
||||
|
||||
let newConfig = Object.assign({}, oldConfig);
|
||||
newConfig[userId] = data;
|
||||
|
||||
let jsonNewConfig = JSON.stringify(newConfig, null, ' ');
|
||||
|
||||
fs.writeFileSync(configPath, jsonNewConfig, 'utf8');
|
||||
return resolve(newConfig[userId]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SearchUtils: SearchUtils
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
const { exec } = require('child_process');
|
||||
const { isMac } = require('../../utils/misc');
|
||||
const searchConfig = require('../searchConfig.js');
|
||||
const log = require('../../log.js');
|
||||
const logLevels = require('../../enums/logLevels.js');
|
||||
|
||||
function checkDiskSpace(path, resolve, reject) {
|
||||
if (!path) {
|
||||
reject(new Error("Please provide path"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMac) {
|
||||
exec("df -k '" + path.replace(/'/g,"'\\''") + "'", (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
if (stderr.indexOf(searchConfig.MAC_PATH_ERROR) !== -1) {
|
||||
return reject(new Error(`${searchConfig.MAC_PATH_ERROR} ${error}`))
|
||||
}
|
||||
return reject(new Error("Error : " + error));
|
||||
}
|
||||
|
||||
let data = stdout.trim().split("\n");
|
||||
|
||||
let disk_info_str = data[data.length - 1].replace( /[\s\n\r]+/g,' ');
|
||||
let freeSpace = disk_info_str.split(' ');
|
||||
let space = freeSpace[3] * 1024;
|
||||
return resolve(space >= searchConfig.MINIMUM_DISK_SPACE);
|
||||
});
|
||||
} else {
|
||||
exec(`"${searchConfig.LIBRARY_CONSTANTS.FREE_DISK_SPACE}" ${path}`, (error, stdout, stderr) => {
|
||||
|
||||
if (error) {
|
||||
log.send(logLevels.ERROR, `Error retrieving free disk space : ${error}`);
|
||||
log.send(logLevels.ERROR, `Error stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
let data = stdout.trim().split(",");
|
||||
|
||||
if (data[ 1 ] === searchConfig.DISK_NOT_READY) {
|
||||
return reject(new Error("Error : Disk not ready"));
|
||||
}
|
||||
|
||||
if (data[ 1 ] === searchConfig.DISK_NOT_FOUND) {
|
||||
return reject(new Error("Error : Disk not found"));
|
||||
}
|
||||
|
||||
let disk_info_str = data[ 0 ];
|
||||
return resolve(disk_info_str >= searchConfig.MINIMUM_DISK_SPACE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkDiskSpace: checkDiskSpace
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
const searchConfig = require('../searchConfig.js');
|
||||
|
||||
function randomString() {
|
||||
let text = "";
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
text += searchConfig.RANDOM_STRING.charAt(Math.floor(Math.random() * searchConfig.RANDOM_STRING.length));
|
||||
}
|
||||
let time = new Date().getTime();
|
||||
return text + time;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
randomString: randomString
|
||||
};
|
@ -1,105 +0,0 @@
|
||||
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
|
||||
};
|
@ -22,7 +22,6 @@ const eventEmitter = require('./eventEmitter');
|
||||
const throttle = require('./utils/throttle.js');
|
||||
const { getConfigField, updateConfigField, readConfigFileSync, getMultipleConfigField } = require('./config.js');
|
||||
const { isMac, isNodeEnv, isWindows10, isWindowsOS } = require('./utils/misc');
|
||||
const { deleteIndexFolder } = require('./search/search.js');
|
||||
const { isWhitelisted, parseDomain } = require('./utils/whitelistHandler');
|
||||
const { initCrashReporterMain, initCrashReporterRenderer } = require('./crashReporter.js');
|
||||
const i18n = require('./translation/i18n');
|
||||
@ -132,23 +131,23 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
&& isCustomTitleBar
|
||||
&& isWindows10();
|
||||
log.send(logLevels.INFO, `we are configuring a custom title bar for windows -> ${isCustomTitleBarEnabled}`);
|
||||
|
||||
|
||||
ctWhitelist = config && config.ctWhitelist;
|
||||
log.send(logLevels.INFO, `we are configuring certificate transparency whitelist for the domains -> ${ctWhitelist}`);
|
||||
|
||||
|
||||
log.send(logLevels.INFO, `creating main window for ${url}`);
|
||||
|
||||
|
||||
if (config && config !== null && config.customFlags) {
|
||||
|
||||
|
||||
log.send(logLevels.INFO, 'Chrome flags config found!');
|
||||
|
||||
|
||||
if (config.customFlags.authServerWhitelist && config.customFlags.authServerWhitelist !== "") {
|
||||
log.send(logLevels.INFO, 'setting ntlm domains');
|
||||
electronSession.defaultSession.allowNTLMCredentialsForDomains(config.customFlags.authServerWhitelist);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
let newWinOpts = {
|
||||
title: 'Symphony',
|
||||
show: true,
|
||||
@ -309,13 +308,13 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
registerShortcuts();
|
||||
handlePermissionRequests(mainWindow.webContents);
|
||||
|
||||
addWindowKey(key, mainWindow);
|
||||
mainWindow.loadURL(url);
|
||||
|
||||
|
||||
rebuildMenu(lang);
|
||||
|
||||
mainWindow.on('close', function (e) {
|
||||
@ -346,12 +345,12 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
}
|
||||
|
||||
mainWindow.on('closed', destroyAllWindows);
|
||||
|
||||
|
||||
// Manage File Downloads
|
||||
mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
|
||||
// When download is in progress, send necessary data to indicate the same
|
||||
webContents.send('downloadProgress');
|
||||
|
||||
|
||||
// Send file path when download is complete
|
||||
item.once('done', (e, state) => {
|
||||
if (state === 'completed') {
|
||||
@ -371,7 +370,7 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
mainWindow.webContents.session.setCertificateVerifyProc(handleCertificateTransparencyChecks);
|
||||
|
||||
function handleNewWindow(event, newWinUrl, frameName, disposition, newWinOptions) {
|
||||
|
||||
|
||||
let newWinParsedUrl = getParsedUrl(newWinUrl);
|
||||
let mainWinParsedUrl = getParsedUrl(url);
|
||||
|
||||
@ -527,18 +526,18 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
browserWin.removeListener('enter-full-screen', throttledFullScreen);
|
||||
browserWin.removeListener('leave-full-screen', throttledLeaveFullScreen);
|
||||
};
|
||||
|
||||
|
||||
browserWin.on('close', () => {
|
||||
browserWin.webContents.removeListener('new-window', handleNewWindow);
|
||||
browserWin.webContents.removeListener('crashed', handleChildWindowCrashEvent);
|
||||
});
|
||||
|
||||
|
||||
browserWin.once('closed', () => {
|
||||
handleChildWindowClosed();
|
||||
});
|
||||
|
||||
|
||||
handlePermissionRequests(browserWin.webContents);
|
||||
|
||||
|
||||
browserWin.webContents.session.setCertificateVerifyProc(handleCertificateTransparencyChecks);
|
||||
}
|
||||
});
|
||||
@ -550,7 +549,6 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
|
||||
// whenever the main window is navigated for ex: window.location.href or url redirect
|
||||
mainWindow.webContents.on('will-navigate', function (event, navigatedURL) {
|
||||
deleteIndexFolder();
|
||||
isWhitelisted(navigatedURL)
|
||||
.catch(() => {
|
||||
event.preventDefault();
|
||||
@ -562,7 +560,7 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Register shortcuts for the app
|
||||
*/
|
||||
@ -585,7 +583,7 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets permission requests for the window
|
||||
* @param webContents Web contents of the window
|
||||
@ -662,22 +660,22 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
function handleCertificateTransparencyChecks(request, callback) {
|
||||
|
||||
|
||||
const { hostname: hostUrl, errorCode } = request;
|
||||
|
||||
|
||||
if (errorCode === 0) {
|
||||
return callback(0);
|
||||
}
|
||||
|
||||
|
||||
let { tld, domain } = parseDomain(hostUrl);
|
||||
let host = domain + tld;
|
||||
|
||||
|
||||
if (ctWhitelist && Array.isArray(ctWhitelist) && ctWhitelist.length > 0 && ctWhitelist.indexOf(host) > -1) {
|
||||
return callback(0);
|
||||
}
|
||||
|
||||
|
||||
return callback(-2);
|
||||
}
|
||||
|
||||
@ -988,7 +986,7 @@ function checkExternalDisplay(appBounds) {
|
||||
const width = appBounds.width;
|
||||
const height = appBounds.height;
|
||||
const factor = 0.2;
|
||||
|
||||
|
||||
// Loops through all the available displays and
|
||||
// verifies if the wrapper exists within the display bounds
|
||||
// returns false if not exists otherwise true
|
||||
@ -1013,7 +1011,7 @@ function checkExternalDisplay(appBounds) {
|
||||
* was removed and if the wrapper was contained within that bounds
|
||||
*/
|
||||
function repositionMainWindow() {
|
||||
|
||||
|
||||
const { workArea } = electron.screen.getPrimaryDisplay();
|
||||
const bounds = workArea;
|
||||
|
||||
|
11
package.json
11
package.json
@ -16,8 +16,6 @@
|
||||
"spectron-test": "npm run lint && npm rebuild --build-from-source robotjs && cross-env ELECTRON_QA=true jest --config tests/spectron/jest_spectron.json --runInBand && npm run rebuild",
|
||||
"demo-win": "npm run prebuild && cross-env ELECTRON_DEV=true electron . --url=file:///demo/index.html",
|
||||
"demo-mac": "npm run prebuild && cross-env ELECTRON_DEV=true electron . --url=file://$(pwd)/demo/index.html",
|
||||
"search-win": "npm run prebuild && cross-env ELECTRON_DEV=true electron . --url=file:///demo/search.html",
|
||||
"search-mac": "npm run prebuild && cross-env ELECTRON_DEV=true electron . --url=file://$(pwd)/demo/search.html",
|
||||
"unpacked-mac": "npm run prebuild && npm run test && build --mac --dir",
|
||||
"packed-mac": "npm run unpacked-mac && packagesbuild -v installer/mac/symphony-mac-packager.pkgproj",
|
||||
"unpacked-win": "npm run prebuild && npm run test && build --win --x64 --dir",
|
||||
@ -50,10 +48,7 @@
|
||||
"extraFiles": [
|
||||
"config/Symphony.config",
|
||||
"library/libsymphonysearch.dylib",
|
||||
"library/indexvalidator.exec",
|
||||
"library/lz4.exec",
|
||||
"library/search-launch-agent.sh",
|
||||
"library/search-launch-daemon.sh"
|
||||
"library/lz4.exec"
|
||||
],
|
||||
"appId": "com.symphony.electron-desktop",
|
||||
"mac": {
|
||||
@ -122,19 +117,17 @@
|
||||
"electron-log": "2.2.15",
|
||||
"electron-spellchecker": "1.1.2",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"ffi": "2.2.0",
|
||||
"filesize": "3.6.1",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.difference": "4.5.0",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"lodash.omit": "4.5.0",
|
||||
"lodash.pick": "4.4.0",
|
||||
"ref": "1.3.5",
|
||||
"shell-path": "2.1.0",
|
||||
"winreg": "1.2.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.2",
|
||||
"electron-utils": "git+https://github.com/symphonyoss/electron-utils.git#v1.0.5"
|
||||
"swift-search": "1.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,572 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { isWindowsOS } = require('../js/utils/misc.js');
|
||||
|
||||
let executionPath = null;
|
||||
let userConfigDir = null;
|
||||
|
||||
let searchConfig;
|
||||
let SearchApi;
|
||||
let libSymphonySearch;
|
||||
|
||||
jest.mock('electron', function() {
|
||||
return {
|
||||
app: {
|
||||
getPath: mockedGetPath,
|
||||
getName: mockedGetName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function mockedGetName() {
|
||||
return 'Symphony';
|
||||
}
|
||||
|
||||
function mockedGetPath(type) {
|
||||
if (type === 'exe') {
|
||||
return executionPath;
|
||||
}
|
||||
|
||||
if (type === 'userData') {
|
||||
return userConfigDir
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
describe('Tests for Search', function() {
|
||||
|
||||
let userId;
|
||||
let key;
|
||||
let dataFolderPath;
|
||||
let currentDate = new Date().getTime();
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
|
||||
|
||||
beforeAll(function (done) {
|
||||
userId = 12345678910112;
|
||||
key = 'jjjehdnctsjyieoalskcjdhsnahsadndfnusdfsdfsd=';
|
||||
|
||||
executionPath = path.join(__dirname, 'library');
|
||||
if (isWindowsOS) {
|
||||
executionPath = path.join(__dirname, '..', 'library');
|
||||
}
|
||||
userConfigDir = path.join(__dirname, '..');
|
||||
libSymphonySearch = require('../js/search/searchLibrary.js');
|
||||
searchConfig = require('../js/search/searchConfig.js');
|
||||
let root = path.join(userConfigDir, `${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${userId}.enc`);
|
||||
if (fs.existsSync(root)) {
|
||||
fs.unlinkSync(root);
|
||||
}
|
||||
const { Search } = require('../js/search/search.js');
|
||||
SearchApi = new Search(userId, key);
|
||||
|
||||
libSymphonySearch.symSEDestroy();
|
||||
dataFolderPath = path.join(userConfigDir, 'data');
|
||||
if (fs.existsSync(dataFolderPath)) {
|
||||
deleteIndexFolders(dataFolderPath)
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
afterAll(function (done) {
|
||||
setTimeout(function () {
|
||||
|
||||
libSymphonySearch.symSEDestroy();
|
||||
deleteIndexFolders(dataFolderPath);
|
||||
let root = path.join(userConfigDir, `${searchConfig.FOLDERS_CONSTANTS.PREFIX_NAME}_${userId}.enc`);
|
||||
if (fs.existsSync(root)) {
|
||||
fs.unlinkSync(root);
|
||||
}
|
||||
let script = `${searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH}/.symphony`;
|
||||
if (fs.existsSync(script)) {
|
||||
deleteIndexFolders(script);
|
||||
}
|
||||
done();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
function deleteIndexFolders(location, isEncryption) {
|
||||
if (!isEncryption) {
|
||||
libSymphonySearch.symSEDestroy();
|
||||
}
|
||||
if (fs.existsSync(location)) {
|
||||
fs.readdirSync(location).forEach(function(file) {
|
||||
let curPath = path.join(location, file);
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteIndexFolders(curPath, true);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(location);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Search Initial checks', function() {
|
||||
|
||||
it('should be initialized', function (done) {
|
||||
setTimeout(function () {
|
||||
|
||||
expect(SearchApi.isInitialized).toBe(true);
|
||||
expect(SearchApi.messageData).toEqual([]);
|
||||
expect(SearchApi.isRealTimeIndexing).toBe(false);
|
||||
|
||||
done();
|
||||
}, 3000)
|
||||
});
|
||||
|
||||
it('should isLibInit to true', function () {
|
||||
let init = SearchApi.isLibInit();
|
||||
expect(init).toEqual(true);
|
||||
});
|
||||
|
||||
it('should isLibInit to false', function () {
|
||||
SearchApi.isInitialized = false;
|
||||
let init = SearchApi.isLibInit();
|
||||
expect(init).toEqual(false);
|
||||
SearchApi.isInitialized = true;
|
||||
});
|
||||
|
||||
it('should exist index folder', function() {
|
||||
expect(fs.existsSync(path.join(userConfigDir, 'data', 'search_index_12345678910112'))).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Batch indexing process tests', function () {
|
||||
|
||||
it('should index in a batch', function (done) {
|
||||
let messages = [{
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "it works"
|
||||
}, {
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "it works"
|
||||
}, {
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "it works"
|
||||
}];
|
||||
const indexBatch = jest.spyOn(SearchApi, 'indexBatch');
|
||||
SearchApi.indexBatch(JSON.stringify(messages)).then(function () {
|
||||
expect(indexBatch).toHaveBeenCalledWith(JSON.stringify(messages));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not batch index', function (done) {
|
||||
const indexBatch = jest.spyOn(SearchApi, 'indexBatch');
|
||||
SearchApi.indexBatch().catch(function (err) {
|
||||
expect(indexBatch).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not batch index invalid object', function (done) {
|
||||
const indexBatch = jest.spyOn(SearchApi, 'indexBatch');
|
||||
SearchApi.indexBatch('message').catch(function (err) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(indexBatch).toHaveBeenCalledWith('message');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not batch index parse error', function (done) {
|
||||
let message = {
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "it works"
|
||||
};
|
||||
const indexBatch = jest.spyOn(SearchApi, 'indexBatch');
|
||||
SearchApi.indexBatch(JSON.stringify(message)).catch(function (err) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(indexBatch).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not batch index isInitialized is false', function (done) {
|
||||
SearchApi.isInitialized = false;
|
||||
let message = [ {
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "it fails"
|
||||
} ];
|
||||
const indexBatch = jest.spyOn(SearchApi, 'indexBatch');
|
||||
SearchApi.indexBatch(JSON.stringify(message)).catch(function (err) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(indexBatch).toHaveBeenCalledWith(JSON.stringify(message));
|
||||
SearchApi.isInitialized = true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should match messages length after batch indexing', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('it works', [], [], '', undefined, undefined, 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(3);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
done()
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge batch index to user index', function (done) {
|
||||
const memoryIndexToFSIndex = jest.spyOn(SearchApi, 'memoryIndexToFSIndex');
|
||||
SearchApi.memoryIndexToFSIndex().then(function () {
|
||||
expect(memoryIndexToFSIndex).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should exist data folder after ram index to fs index', function () {
|
||||
expect(fs.existsSync(dataFolderPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RealTime indexing process', function () {
|
||||
|
||||
it('should index realTime message', function () {
|
||||
let message = [{
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "realtime working"
|
||||
}];
|
||||
|
||||
const batchRealTimeIndexing = jest.spyOn(SearchApi, 'batchRealTimeIndexing');
|
||||
SearchApi.batchRealTimeIndexing(message);
|
||||
expect(batchRealTimeIndexing).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should match message length', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('realtime working', ["71811853189212"], ["Au8O2xKHyX1LtE6zW019GX///rZYegAtdA=="], '', undefined, undefined, 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(3);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should not index realTime message', function (done) {
|
||||
let message = [{
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "isRealTimeIndexing"
|
||||
}];
|
||||
|
||||
const batchRealTimeIndexing = jest.spyOn(SearchApi, 'batchRealTimeIndexing');
|
||||
const realTimeIndexing = jest.spyOn(SearchApi, 'realTimeIndexing');
|
||||
SearchApi.isRealTimeIndexing = true;
|
||||
expect(SearchApi.checkIsRealTimeIndexing()).toBe(true);
|
||||
SearchApi.batchRealTimeIndexing(message);
|
||||
expect(batchRealTimeIndexing).toHaveBeenCalled();
|
||||
expect(realTimeIndexing).not.toBeCalled();
|
||||
setTimeout(function () {
|
||||
|
||||
SearchApi.searchQuery('isRealTimeIndexing', [], [], '', undefined, undefined, 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
}, 6000)
|
||||
});
|
||||
|
||||
it('should not call the real-time index', function () {
|
||||
let message = [{
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "isRealTimeIndexing"
|
||||
}];
|
||||
|
||||
const batchRealTimeIndexing = jest.spyOn(SearchApi, 'batchRealTimeIndexing');
|
||||
const realTimeIndexing = jest.spyOn(SearchApi, 'realTimeIndexing');
|
||||
SearchApi.isRealTimeIndexing = true;
|
||||
SearchApi.batchRealTimeIndexing(message);
|
||||
expect(batchRealTimeIndexing).toHaveBeenCalled();
|
||||
expect(realTimeIndexing).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not realTime index invalid object', function () {
|
||||
let message = [{
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "isRealTimeIndexing"
|
||||
}];
|
||||
const realTimeIndexing = jest.spyOn(SearchApi, 'realTimeIndexing');
|
||||
expect(function () {
|
||||
SearchApi.realTimeIndexing('message')
|
||||
}).toThrow();
|
||||
|
||||
expect(function () {
|
||||
SearchApi.realTimeIndexing()
|
||||
}).toThrow();
|
||||
|
||||
SearchApi.isInitialized = false;
|
||||
expect(function () {
|
||||
SearchApi.realTimeIndexing(JSON.stringify(message))
|
||||
}).toThrow(new Error('Library not initialized'));
|
||||
SearchApi.isInitialized = true;
|
||||
expect(realTimeIndexing).toHaveBeenCalled();
|
||||
expect(realTimeIndexing).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should return realTime bool', function () {
|
||||
const checkIsRealTimeIndexing = jest.spyOn(SearchApi, 'checkIsRealTimeIndexing');
|
||||
SearchApi.isRealTimeIndexing = true;
|
||||
expect(SearchApi.checkIsRealTimeIndexing()).toBe(true);
|
||||
SearchApi.isRealTimeIndexing = false;
|
||||
expect(SearchApi.checkIsRealTimeIndexing()).toBe(false);
|
||||
expect(checkIsRealTimeIndexing).toHaveBeenCalled();
|
||||
expect(checkIsRealTimeIndexing).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should delete realtime index', function () {
|
||||
const deleteRealTimeFolder = jest.spyOn(SearchApi, 'deleteRealTimeFolder');
|
||||
SearchApi.deleteRealTimeFolder();
|
||||
expect(deleteRealTimeFolder).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test for encryption of the index', function () {
|
||||
|
||||
it('should encrypt user index', function (done) {
|
||||
const encryptIndex = jest.spyOn(SearchApi, 'encryptIndex');
|
||||
SearchApi.encryptIndex(key);
|
||||
expect(encryptIndex).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should exist encrypted file', function (done) {
|
||||
setTimeout(function () {
|
||||
|
||||
expect(fs.existsSync(path.join(userConfigDir, 'search_index_12345678910112.enc'))).toBe(true);
|
||||
expect(fs.existsSync(path.join(userConfigDir, 'search_index_12345678910112.tar.lz4'))).toBe(false);
|
||||
|
||||
done();
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test for latest timestamp', function () {
|
||||
|
||||
it('should get the latest timestamp', function (done) {
|
||||
const getLatestMessageTimestamp = jest.spyOn(SearchApi, 'getLatestMessageTimestamp');
|
||||
SearchApi.getLatestMessageTimestamp().then(function (res) {
|
||||
expect(res).toEqual(currentDate.toString());
|
||||
expect(getLatestMessageTimestamp).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get the latest timestamp', function (done) {
|
||||
const getLatestMessageTimestamp = jest.spyOn(SearchApi, 'getLatestMessageTimestamp');
|
||||
SearchApi.isInitialized = false;
|
||||
SearchApi.getLatestMessageTimestamp().catch(function (err) {
|
||||
expect(err).toEqual(new Error('Not initialized'));
|
||||
expect(getLatestMessageTimestamp).toHaveBeenCalled();
|
||||
SearchApi.isInitialized = true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be equal to 0000000000000', function (done) {
|
||||
const getLatestMessageTimestamp = jest.spyOn(SearchApi, 'getLatestMessageTimestamp');
|
||||
libSymphonySearch.symSEClearMainRAMIndex();
|
||||
libSymphonySearch.symSEClearRealtimeRAMIndex();
|
||||
SearchApi.getLatestMessageTimestamp().then(function (res) {
|
||||
expect(getLatestMessageTimestamp).toHaveBeenCalled();
|
||||
expect(getLatestMessageTimestamp).toHaveBeenCalledTimes(3);
|
||||
expect(res).toEqual('0000000000000');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test to decrypt the index', function () {
|
||||
|
||||
it('should decrypt the index', function (done) {
|
||||
setTimeout(function () {
|
||||
const decryptAndInit = jest.spyOn(SearchApi, 'decryptAndInit');
|
||||
SearchApi.decryptAndInit();
|
||||
expect(decryptAndInit).toHaveBeenCalled();
|
||||
done();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
it('should get message from the decrypted index', function (done) {
|
||||
setTimeout(function () {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
let endTime = new Date().getTime();
|
||||
let startTime = new Date().getTime() - (4 * 31 * 24 * 60 * 60 * 1000);
|
||||
SearchApi.searchQuery('it works', [], [], '', startTime.toString(), endTime.toString(), '0', 0.2, 0.1).then(function (res) {
|
||||
expect(res.messages.length).toEqual(3);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
done()
|
||||
});
|
||||
}, 3000)
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test for search functions', function () {
|
||||
|
||||
it('should search fail isInitialized is false', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.isInitialized = false;
|
||||
SearchApi.searchQuery('it works', [], [], '', '', '', 25, 0, 0).catch(function (err) {
|
||||
expect(err).toEqual(new Error('Library not initialized'));
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
SearchApi.isInitialized = true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter search limit ', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('works', [], [], '', '', '', 2, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toBe(2);
|
||||
expect(searchQuery).toHaveBeenCalledTimes(6);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should search fails result cleared', function (done) {
|
||||
libSymphonySearch.symSEClearMainRAMIndex();
|
||||
libSymphonySearch.symSEClearRealtimeRAMIndex();
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('it works', [], [], '', '', '', 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toBe(0);
|
||||
expect(searchQuery).toHaveBeenCalledTimes(7);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
SearchApi = undefined;
|
||||
const { Search } = require('../js/search/search.js');
|
||||
SearchApi = new Search(userId, key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should search fails query is undefined', function (done) {
|
||||
setTimeout(function () {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
expect(SearchApi.isInitialized).toBe(true);
|
||||
SearchApi.searchQuery(undefined, [], [], '', '', '', 25, 0, 0).catch(function (err) {
|
||||
expect(err).toEqual(new Error('Search query error'));
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
it('should search for hashtag', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('#123 "testing"', [], [], 'attachment', '', '', 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(0);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should search for pdf', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('', [], [], 'pdf', '', '', 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(0);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
expect(searchQuery).toHaveBeenCalledTimes(3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should index for testing quote', function (done) {
|
||||
let messages = [{
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "quote search"
|
||||
}, {
|
||||
messageId: "Jc+4K8RtPxHJfyuDQU9atX///qN3KHYXdA==",
|
||||
threadId: "Au8O2xKHyX1LtE6zW019GX///rZYegAtdA==",
|
||||
ingestionDate: currentDate.toString(),
|
||||
senderId: "71811853189212",
|
||||
chatType: "CHATROOM",
|
||||
isPublic: "false",
|
||||
sendingApp: "lc",
|
||||
text: "search"
|
||||
}];
|
||||
const indexBatch = jest.spyOn(SearchApi, 'indexBatch');
|
||||
SearchApi.indexBatch(JSON.stringify(messages)).then(function () {
|
||||
expect(indexBatch).toHaveBeenCalledWith(JSON.stringify(messages));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should search without quote', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('search', [], [], undefined, '', '', 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(2);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
expect(searchQuery).toHaveBeenCalledTimes(4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should quote search', function (done) {
|
||||
const searchQuery = jest.spyOn(SearchApi, 'searchQuery');
|
||||
SearchApi.searchQuery('\"quote search\"', [], [], undefined, '', '', 25, 0, 0).then(function (res) {
|
||||
expect(res.messages.length).toEqual(1);
|
||||
expect(searchQuery).toHaveBeenCalled();
|
||||
expect(searchQuery).toHaveBeenCalledTimes(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,162 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { isMac, isWindowsOS } = require('../js/utils/misc.js');
|
||||
|
||||
let executionPath = null;
|
||||
let userConfigDir = null;
|
||||
|
||||
let SearchUtilsAPI;
|
||||
let searchConfig;
|
||||
|
||||
jest.mock('electron', function() {
|
||||
return {
|
||||
app: {
|
||||
getPath: mockedGetPath
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function mockedGetPath(type) {
|
||||
switch (type) {
|
||||
case 'exe':
|
||||
return executionPath;
|
||||
case 'userData':
|
||||
return userConfigDir;
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
describe('Tests for Search Utils', function() {
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
|
||||
|
||||
beforeAll(function (done) {
|
||||
executionPath = path.join(__dirname, 'library');
|
||||
if (isWindowsOS) {
|
||||
executionPath = path.join(__dirname, '..', 'library');
|
||||
}
|
||||
userConfigDir = path.join(__dirname, '..');
|
||||
searchConfig = require('../js/search/searchConfig.js');
|
||||
const { SearchUtils } = require('../js/search/searchUtils.js');
|
||||
SearchUtilsAPI = new SearchUtils();
|
||||
SearchUtilsAPI.path = userConfigDir;
|
||||
if (fs.existsSync(searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE)) {
|
||||
fs.unlinkSync(searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
afterAll(function (done) {
|
||||
fs.unlinkSync(searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE);
|
||||
done();
|
||||
});
|
||||
|
||||
describe('Tests for checking disk space', function () {
|
||||
|
||||
it('should return free space', function (done) {
|
||||
const checkFreeSpace = jest.spyOn(SearchUtilsAPI, 'checkFreeSpace');
|
||||
SearchUtilsAPI.checkFreeSpace().then(function () {
|
||||
expect(checkFreeSpace).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error', function (done) {
|
||||
const checkFreeSpace = jest.spyOn(SearchUtilsAPI, 'checkFreeSpace');
|
||||
if (isMac) {
|
||||
searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH = undefined;
|
||||
SearchUtilsAPI.checkFreeSpace().catch(function (err) {
|
||||
expect(err).toEqual(new Error("Please provide path"));
|
||||
expect(checkFreeSpace).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH = undefined;
|
||||
SearchUtilsAPI.checkFreeSpace().catch(function (err) {
|
||||
expect(err).toBeTruthy();
|
||||
expect(checkFreeSpace).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should return error invalid path', function (done) {
|
||||
const checkFreeSpace = jest.spyOn(SearchUtilsAPI, 'checkFreeSpace');
|
||||
SearchUtilsAPI.path = './tp';
|
||||
if (isWindowsOS) {
|
||||
searchConfig.FOLDERS_CONSTANTS.USER_DATA_PATH = 'A://test';
|
||||
searchConfig.LIBRARY_CONSTANTS.FREE_DISK_SPACE = path.join(__dirname, '..',
|
||||
"node_modules/electron-utils/FreeDiskSpace/bin/Release/FreeDiskSpace.exe");
|
||||
}
|
||||
SearchUtilsAPI.checkFreeSpace().catch(function (err) {
|
||||
searchConfig.LIBRARY_CONSTANTS.FREE_DISK_SPACE = path.join(__dirname, '..',
|
||||
"library/FreeDiskSpace.exe");
|
||||
expect(checkFreeSpace).toHaveBeenCalled();
|
||||
expect(err).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test for search users config', function () {
|
||||
|
||||
it('should return null for new user config', function (done) {
|
||||
SearchUtilsAPI.getSearchUserConfig(1234567891011).then(function (res) {
|
||||
expect(res).toEqual(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should exist users config file', function (done) {
|
||||
setTimeout(function () {
|
||||
expect(fs.existsSync(searchConfig.FOLDERS_CONSTANTS.USER_CONFIG_FILE)).toEqual(true);
|
||||
done();
|
||||
}, 2000)
|
||||
});
|
||||
|
||||
it('should exist users config file', function (done) {
|
||||
setTimeout(function () {
|
||||
SearchUtilsAPI.getSearchUserConfig(1234567891011).then(function (res) {
|
||||
expect(res).toEqual({});
|
||||
done();
|
||||
});
|
||||
}, 3000)
|
||||
});
|
||||
|
||||
it('should update user config file', function (done) {
|
||||
let data = {
|
||||
rotationId: 0,
|
||||
version: 1,
|
||||
language: 'en'
|
||||
};
|
||||
SearchUtilsAPI.updateUserConfig(1234567891011, data).then(function (res) {
|
||||
data.indexVersion = 'v1';
|
||||
expect(res).toEqual(data);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should modify user config file', function (done) {
|
||||
let data = {
|
||||
rotationId: 1,
|
||||
version: 1,
|
||||
language: 'en'
|
||||
};
|
||||
SearchUtilsAPI.updateUserConfig(1234567891011, data).then(function (res) {
|
||||
expect(res.rotationId).toEqual(1);
|
||||
expect(res.indexVersion).toEqual('v1');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should create user if not exist', function (done) {
|
||||
SearchUtilsAPI.getSearchUserConfig(2234567891011).catch(function (err) {
|
||||
expect(err).toEqual(null);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user