33const { app, BrowserWindow, Menu, MenuItem, session } = require ( "electron" ) ;
44const path = require ( "path" ) ;
55const url = require ( "url" ) ;
6- const childProcess = require ( "child_process" ) ;
76const { ipcMain, shell } = require ( "electron" ) ;
87const fs = require ( "fs" ) ;
9- const steam = require ( './steam' ) ;
8+ const steam = require ( "./steam" ) ;
9+ const asyncLock = require ( "async-lock" ) ;
10+
1011const isDev = process . argv . indexOf ( "--dev" ) >= 0 ;
1112const isLocal = process . argv . indexOf ( "--local" ) >= 0 ;
1213
@@ -153,7 +154,82 @@ ipcMain.on("exit-app", (event, flag) => {
153154 app . quit ( ) ;
154155} ) ;
155156
156- function performFsJob ( job ) {
157+ let renameCounter = 1 ;
158+
159+ const fileLock = new asyncLock ( {
160+ timeout : 30000 ,
161+ maxPending : 1000 ,
162+ } ) ;
163+
164+ function niceFileName ( filename ) {
165+ return filename . replace ( storePath , "@" ) ;
166+ }
167+
168+ async function writeFileSafe ( filename , contents ) {
169+ ++ renameCounter ;
170+ const prefix = "[ " + renameCounter + ":" + niceFileName ( filename ) + " ] " ;
171+ const transactionId = String ( new Date ( ) . getTime ( ) ) + "." + renameCounter ;
172+
173+ if ( fileLock . isBusy ( ) ) {
174+ console . warn ( prefix , "Concurrent write process on" , filename ) ;
175+ }
176+
177+ fileLock . acquire ( filename , async ( ) => {
178+ console . log ( prefix , "Starting write on" , niceFileName ( filename ) , "in transaction" , transactionId ) ;
179+
180+ if ( ! fs . existsSync ( filename ) ) {
181+ // this one is easy
182+ console . log ( prefix , "Writing file instantly because it does not exist:" , niceFileName ( filename ) ) ;
183+ await fs . promises . writeFile ( filename , contents , { encoding : "utf8" } ) ;
184+ return ;
185+ }
186+
187+ // first, write a temporary file (.tmp-XXX)
188+ const tempName = filename + ".tmp-" + transactionId ;
189+ console . log ( prefix , "Writing temporary file" , niceFileName ( tempName ) ) ;
190+ await fs . promises . writeFile ( tempName , contents , { encoding : "utf8" } ) ;
191+
192+ // now, rename the original file to (.backup-XXX)
193+ const oldTemporaryName = filename + ".backup-" + transactionId ;
194+ console . log (
195+ prefix ,
196+ "Renaming old file" ,
197+ niceFileName ( filename ) ,
198+ "to" ,
199+ niceFileName ( oldTemporaryName )
200+ ) ;
201+ await fs . promises . rename ( filename , oldTemporaryName ) ;
202+
203+ // now, rename the temporary file (.tmp-XXX) to the target
204+ console . log (
205+ prefix ,
206+ "Renaming the temporary file" ,
207+ niceFileName ( tempName ) ,
208+ "to the original" ,
209+ niceFileName ( filename )
210+ ) ;
211+ await fs . promises . rename ( tempName , filename ) ;
212+
213+ // we are done now, try to create a backup, but don't fail if the backup fails
214+ try {
215+ // check if there is an old backup file
216+ const backupFileName = filename + ".backup" ;
217+ if ( fs . existsSync ( backupFileName ) ) {
218+ console . log ( prefix , "Deleting old backup file" , niceFileName ( backupFileName ) ) ;
219+ // delete the old backup
220+ await fs . promises . unlink ( backupFileName ) ;
221+ }
222+
223+ // rename the old file to the new backup file
224+ console . log ( prefix , "Moving" , niceFileName ( oldTemporaryName ) , "to the backup file location" ) ;
225+ await fs . promises . rename ( oldTemporaryName , backupFileName ) ;
226+ } catch ( ex ) {
227+ console . error ( prefix , "Failed to switch backup files:" , ex ) ;
228+ }
229+ } ) ;
230+ }
231+
232+ async function performFsJob ( job ) {
157233 const fname = path . join ( storePath , job . filename ) ;
158234
159235 switch ( job . type ) {
@@ -165,38 +241,35 @@ function performFsJob(job) {
165241 } ;
166242 }
167243
168- let contents = "" ;
169244 try {
170- contents = fs . readFileSync ( fname , { encoding : "utf8" } ) ;
245+ const data = await fs . promises . readFile ( fname , { encoding : "utf8" } ) ;
246+ return {
247+ success : true ,
248+ data,
249+ } ;
171250 } catch ( ex ) {
172251 return {
173252 error : ex ,
174253 } ;
175254 }
176-
177- return {
178- success : true ,
179- data : contents ,
180- } ;
181255 }
182256 case "write" : {
183257 try {
184- fs . writeFileSync ( fname , job . contents ) ;
258+ await writeFileSafe ( fname , job . contents ) ;
259+ return {
260+ success : true ,
261+ data : job . contents ,
262+ } ;
185263 } catch ( ex ) {
186264 return {
187265 error : ex ,
188266 } ;
189267 }
190-
191- return {
192- success : true ,
193- data : job . contents ,
194- } ;
195268 }
196269
197270 case "delete" : {
198271 try {
199- fs . unlinkSync ( fname ) ;
272+ await fs . promises . unlink ( fname ) ;
200273 } catch ( ex ) {
201274 return {
202275 error : ex ,
@@ -214,15 +287,10 @@ function performFsJob(job) {
214287 }
215288}
216289
217- ipcMain . on ( "fs-job" , ( event , arg ) => {
218- const result = performFsJob ( arg ) ;
290+ ipcMain . on ( "fs-job" , async ( event , arg ) => {
291+ const result = await performFsJob ( arg ) ;
219292 event . reply ( "fs-response" , { id : arg . id , result } ) ;
220293} ) ;
221294
222- ipcMain . on ( "fs-sync-job" , ( event , arg ) => {
223- const result = performFsJob ( arg ) ;
224- event . returnValue = result ;
225- } ) ;
226-
227295steam . init ( isDev ) ;
228296steam . listen ( ) ;
0 commit comments