1 // SPDX-License-Identifier: GPL-2.0-or-later
5 * @brief Code file routines
10 #include "UnicodeString.h"
11 #include "DiffWrapper.h"
12 #include "PathContext.h"
16 #include "DirTravel.h"
17 #include "OptionsDiffOptions.h"
19 #include "codepage_detect.h"
20 #include "OptionsMgr.h"
21 #include "OptionsDef.h"
22 #include "ClipBoard.h"
29 * @brief Default constructor.
31 CPatchTool::CPatchTool() : m_bOpenToEditor(false), m_bCopyToClipbard(false)
36 * @brief Default destructor.
38 CPatchTool::~CPatchTool() = default;
41 * @brief Adds files to list for patching.
42 * @param [in] file1 First file to add.
43 * @param [in] file2 Second file to add.
45 void CPatchTool::AddFiles(const String &file1, const String &file2)
51 // TODO: Read and add file's timestamps
52 m_fileList.push_back(tFiles);
56 * @brief Add files with alternative paths.
57 * This function adds files with alternative paths. Alternative path is the
58 * one that is added to the patch file. So while @p file1 and @p file2 are
59 * paths in disk (can be temp file names), @p altPath1 and @p altPath2 are
60 * "visible " paths printed to the patch file.
61 * @param [in] file1 First path in disk.
62 * @param [in] altPath1 First path as printed to the patch file.
63 * @param [in] file2 Second path in disk.
64 * @param [in] altPath2 Second path as printed to the patch file.
66 void CPatchTool::AddFiles(const String &file1, const String &altPath1,
67 const String &file2, const String &altPath2)
72 tFiles.pathLeft = altPath1;
73 tFiles.pathRight = altPath2;
75 // TODO: Read and add file's timestamps
76 m_fileList.push_back(tFiles);
80 * @brief Create a patch from files given.
81 * @note Files can be given using AddFiles() or selecting using
84 int CPatchTool::CreatePatch()
91 // If files already inserted, add them to dialog
92 for(std::vector<PATCHFILES>::iterator iter = m_fileList.begin(); iter != m_fileList.end(); ++iter)
94 dlgPatch.AddItem(*iter);
97 if (ShowDialog(&dlgPatch))
101 if (!paths::CreateIfNeeded(paths::GetPathOnly(dlgPatch.m_fileResult)))
103 LangMessageBox(IDS_FOLDER_NOTEXIST, MB_OK | MB_ICONSTOP);
107 // Select patch create -mode
108 m_diffWrapper.SetCreatePatchFile(dlgPatch.m_fileResult);
109 m_diffWrapper.SetAppendFiles(dlgPatch.m_appendFile);
110 m_diffWrapper.SetPrediffer(nullptr);
112 size_t fileCount = dlgPatch.GetItemCount();
114 std::vector<PATCHFILES> fileList;
115 for (size_t index = 0; index < fileCount; index++)
117 const PATCHFILES& tFiles = dlgPatch.GetItemAt(index);
118 if (paths::DoesPathExist(tFiles.lfile) == paths::IS_EXISTING_DIR && paths::DoesPathExist(tFiles.rfile) == paths::IS_EXISTING_DIR)
120 // Walk given folders recursively and adds found files into patch list
121 String lfile = tFiles.lfile;
122 String rfile = tFiles.rfile;
123 paths::normalize(lfile);
124 paths::normalize(rfile);
125 PathContext paths((tFiles.pathLeft.empty() ? _T("") : paths::GetParentPath(lfile)),
126 (tFiles.pathRight.empty() ? _T("") : paths::GetParentPath(rfile)));
127 String subdir[2] = { (tFiles.pathLeft.empty() ? lfile : tFiles.pathLeft),
128 (tFiles.pathRight.empty() ? rfile : tFiles.pathRight) };
129 GetItemsForPatchList(paths, subdir, &fileList);
132 fileList.push_back(tFiles);
135 fileCount = fileList.size();
137 m_diffWrapper.WritePatchFileHeader(dlgPatch.m_outputStyle, dlgPatch.m_appendFile);
138 m_diffWrapper.SetAppendFiles(true);
140 bool bShowedBinaryMessage = false;
141 int writeFileCount = 0;
143 for (size_t index = 0; index < fileCount; index++)
145 const PATCHFILES& tFiles = fileList[index];
146 String filename1 = tFiles.lfile.length() == 0 ? _T("NUL") : tFiles.lfile;
147 String filename2 = tFiles.rfile.length() == 0 ? _T("NUL") : tFiles.rfile;
149 // Set up DiffWrapper
150 m_diffWrapper.SetPaths(PathContext(filename1, filename2), false);
151 m_diffWrapper.SetAlternativePaths(PathContext(tFiles.pathLeft, tFiles.pathRight));
152 m_diffWrapper.SetCompareFiles(PathContext(tFiles.lfile, tFiles.rfile));
153 bool bDiffSuccess = m_diffWrapper.RunFileDiff();
154 m_diffWrapper.GetDiffStatus(&status);
158 LangMessageBox(IDS_FILEERROR, MB_ICONSTOP);
162 else if (status.bBinaries)
164 if (!bShowedBinaryMessage)
166 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING);
167 bShowedBinaryMessage = true;
170 else if (status.bPatchFileFailed)
172 String errMsg = strutils::format_string1(_("Could not write to file %1."), dlgPatch.m_fileResult);
173 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
184 m_diffWrapper.WritePatchFileTerminator(dlgPatch.m_outputStyle);
186 if (bResult && writeFileCount > 0)
188 LangMessageBox(IDS_DIFF_SUCCEEDED, MB_ICONINFORMATION|MB_DONT_DISPLAY_AGAIN,
191 m_sPatchFile = dlgPatch.m_fileResult;
192 m_bOpenToEditor = dlgPatch.m_openToEditor;
193 m_bCopyToClipbard = dlgPatch.m_copyToClipboard;
197 dlgPatch.ClearItems();
201 CMergeApp::OpenFileToExternalEditor(m_sPatchFile);
202 if (m_bCopyToClipbard)
205 if (file.OpenReadOnly(m_sPatchFile))
207 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
208 FileTextEncoding encoding = codepage_detect::Guess(m_sPatchFile, iGuessEncodingType);
209 file.SetUnicoding(encoding.m_unicoding);
210 file.SetCodepage(encoding.m_codepage);
211 file.SetBom(encoding.m_bom);
215 file.ReadStringAll(lines);
217 PutToClipboard(lines, AfxGetMainWnd()->m_hWnd);
225 * @brief Show patch options dialog and check options selected.
226 * @return `true` if user wants to create a patch (didn't cancel dialog).
228 bool CPatchTool::ShowDialog(CPatchDlg *pDlgPatch)
230 DIFFOPTIONS diffOptions = {0};
231 PATCHOPTIONS patchOptions;
234 if (pDlgPatch->DoModal() == IDOK)
236 // There must be one filepair
237 if (pDlgPatch->GetItemCount() < 1)
240 // These two are from dropdown list - can't be wrong
241 patchOptions.outputStyle = pDlgPatch->m_outputStyle;
242 patchOptions.nContext = pDlgPatch->m_contextLines;
244 // Checkbox - can't be wrong
245 patchOptions.bAddCommandline = pDlgPatch->m_includeCmdLine;
246 m_diffWrapper.SetPatchOptions(&patchOptions);
248 // These are from checkboxes and radiobuttons - can't be wrong
249 m_diffWrapper.SetAppendFiles(pDlgPatch->m_appendFile);
250 Options::DiffOptions::Load(GetOptionsMgr(), diffOptions);
251 m_diffWrapper.SetOptions(&diffOptions);
260 * @brief Add one compare item to patch list.
261 * @param [in] sDir1 Left subdirectory.
262 * @param [in] sDir2 Right subdirectory.
263 * @param [in] ent1 Left item data to add.
264 * @param [in] ent2 Right item data to add.
265 * @param [out] fileList Patch files list.
267 void CPatchTool::AddFilesToList(const String& sDir1, const String& sDir2, const DirItem* ent1, const DirItem* ent2, std::vector<PATCHFILES>* fileList)
269 if ((ent1 == nullptr && ent2 == nullptr) || fileList == nullptr)
272 static const TCHAR backslash[] = _T("\\");
278 tFiles.lfile = ent1->path.get() + backslash + ent1->filename.get();
280 String pathLeft = _T("");
282 pathLeft = sDir1 + backslash;
283 pathLeft += ent1->filename.get();
284 tFiles.pathLeft = std::move(pathLeft);
289 tFiles.rfile = ent2->path.get() + backslash + ent2->filename.get();
291 String pathRight = _T("");
293 pathRight = sDir2 + backslash;
294 pathRight += ent2->filename.get();
296 tFiles.pathRight = std::move(pathRight);
299 fileList->push_back(tFiles);
303 * @brief This function walks given folders and adds found files into patch list.
304 * We walk all subfolders and add the files they contain into list.
306 * @param [in] paths Root paths of compare
307 * @param [in] subdir Subdirectories under root path
308 * @param [out] fileList Patch files list
309 * @return 1 normally, 0 if no directories and files exist.
310 * @remark This function was written based on DirScan_GetItems() in DirScan.cpp
312 int CPatchTool::GetItemsForPatchList(const PathContext& paths, const String subdir[], std::vector<PATCHFILES>* fileList)
314 static const TCHAR backslash[] = _T("\\");
315 int nDirs = paths.GetSize();
320 std::copy(paths.begin(), paths.end(), sDir);
322 if (!subdir[0].empty())
324 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
326 sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
327 subprefix[nIndex] = subdir[nIndex] + backslash;
331 DirItemArray dirs[2], aFiles[2];
332 for (int nIndex = 0; nIndex < nDirs; nIndex++)
333 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], false);
337 for (nIndex = 0; nIndex < nDirs; nIndex++)
338 if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
343 // Handle directories
344 // i points to current directory in left list (dirs[0])
345 // j points to current directory in right list (dirs[1])
346 DirItemArray::size_type i = 0, j = 0;
349 if (i >= dirs[0].size() && j >= dirs[1].size())
352 unsigned nDiffCode = DIFFCODE::DIR;
353 // Comparing directories leftDirs[i].name to rightDirs[j].name
354 if (i < dirs[0].size() && (j == dirs[1].size() || collstr(dirs[0][i].filename, dirs[1][j].filename, false) < 0))
356 nDiffCode |= DIFFCODE::FIRST;
358 else if (j < dirs[1].size() && (i == dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, false) < 0))
360 nDiffCode |= DIFFCODE::SECOND;
364 nDiffCode |= DIFFCODE::BOTH;
367 String leftnewsub = (nDiffCode & DIFFCODE::FIRST) ? subprefix[0] + dirs[0][i].filename.get() : subprefix[0] + dirs[1][j].filename.get();
368 String rightnewsub = (nDiffCode & DIFFCODE::SECOND) ? subprefix[1] + dirs[1][j].filename.get() : subprefix[1] + dirs[0][i].filename.get();
370 // Scan recursively all subdirectories too
371 String newsubdir[2] = { leftnewsub, rightnewsub };
372 GetItemsForPatchList(paths, newsubdir, fileList);
374 if (nDiffCode & DIFFCODE::FIRST)
376 if (nDiffCode & DIFFCODE::SECOND)
381 // i points to current file in left list (aFiles[0])
382 // j points to current file in right list (aFiles[1])
386 // Comparing file aFiles[0][i].name to aFiles[1][j].name
387 if (i < aFiles[0].size() && (j == aFiles[1].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) < 0))
389 AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, fileList);
393 if (j < aFiles[1].size() && (i == aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) > 0))
395 AddFilesToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], fileList);
399 if (i < aFiles[0].size())
401 assert(j < aFiles[1].size());
403 AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], fileList);