Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ElectronPlatform: Add support for a event index using Seshat. #11125

Merged
merged 32 commits into from
Nov 26, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1dbdd0a
ElectronPlatform: Add support for a event index using Seshat.
poljar Oct 11, 2019
71023ae
ElectronPlatform: Fix lint errors.
poljar Oct 11, 2019
94196eb
electron-main: Use camle-case for the send_error method.
poljar Nov 8, 2019
a6839af
electron-main: Use a capital letter for the seshat import.
poljar Nov 8, 2019
c3c5756
ElectronPlatform: Implement the EventIndexManager for Seshat.
poljar Nov 13, 2019
449eca6
electron-main: Add a missing break.
poljar Nov 13, 2019
437c59f
electron-main: Check for seshat existence instead of erroring out.
poljar Nov 13, 2019
e9352fc
electron-main: Switch to matrix-seshat.
poljar Nov 14, 2019
b90a94b
electron-main: Enable encryption for Seshat.
poljar Nov 14, 2019
7147af8
ElectronPlatform: Don't scope the event index per user.
poljar Nov 14, 2019
dd2c210
electron-main: Rework the event index initialization and deletion.
poljar Nov 14, 2019
076bf6f
develop: Enable the event indexing feature in labs.
poljar Nov 19, 2019
0813aff
electron-main: Remove an extra newline.
poljar Nov 19, 2019
b17a403
electron-main: No need to normalize the path.
poljar Nov 19, 2019
4a25252
ElectronPlatform: Rename the SeshatIndexerManager.
poljar Nov 19, 2019
73b302f
ElectronPlatform: Fix some type annotations.
poljar Nov 19, 2019
137bedb
ElectronPlatform: Update the path for the BaseEventIndexManager class.
poljar Nov 19, 2019
2f2cbad
electron_app: Remove Seshat from the dependencies.
poljar Nov 21, 2019
d0b5391
docs: Add documentation explaining how to enable Seshat support.
poljar Nov 21, 2019
e96c44c
package.json: Remove the unneeded Neon/Seshat dependencies.
poljar Nov 26, 2019
4c629e8
native-node-modules: Add a header level to the title.
poljar Nov 26, 2019
da4b403
native-node-modules: Don't mention the riot version that supports nat…
poljar Nov 26, 2019
b52141d
native-node-modules: Add a section about cross compilation.
poljar Nov 26, 2019
5f6636e
native-node-modules: Reword the second paragraph.
poljar Nov 26, 2019
b1aff29
native-node-modules: Explain the packaging situation a bit.
poljar Nov 26, 2019
40f2648
native-node-modules: Expand the Seshat subtitle a bit.
poljar Nov 26, 2019
f0fe968
native-node-modules: Capitalize some project names.
poljar Nov 26, 2019
5b8e918
native-node-modules: Explain how to install Rust and link to its docs.
poljar Nov 26, 2019
1869350
native-node-modules: Remove a spurious and.
poljar Nov 26, 2019
b0783a8
native-node-modules: Mention that Seshat requires SQLCipher.
poljar Nov 26, 2019
f28f27a
labs: Document the event indexing labs feature.
poljar Nov 26, 2019
e5956de
labs: Clarify that the event indexing feature supports E2EE search.
poljar Nov 26, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion electron_app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@
"version": "1.4.2",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"scripts": {
"build": "electron-build-env --electron 6.0.3 neon build seshat-node --release",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should really try hard to find a way to build that doesn't duplicate the Electron version in another place, as we'll surely forget to keep them synchronised. Maybe we need a wrapper script to pull it out of the main package.json?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing doesn't even work with the version passed as is. It seems to be only necessary if you need to rebuild seshat for some reason (e.g. if you're linking it from your dev folder) anyways.

Since we're adding run-time checks for seshat anyways it will be safe to remove it as well.

"postinstall": "yarn build"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so this is just to call the line above...?

},
"dependencies": {
"auto-launch": "^5.0.1",
"electron-store": "^2.0.0",
"electron-window-state": "^4.1.0",
"minimist": "^1.2.0",
"png-to-ico": "^1.0.2"
"png-to-ico": "^1.0.2",
"matrix-seshat": "^0.3.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume there will more be changes to move this to documentation in some way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wanna leave it in until it's ready to be merged in case a curious soul wants to try the PR out.

}
}
162 changes: 162 additions & 0 deletions electron_app/src/electron-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ const { migrateFromOldOrigin } = require('./originMigrator');
const windowStateKeeper = require('electron-window-state');
const Store = require('electron-store');

