@@ -197,48 +197,45 @@ export class Manager extends EventEmitter {
197
197
* @returns {string } The path to the player's JSON file
198
198
*/
199
199
private async getPlayerFilePath ( guildId : string ) : Promise < string > {
200
- // Get the directory path to where the player's JSON file will be saved
201
200
const configDir = path . join ( process . cwd ( ) , "magmastream" , "dist" , "sessionData" , "players" ) ;
202
- // Make sure the directory exists, create it if it doesn't
201
+
203
202
try {
204
203
await fs . mkdir ( configDir , { recursive : true } ) ;
204
+ return path . join ( configDir , `${ guildId } .json` ) ;
205
205
} catch ( err ) {
206
- console . error ( "Error creating directory:" , err ) ;
207
- throw err ; // Re-throw to let the caller handle it
206
+ console . error ( "Error ensuring player data directory exists :" , err ) ;
207
+ throw new Error ( `Failed to resolve player file path for guild ${ guildId } ` ) ;
208
208
}
209
-
210
- // Generate the full path to the player's JSON file
211
- return path . join ( configDir , `${ guildId } .json` ) ;
212
209
}
213
210
214
211
/**
215
212
* Saves player states to the JSON file.
216
213
* @param {string } guildId - The guild ID of the player to save
217
214
*/
218
215
public async savePlayerState ( guildId : string ) : Promise < void > {
219
- console . log ( "Entering savePlayerState method for guild:" , guildId ) ; // Check if function is called
220
-
221
- // Make sure getPlayerFilePath is awaited
222
- const playerStateFilePath = await this . getPlayerFilePath ( guildId ) ;
216
+ console . log ( `Attempting to save player state for guild: ${ guildId } ` ) ;
223
217
224
- console . log ( "Players map:" , this . players ) ;
225
- console . log ( "Guild ID:" , guildId ) ;
226
- const player = this . players . get ( guildId ) ;
218
+ try {
219
+ const playerStateFilePath = await this . getPlayerFilePath ( guildId ) ;
220
+ console . log ( `Resolved file path: ${ playerStateFilePath } ` ) ;
227
221
228
- // If the player does not exist or is disconnected, or the voice channel is not specified, do not save the player state
229
- if ( ! player || player . state === StateTypes . Disconnected || ! player . voiceChannelId ) {
230
- // Clean up any inactive players
231
- return this . cleanupInactivePlayers ( ) ;
232
- }
222
+ const player = this . players . get ( guildId ) ;
223
+ if ( ! player || player . state === StateTypes . Disconnected || ! player . voiceChannelId ) {
224
+ console . warn ( `Skipping save for inactive player: ${ guildId } ` ) ;
225
+ return ;
226
+ }
233
227
234
- // Serialize the player instance to avoid circular references
235
- const serializedPlayer = this . serializePlayer ( player ) as unknown as Player ;
228
+ console . log ( `Serializing player state for: ${ guildId } ` ) ;
229
+ const serializedPlayer = this . serializePlayer ( player ) ;
236
230
237
- // Write the serialized player state to the JSON file
238
- fs . writeFile ( playerStateFilePath , JSON . stringify ( serializedPlayer , null , 2 ) , "utf-8" ) ;
231
+ console . log ( `Writing player state to file: ${ playerStateFilePath } ` ) ;
232
+ await fs . writeFile ( playerStateFilePath , JSON . stringify ( serializedPlayer , null , 2 ) , "utf-8" ) ;
233
+ console . log ( `Successfully saved player state for: ${ guildId } ` ) ;
239
234
240
- // Emit a debug event to indicate the player state has been saved
241
- this . emit ( "debug" , `[MANAGER] Saving player: ${ guildId } at location: ${ playerStateFilePath } ` ) ;
235
+ this . emit ( "debug" , `[MANAGER] Player state saved: ${ guildId } ` ) ;
236
+ } catch ( error ) {
237
+ console . error ( `Error saving player state for guild ${ guildId } :` , error ) ;
238
+ }
242
239
}
243
240
244
241
/**
@@ -405,36 +402,37 @@ export class Manager extends EventEmitter {
405
402
* Optionally, it also calls {@link cleanupInactivePlayers} to remove any stale player state files.
406
403
* After saving and cleaning up, it exits the process.
407
404
*/
408
- private async handleShutdown ( ) : Promise < void > {
405
+ public async handleShutdown ( ) : Promise < void > {
409
406
console . warn ( "\x1b[31m%s\x1b[0m" , "MAGMASTREAM WARNING: Shutting down! Please wait, saving active players..." ) ;
410
407
411
- // Create an array of promises for saving player states
412
- const savePromises = Array . from ( this . players . keys ( ) ) . map ( ( guildId ) => {
413
- return new Promise < void > ( async ( resolve , reject ) => {
408
+ try {
409
+ const savePromises = Array . from ( this . players . keys ( ) ) . map ( async ( guildId ) => {
410
+ console . log ( `Saving state for guild: ${ guildId } ` ) ; // Debugging
414
411
try {
415
- await this . savePlayerState ( guildId ) ; // Await the save operation
416
- resolve ( ) ;
412
+ await this . savePlayerState ( guildId ) ;
417
413
} catch ( error ) {
418
414
console . error ( `Error saving player state for guild ${ guildId } :` , error ) ;
419
- reject ( error ) ; // Reject the promise to propagate the error
420
415
}
421
416
} ) ;
422
- } ) ;
423
-
424
- // Wait for all save operations to complete and check for errors
425
- const results = await Promise . allSettled ( savePromises ) ;
426
- const errors = results . filter ( ( result ) => result . status === "rejected" ) ;
427
417
428
- if ( errors . length > 0 ) {
429
- console . error ( "`\x1b[31m%s\x1b[0m" , `MAGMASTREAM ERROR: ${ errors . length } player states failed to save.` ) ;
430
- }
418
+ console . log ( "Waiting for all player states to save..." ) ;
419
+ await Promise . allSettled ( savePromises ) ;
420
+ console . log ( "All player states saved." ) ;
431
421
432
- // Clean up inactive players here
433
- this . cleanupInactivePlayers ( ) ;
422
+ console . log ( "Cleaning up inactive players..." ) ;
423
+ await this . cleanupInactivePlayers ( ) ;
424
+ console . log ( "Cleanup complete." ) ;
434
425
435
- console . warn ( "\x1b[32m%s\x1b[0m" , "MAGMASTREAM INFO: Shutting down complete, exiting..." ) ;
426
+ console . warn ( "\x1b[32m%s\x1b[0m" , "MAGMASTREAM INFO: Shutting down complete, exiting..." ) ;
436
427
437
- setTimeout ( ( ) => process . exit ( errors . length > 0 ? 1 : 0 ) , 100 ) ;
428
+ setTimeout ( ( ) => {
429
+ console . log ( "Exiting process..." ) ;
430
+ process . exit ( 0 ) ;
431
+ } , 500 ) ;
432
+ } catch ( error ) {
433
+ console . error ( "Unexpected error during shutdown:" , error ) ;
434
+ process . exit ( 1 ) ;
435
+ }
438
436
}
439
437
440
438
/**
@@ -489,8 +487,36 @@ export class Manager extends EventEmitter {
489
487
for ( const nodeOptions of this . options . nodes ) new ( Structure . get ( "Node" ) ) ( nodeOptions ) ;
490
488
}
491
489
492
- process . on ( "SIGINT" , async ( ) => await this . handleShutdown ( ) ) ;
493
- process . on ( "SIGTERM" , async ( ) => await this . handleShutdown ( ) ) ;
490
+ process . on ( "SIGINT" , async ( ) => {
491
+ console . warn ( "\x1b[33mSIGINT received! Graceful shutdown initiated...\x1b[0m" ) ;
492
+
493
+ try {
494
+ await this . handleShutdown ( ) ;
495
+ console . warn ( "\x1b[32mShutdown complete. Waiting for Node.js event loop to empty...\x1b[0m" ) ;
496
+
497
+ // Prevent forced exit by Windows
498
+ setTimeout ( ( ) => {
499
+ console . log ( "Exiting now..." ) ;
500
+ process . exit ( 0 ) ;
501
+ } , 2000 ) ;
502
+ } catch ( error ) {
503
+ console . error ( "Error during shutdown:" , error ) ;
504
+ process . exit ( 1 ) ;
505
+ }
506
+ } ) ;
507
+
508
+ process . on ( "SIGTERM" , async ( ) => {
509
+ console . warn ( "\x1b[33mSIGTERM received! Graceful shutdown initiated...\x1b[0m" ) ;
510
+
511
+ try {
512
+ await this . handleShutdown ( ) ;
513
+ console . warn ( "\x1b[32mShutdown complete. Exiting now...\x1b[0m" ) ;
514
+ process . exit ( 0 ) ;
515
+ } catch ( error ) {
516
+ console . error ( "Error during SIGTERM shutdown:" , error ) ;
517
+ process . exit ( 1 ) ;
518
+ }
519
+ } ) ;
494
520
}
495
521
496
522
/**
0 commit comments