1const fs = require('fs'); 2const path = require('path'); 3const { COPYFILE_EXCL } = fs.constants; 4 5function isConflict(origStat, stat) 6{ 7 return stat != null && origStat != null && stat.mtimeMs != origStat.mtimeMs; 8}; 9 10function saveFile(fileObject, data, origStat, overwrite, defEnc, reqId) 11{ 12 var retryCount = 0; 13 var backupCreated = false; 14 15 var writeFile = function() 16 { 17 if (data == null || data.length == 0) 18 { 19 postMessage({error: true, msg: 'empty data', reqId: reqId}); 20 } 21 else 22 { 23 var writeEnc = defEnc || fileObject.encoding; 24 25 fs.writeFile(fileObject.path, data, writeEnc, 26 function (e) 27 { 28 if (e) 29 { 30 postMessage({error: true, msg: 'saving failed', e: e, reqId: reqId}); 31 } 32 else 33 { 34 fs.stat(fileObject.path, function(e2, stat2) 35 { 36 if (e2) 37 { 38 postMessage({error: true, msg: 'stat failed', e: e2, reqId: reqId}); 39 } 40 else 41 { 42 // Workaround for possible writing errors is to check the written 43 // contents of the file and retry 3 times before showing an error 44 fs.readFile(fileObject.path, writeEnc, (err, writtenData) => 45 { 46 if (data != writtenData) 47 { 48 retryCount++; 49 50 if (retryCount < 3) 51 { 52 writeFile(); 53 } 54 else 55 { 56 postMessage({error: true, msg: 'all saving trials failed', e: e, reqId: reqId}); 57 } 58 } 59 else 60 { 61 postMessage({success: true, data: {stat: stat2}, reqId: reqId}); 62 63 if (backupCreated) 64 { 65 fs.unlink(fileObject.bkpPath, (err) => {}); //Ignore errors! 66 } 67 } 68 }); 69 } 70 }); 71 } 72 }); 73 } 74 }; 75 76 function doSaveFile() 77 { 78 //Copy file to backup file (after conflict and stat is checked) 79 fs.copyFile(fileObject.path, fileObject.bkpPath, COPYFILE_EXCL, (err) => 80 { 81 if (!err) 82 { 83 backupCreated = true; 84 } 85 86 writeFile(); 87 }); 88 }; 89 90 if (overwrite) 91 { 92 doSaveFile(); 93 } 94 else 95 { 96 //TODO Using stat before write is not recommended, we can check the error code from writeFile 97 fs.stat(fileObject.path, function(err, stat) 98 { 99 if (isConflict(origStat, stat)) 100 { 101 postMessage({error: true, msg: 'conflict', e: {isConflicted: true}, reqId: reqId}); 102 } 103 else if (err != null && err.code !== 'ENOENT') 104 { 105 postMessage({error: true, msg: 'stat failed', e: err, reqId: reqId}); 106 } 107 else 108 { 109 doSaveFile(); 110 } 111 }); 112 } 113}; 114 115//TODO handle reqId better 116onmessage = function(e) 117{ 118 switch(e.data.action) 119 { 120 case 'saveFile': 121 saveFile(e.data.fileObject, e.data.data, e.data.origStat, e.data.overwrite, e.data.defEnc, e.data.reqId); 122 break; 123 }; 124};