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 19 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/native-node-modules.md
@@ -0,0 +1,44 @@
Native Node Modules
jryans marked this conversation as resolved.
Show resolved Hide resolved

Since v???, the electron version of Riot can make use of native node modules.
jryans marked this conversation as resolved.
Show resolved Hide resolved
These allow Riot to integrate with the desktop in ways that a browser cannot.

While handy, these modules must be compiled and are thus downloaded
pre-compiled during build so that a single OS can compile Riot for all
platforms. If you would like to compile the native node modules from source,
as is done for Riot releases, instead of trusting binaries hosted on npm,
then please read on.
jryans marked this conversation as resolved.
Show resolved Hide resolved

Do note that compiling a module for a particular operating system
(Linux/Mac/Windows) and will need to be done on that operating system.
jryans marked this conversation as resolved.
Show resolved Hide resolved

jryans marked this conversation as resolved.
Show resolved Hide resolved
## Adding Seshat support
jryans marked this conversation as resolved.
Show resolved Hide resolved

Seshat is a native node library that adds support for local event indexing and
jryans marked this conversation as resolved.
Show resolved Hide resolved
full text search in E2E encrypted rooms.

Since Seshat is written in rust the rust compiler and cargo tool-chain need to be
jryans marked this conversation as resolved.
Show resolved Hide resolved
installed before installing Seshat itself. After installing the compiler Seshat
jryans marked this conversation as resolved.
Show resolved Hide resolved
support can be added using yarn inside the `electron_app/` directory:

yarn add matrix-seshat
jryans marked this conversation as resolved.
Show resolved Hide resolved

After this is done the electron version of riot can be run from the main folder
jryans marked this conversation as resolved.
Show resolved Hide resolved
as usual using:

yarn electron

If for some reason recompilation of Seshat is needed, e.g. when using a
development version of Seshat using `yarn link`, or if the initial compilation was
done for the wrong electron version, Seshat can be recompiled with the
`electron-build-env` tool. Again from the `electron_app/` directory:

yarn add electron-build-env

Recompiling Seshat itself can be done like so:

yarn run electron-build-env -- --electron 6.1.1 -- neon build matrix-seshat --release`

Please make sure to include all the `--` as well as the `--release` command line
switch at the end. Modify your electron version accordingly depending on the
version that is installed on your system.
Comment on lines +46 to +59
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 is electron-build-env needed even for the first installation of the matrix-seshat Node module, or is only a rebuild helper?

If it's only used for rebuilds, I wonder if we can find some way to repeat the build that happens at install time without this extra tool somehow, such as (cd node_modules/matrix-seshat; yarn run install) or similar.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I also noticed that the Electron docs suggest electron-rebuild, is that of any use here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe it's only needed to rebuild it, but I'm not 100% on that.

The neon docs state this:

We are working on adding Neon support to electron-rebuild, so you'll be able to just drop Neon dependencies into your app like any other. For now, there's a tool called electron-build-env that you can use for building any Neon dependencies of your Electron app.

As far as I can tell this has not changed as of yet.

Copy link
Collaborator

Choose a reason for hiding this comment

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

After looking around a bit, it's quite difficult to follow what's actually recommended and also matches what we need here. 😓 I don't want to block all this work on sorting it out though, so I filed a separate issue to improve this in a future step.

162 changes: 162 additions & 0 deletions electron_app/src/electron-main.js
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
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
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
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;
}
}