From 36e73b89bc4afbdecdd050c4f6a5c1408ec8b108 Mon Sep 17 00:00:00 2001 From: marley Date: Wed, 17 May 2023 20:38:26 -0700 Subject: [PATCH] Refactoring: migrating to @kayahr/ed-journal. --- package-lock.json | 16 ++++- package.json | 1 + src/models/Body.ts | 42 ++++++------- src/models/Journal.ts | 52 ++++++++------- src/models/NewJournal.ts | 132 +++++++++++++++++++++++++++++++++++++++ src/models/NewSafari.ts | 0 src/models/Safari.ts | 41 ++++++------ 7 files changed, 218 insertions(+), 66 deletions(-) create mode 100644 src/models/NewJournal.ts create mode 100644 src/models/NewSafari.ts diff --git a/package-lock.json b/package-lock.json index b092c24..96f2049 100755 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "GPL-3.0-only", "dependencies": { + "@kayahr/ed-journal": "^2.5.0", "app-root-path": "^3.1.0", "bootstrap": "^5.3.0-alpha3", "chokidar": "^3.5.3", @@ -3307,6 +3308,18 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@kayahr/ed-journal": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@kayahr/ed-journal/-/ed-journal-2.5.0.tgz", + "integrity": "sha512-vMNkAU5Sjn0U9BxdCnu63yR8/+Bpxe+BLsRxSCxpiiRL5ngOLawxIZbclLQySxGdjZ2avuHvQ3c6YRE6tPFOYQ==", + "dependencies": { + "chokidar": "^3.5.3", + "tslib": "^2.4.1" + }, + "funding": { + "url": "https://github.com/kayahr/ed-journal?sponsor=1" + } + }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -10639,8 +10652,7 @@ "node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/type-detect": { "version": "4.0.8", diff --git a/package.json b/package.json index 8c19cab..066282d 100755 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "typescript": "^5.0.4" }, "dependencies": { + "@kayahr/ed-journal": "^2.5.0", "app-root-path": "^3.1.0", "bootstrap": "^5.3.0-alpha3", "chokidar": "^3.5.3", diff --git a/src/models/Body.ts b/src/models/Body.ts index c419d0c..c0aa01a 100755 --- a/src/models/Body.ts +++ b/src/models/Body.ts @@ -1,25 +1,17 @@ +import type { SAAScanComplete, Scan } from '@kayahr/ed-journal'; import type { valuableBody } from '../@types/edsmResponses'; -import type { - asteroidScan, - autoScan, - detailedScan, - planetScan, - starScan, -} from '../@types/journalLines'; import { BodyCodes } from '../data/BodyCodes'; -export interface Body extends starScan<'AutoScan'|'DetailedScan'>, - asteroidScan<'AutoScan'|'DetailedScan'>, - planetScan<'AutoScan'|'DetailedScan'> {} +export interface Body extends Scan {} export class Body { DSSDone: boolean; mappedValue: number; constructor( - journalLine: autoScan|detailedScan|valuableBody|null = null, - DSS: boolean = false, + journalLine: Scan|SAAScanComplete|valuableBody|null = null, + DSS: boolean = false, ) { this.DSSDone = DSS; @@ -67,7 +59,15 @@ export class Body { /* ---------------------------------------------------------------------------- simpleName ---- */ simpleName(): string { - return this.BodyName.replace(this.StarSystem, ''); + let name: string; + + if (typeof this.StarSystem === 'string') { + name = this.BodyName.replace(this.StarSystem, ''); + } else { + name = this.BodyName; + } + + return name; } /* ------------------------------------------------------------------------------ typeIcon ---- */ @@ -78,19 +78,19 @@ export class Body { if (this.isStar() || this.isAsteroid()) { typeIcon = this.nameIcon(); } else { - const planetClass: string = this.PlanetClass.toLowerCase(); + const planetClass: string|undefined = this.PlanetClass?.toLowerCase(); - if (planetClass.includes('metal')) { + if (planetClass?.includes('metal')) { typeIcon = 'ingot'; - } else if (planetClass.includes('icy')) { + } else if (planetClass?.includes('icy')) { typeIcon = 'snowflake'; - } else if (planetClass.includes('earth')) { + } else if (planetClass?.includes('earth')) { typeIcon = 'earth'; - } else if (planetClass.includes('gas giant')) { + } else if (planetClass?.includes('gas giant')) { typeIcon = 'jupiter-1'; - } else if (planetClass.includes('rock')) { + } else if (planetClass?.includes('rock')) { typeIcon = 'asteroid-3'; - } else if (planetClass.includes('water') || planetClass.includes('ammonia')) { + } else if (planetClass?.includes('water') || planetClass?.includes('ammonia')) { typeIcon = 'water-drops'; } } @@ -105,7 +105,7 @@ export class Body { #getValue(): number { const bodyType = this.#getNumericalBodyType(); - const mass = 'MassEM' in this ? this.MassEM : 1; + const mass = this.MassEM !== undefined ? this.MassEM : 1; let terraformState = this.#getNumericalTerraformState(); diff --git a/src/models/Journal.ts b/src/models/Journal.ts index 9ff583f..5c95587 100755 --- a/src/models/Journal.ts +++ b/src/models/Journal.ts @@ -1,16 +1,24 @@ import type { Tail as TailType } from 'tail'; -import type { autoScan, completeFsdJump, detailedScan, journalEntry, navRoute, planetScan } from "../@types/journalLines"; +import type { + autoScan, + completeFsdJump, + detailedScan, + journalEntry, + navRoute, + planetScan, +} from '../@types/journalLines'; const EventEmitter = require('node:events'); import * as _ from 'lodash-es'; -const path = require('node:path'); -const { readFile } = require('node:fs/promises'); -const reverseLineReader = require('reverse-line-reader'); -const Tail = require('tail').Tail; -import { System } from "./System"; -import { Log } from "./Log"; -import { Body } from "./Body"; +const path = require('node:path'); +const { readFile } = require('node:fs/promises'); +const reverseLineReader = require('reverse-line-reader'); +const Tail = require('tail').Tail; + +import { System } from './System'; +import { Log } from './Log'; +import { Body } from './Body'; export class Journal extends EventEmitter { @@ -22,7 +30,7 @@ export class Journal extends EventEmitter { constructor(journalPath: string) { super(); - this.#path = journalPath; + this.#path = journalPath; this.location = new System(); this.navRoute = []; @@ -97,7 +105,7 @@ export class Journal extends EventEmitter { // Look for all scanned bodies before last FSDJump, for same reasons as getting location. #getScannedBodies(): void { - let dssLine: detailedScan|null = null + let dssLine: detailedScan|null = null; reverseLineReader.eachLine(this.#path, (raw: string) => { if (raw) { @@ -110,8 +118,8 @@ export class Journal extends EventEmitter { this.location.bodies.push(new Body(dssLine, true)); } else { // Else, check that the body hasn't already been added (by a DSS scan line). - const dupChecker = {'BodyName': dssLine.BodyName, 'BodyID': dssLine.BodyID}; - const r = _.find(this.location.bodies, dupChecker); + const dupChecker = { 'BodyName': dssLine.BodyName, 'BodyID': dssLine.BodyID }; + const r = _.find(this.location.bodies, dupChecker); if (r === undefined) { // Body was not already logged, so add to list. @@ -140,9 +148,9 @@ export class Journal extends EventEmitter { if ('PlanetClass' in line) { const dupChecker = { 'BodyName': (line as planetScan<'AutoScan'>).BodyName, - 'BodyID': (line as planetScan<'AutoScan'>).BodyID, + 'BodyID': (line as planetScan<'AutoScan'>).BodyID, }; - const r = _.find(this.location.bodies, dupChecker); + const r = _.find(this.location.bodies, dupChecker); if (r === undefined) { this.location.bodies.push(new Body((line as autoScan))); @@ -168,18 +176,18 @@ export class Journal extends EventEmitter { Log.write('Checking for nav route.'); this.#getNavRoute(); - }) + }); } /* ------------------------------------------------------------------------ #getNavRoute ---- */ async #getNavRoute(): Promise { - this.navRoute = [] // Clear previous route, to catch overwritten routes. + this.navRoute = []; // Clear previous route, to catch overwritten routes. let routeFile: string|null = null; try { const filePath: string = path.dirname(this.#path) + '/NavRoute.json'; - routeFile = await readFile(filePath, {encoding: 'utf8'}); + routeFile = await readFile(filePath, { encoding: 'utf8' }); } catch (err) { Log.write(`Error reading nav route file: ${err.message}.`); } @@ -199,7 +207,7 @@ export class Journal extends EventEmitter { if (push && system.SystemAddress !== this.location.SystemAddress) { this.navRoute.push(new System(system)); } - }) + }); if (this.navRoute.length > 0) { Log.write('Nav route set.'); @@ -216,7 +224,7 @@ export class Journal extends EventEmitter { // Watch the journal for changes. watch(): void { - this.#tail = new Tail(this.#path, {useWatchFile: true}); + this.#tail = new Tail(this.#path, { useWatchFile: true }); Log.write(`Watching ${path.basename(this.#path)}...`); @@ -229,7 +237,7 @@ export class Journal extends EventEmitter { // Parse and handle journal lines. #parseLine(raw: string) { const line: journalEntry = JSON.parse(raw); - let dssFlag: boolean = false; + let dssFlag: boolean = false; switch (line.event) { // Hyperspace jump started (3.. 2.. 1..) @@ -286,7 +294,7 @@ export class Journal extends EventEmitter { if (this.navRoute.length > 0) { _.remove(this.navRoute, (system) => { return system.SystemAddress === this.location.SystemAddress; - }) + }); } this.emit('ENTERED_NEW_SYSTEM'); @@ -295,7 +303,7 @@ export class Journal extends EventEmitter { /* --------------------------------------------------------------------- #handleScanLine ---- */ #handleScanLine(line: autoScan|detailedScan, DSS: boolean = false): void { - const dupChecker = {'BodyName': line.BodyName, 'BodyID': line.BodyID}; + const dupChecker = { 'BodyName': line.BodyName, 'BodyID': line.BodyID }; let body: Body|null = null; // If it's a DSS scan, then we should have already added the body to the list. But we'll diff --git a/src/models/NewJournal.ts b/src/models/NewJournal.ts new file mode 100644 index 0000000..f510f52 --- /dev/null +++ b/src/models/NewJournal.ts @@ -0,0 +1,132 @@ +import type { JournalEvent, Scan } from '@kayahr/ed-journal'; +import { Journal as EDJournal } from '@kayahr/ed-journal'; +import { Body } from './Body'; +import { Log } from './Log'; +import { System } from './System'; +import * as _ from 'lodash-es'; + +const EventEmitter = require('events'); +const reverseLineReader = require('reverse-line-reader'); + +export class Journal extends EventEmitter { + readonly #path: string; + location: System; + navRoute: System[]; + #journal?: EDJournal; + + constructor(journalPath: string) { + super(); + + this.#path = journalPath; + this.location = new System(); + this.navRoute = []; + + this.#init(); + } + + /* --------------------------------------------------------------------------------- #init ---- */ + + #init(): void { + Log.write(`Journal initialized. Attempting to find current location.`); + + this.#getLastLocation().then(() => { + if (this.location.name !== 'Unknown') { + Log.write('Attempting to find scanned bodies in current system.'); + this.#getScannedBodies().then(async () => { + + if (this.location.bodies.length > 0) { + Log.write('Scanned bodies found.'); + this.emit('BUILD_BODY_LIST'); + } else { + Log.write('No scanned bodies found in current system.'); + } + + this.#journal = await EDJournal.open({ watch: true, position: 'end' }); + Log.write('Checking for nav route.'); + await this.#getNavRoute(); + }); + } + }); + } + + /* ---------------------------------------------------------------------- #getLastLocation ---- */ + + #getLastLocation(): any { + return reverseLineReader.eachLine(this.#path, (raw: string, last: boolean) => { + if (raw) { + const line = JSON.parse(raw); + + if (line.event === 'FSDJump' || line.event === 'Location') { + this.location = new System(line); + Log.write(`Current location set to ${this.location.name}.`); + this.emit('ENTERED_NEW_SYSTEM'); + return false; + } else if (last) { + Log.write('WARNING: Unable to find last known location.'); + return false; + } + } + }); + } + + /* --------------------------------------------------------------------- #getScannedBodies ---- */ + + #getScannedBodies(): any { + let dssLine: Scan|null = null; + + return reverseLineReader.eachLine(this.#path, (raw: string) => { + if (raw) { + const line: JournalEvent = JSON.parse(raw); + + if (dssLine) { + if (line.event === 'SAAScanComplete') { + this.location.bodies.push(new Body(dssLine, true)); + } else { + const dupChecker = { 'BodyName': dssLine.BodyName, 'BodyID': dssLine.BodyID }; + const duplicate = _.find(this.location.bodies, dupChecker); + + if (duplicate === undefined) { + this.location.bodies.push(new Body(dssLine)); + } + } + + dssLine = null; + } + + if (line.event === 'Scan' && 'ScanType' in line) { + if (line.ScanType === 'Detailed' && !('StarType' in line)) { + dssLine = line as Scan; + + } else if ('StarType' in line) { + this.location.bodies.push(new Body(line as Scan)); + + } else if (line.ScanType == 'AutoScan') { + if ('PlanetClass' in line) { + const dupChecker = { + 'BodyName': (line as Scan).BodyName, + 'BodyID': (line as Scan).BodyID, + }; + const duplicate = _.find(this.location.bodies, dupChecker); + + if (duplicate === undefined) { + this.location.bodies.push(new Body(line as Scan)); + } + + } else { // Asteroid. + this.location.bodies.push(new Body(line as Scan)); + } + } + } else if (line.event === 'FSDJump' || line.event === 'Location') { + return false; + } + } + }); + } + + /* -------------------------------------------------------------------------- #getNavRoute ---- */ + + async #getNavRoute(): Promise { + this.navRoute = []; + + } +} diff --git a/src/models/NewSafari.ts b/src/models/NewSafari.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/models/Safari.ts b/src/models/Safari.ts index 9c4590b..2a59771 100755 --- a/src/models/Safari.ts +++ b/src/models/Safari.ts @@ -1,14 +1,13 @@ -const chokidar = require('chokidar'); -const fs = require('node:fs'); -const {globSync} = require('glob'); +const chokidar = require('chokidar'); +const fs = require('node:fs'); +const { globSync } = require('glob'); +const os = require('node:os'); +const path = require('node:path'); + +import { Journal } from './Journal'; +import { Log } from './Log'; import * as _ from 'lodash-es'; -const os = require('node:os'); -const path = require('node:path'); - -import {Journal} from './Journal'; -import {Log} from './Log'; - export class Safari { static #instance: Safari; @@ -18,13 +17,10 @@ export class Safari { #watcher?: any; - private constructor(isPackaged: boolean, isTesting: boolean = false) { - if (isTesting) { + private constructor(isPackaged: boolean = false) { + if (!isPackaged && os.platform() === 'linux') { this.#journalDir = require('app-root-path').resolve('/test_journals'); - } else if (!isPackaged && os.platform() === 'linux') { // Account for WSL during development - this.#journalDir = '/mnt/c/Users/marle/Saved\ Games/Frontier\ Developments/Elite\ Dangerous/'; - } else if (os.platform() === 'win32') { // Windows this.#journalDir = path.join( os.homedir(), @@ -55,13 +51,13 @@ export class Safari { if (this.#journalDir) { this.#journalPattern = path.join(this.#journalDir, 'Journal.*.log'); - this.journal = this.#getLatestJournal(); + this.journal = this.#getLatestJournal(); } } - static start(isPackaged: boolean, isTesting: boolean = false): Safari { + static start(isPackaged: boolean = false): Safari { if (!Safari.#instance) { - Safari.#instance = new Safari(isPackaged, isTesting); + Safari.#instance = new Safari(isPackaged); } return Safari.#instance; @@ -70,10 +66,13 @@ export class Safari { /* ------------------------------------------------------------------- #getLatestJournal ---- */ // https://stackoverflow.com/questions/15696218/get-the-most-recent-file-in-a-directory-node-js - #getLatestJournal(): Journal | undefined { + #getLatestJournal(): Journal|undefined { // @ts-ignore - const journals = globSync(this.#journalPattern, {windowsPathsNoEscape: true}); - const journalPath: string | undefined = _.maxBy(journals, file => fs.statSync(file).mtime); + const journals = globSync( + this.#journalPattern, + { windowsPathsNoEscape: true }, + ); + const journalPath: string|undefined = _.maxBy(journals, file => fs.statSync(file).mtime); if (journalPath) { Log.write(`New journal file found, now watching ${path.basename(journalPath)}.`); @@ -87,7 +86,7 @@ export class Safari { /* --------------------------------------------------------------------- watchJournalDir ---- */ watchJournalDir(): void { - const options = {usePolling: true, persistent: true, ignoreInitial: true}; + const options = { usePolling: true, persistent: true, ignoreInitial: true }; // @ts-ignore this.#watcher = chokidar.watch(this.#journalPattern, options);