1 // VerCtrl.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/StringToInt.h"
6 
7 #include "../../../Windows/FileName.h"
8 #include "../../../Windows/FileFind.h"
9 
10 #include "App.h"
11 #include "RegistryUtils.h"
12 #include "OverwriteDialog.h"
13 
14 #include "resource.h"
15 
16 using namespace NWindows;
17 using namespace NFile;
18 using namespace NFind;
19 using namespace NDir;
20 
ConvertPath_to_Ctrl(const FString & path)21 static FString ConvertPath_to_Ctrl(const FString &path)
22 {
23   FString s = path;
24   s.Replace(FChar(':'), FChar('_'));
25   return s;
26 }
27 
28 struct CFileDataInfo
29 {
30   CByteBuffer Data;
31   BY_HANDLE_FILE_INFORMATION Info;
32   bool IsOpen;
33 
CFileDataInfoCFileDataInfo34   CFileDataInfo(): IsOpen (false) {}
GetSizeCFileDataInfo35   UInt64 GetSize() const { return (((UInt64)Info.nFileSizeHigh) << 32) + Info.nFileSizeLow; }
36   bool Read(const FString &path);
37 };
38 
39 
Read(const FString & path)40 bool CFileDataInfo::Read(const FString &path)
41 {
42   IsOpen = false;
43   NIO::CInFile file;
44   if (!file.Open(path))
45     return false;
46   if (!file.GetFileInformation(&Info))
47     return false;
48 
49   const UInt64 size = GetSize();
50   const size_t size2 = (size_t)size;
51   if (size2 != size || size2 > (1 << 28))
52   {
53     SetLastError(1);
54     return false;
55   }
56 
57   Data.Alloc(size2);
58 
59   size_t processedSize;
60   if (!file.ReadFull(Data, size2, processedSize))
61     return false;
62   if (processedSize != size2)
63   {
64     SetLastError(1);
65     return false;
66   }
67   IsOpen = true;
68   return true;
69 }
70 
71 
CreateComplexDir_for_File(const FString & path)72 static bool CreateComplexDir_for_File(const FString &path)
73 {
74   FString resDirPrefix;
75   FString resFileName;
76   if (!GetFullPathAndSplit(path, resDirPrefix, resFileName))
77     return false;
78   return CreateComplexDir(resDirPrefix);
79 }
80 
81 
ParseNumberString(const FString & s,UInt32 & number)82 static bool ParseNumberString(const FString &s, UInt32 &number)
83 {
84   const FChar *end;
85   UInt64 result = ConvertStringToUInt64(s, &end);
86   if (*end != 0 || s.IsEmpty() || result > (UInt32)0x7FFFFFFF)
87     return false;
88   number = (UInt32)result;
89   return true;
90 }
91 
92 
WriteFile(const FString & path,bool createAlways,const CFileDataInfo & fdi,const CPanel & panel)93 static void WriteFile(const FString &path, bool createAlways, const CFileDataInfo &fdi, const CPanel &panel)
94 {
95   NIO::COutFile outFile;
96   if (!outFile.Create(path, createAlways)) // (createAlways = false) means CREATE_NEW
97   {
98     panel.MessageBox_LastError();
99     return;
100   }
101   UInt32 processedSize;
102   if (!outFile.Write(fdi.Data, (UInt32)fdi.Data.Size(), processedSize))
103   {
104     panel.MessageBox_LastError();
105     return;
106   }
107   if (processedSize != fdi.Data.Size())
108   {
109     panel.MessageBox_Error(L"Write error");
110     return;
111   }
112   if (!outFile.SetTime(
113       &fdi.Info.ftCreationTime,
114       &fdi.Info.ftLastAccessTime,
115       &fdi.Info.ftLastWriteTime))
116   {
117     panel.MessageBox_LastError();
118     return;
119   }
120 
121   if (!SetFileAttrib(path, fdi.Info.dwFileAttributes))
122   {
123     panel.MessageBox_LastError();
124     return;
125   }
126 }
127 
128 
FILETIME_to_UInt64(const FILETIME & ft)129 static UInt64 FILETIME_to_UInt64(const FILETIME &ft)
130 {
131   return ft.dwLowDateTime | ((UInt64)ft.dwHighDateTime << 32);
132 }
133 
UInt64_TO_FILETIME(UInt64 v,FILETIME & ft)134 static void UInt64_TO_FILETIME(UInt64 v, FILETIME &ft)
135 {
136   ft.dwLowDateTime = (DWORD)v;
137   ft.dwHighDateTime = (DWORD)(v >> 32);
138 }
139 
140 
VerCtrl(unsigned id)141 void CApp::VerCtrl(unsigned id)
142 {
143   const CPanel &panel = GetFocusedPanel();
144 
145   if (!panel.Is_IO_FS_Folder())
146   {
147     panel.MessageBox_Error_UnsupportOperation();
148     return;
149   }
150 
151   CRecordVector<UInt32> indices;
152   panel.GetSelectedItemsIndices(indices);
153 
154   if (indices.Size() != 1)
155   {
156     // panel.MessageBox_Error_UnsupportOperation();
157     return;
158   }
159 
160   const FString path = us2fs(panel.GetItemFullPath(indices[0]));
161 
162   UString vercPath;
163   ReadReg_VerCtrlPath(vercPath);
164   if (vercPath.IsEmpty())
165     return;
166   NName::NormalizeDirPathPrefix(vercPath);
167 
168   FString dirPrefix;
169   FString fileName;
170   if (!GetFullPathAndSplit(path, dirPrefix, fileName))
171   {
172     panel.MessageBox_LastError();
173     return;
174   }
175 
176   const FString dirPrefix2 = us2fs(vercPath) + ConvertPath_to_Ctrl(dirPrefix);
177   const FString path2 = dirPrefix2 + fileName;
178 
179   bool sameTime = false;
180   bool sameData = false;
181   bool areIdentical = false;
182 
183   CFileDataInfo fdi, fdi2;
184   if (!fdi.Read(path))
185   {
186     panel.MessageBox_LastError();
187     return;
188   }
189 
190   if (fdi2.Read(path2))
191   {
192     sameData = (fdi.Data == fdi2.Data);
193     sameTime = (CompareFileTime(&fdi.Info.ftLastWriteTime, &fdi2.Info.ftLastWriteTime) == 0);
194     areIdentical = (sameData && sameTime);
195   }
196 
197   const bool isReadOnly = NAttributes::IsReadOnly(fdi.Info.dwFileAttributes);
198 
199   if (id == IDM_VER_EDIT)
200   {
201     if (!isReadOnly)
202     {
203       panel.MessageBox_Error(L"File is not read-only");
204       return;
205     }
206 
207     if (!areIdentical)
208     {
209       if (fdi2.IsOpen)
210       {
211         NFind::CEnumerator enumerator;
212         FString d2 = dirPrefix2;
213         d2 += "_7vc";
214         d2.Add_PathSepar();
215         d2 += fileName;
216         d2.Add_PathSepar();
217         enumerator.SetDirPrefix(d2);
218         NFind::CDirEntry fi;
219         Int32 maxVal = -1;
220         while (enumerator.Next(fi))
221         {
222           UInt32 val;
223           if (!ParseNumberString(fi.Name, val))
224             continue;
225           if ((Int32)val > maxVal)
226             maxVal = val;
227         }
228 
229         UInt32 next = (UInt32)maxVal + 1;
230         if (maxVal < 0)
231         {
232           next = 1;
233           if (!::CreateComplexDir_for_File(path2))
234           {
235             panel.MessageBox_LastError();
236             return;
237           }
238         }
239 
240         // we rename old file2 to some name;
241         FString path_num = d2;
242         {
243           AString t;
244           t.Add_UInt32((UInt32)next);
245           while (t.Len() < 3)
246             t.InsertAtFront('0');
247           path_num += t;
248         }
249 
250         if (maxVal < 0)
251         {
252           if (!::CreateComplexDir_for_File(path_num))
253           {
254             panel.MessageBox_LastError();
255             return;
256           }
257         }
258 
259         if (!NDir::MyMoveFile(path2, path_num))
260         {
261           panel.MessageBox_LastError();
262           return;
263         }
264       }
265       else
266       {
267         if (!::CreateComplexDir_for_File(path2))
268         {
269           panel.MessageBox_LastError();
270           return;
271         }
272       }
273       /*
274       if (!::CopyFile(fs2fas(path), fs2fas(path2), TRUE))
275       {
276         panel.MessageBox_LastError();
277         return;
278       }
279       */
280       WriteFile(path2,
281           false,  // (createAlways = false) means CREATE_NEW
282           fdi, panel);
283     }
284 
285     if (!SetFileAttrib(path, fdi.Info.dwFileAttributes & ~(DWORD)FILE_ATTRIBUTE_READONLY))
286     {
287       panel.MessageBox_LastError();
288       return;
289     }
290 
291     return;
292   }
293 
294   if (isReadOnly)
295   {
296     panel.MessageBox_Error(L"File is read-only");
297     return;
298   }
299 
300   if (id == IDM_VER_COMMIT)
301   {
302     if (sameData)
303     {
304       if (!sameTime)
305       {
306         panel.MessageBox_Error(
307           L"Same data, but different timestamps.\n"
308           L"Use `Revert` to recover timestamp.");
309         return;
310       }
311     }
312 
313     const UInt64 timeStampOriginal = FILETIME_to_UInt64(fdi.Info.ftLastWriteTime);
314     UInt64 timeStamp2 = 0;
315     if (fdi2.IsOpen)
316       timeStamp2 = FILETIME_to_UInt64(fdi2.Info.ftLastWriteTime);
317 
318     if (timeStampOriginal > timeStamp2)
319     {
320       const UInt64 k_Ntfs_prec = 10000000;
321       UInt64 timeStamp = timeStampOriginal;
322       const UInt32 k_precs[] = { 60 * 60, 60, 2, 1 };
323       for (unsigned i = 0; i < ARRAY_SIZE(k_precs); i++)
324       {
325         timeStamp = timeStampOriginal;
326         const UInt64 prec = k_Ntfs_prec * k_precs[i];
327         // timeStamp += prec - 1; // for rounding up
328         timeStamp /= prec;
329         timeStamp *= prec;
330         if (timeStamp > timeStamp2)
331           break;
332       }
333 
334       if (timeStamp != timeStampOriginal
335           && timeStamp > timeStamp2)
336       {
337         FILETIME mTime;
338         UInt64_TO_FILETIME(timeStamp, mTime);
339         // NDir::SetFileAttrib(path, 0);
340         {
341           NIO::COutFile outFile;
342           if (!outFile.Open(path, OPEN_EXISTING))
343           {
344             panel.MessageBox_LastError();
345             return;
346             // if (::GetLastError() != ERROR_SUCCESS)
347             // throw "open error";
348           }
349           else
350           {
351             const UInt64 cTime = FILETIME_to_UInt64(fdi.Info.ftCreationTime);
352             if (cTime > timeStamp)
353               outFile.SetTime(&mTime, NULL, &mTime);
354             else
355               outFile.SetMTime(&mTime);
356           }
357         }
358       }
359     }
360 
361     if (!SetFileAttrib(path, fdi.Info.dwFileAttributes | FILE_ATTRIBUTE_READONLY))
362     {
363       panel.MessageBox_LastError();
364       return;
365     }
366     return;
367   }
368 
369   if (id == IDM_VER_REVERT)
370   {
371     if (!fdi2.IsOpen)
372     {
373       panel.MessageBox_Error(L"No file to revert");
374       return;
375     }
376     if (!sameData || !sameTime)
377     {
378       if (!sameData)
379       {
380         /*
381         UString m;
382         m = "Are you sure you want to revert file ?";
383         m.Add_LF();
384         m += path;
385         if (::MessageBoxW(panel.GetParent(), m, L"Version Control: File Revert", MB_OKCANCEL | MB_ICONQUESTION) != IDOK)
386           return;
387         */
388         COverwriteDialog dialog;
389 
390         dialog.OldFileInfo.SetTime(&fdi.Info.ftLastWriteTime);
391         dialog.OldFileInfo.SetSize(fdi.GetSize());
392         dialog.OldFileInfo.Name = fs2us(path);
393 
394         dialog.NewFileInfo.SetTime(&fdi2.Info.ftLastWriteTime);
395         dialog.NewFileInfo.SetSize(fdi2.GetSize());
396         dialog.NewFileInfo.Name = fs2us(path2);
397 
398         dialog.ShowExtraButtons = false;
399         dialog.DefaultButton_is_NO = true;
400 
401         INT_PTR writeAnswer = dialog.Create(panel.GetParent());
402 
403         if (writeAnswer != IDYES)
404           return;
405       }
406 
407       WriteFile(path,
408           true,  // (createAlways = true) means CREATE_ALWAYS
409           fdi2, panel);
410     }
411     else
412     {
413       if (!SetFileAttrib(path, fdi2.Info.dwFileAttributes | FILE_ATTRIBUTE_READONLY))
414       {
415         panel.MessageBox_LastError();
416         return;
417       }
418     }
419     return;
420   }
421 
422   // if (id == IDM_VER_DIFF)
423   {
424     if (!fdi2.IsOpen)
425       return;
426     DiffFiles(fs2us(path2), fs2us(path));
427   }
428 }
429