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_ALWAYS_or_NEW(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.Get_ItemIndices_Selected(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 = (Int32)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 < Z7_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_EXISTING(path))
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.Path = fs2us(path);
393
394 dialog.NewFileInfo.SetTime(fdi2.Info.ftLastWriteTime);
395 dialog.NewFileInfo.SetSize(fdi2.GetSize());
396 dialog.NewFileInfo.Path = 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