Skip to content
This repository was archived by the owner on Oct 24, 2021. It is now read-only.

Commit 0036bb6

Browse files
authored
feat: export log if configured to do so
feat: export log if configured to do so
2 parents f3d1f73 + fd6e1cb commit 0036bb6

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

src/GameState.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ export class MatchLogEntry {
128128
}
129129

130130
export class GameState {
131+
startTime: number;
132+
131133
playerCount: number;
132134

133135
gameOverCount: number;
@@ -155,6 +157,23 @@ export class GameState {
155157
return this.players.length;
156158
}
157159

160+
/**
161+
* Returns true if the game state is active for an ongoing game
162+
*/
163+
get active(): boolean {
164+
return Boolean(this.startTime) && !this.complete;
165+
}
166+
167+
/**
168+
* Returns true if the gamestate is representing a completed game.
169+
*/
170+
get complete(): boolean {
171+
return this.gameOverCount === 2;
172+
}
173+
174+
/**
175+
* Resets the game state to default conditions.
176+
*/
158177
reset(): void {
159178
this.players = [];
160179
this.matchLog = [];
@@ -163,6 +182,14 @@ export class GameState {
163182
this.#missingEntityIds.clear();
164183
}
165184

185+
/**
186+
* Resets the game state to default conditions and marks it as a game that has begun.
187+
*/
188+
start(): void {
189+
this.reset();
190+
this.startTime = Date.now();
191+
}
192+
166193
addPlayer(player: Player): Player {
167194
const existingPlayer = this.players.find(p => p.id === player.id);
168195
if (existingPlayer) {

src/LogWatcher.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ export interface Options {
2020
logFile: string;
2121
configFile: string;
2222

23+
/**
24+
* Path to save any log files
25+
*/
26+
logDirectory?: string;
27+
2328
/**
2429
* The number of lines in each parsing group.
2530
*/
@@ -71,6 +76,9 @@ export class LogWatcher extends HspEventsEmitterClass {
7176

7277
private _updateDebouncer: (filePath: string, stats: fs.Stats) => void;
7378

79+
private _logStream: fs.WriteStream | null = null;
80+
private _linesQueued = new Array<string>();
81+
7482
constructor(options?: Partial<Options>) {
7583
super();
7684

@@ -89,6 +97,11 @@ export class LogWatcher extends HspEventsEmitterClass {
8997
throw new Error('Log file path does not exist.');
9098
}
9199

100+
if (this.options.logDirectory) {
101+
log('output log directory: %s', this.options.logDirectory);
102+
fs.mkdirSync(this.options.logDirectory, {recursive: true});
103+
}
104+
92105
// Copy local config file to the correct location.
93106
// We're just gonna do this every time.
94107
const localConfigFile = path.join(__dirname, '../log.config');
@@ -173,6 +186,8 @@ export class LogWatcher extends HspEventsEmitterClass {
173186
this.emit('gamestate-changed', gameState);
174187
updated = false;
175188
}
189+
190+
this._handleLogging(line, gameState);
176191
});
177192

178193
if (updated) {
@@ -182,6 +197,55 @@ export class LogWatcher extends HspEventsEmitterClass {
182197
return gameState;
183198
}
184199

200+
/**
201+
* Internal method to potentially write a line to the output log (if enabled).
202+
* @param line
203+
* @param gameState
204+
*/
205+
private _handleLogging(line: string, gameState: GameState) {
206+
const activeOrComplete = gameState.active || gameState.complete;
207+
if (!this.options.logDirectory || !activeOrComplete) {
208+
return;
209+
}
210+
211+
// If there's no file stream and we have enough info to create one, then create one.
212+
if (!this._logStream && gameState.active && gameState.numPlayers === 2) {
213+
const [player1, player2] = gameState.getAllPlayers();
214+
const ext = path.extname(this.options.logFile);
215+
const filename = `${gameState.startTime}_${player1?.name ?? 'unknown'}_vs_${player2?.name ?? 'unknown'}${ext}`;
216+
217+
// Convert the name to something safe to save
218+
const specialChars = String.raw`<>:"/\|?*`.split('');
219+
const filenameSlugged = filename.split('').map(c => specialChars.includes(c) ? '!' : c).join('');
220+
221+
// Create write stream
222+
const filepath = path.join(this.options.logDirectory, filenameSlugged);
223+
this._logStream = fs.createWriteStream(path.normalize(filepath));
224+
225+
// Flush our "buffer"
226+
this._logStream.write(this._linesQueued.join(''));
227+
this._linesQueued = [];
228+
}
229+
230+
// Write to output log.
231+
// If we are still waiting for players to load,write to a buffer beforehand.
232+
// This is because the filename is decided AFTER the file has started.
233+
if (gameState.active || (gameState.complete && this._logStream)) {
234+
if (this._logStream) {
235+
this._logStream.write(line + '\n');
236+
} else {
237+
// No file stream, so write to our "buffer"
238+
this._linesQueued.push(line + '\n');
239+
}
240+
}
241+
242+
// If the game is complete, close the output stream
243+
if (gameState.complete) {
244+
this._logStream?.end();
245+
this._logStream = null;
246+
}
247+
}
248+
185249
private _update(filePath: string, stats: fs.Stats): void {
186250
// We're only going to read the portion of the file that we have not read so far.
187251
const newFileSize = stats.size;

src/line-parsers/game-start.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export class GameStartLineParser extends AbstractLineParser {
88
eventName = 'game-start' as const;
99

1010
lineMatched(_: string[], gameState: GameState): void {
11-
gameState.reset();
11+
gameState.start();
1212
}
1313

1414
formatLogMessage(): string {

test/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ describe('hearthstone-log-watcher', () => {
7272
// This is being tested elsewhere, so clear it out here
7373
// Removed as a judgement call because this tests way too much.
7474
gameState.matchLog = [];
75+
delete gameState.startTime;
7576

7677
expect(gameState).deep.equal({
7778
playerCount: 2,

0 commit comments

Comments
 (0)