@@ -4,6 +4,19 @@ const axios = require("axios");
44const fs = require ( "fs" ) ;
55const path = require ( "path" ) ;
66
7+ function atomicWrite ( filePath , data ) {
8+ const dir = path . dirname ( filePath ) ;
9+ const ext = path . extname ( filePath ) ;
10+ const base = path . basename ( filePath , ext ) ;
11+ const tmpPath = path . join (
12+ dir ,
13+ `${ base } .tmp.${ process . pid } .${ Date . now ( ) } ${ ext } ` ,
14+ ) ;
15+
16+ fs . writeFileSync ( tmpPath , JSON . stringify ( data , null , 2 ) , "utf8" ) ;
17+ fs . renameSync ( tmpPath , filePath ) ;
18+ }
19+
720async function fetchData ( url ) {
821 try {
922 const res = await axios . get ( url , { timeout : 15000 } ) ;
@@ -69,7 +82,7 @@ function updateUserHistory(user, DATA_DIR) {
6982
7083 history . sort ( ( a , b ) => new Date ( a . date ) - new Date ( b . date ) ) ;
7184
72- fs . writeFileSync ( userHistoryPath , JSON . stringify ( history , null , 2 ) , "utf8" ) ;
85+ atomicWrite ( userHistoryPath , history ) ;
7386}
7487
7588function assignCompetitionRanks ( sortedData ) {
@@ -161,6 +174,25 @@ async function computeRankChanges(currentSorted, filename) {
161174 const DATA_DIR = process . env . DATA_DIR || path . join ( __dirname , ".." , "data" ) ;
162175 console . log ( `Using data directory: ${ DATA_DIR } ` ) ;
163176
177+ // Clean up leftover tmp files from previous crashes
178+ const tmpCleanupDirs = [ DATA_DIR , path . join ( DATA_DIR , "daily" ) ] ;
179+ tmpCleanupDirs . forEach ( ( dirPath ) => {
180+ try {
181+ if ( fs . existsSync ( dirPath ) ) {
182+ const tmpFiles = fs
183+ . readdirSync ( dirPath )
184+ . filter ( ( f ) => f . includes ( ".tmp." ) ) ;
185+ tmpFiles . forEach ( ( f ) => {
186+ const filePath = path . join ( dirPath , f ) ;
187+ fs . unlinkSync ( filePath ) ;
188+ console . log ( `Cleaned up leftover tmp file: ${ f } ` ) ;
189+ } ) ;
190+ }
191+ } catch ( err ) {
192+ console . warn ( `Failed to clean tmp files in ${ dirPath } :` , err . message ) ;
193+ }
194+ } ) ;
195+
164196 console . log ( "Loading users..." ) ;
165197 const userFilePath = path . join ( DATA_DIR , "users.json" ) ;
166198 let users = [ ] ;
@@ -204,7 +236,7 @@ async function computeRankChanges(currentSorted, filename) {
204236 console . log ( "Writing daily data to file..." ) ;
205237 const filepath = path . join ( DATA_DIR , "daily" , getFileName ( 0 ) ) ;
206238 try {
207- fs . writeFileSync ( filepath , JSON . stringify ( overallData , null , 2 ) , "utf8" ) ;
239+ atomicWrite ( filepath , overallData ) ;
208240 console . log ( "Daily data saved successfully" ) ;
209241 } catch ( err ) {
210242 console . error ( `Failed to write json file: ` , err . message ) ;
@@ -247,11 +279,7 @@ async function computeRankChanges(currentSorted, filename) {
247279
248280 await computeRankChanges ( overallData , "overall.json" ) ;
249281 try {
250- fs . writeFileSync (
251- overallFilepath ,
252- JSON . stringify ( overallData , null , 2 ) ,
253- "utf8" ,
254- ) ;
282+ atomicWrite ( overallFilepath , overallData ) ;
255283 console . log ( "Daily data saved successfully" ) ;
256284 } catch ( err ) {
257285 console . error ( `Failed to write json file: ` , err . message ) ;
@@ -307,7 +335,7 @@ async function computeRankChanges(currentSorted, filename) {
307335 const dailyFilepath = path . join ( DATA_DIR , "daily.json" ) ;
308336 await computeRankChanges ( dailyData , "daily.json" ) ;
309337 try {
310- fs . writeFileSync ( dailyFilepath , JSON . stringify ( dailyData , null , 2 ) , "utf8" ) ;
338+ atomicWrite ( dailyFilepath , dailyData ) ;
311339 console . log ( "Daily data saved successfully" ) ;
312340 } catch ( err ) {
313341 console . error ( `Failed to write json file: ` , err . message ) ;
@@ -363,11 +391,7 @@ async function computeRankChanges(currentSorted, filename) {
363391 const weeklyFilepath = path . join ( DATA_DIR , "weekly.json" ) ;
364392 await computeRankChanges ( weeklyData , "weekly.json" ) ;
365393 try {
366- fs . writeFileSync (
367- weeklyFilepath ,
368- JSON . stringify ( weeklyData , null , 2 ) ,
369- "utf8" ,
370- ) ;
394+ atomicWrite ( weeklyFilepath , weeklyData ) ;
371395 console . log ( "Weekly data saved successfully" ) ;
372396 } catch ( err ) {
373397 console . error ( `Failed to write json file: ` , err . message ) ;
@@ -423,11 +447,7 @@ async function computeRankChanges(currentSorted, filename) {
423447 const monthlyFilepath = path . join ( DATA_DIR , "monthly.json" ) ;
424448 await computeRankChanges ( monthlyData , "monthly.json" ) ;
425449 try {
426- fs . writeFileSync (
427- monthlyFilepath ,
428- JSON . stringify ( monthlyData , null , 2 ) ,
429- "utf8" ,
430- ) ;
450+ atomicWrite ( monthlyFilepath , monthlyData ) ;
431451 console . log ( "Monthly data saved successfully" ) ;
432452 } catch ( err ) {
433453 console . error ( `Failed to write json file: ` , err . message ) ;
@@ -484,22 +504,14 @@ async function computeRankChanges(currentSorted, filename) {
484504 const noChanges =
485505 rankChanges . length === 0 && newUsers . length === 0 && totalNewSolves === 0 ;
486506
487- fs . writeFileSync (
488- changesFilepath ,
489- JSON . stringify (
490- {
491- sync_time : new Date ( ) . toISOString ( ) ,
492- rank_changes : rankChanges ,
493- new_users : newUsers ,
494- total_new_solves : totalNewSolves ,
495- users_with_new_solves : usersWithNewSolves ,
496- no_changes : noChanges ,
497- } ,
498- null ,
499- 2 ,
500- ) ,
501- "utf8" ,
502- ) ;
507+ atomicWrite ( changesFilepath , {
508+ sync_time : new Date ( ) . toISOString ( ) ,
509+ rank_changes : rankChanges ,
510+ new_users : newUsers ,
511+ total_new_solves : totalNewSolves ,
512+ users_with_new_solves : usersWithNewSolves ,
513+ no_changes : noChanges ,
514+ } ) ;
503515 console . log ( "changes.json saved successfully" ) ;
504516 } catch ( err ) {
505517 console . error ( "Failed to write changes.json: " , err . message ) ;
@@ -510,18 +522,10 @@ async function computeRankChanges(currentSorted, filename) {
510522 try {
511523 const now = new Date ( ) ;
512524 const nextSync = new Date ( now . getTime ( ) + 5 * 60 * 1000 ) ;
513- fs . writeFileSync (
514- syncFilepath ,
515- JSON . stringify (
516- {
517- lastSync : now . toISOString ( ) ,
518- nextSync : nextSync . toISOString ( ) ,
519- } ,
520- null ,
521- 2 ,
522- ) ,
523- "utf8" ,
524- ) ;
525+ atomicWrite ( syncFilepath , {
526+ lastSync : now . toISOString ( ) ,
527+ nextSync : nextSync . toISOString ( ) ,
528+ } ) ;
525529 console . log ( "Sync timestamp saved successfully" ) ;
526530 } catch ( err ) {
527531 console . error ( `Failed to write sync file: ` , err . message ) ;
0 commit comments