const fs = require('fs');
const afs = fs.promises;

let Seshat = null;

try {
Seshat = require('matrix-seshat');
} catch (e) {
}

if (argv["help"]) {
console.log("Options:");
console.log(" --profile-dir {path}: Path to where to store the profile.");
Expand Down Expand Up @@ -82,8 +92,11 @@ try {
// Could not load local config, this is expected in most cases.
}

const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
const store = new Store({ name: "electron-config" });

let eventIndex = null;

let mainWindow = null;
global.appQuitting = false;
global.minimizeToTray = store.get('minimizeToTray', true);
Expand Down Expand Up @@ -200,6 +213,7 @@ ipcMain.on('ipcCall', async function(ev, payload) {
case 'getConfig':
ret = vectorConfig;
break;

default:
mainWindow.webContents.send('ipcReply', {
id: payload.id,
Expand All @@ -214,6 +228,154 @@ ipcMain.on('ipcCall', async function(ev, payload) {
});
});

ipcMain.on('seshat', async function(ev, payload) {
if (!mainWindow) return;

const sendError = (id, e) => {
const error = {
message: e.message
}

mainWindow.webContents.send('seshatReply', {
id:id,
error: error
});
}

const args = payload.args || [];
let ret;

switch (payload.name) {
case 'supportsEventIndexing':
if (Seshat === null) ret = false;
else ret = true;
break;

case 'initEventIndex':
if (eventIndex === null) {
try {
await afs.mkdir(eventStorePath, {recursive: true});
eventIndex = new Seshat(eventStorePath, {passphrase: "DEFAULT_PASSPHRASE"});
} catch (e) {
sendError(payload.id, e);
return;
}
}
break;

case 'closeEventIndex':
eventIndex = null;
break;

case 'deleteEventIndex':
const deleteFolderRecursive = async(p) => {
for (let entry of await afs.readdir(p)) {
const curPath = path.join(p, entry);
await afs.unlink(curPath);
}
}

try {
await deleteFolderRecursive(eventStorePath);
} catch (e) {
jryans marked this conversation as resolved.
Show resolved Hide resolved
}

break;

case 'isEventIndexEmpty':
if (eventIndex === null) ret = true;
else ret = await eventIndex.isEmpty();
break;

case 'addEventToIndex':
try {
eventIndex.addEvent(args[0], args[1]);
} catch (e) {
sendError(payload.id, e);
return;
}
break;

case 'commitLiveEvents':
try {
ret = await eventIndex.commit();
} catch (e) {
sendError(payload.id, e);
return;
}
break;

case 'searchEventIndex':
try {
ret = await eventIndex.search(args[0]);
} catch (e) {
sendError(payload.id, e);
return;
}
break;

case 'addHistoricEvents':
if (eventIndex === null) ret = false;
else {
try {
ret = await eventIndex.addHistoricEvents(
args[0], args[1], args[2]);
} catch (e) {
sendError(payload.id, e);
return;
}
}
break;

case 'removeCrawlerCheckpoint':
if (eventIndex === null) ret = false;
else {
try {
ret = await eventIndex.removeCrawlerCheckpoint(args[0]);
} catch (e) {
sendError(payload.id, e);
return;
}
}
break;

case 'addCrawlerCheckpoint':
if (eventIndex === null) ret = false;
else {
try {
ret = await eventIndex.addCrawlerCheckpoint(args[0]);
} catch (e) {
sendError(payload.id, e);
return;
}
}
break;

case 'loadCheckpoints':
if (eventIndex === null) ret = [];
else {
try {
ret = await eventIndex.loadCheckpoints();
} catch (e) {
ret = [];
}
}
break;

default:
mainWindow.webContents.send('seshatReply', {
id: payload.id,
error: "Unknown IPC Call: " + payload.name,
});
return;
}

mainWindow.webContents.send('seshatReply', {
id: payload.id,
reply: ret,
});
});

app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');

const gotLock = app.requestSingleInstanceLock();
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@
"source-map-loader": "^0.2.4",
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.11"
"webpack-dev-server": "^3.1.11",
"electron-build-env": "^0.2.0",
jryans marked this conversation as resolved.
Show resolved Hide resolved
"neon-cli": "^0.3.1"
},
"build": {
"appId": "im.riot.app",
Expand Down
3 changes: 2 additions & 1 deletion riot.im/develop/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"feature_sas": "labs",
"feature_room_breadcrumbs": "labs",
"feature_state_counters": "labs",
"feature_many_integration_managers": "labs"
"feature_many_integration_managers": "labs",
"feature_event_indexing": "labs"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a short explanation of this flag in the labs docs as per the guide.

},
"welcomeUserId": "@riot-bot:matrix.org",
"piwik": {
Expand Down
97 changes: 97 additions & 0 deletions src/vector/platform/ElectronPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
*/

import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import BaseEventIndexManager from 'matrix-react-sdk/lib/indexing/BaseEventIndexManager';
import dis from 'matrix-react-sdk/lib/dispatcher';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import Promise from 'bluebird';
Expand Down Expand Up @@ -66,12 +67,104 @@ function getUpdateCheckStatus(status) {
}
}

class SeshatIndexManager extends BaseEventIndexManager {
constructor() {
super();

this._pendingIpcCalls = {};
this._nextIpcCallId = 0;
ipcRenderer.on('seshatReply', this._onIpcReply.bind(this));
}

async _ipcCall(name: string, ...args: []): Promise<{}> {
// TODO this should be moved into the preload.js file.
const ipcCallId = ++this._nextIpcCallId;
return new Promise((resolve, reject) => {
this._pendingIpcCalls[ipcCallId] = {resolve, reject};
window.ipcRenderer.send('seshat', {id: ipcCallId, name, args});
});
}

_onIpcReply(ev: {}, payload: {}) {
if (payload.id === undefined) {
console.warn("Ignoring IPC reply with no ID");
return;
}

if (this._pendingIpcCalls[payload.id] === undefined) {
console.warn("Unknown IPC payload ID: " + payload.id);
return;
}

const callbacks = this._pendingIpcCalls[payload.id];
delete this._pendingIpcCalls[payload.id];
if (payload.error) {
callbacks.reject(payload.error);
} else {
callbacks.resolve(payload.reply);
}
}

async supportsEventIndexing(): Promise<boolean> {
return this._ipcCall('supportsEventIndexing');
}

async initEventIndex(): Promise<> {
return this._ipcCall('initEventIndex');
}

async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<> {
return this._ipcCall('addEventToIndex', ev, profile);
}

async isEventIndexEmpty(): Promise<boolean> {
return this._ipcCall('isEventIndexEmpty');
}

async commitLiveEvents(): Promise<> {
return this._ipcCall('commitLiveEvents');
}

async searchEventIndex(searchConfig: SearchConfig): Promise<SearchResult> {
return this._ipcCall('searchEventIndex', searchConfig);
}

async addHistoricEvents(
events: [HistoricEvent],
checkpoint: CrawlerCheckpoint | null,
oldCheckpoint: CrawlerCheckpoint | null,
): Promise<> {
return this._ipcCall('addHistoricEvents', events, checkpoint, oldCheckpoint);
}

async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
return this._ipcCall('addCrawlerCheckpoint', checkpoint);
}

async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
return this._ipcCall('removeCrawlerCheckpoint', checkpoint);
}

async loadCheckpoints(): Promise<[CrawlerCheckpoint]> {
return this._ipcCall('loadCheckpoints');
}

async closeEventIndex(): Promise<> {
return this._ipcCall('closeEventIndex');
}

async deleteEventIndex(): Promise<> {
return this._ipcCall('deleteEventIndex');
}
}

export default class ElectronPlatform extends VectorBasePlatform {
constructor() {
super();

this._pendingIpcCalls = {};
this._nextIpcCallId = 0;
this.eventIndexManager = new SeshatIndexManager();

dis.register(_onAction);
/*
Expand Down Expand Up @@ -293,4 +386,8 @@ export default class ElectronPlatform extends VectorBasePlatform {
callbacks.resolve(payload.reply);
}
}

getEventIndexingManager(): BaseEventIndexManager | null {
return this.eventIndexManager;
}
}