2019-10-07 17:11:53 -06:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import Moment from 'moment';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import cx from 'classnames';
|
|
|
|
import { createWriteStream } from 'streamsaver';
|
|
|
|
import Panda from '@commaai/pandajs';
|
|
|
|
import CommaAuth from '@commaai/my-comma-auth';
|
|
|
|
import { raw as RawDataApi, drives as DrivesApi } from '@commaai/comma-api';
|
2019-09-16 11:38:41 -06:00
|
|
|
import {
|
|
|
|
USE_UNLOGGER,
|
|
|
|
PART_SEGMENT_LENGTH,
|
|
|
|
STREAMING_WINDOW,
|
|
|
|
GITHUB_AUTH_TOKEN_KEY
|
2019-10-07 17:11:53 -06:00
|
|
|
} from './config';
|
|
|
|
import * as GithubAuth from './api/github-auth';
|
|
|
|
|
|
|
|
import DBC from './models/can/dbc';
|
|
|
|
import Meta from './components/Meta';
|
|
|
|
import Explorer from './components/Explorer';
|
|
|
|
import OnboardingModal from './components/Modals/OnboardingModal';
|
|
|
|
import SaveDbcModal from './components/SaveDbcModal';
|
|
|
|
import LoadDbcModal from './components/LoadDbcModal';
|
|
|
|
import debounce from './utils/debounce';
|
|
|
|
import EditMessageModal from './components/EditMessageModal';
|
|
|
|
import LoadingBar from './components/LoadingBar';
|
2017-12-12 19:24:01 -07:00
|
|
|
import {
|
|
|
|
persistDbc,
|
|
|
|
fetchPersistedDbc,
|
|
|
|
unpersistGithubAuthToken
|
2019-10-07 17:11:53 -06:00
|
|
|
} from './api/localstorage';
|
|
|
|
import OpenDbc from './api/OpenDbc';
|
|
|
|
import UnloggerClient from './api/unlogger';
|
|
|
|
import * as ObjectUtils from './utils/object';
|
|
|
|
import { hash } from './utils/string';
|
|
|
|
import { modifyQueryParameters } from './utils/url';
|
|
|
|
|
|
|
|
const RLogDownloader = require('./workers/rlog-downloader.worker.js');
|
|
|
|
const LogCSVDownloader = require('./workers/dbc-csv-downloader.worker.js');
|
|
|
|
const MessageParser = require('./workers/message-parser.worker.js');
|
|
|
|
const CanStreamerWorker = require('./workers/CanStreamerWorker.worker.js');
|
2017-06-28 19:03:21 -06:00
|
|
|
|
2018-09-03 15:13:25 -06:00
|
|
|
export default class CanExplorer extends Component {
|
2017-12-12 19:24:01 -07:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
messages: {},
|
2019-10-09 12:32:17 -06:00
|
|
|
thumbnails: [],
|
2017-12-12 19:24:01 -07:00
|
|
|
selectedMessages: [],
|
2018-09-03 15:13:25 -06:00
|
|
|
route: null,
|
2019-08-28 12:25:35 -06:00
|
|
|
canFrameOffset: 0,
|
2019-02-13 15:27:27 -07:00
|
|
|
firstCanTime: null,
|
2017-12-12 19:24:01 -07:00
|
|
|
lastBusTime: null,
|
|
|
|
selectedMessage: null,
|
2018-09-03 15:13:25 -06:00
|
|
|
currentParts: [0, 0],
|
2019-09-09 14:26:43 -06:00
|
|
|
currentPart: 0,
|
|
|
|
currentWorkers: {},
|
|
|
|
loadingParts: [],
|
|
|
|
loadedParts: [],
|
2017-12-12 19:24:01 -07:00
|
|
|
showOnboarding: false,
|
|
|
|
showLoadDbc: false,
|
|
|
|
showSaveDbc: false,
|
|
|
|
showEditMessageModal: false,
|
|
|
|
editMessageModalMessage: null,
|
|
|
|
dbc: props.dbc ? props.dbc : new DBC(),
|
|
|
|
dbcText: props.dbc ? props.dbc.text() : new DBC().text(),
|
2019-10-07 17:11:53 -06:00
|
|
|
dbcFilename: props.dbcFilename ? props.dbcFilename : 'New_DBC',
|
2017-12-12 19:24:01 -07:00
|
|
|
dbcLastSaved: null,
|
2018-09-03 15:13:25 -06:00
|
|
|
seekTime: 0,
|
2017-12-12 19:24:01 -07:00
|
|
|
seekIndex: 0,
|
|
|
|
maxByteStateChangeCount: 0,
|
|
|
|
isLoading: true,
|
|
|
|
partsLoaded: 0,
|
|
|
|
spawnWorkerHash: null,
|
|
|
|
attemptingPandaConnection: false,
|
|
|
|
pandaNoDeviceSelected: false,
|
|
|
|
live: false,
|
|
|
|
isGithubAuthenticated:
|
2019-09-16 11:38:41 -06:00
|
|
|
props.githubAuthToken !== null && props.githubAuthToken !== undefined,
|
|
|
|
shareUrl: null,
|
|
|
|
logUrls: null
|
2017-05-29 17:52:17 -06:00
|
|
|
};
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
this.openDbcClient = new OpenDbc(props.githubAuthToken);
|
|
|
|
if (USE_UNLOGGER) {
|
|
|
|
this.unloggerClient = new UnloggerClient();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.showOnboarding = this.showOnboarding.bind(this);
|
|
|
|
this.hideOnboarding = this.hideOnboarding.bind(this);
|
|
|
|
this.showLoadDbc = this.showLoadDbc.bind(this);
|
|
|
|
this.hideLoadDbc = this.hideLoadDbc.bind(this);
|
|
|
|
this.showSaveDbc = this.showSaveDbc.bind(this);
|
|
|
|
this.hideSaveDbc = this.hideSaveDbc.bind(this);
|
|
|
|
this.showEditMessageModal = this.showEditMessageModal.bind(this);
|
|
|
|
this.hideEditMessageModal = this.hideEditMessageModal.bind(this);
|
|
|
|
this.onDbcSelected = this.onDbcSelected.bind(this);
|
|
|
|
this.onDbcSaved = this.onDbcSaved.bind(this);
|
|
|
|
this.onConfirmedSignalChange = this.onConfirmedSignalChange.bind(this);
|
|
|
|
this.onPartChange = this.onPartChange.bind(this);
|
|
|
|
this.onMessageFrameEdited = this.onMessageFrameEdited.bind(this);
|
|
|
|
this.onSeek = this.onSeek.bind(this);
|
|
|
|
this.onUserSeek = this.onUserSeek.bind(this);
|
|
|
|
this.onMessageSelected = this.onMessageSelected.bind(this);
|
|
|
|
this.onMessageUnselected = this.onMessageUnselected.bind(this);
|
|
|
|
this.initCanData = this.initCanData.bind(this);
|
|
|
|
this.updateSelectedMessages = this.updateSelectedMessages.bind(this);
|
|
|
|
this.handlePandaConnect = this.handlePandaConnect.bind(this);
|
|
|
|
this.processStreamedCanMessages = this.processStreamedCanMessages.bind(
|
|
|
|
this
|
|
|
|
);
|
|
|
|
this.onStreamedCanMessagesProcessed = this.onStreamedCanMessagesProcessed.bind(
|
|
|
|
this
|
|
|
|
);
|
|
|
|
this.showingModal = this.showingModal.bind(this);
|
|
|
|
this.lastMessageEntriesById = this.lastMessageEntriesById.bind(this);
|
|
|
|
this.githubSignOut = this.githubSignOut.bind(this);
|
2018-03-17 17:54:07 -06:00
|
|
|
this.downloadLogAsCSV = this.downloadLogAsCSV.bind(this);
|
2018-03-18 18:46:30 -06:00
|
|
|
|
|
|
|
this.pandaReader = new Panda();
|
|
|
|
this.pandaReader.onMessage(this.processStreamedCanMessages);
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillMount() {
|
|
|
|
const { dongleId, name } = this.props;
|
2019-06-14 15:39:49 -06:00
|
|
|
if (CommaAuth.isAuthenticated() && !name) {
|
|
|
|
this.showOnboarding();
|
2019-09-16 11:38:41 -06:00
|
|
|
} else if (
|
2019-10-07 17:11:53 -06:00
|
|
|
this.props.max
|
|
|
|
&& this.props.url
|
|
|
|
&& !this.props.exp
|
|
|
|
&& !this.props.sig
|
2019-09-16 11:38:41 -06:00
|
|
|
) {
|
2018-05-09 12:06:52 -06:00
|
|
|
// probably the demo!
|
2017-12-12 19:24:01 -07:00
|
|
|
const { max, url } = this.props;
|
2019-10-07 17:11:53 -06:00
|
|
|
const startTime = Moment(name, 'YYYY-MM-DD--H-m-s');
|
2017-12-12 19:24:01 -07:00
|
|
|
|
|
|
|
const route = {
|
2019-10-07 17:11:53 -06:00
|
|
|
fullname: `${dongleId}|${name}`,
|
2017-12-12 19:24:01 -07:00
|
|
|
proclog: max,
|
2019-10-07 17:11:53 -06:00
|
|
|
url,
|
2017-12-12 19:24:01 -07:00
|
|
|
start_time: startTime
|
|
|
|
};
|
2018-09-03 15:13:25 -06:00
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
route,
|
|
|
|
currentParts: [0, Math.min(max, PART_SEGMENT_LENGTH - 1)]
|
|
|
|
},
|
|
|
|
this.initCanData
|
|
|
|
);
|
2017-12-12 19:24:01 -07:00
|
|
|
} else if (dongleId && name) {
|
2019-10-07 17:11:53 -06:00
|
|
|
const routeName = `${dongleId}|${name}`;
|
|
|
|
let urlPromise;
|
|
|
|
let logUrlsPromise;
|
2019-09-16 11:38:41 -06:00
|
|
|
|
2019-06-14 15:39:49 -06:00
|
|
|
if (this.props.url) {
|
|
|
|
urlPromise = Promise.resolve(this.props.url);
|
|
|
|
} else {
|
2019-10-07 17:11:53 -06:00
|
|
|
urlPromise = DrivesApi.getRouteInfo(routeName).then((route) => route.url);
|
2019-06-14 15:39:49 -06:00
|
|
|
}
|
2019-09-16 11:38:41 -06:00
|
|
|
|
|
|
|
if (this.props.sig && this.props.exp) {
|
|
|
|
logUrlsPromise = RawDataApi.getLogUrls(routeName, {
|
|
|
|
sig: this.props.sig,
|
|
|
|
exp: this.props.exp
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
logUrlsPromise = RawDataApi.getLogUrls(routeName);
|
|
|
|
}
|
|
|
|
Promise.all([urlPromise, logUrlsPromise])
|
2019-10-07 17:11:53 -06:00
|
|
|
.then((initData) => {
|
|
|
|
const [url, logUrls] = initData;
|
2019-06-14 15:39:49 -06:00
|
|
|
const newState = {
|
|
|
|
route: {
|
|
|
|
fullname: routeName,
|
|
|
|
proclog: logUrls.length - 1,
|
2019-10-07 17:11:53 -06:00
|
|
|
start_time: Moment(name, 'YYYY-MM-DD--H-m-s'),
|
2019-06-14 15:39:49 -06:00
|
|
|
url
|
|
|
|
},
|
|
|
|
currentParts: [
|
|
|
|
0,
|
|
|
|
Math.min(logUrls.length - 1, PART_SEGMENT_LENGTH - 1)
|
2019-09-16 11:38:41 -06:00
|
|
|
],
|
|
|
|
logUrls
|
2019-06-14 15:39:49 -06:00
|
|
|
};
|
|
|
|
this.setState(newState, this.initCanData);
|
2019-09-16 11:38:41 -06:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
DrivesApi.getShareSignature(routeName).then((shareSignature) => this.setState({
|
|
|
|
shareUrl: modifyQueryParameters({
|
|
|
|
add: {
|
|
|
|
exp: shareSignature.exp,
|
|
|
|
sig: shareSignature.sig,
|
|
|
|
max: logUrls.length - 1,
|
|
|
|
url
|
|
|
|
},
|
|
|
|
remove: [GITHUB_AUTH_TOKEN_KEY]
|
2019-09-16 11:38:41 -06:00
|
|
|
})
|
2019-10-07 17:11:53 -06:00
|
|
|
}));
|
2017-12-12 19:24:01 -07:00
|
|
|
})
|
2019-10-07 17:11:53 -06:00
|
|
|
.catch((err) => {
|
2019-06-14 15:39:49 -06:00
|
|
|
console.error(err);
|
2017-11-06 18:52:43 -07:00
|
|
|
this.showOnboarding();
|
2017-08-03 15:41:52 -06:00
|
|
|
});
|
2017-12-12 19:24:01 -07:00
|
|
|
} else {
|
|
|
|
this.showOnboarding();
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
2017-05-29 17:52:17 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
initCanData() {
|
2018-09-03 15:13:25 -06:00
|
|
|
const { route } = this.state;
|
2017-06-28 19:03:21 -06:00
|
|
|
|
2019-10-03 13:47:08 -06:00
|
|
|
this.spawnWorker(this.state.currentParts);
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
onDbcSelected(dbcFilename, dbc) {
|
2018-09-03 15:13:25 -06:00
|
|
|
const { route } = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
this.hideLoadDbc();
|
|
|
|
this.persistDbc({ dbcFilename, dbc });
|
|
|
|
|
|
|
|
if (route) {
|
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
dbc,
|
|
|
|
dbcFilename,
|
|
|
|
dbcText: dbc.text(),
|
|
|
|
partsLoaded: 0,
|
|
|
|
selectedMessage: null,
|
|
|
|
messages: {}
|
|
|
|
},
|
|
|
|
() => {
|
2017-08-03 15:41:52 -06:00
|
|
|
// Pass DBC text to webworker b/c can't pass instance of es6 class
|
2019-09-09 14:26:43 -06:00
|
|
|
this.spawnWorker();
|
2017-12-06 14:27:50 -07:00
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
);
|
|
|
|
} else {
|
2019-10-07 17:11:53 -06:00
|
|
|
this.setState({
|
|
|
|
dbc,
|
|
|
|
dbcFilename,
|
|
|
|
dbcText: dbc.text(),
|
|
|
|
messages: {}
|
|
|
|
});
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onDbcSaved(dbcFilename) {
|
|
|
|
const dbcLastSaved = Moment();
|
|
|
|
this.setState({ dbcLastSaved, dbcFilename });
|
|
|
|
this.hideSaveDbc();
|
|
|
|
}
|
|
|
|
|
|
|
|
// async downloadDbcFile() {
|
|
|
|
// const blob = new Blob([this.props.dbc.text()], {type: "text/plain;charset=utf-8"});
|
|
|
|
// const filename = this.state.dbcFilename.replace(/\.dbc/g, '') + '.dbc';
|
|
|
|
// FileSaver.saveAs(blob, filename, true);
|
|
|
|
// }
|
|
|
|
|
2018-03-17 17:54:07 -06:00
|
|
|
downloadLogAsCSV() {
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('downloadLogAsCSV:start');
|
2018-03-17 17:54:07 -06:00
|
|
|
const { dbcFilename } = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
const fileStream = createWriteStream(
|
2019-10-07 17:11:53 -06:00
|
|
|
`${dbcFilename.replace(/\.dbc/g, '-')}${+new Date()}.csv`
|
2017-12-12 19:24:01 -07:00
|
|
|
);
|
|
|
|
const writer = fileStream.getWriter();
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
|
2018-03-17 17:54:07 -06:00
|
|
|
if (this.state.live) {
|
|
|
|
return this.downloadLiveLogAsCSV(dataHandler);
|
|
|
|
}
|
|
|
|
return this.downloadRawLogAsCSV(dataHandler);
|
|
|
|
|
|
|
|
function dataHandler(e) {
|
|
|
|
const { logData, shouldClose, progress } = e.data;
|
2017-12-12 19:24:01 -07:00
|
|
|
if (shouldClose) {
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('downloadLogAsCSV:close');
|
2017-12-12 19:24:01 -07:00
|
|
|
writer.close();
|
|
|
|
return;
|
2017-12-06 14:27:50 -07:00
|
|
|
}
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('CSV export progress:', progress);
|
|
|
|
const uint8array = encoder.encode(`${logData}\n`);
|
2017-12-12 19:24:01 -07:00
|
|
|
writer.write(uint8array);
|
2018-03-17 17:54:07 -06:00
|
|
|
}
|
|
|
|
}
|
2019-10-07 17:11:53 -06:00
|
|
|
|
2018-03-17 17:54:07 -06:00
|
|
|
downloadRawLogAsCSV(handler) {
|
2019-10-03 12:30:40 -06:00
|
|
|
return this.downloadLiveLogAsCSV(handler);
|
2018-03-17 17:54:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
downloadLiveLogAsCSV(handler) {
|
|
|
|
// Trigger processing of in-memory data in worker
|
|
|
|
// this method *could* just fetch the data needed for the worked, but
|
|
|
|
// eventually this might be in it's own worker instead of the shared one
|
|
|
|
const { firstCanTime, canFrameOffset } = this.state;
|
|
|
|
const worker = new LogCSVDownloader();
|
|
|
|
|
|
|
|
worker.onmessage = handler;
|
|
|
|
|
|
|
|
worker.postMessage({
|
2019-10-07 17:11:53 -06:00
|
|
|
data: Object.keys(this.state.messages).map((sourceId) => {
|
|
|
|
const source = this.state.messages[sourceId];
|
2018-03-17 17:54:07 -06:00
|
|
|
return {
|
|
|
|
id: source.id,
|
|
|
|
bus: source.bus,
|
|
|
|
address: source.address,
|
|
|
|
entries: source.entries.slice()
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
canStartTime: firstCanTime - canFrameOffset
|
|
|
|
});
|
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
|
2019-10-09 12:32:17 -06:00
|
|
|
mergeThumbnails(newThumbnails) {
|
|
|
|
const { thumbnails } = this.state;
|
|
|
|
if (!newThumbnails || !newThumbnails.length) {
|
|
|
|
return thumbnails;
|
|
|
|
}
|
|
|
|
if (!thumbnails.length) {
|
|
|
|
return newThumbnails;
|
|
|
|
}
|
|
|
|
|
|
|
|
let oldIndex = 0;
|
|
|
|
let newIndex = 0;
|
|
|
|
|
|
|
|
// is old immediately after new?
|
|
|
|
if (newThumbnails[0].monoTime > thumbnails[thumbnails.length - 1]) {
|
|
|
|
return thumbnails.concat(newThumbnails);
|
|
|
|
}
|
|
|
|
// is new immediately after old?
|
|
|
|
if (newThumbnails[newThumbnails.length - 1] < thumbnails[0]) {
|
|
|
|
return newThumbnails.concat(thumbnails);
|
|
|
|
}
|
|
|
|
let result = [];
|
|
|
|
while (oldIndex < thumbnails.length && newIndex < newThumbnails.length) {
|
|
|
|
if (thumbnails[oldIndex].monoTime < newThumbnails[newIndex].monoTime) {
|
|
|
|
result.push(thumbnails[oldIndex]);
|
|
|
|
oldIndex += 1;
|
|
|
|
} else {
|
|
|
|
result.push(newThumbnails[newIndex]);
|
|
|
|
newIndex += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (oldIndex < thumbnails.length) {
|
|
|
|
result = result.concat(thumbnails.slice(oldIndex));
|
|
|
|
} else if (newIndex < newThumbnails.length) {
|
|
|
|
result = result.concat(newThumbnails.slice(newIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
addAndRehydrateMessages(newMessages, options) {
|
|
|
|
// Adds new message entries to messages state
|
|
|
|
// and "rehydrates" ES6 classes (message frame)
|
|
|
|
// lost from JSON serialization in webworker data cloning.
|
2018-05-03 12:23:45 -06:00
|
|
|
options = options || {};
|
2017-12-12 19:24:01 -07:00
|
|
|
|
|
|
|
const messages = { ...this.state.messages };
|
2019-10-07 17:11:53 -06:00
|
|
|
for (const key in newMessages) {
|
2017-12-12 19:24:01 -07:00
|
|
|
// add message
|
|
|
|
if (options.replace !== true && key in messages) {
|
2019-09-09 14:26:43 -06:00
|
|
|
// should merge here instead of concat
|
|
|
|
// assumes messages are always sequential
|
2019-10-07 17:11:53 -06:00
|
|
|
const msgEntries = messages[key].entries;
|
|
|
|
const newMsgEntries = newMessages[key].entries;
|
|
|
|
const msgLength = msgEntries.length;
|
|
|
|
const newMsgLength = newMsgEntries.length;
|
|
|
|
const entryLength = msgLength + newMsgLength;
|
2019-09-09 14:26:43 -06:00
|
|
|
messages[key].entries = Array(entryLength);
|
|
|
|
|
|
|
|
let msgIndex = 0;
|
|
|
|
let newMsgIndex = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < entryLength; ++i) {
|
|
|
|
if (newMsgIndex >= newMsgLength) {
|
|
|
|
messages[key].entries[i] = msgEntries[msgIndex++];
|
|
|
|
} else if (msgIndex >= msgLength) {
|
|
|
|
messages[key].entries[i] = newMsgEntries[newMsgIndex++];
|
|
|
|
} else if (
|
|
|
|
msgEntries[msgIndex].relTime <= newMsgEntries[newMsgIndex].relTime
|
|
|
|
) {
|
|
|
|
messages[key].entries[i] = msgEntries[msgIndex++];
|
|
|
|
} else if (
|
|
|
|
msgEntries[msgIndex].relTime >= newMsgEntries[newMsgIndex].relTime
|
|
|
|
) {
|
|
|
|
messages[key].entries[i] = newMsgEntries[newMsgIndex++];
|
|
|
|
}
|
|
|
|
}
|
2019-10-07 17:11:53 -06:00
|
|
|
messages[key].byteStateChangeCounts = newMessages[key].byteStateChangeCounts;
|
2017-12-12 19:24:01 -07:00
|
|
|
} else {
|
|
|
|
messages[key] = newMessages[key];
|
2018-07-24 16:15:09 -06:00
|
|
|
messages[key].frame = this.state.dbc.getMessageFrame(
|
2017-12-12 19:24:01 -07:00
|
|
|
messages[key].address
|
|
|
|
);
|
2017-08-03 15:41:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
2019-09-09 14:26:43 -06:00
|
|
|
cancelWorker(workerHash) {
|
2019-10-07 17:11:53 -06:00
|
|
|
const currentWorkers = { ...this.state.currentWorkers };
|
|
|
|
const { worker, part } = currentWorkers[workerHash];
|
|
|
|
const loadingParts = this.state.loadingParts.filter((p) => p !== part);
|
|
|
|
const loadedParts = this.state.loadedParts.filter((p) => p !== part);
|
2019-09-09 14:26:43 -06:00
|
|
|
delete currentWorkers[workerHash];
|
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('Stoping worker', workerHash, 'for part', part);
|
2019-09-30 19:13:01 -06:00
|
|
|
worker.postMessage({
|
2019-10-07 17:11:53 -06:00
|
|
|
action: 'terminate'
|
2019-09-30 19:13:01 -06:00
|
|
|
});
|
2019-09-09 14:26:43 -06:00
|
|
|
|
|
|
|
this.setState({
|
|
|
|
currentWorkers,
|
|
|
|
loadingParts,
|
|
|
|
loadedParts
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
spawnWorker(options) {
|
|
|
|
let { currentParts, currentWorkers } = this.state;
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('Checking worker for', currentParts);
|
2018-09-03 15:13:25 -06:00
|
|
|
if (!this.state.isLoading) {
|
|
|
|
this.setState({ isLoading: true });
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
2019-09-09 14:26:43 -06:00
|
|
|
const [minPart, maxPart] = currentParts;
|
|
|
|
// cancel old workers that are still loading data no longer inside the window
|
2019-10-07 17:11:53 -06:00
|
|
|
Object.keys(currentWorkers).forEach((workerHash) => {
|
2019-09-09 14:26:43 -06:00
|
|
|
if (
|
2019-10-07 17:11:53 -06:00
|
|
|
currentWorkers[workerHash].part < minPart
|
|
|
|
|| currentWorkers[workerHash].part > maxPart
|
2019-09-09 14:26:43 -06:00
|
|
|
) {
|
|
|
|
this.cancelWorker(workerHash);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// updated worker list (post canceling, and this time a copy)
|
|
|
|
currentWorkers = { ...this.state.currentWorkers };
|
2018-05-03 12:23:45 -06:00
|
|
|
|
2019-09-09 14:26:43 -06:00
|
|
|
let { loadingParts, loadedParts, currentPart } = this.state;
|
|
|
|
|
|
|
|
// clean this up just in case, the cancel process above *should* have already done this
|
|
|
|
// they have at most 4 entries so it's trivial
|
2019-10-07 17:11:53 -06:00
|
|
|
loadingParts = loadingParts.filter((p) => p >= minPart && p <= maxPart);
|
|
|
|
loadedParts = loadedParts.filter((p) => p >= minPart && p <= maxPart);
|
2019-09-09 14:26:43 -06:00
|
|
|
|
|
|
|
let part = -1;
|
2019-10-07 17:11:53 -06:00
|
|
|
const allWorkerParts = Object.keys(currentWorkers).map(
|
|
|
|
(i) => currentWorkers[i].part
|
2019-09-09 14:26:43 -06:00
|
|
|
);
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2019-09-09 14:26:43 -06:00
|
|
|
for (let partOffset = 0; partOffset <= maxPart - minPart; ++partOffset) {
|
|
|
|
let tempPart = currentPart + partOffset;
|
|
|
|
if (tempPart > maxPart) {
|
2019-09-30 19:13:01 -06:00
|
|
|
tempPart = minPart + ((tempPart - minPart) % (maxPart - minPart + 1));
|
2019-09-09 14:26:43 -06:00
|
|
|
}
|
|
|
|
if (allWorkerParts.indexOf(tempPart) === -1) {
|
|
|
|
part = tempPart;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (part === -1) {
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('Loading complete');
|
2019-09-09 14:26:43 -06:00
|
|
|
this.setState({ isLoading: false });
|
|
|
|
return;
|
2017-08-03 15:41:52 -06:00
|
|
|
}
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('Starting worker for part', part);
|
2019-09-09 14:26:43 -06:00
|
|
|
// options is object of {part, prevMsgEntries, spawnWorkerHash, prepend}
|
|
|
|
options = options || {};
|
2019-10-07 17:11:53 -06:00
|
|
|
let { prevMsgEntries } = options;
|
|
|
|
const prepend = false;
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
const {
|
|
|
|
dbc,
|
|
|
|
dbcFilename,
|
2018-09-03 15:13:25 -06:00
|
|
|
route,
|
2017-12-12 19:24:01 -07:00
|
|
|
firstCanTime,
|
2019-10-09 12:32:17 -06:00
|
|
|
canFrameOffset
|
2017-12-12 19:24:01 -07:00
|
|
|
} = this.state;
|
2019-10-09 12:32:17 -06:00
|
|
|
let { maxByteStateChangeCount } = this.state;
|
2019-09-09 14:26:43 -06:00
|
|
|
|
|
|
|
if (!prevMsgEntries) {
|
|
|
|
// we have previous messages loaded
|
2019-10-07 17:11:53 -06:00
|
|
|
const { messages } = this.state;
|
2019-09-09 14:26:43 -06:00
|
|
|
prevMsgEntries = {};
|
2019-10-07 17:11:53 -06:00
|
|
|
Object.keys(messages).forEach((key) => {
|
|
|
|
const { entries } = messages[key];
|
2019-09-09 14:26:43 -06:00
|
|
|
prevMsgEntries[key] = entries[entries.length - 1];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-03 12:23:45 -06:00
|
|
|
// var worker = new CanFetcher();
|
2019-10-07 17:11:53 -06:00
|
|
|
const worker = new RLogDownloader();
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
const spawnWorkerHash = hash(Math.random().toString(16));
|
2019-09-09 14:26:43 -06:00
|
|
|
currentWorkers[spawnWorkerHash] = {
|
|
|
|
part,
|
|
|
|
worker
|
|
|
|
};
|
|
|
|
|
|
|
|
loadingParts = [part, ...loadingParts];
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
currentWorkers,
|
|
|
|
loadingParts
|
|
|
|
});
|
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
worker.onmessage = (e) => {
|
2019-09-09 14:26:43 -06:00
|
|
|
if (this.state.currentWorkers[spawnWorkerHash] === undefined) {
|
2019-10-07 17:11:53 -06:00
|
|
|
console.log('Worker was canceled');
|
2017-12-12 19:24:01 -07:00
|
|
|
return;
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
if (this.state.dbcFilename !== dbcFilename) {
|
|
|
|
// DBC changed while this worker was running
|
|
|
|
// -- don't update messages and halt recursion.
|
|
|
|
return;
|
|
|
|
}
|
2017-08-03 17:56:59 -06:00
|
|
|
|
2019-10-09 12:32:17 -06:00
|
|
|
maxByteStateChangeCount = e.data.maxByteStateChangeCount;
|
|
|
|
const { newMessages, newThumbnails, isFinished } = e.data;
|
2017-12-12 19:24:01 -07:00
|
|
|
if (maxByteStateChangeCount > this.state.maxByteStateChangeCount) {
|
|
|
|
this.setState({ maxByteStateChangeCount });
|
2017-08-21 19:24:51 -06:00
|
|
|
} else {
|
2017-12-12 19:24:01 -07:00
|
|
|
maxByteStateChangeCount = this.state.maxByteStateChangeCount;
|
2017-08-21 19:24:51 -06:00
|
|
|
}
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
const messages = this.addAndRehydrateMessages(
|
|
|
|
newMessages,
|
|
|
|
maxByteStateChangeCount
|
|
|
|
);
|
|
|
|
const prevMsgEntries = {};
|
2019-10-09 12:32:17 -06:00
|
|
|
Object.keys(newMessages).forEach((key) => {
|
2019-10-07 17:11:53 -06:00
|
|
|
prevMsgEntries[key] = newMessages[key].entries[newMessages[key].entries.length - 1];
|
2019-10-09 12:32:17 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
const thumbnails = this.mergeThumbnails(newThumbnails);
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2018-05-03 12:23:45 -06:00
|
|
|
if (!isFinished) {
|
2019-10-09 12:32:17 -06:00
|
|
|
this.setState({ messages, thumbnails });
|
2018-05-03 12:23:45 -06:00
|
|
|
} else {
|
2019-10-07 17:11:53 -06:00
|
|
|
const loadingParts = this.state.loadingParts.filter((p) => p !== part);
|
|
|
|
const loadedParts = [part, ...this.state.loadedParts];
|
2019-09-09 14:26:43 -06:00
|
|
|
|
2018-05-03 12:23:45 -06:00
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
messages,
|
2019-10-09 12:32:17 -06:00
|
|
|
thumbnails,
|
2019-09-09 14:26:43 -06:00
|
|
|
partsLoaded: this.state.partsLoaded + 1,
|
|
|
|
loadingParts,
|
|
|
|
loadedParts
|
2018-05-03 12:23:45 -06:00
|
|
|
},
|
|
|
|
() => {
|
2019-09-09 14:26:43 -06:00
|
|
|
this.spawnWorker({
|
|
|
|
prevMsgEntries,
|
|
|
|
spawnWorkerHash,
|
|
|
|
prepend
|
|
|
|
});
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
2018-05-03 12:23:45 -06:00
|
|
|
);
|
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
};
|
2017-07-07 16:06:47 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
worker.postMessage({
|
2018-05-03 12:23:45 -06:00
|
|
|
// old stuff for reverse compatibility for easier testing
|
2017-12-12 19:24:01 -07:00
|
|
|
base: route.url,
|
|
|
|
num: part,
|
2018-05-03 12:23:45 -06:00
|
|
|
|
2018-05-09 12:06:52 -06:00
|
|
|
// so that we don't try to read metadata about it...
|
|
|
|
isDemo: this.props.isDemo,
|
2019-09-16 11:38:41 -06:00
|
|
|
isLegacyShare: this.props.isLegacyShare,
|
|
|
|
logUrls: this.state.logUrls,
|
2018-05-09 12:06:52 -06:00
|
|
|
|
2018-05-03 12:23:45 -06:00
|
|
|
// data that is used
|
|
|
|
dbcText: dbc.text(),
|
|
|
|
route: route.fullname,
|
2019-10-07 17:11:53 -06:00
|
|
|
part,
|
2019-02-13 15:27:27 -07:00
|
|
|
canStartTime: firstCanTime != null ? firstCanTime - canFrameOffset : null,
|
2017-12-12 19:24:01 -07:00
|
|
|
prevMsgEntries,
|
|
|
|
maxByteStateChangeCount
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
showingModal() {
|
|
|
|
const {
|
|
|
|
showOnboarding,
|
|
|
|
showLoadDbc,
|
|
|
|
showSaveDbc,
|
|
|
|
showAddSignal,
|
|
|
|
showEditMessageModal
|
|
|
|
} = this.state;
|
|
|
|
return (
|
2019-10-07 17:11:53 -06:00
|
|
|
showOnboarding
|
|
|
|
|| showLoadDbc
|
|
|
|
|| showSaveDbc
|
|
|
|
|| showAddSignal
|
|
|
|
|| showEditMessageModal
|
2017-12-12 19:24:01 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
showOnboarding() {
|
|
|
|
this.setState({ showOnboarding: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
hideOnboarding() {
|
|
|
|
this.setState({ showOnboarding: false });
|
|
|
|
}
|
|
|
|
|
|
|
|
showLoadDbc() {
|
|
|
|
this.setState({ showLoadDbc: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
hideLoadDbc() {
|
|
|
|
this.setState({ showLoadDbc: false });
|
|
|
|
}
|
|
|
|
|
|
|
|
showSaveDbc() {
|
|
|
|
this.setState({ showSaveDbc: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
hideSaveDbc() {
|
|
|
|
this.setState({ showSaveDbc: false });
|
|
|
|
}
|
|
|
|
|
|
|
|
reparseMessages(messages) {
|
|
|
|
this.setState({ isLoading: true });
|
|
|
|
|
|
|
|
const { dbc } = this.state;
|
2019-10-07 17:11:53 -06:00
|
|
|
const worker = new MessageParser();
|
|
|
|
worker.onmessage = (e) => {
|
2017-12-12 19:24:01 -07:00
|
|
|
let messages = e.data;
|
|
|
|
messages = this.addAndRehydrateMessages(messages, { replace: true });
|
|
|
|
|
|
|
|
this.setState({ messages, isLoading: false });
|
|
|
|
};
|
2017-07-07 16:06:47 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
worker.postMessage({
|
|
|
|
messages,
|
|
|
|
dbcText: dbc.text(),
|
|
|
|
canStartTime: this.state.firstCanTime
|
|
|
|
});
|
|
|
|
}
|
2017-07-20 20:24:52 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
updateMessageFrame(messageId, frame) {
|
|
|
|
const { messages } = this.state;
|
2017-07-20 20:24:52 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
messages[messageId].frame = frame;
|
|
|
|
this.setState({ messages });
|
|
|
|
}
|
2017-07-20 19:42:08 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
persistDbc({ dbcFilename, dbc }) {
|
2018-09-03 15:13:25 -06:00
|
|
|
const { route } = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
if (route) {
|
|
|
|
persistDbc(route.fullname, { dbcFilename, dbc });
|
|
|
|
} else {
|
2019-10-07 17:11:53 -06:00
|
|
|
persistDbc('live', { dbcFilename, dbc });
|
2017-07-05 08:56:24 -06:00
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
2017-06-15 00:22:52 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
onConfirmedSignalChange(message, signals) {
|
2017-12-15 15:29:13 -07:00
|
|
|
const { dbc, dbcFilename } = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
dbc.setSignals(message.address, { ...signals });
|
2017-06-20 17:46:07 -06:00
|
|
|
|
2018-07-24 16:15:09 -06:00
|
|
|
this.updateMessageFrame(message.id, dbc.getMessageFrame(message.address));
|
2017-06-30 21:12:47 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
this.persistDbc({ dbcFilename, dbc });
|
2017-06-19 21:40:07 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
const messages = {};
|
|
|
|
const newMessage = { ...message };
|
2018-07-24 16:15:09 -06:00
|
|
|
const frame = dbc.getMessageFrame(message.address);
|
2017-12-12 19:24:01 -07:00
|
|
|
newMessage.frame = frame;
|
2017-06-19 21:40:07 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
messages[message.id] = newMessage;
|
2017-06-28 19:03:21 -06:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
this.setState({ dbc, dbcText: dbc.text() }, () => this.reparseMessages(messages));
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
2017-08-21 19:24:51 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
partChangeDebounced = debounce(() => {
|
2019-10-07 17:11:53 -06:00
|
|
|
const [minPart, maxPart] = this.state.currentParts;
|
|
|
|
const messages = { ...this.state.messages };
|
2019-09-09 14:26:43 -06:00
|
|
|
// update messages to only preserve entries in new part range
|
2019-10-07 17:11:53 -06:00
|
|
|
const minTime = minPart * 60;
|
|
|
|
const maxTime = maxPart * 60 + 60;
|
|
|
|
Object.keys(messages).forEach((key) => {
|
|
|
|
const { entries } = messages[key];
|
2019-09-09 14:26:43 -06:00
|
|
|
let minIndex = 0;
|
|
|
|
let maxIndex = entries.length - 1;
|
|
|
|
while (minIndex < entries.length && entries[minIndex].relTime < minTime) {
|
|
|
|
minIndex++;
|
|
|
|
}
|
|
|
|
while (maxIndex > minIndex && entries[maxIndex].relTime > maxTime) {
|
|
|
|
maxIndex--;
|
|
|
|
}
|
|
|
|
if (minIndex > 0 || maxIndex < entries.length - 1) {
|
|
|
|
messages[key].entries = entries.slice(minIndex, maxIndex + 1);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
messages
|
|
|
|
});
|
|
|
|
|
|
|
|
this.spawnWorker();
|
2017-12-12 19:24:01 -07:00
|
|
|
}, 500);
|
2017-06-20 17:46:07 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
onPartChange(part) {
|
2019-10-07 17:11:53 -06:00
|
|
|
let {
|
|
|
|
currentParts, currentPart, canFrameOffset, route
|
|
|
|
} = this.state;
|
2019-09-09 14:26:43 -06:00
|
|
|
if (canFrameOffset === -1 || part === currentPart) {
|
2017-12-12 19:24:01 -07:00
|
|
|
return;
|
2017-06-19 21:40:07 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
// determine new parts to load, whether to prepend or append
|
2019-09-09 14:26:43 -06:00
|
|
|
let maxPart = Math.min(route.proclog, part + 1);
|
2019-10-07 17:11:53 -06:00
|
|
|
const minPart = Math.max(0, maxPart - PART_SEGMENT_LENGTH + 1);
|
2019-09-09 14:26:43 -06:00
|
|
|
if (minPart === 0) {
|
|
|
|
maxPart = Math.min(route.proclog, 2);
|
|
|
|
}
|
2018-09-03 15:13:25 -06:00
|
|
|
const currentPartSpan = currentParts[1] - currentParts[0] + 1;
|
2017-06-23 15:17:35 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
// update current parts
|
2019-09-09 14:26:43 -06:00
|
|
|
currentParts = [minPart, maxPart];
|
|
|
|
currentPart = part;
|
2017-12-12 19:24:01 -07:00
|
|
|
|
2019-09-11 14:36:15 -06:00
|
|
|
if (
|
2019-10-07 17:11:53 -06:00
|
|
|
currentPart !== this.state.currentPart
|
|
|
|
|| currentParts[0] !== this.state.currentParts[0]
|
|
|
|
|| currentParts[1] !== this.state.currentParts[1]
|
2019-09-11 14:36:15 -06:00
|
|
|
) {
|
|
|
|
// update state then load new parts
|
|
|
|
this.setState({ currentParts, currentPart }, this.partChangeDebounced);
|
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
showEditMessageModal(msgKey) {
|
|
|
|
const msg = this.state.messages[msgKey];
|
|
|
|
if (!msg.frame) {
|
|
|
|
msg.frame = this.state.dbc.createFrame(msg.address);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
showEditMessageModal: true,
|
|
|
|
editMessageModalMessage: msgKey,
|
|
|
|
messages: this.state.messages,
|
|
|
|
dbcText: this.state.dbc.text()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
hideEditMessageModal() {
|
|
|
|
this.setState({ showEditMessageModal: false });
|
|
|
|
}
|
|
|
|
|
|
|
|
onMessageFrameEdited(messageFrame) {
|
2019-10-07 17:11:53 -06:00
|
|
|
const {
|
|
|
|
messages, dbcFilename, dbc, editMessageModalMessage
|
|
|
|
} = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
const message = { ...messages[editMessageModalMessage] };
|
2017-12-12 19:24:01 -07:00
|
|
|
message.frame = messageFrame;
|
|
|
|
dbc.messages.set(messageFrame.id, messageFrame);
|
|
|
|
this.persistDbc({ dbcFilename, dbc });
|
|
|
|
|
|
|
|
messages[editMessageModalMessage] = message;
|
|
|
|
this.setState({ messages, dbc, dbcText: dbc.text() });
|
|
|
|
this.hideEditMessageModal();
|
|
|
|
}
|
|
|
|
|
|
|
|
onSeek(seekIndex, seekTime) {
|
|
|
|
this.setState({ seekIndex, seekTime });
|
2019-09-09 14:26:43 -06:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
const { currentPart } = this.state;
|
|
|
|
const part = ~~(seekTime / 60);
|
2019-09-09 14:26:43 -06:00
|
|
|
if (part !== currentPart) {
|
|
|
|
this.onPartChange(part);
|
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
onUserSeek(seekTime) {
|
|
|
|
if (USE_UNLOGGER) {
|
|
|
|
this.unloggerClient.seek(this.props.dongleId, this.props.name, seekTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
const msg = this.state.messages[this.state.selectedMessage];
|
|
|
|
let seekIndex;
|
|
|
|
if (msg) {
|
2019-10-07 17:11:53 -06:00
|
|
|
seekIndex = msg.entries.findIndex((e) => e.relTime >= seekTime);
|
2017-12-12 19:24:01 -07:00
|
|
|
if (seekIndex === -1) {
|
|
|
|
seekIndex = 0;
|
2017-07-04 19:15:58 -06:00
|
|
|
}
|
2017-12-12 19:24:01 -07:00
|
|
|
} else {
|
|
|
|
seekIndex = 0;
|
2017-06-29 18:04:40 -06:00
|
|
|
}
|
|
|
|
|
2019-09-09 14:26:43 -06:00
|
|
|
this.onSeek(seekIndex, seekTime);
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
2017-06-23 17:06:34 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
onMessageSelected(msgKey) {
|
2018-09-03 15:13:25 -06:00
|
|
|
let { seekTime, seekIndex, messages } = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
const msg = messages[msgKey];
|
2017-06-23 15:17:35 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
if (seekTime > 0 && msg.entries.length > 0) {
|
2019-10-07 17:11:53 -06:00
|
|
|
seekIndex = msg.entries.findIndex((e) => e.relTime >= seekTime);
|
2017-12-12 19:24:01 -07:00
|
|
|
if (seekIndex === -1) {
|
|
|
|
seekIndex = 0;
|
2017-06-23 15:17:35 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
seekTime = msg.entries[seekIndex].relTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({ seekTime, seekIndex, selectedMessage: msgKey });
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSelectedMessages(selectedMessages) {
|
|
|
|
this.setState({ selectedMessages });
|
|
|
|
}
|
|
|
|
|
|
|
|
onMessageUnselected(msgKey) {
|
|
|
|
this.setState({ selectedMessage: null });
|
|
|
|
}
|
|
|
|
|
|
|
|
loginWithGithub() {
|
2018-09-03 15:13:25 -06:00
|
|
|
const { route } = this.state;
|
2017-12-12 19:24:01 -07:00
|
|
|
return (
|
|
|
|
<a
|
|
|
|
href={GithubAuth.authorizeUrl(
|
2019-10-07 17:11:53 -06:00
|
|
|
route && route.fullname ? route.fullname : ''
|
2017-12-12 19:24:01 -07:00
|
|
|
)}
|
|
|
|
className="button button--dark button--inline"
|
|
|
|
>
|
|
|
|
<i className="fa fa-github" />
|
|
|
|
<span> Log in with Github</span>
|
|
|
|
</a>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
lastMessageEntriesById(obj, [msgId, message]) {
|
|
|
|
obj[msgId] = message.entries[message.entries.length - 1];
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
processStreamedCanMessages(newCanMessages) {
|
|
|
|
const { dbcText } = this.state;
|
|
|
|
const {
|
|
|
|
firstCanTime,
|
|
|
|
lastBusTime,
|
|
|
|
messages,
|
|
|
|
maxByteStateChangeCount
|
|
|
|
} = this.state;
|
|
|
|
// map msg id to arrays
|
|
|
|
const prevMsgEntries = Object.entries(messages).reduce(
|
|
|
|
this.lastMessageEntriesById,
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
const byteStateChangeCountsByMessage = Object.entries(messages).reduce(
|
|
|
|
(obj, [msgId, msg]) => {
|
|
|
|
obj[msgId] = msg.byteStateChangeCounts;
|
2017-08-03 15:41:52 -06:00
|
|
|
return obj;
|
2017-12-12 19:24:01 -07:00
|
|
|
},
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
this.canStreamerWorker.postMessage({
|
|
|
|
newCanMessages,
|
|
|
|
prevMsgEntries,
|
|
|
|
firstCanTime,
|
|
|
|
dbcText,
|
|
|
|
lastBusTime,
|
|
|
|
byteStateChangeCountsByMessage,
|
|
|
|
maxByteStateChangeCount
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
firstEntryIndexInsideStreamingWindow(entries) {
|
|
|
|
const lastEntryTime = entries[entries.length - 1].relTime;
|
|
|
|
const windowFloor = lastEntryTime - STREAMING_WINDOW;
|
|
|
|
|
|
|
|
for (let i = 0; i < entries.length; i++) {
|
|
|
|
if (entries[i].relTime > windowFloor) {
|
|
|
|
return i;
|
2017-08-03 15:41:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
return 0;
|
|
|
|
}
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
enforceStreamingMessageWindow(messages) {
|
2019-10-07 17:11:53 -06:00
|
|
|
const messageIds = Object.keys(messages);
|
2017-12-12 19:24:01 -07:00
|
|
|
for (let i = 0; i < messageIds.length; i++) {
|
|
|
|
const messageId = messageIds[i];
|
|
|
|
const message = messages[messageId];
|
|
|
|
if (message.entries.length < 2) {
|
|
|
|
continue;
|
2017-08-03 15:41:52 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
const lastEntryTime = message.entries[message.entries.length - 1].relTime;
|
|
|
|
const entrySpan = lastEntryTime - message.entries[0].relTime;
|
|
|
|
if (entrySpan > STREAMING_WINDOW) {
|
|
|
|
const newEntryFloor = this.firstEntryIndexInsideStreamingWindow(
|
|
|
|
message.entries
|
|
|
|
);
|
|
|
|
message.entries = message.entries.slice(newEntryFloor);
|
|
|
|
messages[messageId] = message;
|
2017-08-03 15:41:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
_onStreamedCanMessagesProcessed(data) {
|
|
|
|
let {
|
|
|
|
newMessages,
|
|
|
|
seekTime,
|
|
|
|
lastBusTime,
|
|
|
|
firstCanTime,
|
|
|
|
maxByteStateChangeCount
|
|
|
|
} = data;
|
|
|
|
|
|
|
|
if (maxByteStateChangeCount < this.state.maxByteStateChangeCount) {
|
|
|
|
maxByteStateChangeCount = this.state.maxByteStateChangeCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
let messages = this.addAndRehydrateMessages(newMessages);
|
|
|
|
messages = this.enforceStreamingMessageWindow(messages);
|
|
|
|
let { seekIndex, selectedMessages } = this.state;
|
|
|
|
if (
|
2019-10-07 17:11:53 -06:00
|
|
|
selectedMessages.length > 0
|
|
|
|
&& messages[selectedMessages[0]] !== undefined
|
2017-12-12 19:24:01 -07:00
|
|
|
) {
|
2019-09-30 19:13:01 -06:00
|
|
|
seekIndex = Math.max(0, messages[selectedMessages[0]].entries.length - 1);
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
this.setState({
|
|
|
|
messages,
|
|
|
|
seekTime,
|
|
|
|
seekIndex,
|
|
|
|
lastBusTime,
|
|
|
|
firstCanTime,
|
|
|
|
maxByteStateChangeCount
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onStreamedCanMessagesProcessed(e) {
|
|
|
|
this._onStreamedCanMessagesProcessed(e.data);
|
|
|
|
}
|
|
|
|
|
2018-03-18 18:46:30 -06:00
|
|
|
async handlePandaConnect(e) {
|
2017-12-12 19:24:01 -07:00
|
|
|
this.setState({ attemptingPandaConnection: true, live: true });
|
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
const persistedDbc = fetchPersistedDbc('live');
|
2017-12-12 19:24:01 -07:00
|
|
|
if (persistedDbc) {
|
2019-10-07 17:11:53 -06:00
|
|
|
const { dbc, dbcText } = persistedDbc;
|
2017-12-12 19:24:01 -07:00
|
|
|
this.setState({ dbc, dbcText });
|
|
|
|
}
|
|
|
|
this.canStreamerWorker = new CanStreamerWorker();
|
|
|
|
this.canStreamerWorker.onmessage = this.onStreamedCanMessagesProcessed;
|
2018-03-18 18:46:30 -06:00
|
|
|
|
|
|
|
// if any errors go off during connection, mark as not trying to connect anymore...
|
2019-10-07 17:11:53 -06:00
|
|
|
const unlisten = this.pandaReader.onError((err) => {
|
2018-05-03 13:14:39 -06:00
|
|
|
console.error(err.stack || err);
|
|
|
|
this.setState({ attemptingPandaConnection: false });
|
|
|
|
});
|
2018-03-18 18:46:30 -06:00
|
|
|
try {
|
|
|
|
await this.pandaReader.start();
|
|
|
|
this.setState({
|
|
|
|
showOnboarding: false,
|
|
|
|
showLoadDbc: true
|
2017-12-12 19:24:01 -07:00
|
|
|
});
|
2018-03-18 18:46:30 -06:00
|
|
|
} catch (e) {}
|
|
|
|
this.setState({ attemptingPandaConnection: false });
|
|
|
|
unlisten();
|
2017-12-12 19:24:01 -07:00
|
|
|
}
|
|
|
|
|
2018-03-18 18:46:30 -06:00
|
|
|
githubSignOut(e, dataArray) {
|
2017-12-12 19:24:01 -07:00
|
|
|
unpersistGithubAuthToken();
|
|
|
|
this.setState({ isGithubAuthenticated: false });
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2019-10-09 12:32:17 -06:00
|
|
|
const {
|
|
|
|
route,
|
|
|
|
messages,
|
|
|
|
selectedMessages,
|
|
|
|
currentParts,
|
|
|
|
dbcFilename,
|
|
|
|
dbcLastSaved,
|
|
|
|
seekTime,
|
|
|
|
seekIndex,
|
|
|
|
shareUrl,
|
|
|
|
maxByteStateChangeCount,
|
|
|
|
live,
|
|
|
|
thumbnails,
|
|
|
|
selectedMessage,
|
|
|
|
canFrameOffset,
|
|
|
|
firstCanTime,
|
|
|
|
currentPart,
|
|
|
|
partsLoaded
|
|
|
|
} = this.state;
|
|
|
|
|
2017-12-12 19:24:01 -07:00
|
|
|
return (
|
2018-09-03 15:13:25 -06:00
|
|
|
<div
|
|
|
|
id="cabana"
|
2019-10-07 17:11:53 -06:00
|
|
|
className={cx({ 'is-showing-modal': this.showingModal() })}
|
2018-09-03 15:13:25 -06:00
|
|
|
>
|
|
|
|
{this.state.isLoading ? (
|
|
|
|
<LoadingBar isLoading={this.state.isLoading} />
|
|
|
|
) : null}
|
|
|
|
<div className="cabana-header">
|
|
|
|
<a className="cabana-header-logo" href="">
|
|
|
|
Comma Cabana
|
|
|
|
</a>
|
|
|
|
<div className="cabana-header-account">
|
|
|
|
{this.state.isGithubAuthenticated ? (
|
|
|
|
<div>
|
|
|
|
<p>GitHub Authenticated</p>
|
|
|
|
<p
|
|
|
|
className="cabana-header-account-signout"
|
|
|
|
onClick={this.githubSignOut}
|
|
|
|
>
|
|
|
|
Sign out
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
this.loginWithGithub()
|
|
|
|
)}
|
2017-12-12 19:24:01 -07:00
|
|
|
</div>
|
2018-09-03 15:13:25 -06:00
|
|
|
</div>
|
|
|
|
<div className="cabana-window">
|
|
|
|
<Meta
|
2019-10-09 12:32:17 -06:00
|
|
|
url={this.state.route ? route.url : null}
|
|
|
|
messages={messages}
|
|
|
|
selectedMessages={selectedMessages}
|
2018-09-03 15:13:25 -06:00
|
|
|
updateSelectedMessages={this.updateSelectedMessages}
|
|
|
|
showEditMessageModal={this.showEditMessageModal}
|
2019-10-09 12:32:17 -06:00
|
|
|
currentParts={currentParts}
|
2018-09-03 15:13:25 -06:00
|
|
|
onMessageSelected={this.onMessageSelected}
|
|
|
|
onMessageUnselected={this.onMessageUnselected}
|
|
|
|
showLoadDbc={this.showLoadDbc}
|
|
|
|
showSaveDbc={this.showSaveDbc}
|
2019-10-09 12:32:17 -06:00
|
|
|
dbcFilename={dbcFilename}
|
|
|
|
dbcLastSaved={dbcLastSaved}
|
2018-09-03 15:13:25 -06:00
|
|
|
dongleId={this.props.dongleId}
|
|
|
|
name={this.props.name}
|
2019-10-09 12:32:17 -06:00
|
|
|
route={route}
|
|
|
|
seekTime={seekTime}
|
|
|
|
seekIndex={seekIndex}
|
|
|
|
shareUrl={shareUrl}
|
|
|
|
maxByteStateChangeCount={maxByteStateChangeCount}
|
2018-09-03 15:13:25 -06:00
|
|
|
isDemo={this.props.isDemo}
|
2019-10-09 12:32:17 -06:00
|
|
|
live={live}
|
2018-09-03 15:13:25 -06:00
|
|
|
saveLog={debounce(this.downloadLogAsCSV, 500)}
|
|
|
|
/>
|
2019-10-09 12:32:17 -06:00
|
|
|
{route || live ? (
|
2018-09-03 15:13:25 -06:00
|
|
|
<Explorer
|
2019-10-09 12:32:17 -06:00
|
|
|
url={route ? route.url : null}
|
|
|
|
live={live}
|
|
|
|
messages={messages}
|
|
|
|
thumbnails={thumbnails}
|
|
|
|
selectedMessage={selectedMessage}
|
2018-09-03 15:13:25 -06:00
|
|
|
onConfirmedSignalChange={this.onConfirmedSignalChange}
|
|
|
|
onSeek={this.onSeek}
|
|
|
|
onUserSeek={this.onUserSeek}
|
2019-10-09 12:32:17 -06:00
|
|
|
canFrameOffset={canFrameOffset}
|
|
|
|
firstCanTime={firstCanTime}
|
|
|
|
seekTime={seekTime}
|
|
|
|
seekIndex={seekIndex}
|
|
|
|
currentParts={currentParts}
|
|
|
|
selectedPart={currentPart}
|
|
|
|
partsLoaded={partsLoaded}
|
2018-09-03 15:13:25 -06:00
|
|
|
autoplay={this.props.autoplay}
|
2017-12-12 19:24:01 -07:00
|
|
|
showEditMessageModal={this.showEditMessageModal}
|
2018-09-03 15:13:25 -06:00
|
|
|
onPartChange={this.onPartChange}
|
|
|
|
routeStartTime={
|
2019-10-09 12:32:17 -06:00
|
|
|
route ? route.start_time : Moment()
|
2018-09-03 15:13:25 -06:00
|
|
|
}
|
2019-10-09 12:32:17 -06:00
|
|
|
partsCount={route ? route.proclog : 0}
|
2018-08-15 13:53:21 -06:00
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</div>
|
2018-09-03 15:13:25 -06:00
|
|
|
|
|
|
|
{this.state.showOnboarding ? (
|
|
|
|
<OnboardingModal
|
|
|
|
handlePandaConnect={this.handlePandaConnect}
|
|
|
|
attemptingPandaConnection={this.state.attemptingPandaConnection}
|
|
|
|
routes={this.state.routes}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{this.state.showLoadDbc ? (
|
|
|
|
<LoadDbcModal
|
|
|
|
onDbcSelected={this.onDbcSelected}
|
|
|
|
handleClose={this.hideLoadDbc}
|
|
|
|
openDbcClient={this.openDbcClient}
|
|
|
|
loginWithGithub={this.loginWithGithub()}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{this.state.showSaveDbc ? (
|
|
|
|
<SaveDbcModal
|
|
|
|
dbc={this.state.dbc}
|
|
|
|
sourceDbcFilename={this.state.dbcFilename}
|
|
|
|
onDbcSaved={this.onDbcSaved}
|
|
|
|
handleClose={this.hideSaveDbc}
|
|
|
|
openDbcClient={this.openDbcClient}
|
|
|
|
hasGithubAuth={this.props.githubAuthToken !== null}
|
|
|
|
loginWithGithub={this.loginWithGithub()}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{this.state.showEditMessageModal ? (
|
|
|
|
<EditMessageModal
|
|
|
|
handleClose={this.hideEditMessageModal}
|
|
|
|
handleSave={this.onMessageFrameEdited}
|
|
|
|
message={this.state.messages[this.state.editMessageModalMessage]}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</div>
|
2017-12-12 19:24:01 -07:00
|
|
|
);
|
|
|
|
}
|
2017-05-29 17:52:17 -06:00
|
|
|
}
|
2019-10-09 12:32:17 -06:00
|
|
|
|
|
|
|
CanExplorer.propTypes = {
|
|
|
|
dongleId: PropTypes.string,
|
|
|
|
name: PropTypes.string,
|
|
|
|
dbc: PropTypes.instanceOf(DBC),
|
|
|
|
dbcFilename: PropTypes.string,
|
|
|
|
githubAuthToken: PropTypes.string,
|
|
|
|
autoplay: PropTypes.bool,
|
|
|
|
max: PropTypes.number,
|
|
|
|
url: PropTypes.string
|
|
|
|
};
|