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};