OSDN Git Service

Update CWindowsManagerDialog - check some pointers for null and made … (#824) (2)
[winmerge-jp/winmerge-jp.git] / Src / ConfigLog.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** 
3  * @file  ConfigLog.cpp
4  *
5  * @brief CConfigLog implementation
6  */
7
8 #include "pch.h"
9 #include "ConfigLog.h"
10 #include <cassert>
11 #include <windows.h>
12 #include <memory>
13 #include "Constants.h"
14 #include "VersionInfo.h"
15 #include "UniFile.h"
16 #include "Plugins.h"
17 #include "TFile.h"
18 #include "paths.h"
19 #include "locality.h"
20 #include "unicoder.h"
21 #include "Environment.h"
22 #include "MergeApp.h"
23 #include "OptionsMgr.h"
24 #include "TempFile.h"
25 #include "UniFile.h"
26 #include "RegKey.h"
27
28 CConfigLog::CConfigLog()
29 : m_pfile(new UniStdioFile())
30 {
31 }
32
33 CConfigLog::~CConfigLog()
34 {
35         CloseFile();
36 }
37
38
39 /** 
40  * @brief Get details of the Compiler Version from _MSC_VER, etc.
41  *              Infer the Visual Studio version
42  */
43 static String GetCompilerVersion()
44 {
45         String sVisualStudio = _T(""); 
46 #ifdef _MSC_VER
47 #if     _MSC_VER <  1800
48         # error "** Unknown OLD Version of Visual Studio **"
49 #elif   _MSC_VER == 1800
50         sVisualStudio = _T("VS.2013 (12.0) - "); 
51 #elif   _MSC_VER == 1900
52         sVisualStudio = _T("VS.2015 (14.0) - "); 
53 #elif   _MSC_VER == 1910
54         sVisualStudio = _T("VS.2017 (15.0) - "); 
55 #elif   _MSC_VER == 1911
56         sVisualStudio = _T("VS.2017 (15.3) - "); 
57 #elif   _MSC_VER >= 1912 && _MSC_VER <  1920
58         sVisualStudio = strutils::format(_T("VS.2017 (15.%d) - "), 5 + (_MSC_VER - 1912));
59 #elif   _MSC_VER >= 1920 && _MSC_VER <  2000
60         sVisualStudio = strutils::format(_T("VS.2019 (16.%d) - "), (_MSC_VER - 1920));
61 #elif   _MSC_VER >= 2000
62         # error "** Unknown NEW Version of Visual Studio **"
63 #endif
64 #endif
65
66         return strutils::format(_T("%sC/C++ Compiler %02i.%02i.%05i.%i"),
67                 sVisualStudio,
68                 (int)(_MSC_VER / 100), (int)(_MSC_VER % 100), (int)(_MSC_FULL_VER % 100000), _MSC_BUILD
69         );
70 }
71
72 /** 
73  * @brief Get the Modified time of fully qualified file path and name
74  */
75 static String GetLastModified(const String &path) 
76 {
77         String sPath2 = path;
78         if (sPath2[0] == '.')
79         {
80                 CVersionInfo EXEversion;
81                 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
82                 sPath2 = sEXEPath + _T("\\") + sPath2;
83         }
84         TFile file(sPath2);
85
86         String sModifiedTime = _T("");
87         if (file.exists())
88         {
89                 Poco::Timestamp mtime(file.getLastModified());
90
91                 const int64_t r = (mtime.epochTime());
92                 sModifiedTime = locality::TimeString(&r);
93         }
94         return sModifiedTime;
95 }
96
97 /** 
98  * @brief Write plugin names
99  */
100 void CConfigLog::WritePluginsInLogFile(const wchar_t *transformationEvent)
101 {
102         CVersionInfo EXEversion;
103         String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
104
105         // get an array with the available scripts
106         PluginArray * piPluginArray; 
107
108         piPluginArray = 
109                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(transformationEvent);
110
111         for (size_t iPlugin = 0 ; iPlugin < piPluginArray->size() ; iPlugin++)
112         {
113                 const PluginInfoPtr& plugin = piPluginArray->at(iPlugin);
114
115                 String sFileName = paths::GetLongPath(plugin->m_filepath);
116                 if (sFileName.length() > sEXEPath.length())
117                         if (sFileName.substr(0, sEXEPath.length()) == sEXEPath)
118                                 sFileName = _T(".") + sFileName.erase(0, sEXEPath.length());
119                 
120                 String sModifiedTime = _T("");
121                 sModifiedTime = GetLastModified(plugin->m_filepath);
122                 if (!sModifiedTime.empty())
123                         sModifiedTime = _T("[") + sModifiedTime + _T("]");
124                 
125                 String sPluginText = strutils::format
126                         (_T("\r\n  %s%-36s path=%s  %s"),
127                         plugin->m_disabled ? _T("!") : _T(" "),
128                         plugin->m_name,
129                         sFileName,
130                         sModifiedTime
131                         );
132                 m_pfile->WriteString(sPluginText);
133         }
134 }
135
136 /**
137  * @brief String wrapper around API call GetLocaleInfo
138  */
139 static String GetLocaleString(LCID locid, LCTYPE lctype)
140 {
141         TCHAR buffer[512];
142         if (!GetLocaleInfo(locid, lctype, buffer, sizeof(buffer)/sizeof(buffer[0])))
143                 buffer[0] = 0;
144         return buffer;
145 }
146
147 /**
148  * @brief Write string item
149  */
150 void CConfigLog::WriteItem(int indent, const String& key, const TCHAR *value /*= nullptr*/)
151 {
152         String text = strutils::format(value ? _T("%*.0s%s: %s\r\n") : _T("%*.0s%s:\r\n"), indent, key, key, value);
153         m_pfile->WriteString(text);
154 }
155
156 /**
157  * @brief Write string item
158  */
159 void CConfigLog::WriteItem(int indent, const String& key, const String &str)
160 {
161         WriteItem(indent, key, str.c_str());
162 }
163
164 /**
165  * @brief Write int item
166  */
167 void CConfigLog::WriteItem(int indent, const String& key, long value)
168 {
169         String text = strutils::format(_T("%*.0s%s: %ld\r\n"), indent, key, key, value);
170         m_pfile->WriteString(text);
171 }
172
173 /**
174  * @brief Write out various possibly relevant windows locale information
175  */
176 void CConfigLog::WriteLocaleSettings(unsigned locid, const String& title)
177 {
178         WriteItem(1, title);
179         WriteItem(2, _T("Def ANSI codepage"), GetLocaleString(locid, LOCALE_IDEFAULTANSICODEPAGE));
180         WriteItem(2, _T("Def OEM codepage"), GetLocaleString(locid, LOCALE_IDEFAULTCODEPAGE));
181         WriteItem(2, _T("Country"), GetLocaleString(locid, LOCALE_SENGCOUNTRY));
182         WriteItem(2, _T("Language"), GetLocaleString(locid, LOCALE_SENGLANGUAGE));
183         WriteItem(2, _T("Language code"), GetLocaleString(locid, LOCALE_ILANGUAGE));
184         WriteItem(2, _T("ISO Language code"), GetLocaleString(locid, LOCALE_SISO639LANGNAME));
185 }
186
187 /**
188  * @brief Write version of a single executable file
189  */
190 void CConfigLog::WriteVersionOf1(int indent, const String& path)
191 {
192         String path2 = path;
193         if (path2.find(_T(".\\")) == 0)
194         {
195                 // Remove "relative path" info for Win API calls.
196                 const TCHAR *pf = path2.c_str();
197                 path2 = String(pf+2);
198         }
199         String name = paths::FindFileName(path2);
200         CVersionInfo vi(path2.c_str(), true);
201         String sModifiedTime = _T("");
202         if (name != path)
203         {
204                 sModifiedTime = GetLastModified(path);
205                 if (!sModifiedTime.empty())
206                         sModifiedTime = _T("  [") + sModifiedTime + _T("]");
207         }
208         String text = strutils::format
209         (
210                 name == path
211                         ?       _T(" %*s%-18s %s=%u.%02u %s=%04u\r\n")
212                         :       _T(" %*s%-18s %s=%u.%02u %s=%04u path=%s%s\r\n"),
213                 indent,
214                 // Tilde prefix for modules currently mapped into WinMerge
215                 GetModuleHandle(path2.c_str()) 
216                         ? _T("~") 
217                         : _T("")/*name*/,
218                 name,
219                 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwMajorVersion)
220                         ?       _T("dllversion")
221                         :       _T("version"),
222                 vi.m_dvi.dwMajorVersion,
223                 vi.m_dvi.dwMinorVersion,
224                 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwBuildNumber)
225                         ?       _T("dllbuild")
226                         :       _T("build"),
227                 vi.m_dvi.dwBuildNumber,
228                 path,
229                 sModifiedTime
230         );
231         m_pfile->WriteString(text);
232 }
233
234 /**
235  * @brief Write winmerge configuration
236  */
237 void CConfigLog::WriteWinMergeConfig()
238 {
239         TempFile tmpfile;
240         String tmppath = tmpfile.Create();
241         GetOptionsMgr()->ExportOptions(tmppath, true);
242         UniMemFile ufile;
243         if (!ufile.OpenReadOnly(tmppath))
244                 return;
245         String line;
246         bool lossy;
247         while (ufile.ReadString(line, &lossy)) 
248         {
249                 String prefix = _T("  ");
250                 if (line[0] == _T('[') )
251                         prefix = _T(" ");
252                 FileWriteString(prefix + line + _T("\r\n"));
253         }
254         ufile.Close();
255 }
256
257 /** 
258  * @brief Write logfile
259  */
260 bool CConfigLog::DoFile(String &sError)
261 {
262         CVersionInfo version;
263         String text;
264
265         String sFileName = paths::ConcatPath(env::GetMyDocuments(), WinMergeDocumentsFolder);
266         paths::CreateIfNeeded(sFileName);
267         m_sFileName = paths::ConcatPath(sFileName, _T("WinMerge.txt"));
268
269         if (!m_pfile->OpenCreateUtf8(m_sFileName))
270         {
271                 m_sFileName = paths::ConcatPath(env::GetTemporaryPath(), _T("WinMerge.txt"));
272                 if (!m_pfile->OpenCreateUtf8(m_sFileName))
273                 {
274                         const UniFile::UniError& err = m_pfile->GetLastUniError();
275                         sError = err.GetError();
276                         return false;
277                 }
278         }
279         m_pfile->SetBom(true);
280         m_pfile->WriteBom();
281
282 // Begin log
283         FileWriteString(_T("WinMerge Configuration Log\r\n"));
284         FileWriteString(_T("--------------------------\r\n"));
285         FileWriteString(_T("\r\nLog Saved to:         "));
286         FileWriteString(m_sFileName);
287         FileWriteString(_T("\r\n                >> >> Please add this information (or attach this file) when reporting bugs << <<"));
288
289 // Platform stuff
290         
291         FileWriteString(_T("\r\n\r\nWindows Info:         "));
292         text = GetWindowsVer();
293         FileWriteString(text);
294         text = GetProcessorInfo();
295         if (text != _T(""))
296         {               
297                 FileWriteString(_T("\r\n Processor:           "));
298                 FileWriteString(text);
299         }
300
301 // WinMerge stuff
302
303         FileWriteString(_T("\r\n\r\nWinMerge Info:"));
304         String sEXEFullFileName = paths::GetLongPath(version.GetFullFileName(), false);
305         FileWriteString(_T("\r\n Code File:           "));
306         FileWriteString(sEXEFullFileName);
307
308         FileWriteString(_T("\r\n Version:             "));
309         FileWriteString(version.GetProductVersion());
310         String privBuild = version.GetPrivateBuild();
311         if (!privBuild.empty())
312         {
313                 FileWriteString(_T(" + ") + privBuild);
314         }
315
316         FileWriteString(_T("\r\n Code File Modified:  "));
317         FileWriteString(GetLastModified(sEXEFullFileName));
318
319         FileWriteString(_T("\r\n Build Config:       "));
320         FileWriteString(GetBuildFlags());
321
322         FileWriteString(_T("\r\n Build Software:      "));
323         FileWriteString(GetCompilerVersion());
324
325         LPCTSTR szCmdLine = ::GetCommandLine();
326         assert(szCmdLine != nullptr);
327
328         // Skip the quoted executable file name.
329         if (szCmdLine != nullptr)
330         {
331                 szCmdLine = _tcschr(szCmdLine, '"');
332                 if (szCmdLine != nullptr)
333                 {
334                         szCmdLine += 1; // skip the opening quote.
335                         szCmdLine = _tcschr(szCmdLine, '"');
336                         if (szCmdLine != nullptr)
337                         {
338                                 szCmdLine += 1; // skip the closing quote.
339                         }
340                 }
341         }
342
343         // The command line include a space after the executable file name,
344         // which mean that empty command line will have length of one.
345         if (!szCmdLine || lstrlen(szCmdLine) < 2)
346         {
347                 szCmdLine = _T(" none");
348         }
349
350         FileWriteString(_T("\r\n\r\nCommand Line:        "));
351         FileWriteString(szCmdLine);
352
353         FileWriteString(_T("\r\n\r\nModule Names:         '~' prefix indicates module is loaded into the WinMerge process.\r\n"));
354         FileWriteString(_T(" Windows:\r\n"));
355         WriteVersionOf1(2, _T("kernel32.dll"));
356         WriteVersionOf1(2, _T("shell32.dll"));
357         WriteVersionOf1(2, _T("shlwapi.dll"));
358         WriteVersionOf1(2, _T("COMCTL32.dll"));
359         WriteVersionOf1(2, _T("msvcrt.dll"));
360         FileWriteString(_T(        " WinMerge:            Path names are relative to the Code File's directory.\r\n"));
361         WriteVersionOf1(2, _T(".\\ShellExtensionU.dll"));
362         WriteVersionOf1(2, _T(".\\ShellExtensionX64.dll"));
363         WriteVersionOf1(2, _T(".\\ShellExtensionARM64.dll"));
364         WriteVersionOf1(2, _T(".\\Frhed\\hekseditU.dll"));
365         WriteVersionOf1(2, _T(".\\WinIMerge\\WinIMergeLib.dll"));
366         WriteVersionOf1(2, _T(".\\Merge7z\\7z.dll"));
367
368 // System settings
369         FileWriteString(_T("\r\nSystem Settings:\r\n"));
370         FileWriteString(_T(" Codepage Settings:\r\n"));
371         WriteItem(2, _T("ANSI codepage"), GetACP());
372         WriteItem(2, _T("OEM codepage"), GetOEMCP());
373 #ifndef UNICODE
374         WriteItem(2, _T("multibyte codepage"), _getmbcp());
375 #endif
376         WriteLocaleSettings(GetThreadLocale(), _T("Locale (Thread)"));
377         WriteLocaleSettings(LOCALE_USER_DEFAULT, _T("Locale (User)"));
378         WriteLocaleSettings(LOCALE_SYSTEM_DEFAULT, _T("Locale (System)"));
379
380 // Plugins
381         FileWriteString(_T("\r\nPlugins:                                '!' Prefix indicates the plugin is Disabled.\r\n"));
382         FileWriteString(    _T(" Unpackers:                             Path names are relative to the Code File's directory."));
383         WritePluginsInLogFile(L"FILE_PACK_UNPACK");
384         WritePluginsInLogFile(L"BUFFER_PACK_UNPACK");
385         WritePluginsInLogFile(L"FILE_FOLDER_PACK_UNPACK");
386         FileWriteString(_T("\r\n Prediffers: "));
387         WritePluginsInLogFile(L"FILE_PREDIFF");
388         WritePluginsInLogFile(L"BUFFER_PREDIFF");
389         FileWriteString(_T("\r\n Editor scripts: "));
390         WritePluginsInLogFile(L"EDITOR_SCRIPT");
391         if (!plugin::IsWindowsScriptThere())
392                 FileWriteString(_T("\r\n .sct scripts disabled (Windows Script Host not found)\r\n"));
393
394         FileWriteString(_T("\r\n\r\n"));
395
396 // WinMerge settings
397         FileWriteString(_T("\r\nWinMerge configuration:\r\n"));
398         WriteWinMergeConfig();
399
400         CloseFile();
401
402         return true;
403 }
404
405 /** 
406  * @brief Parse Windows version data to string.
407  * @return String describing Windows version.
408  */
409 String CConfigLog::GetWindowsVer()
410 {
411         CRegKeyEx key;
412         if (key.QueryRegMachine(_T("Software\\Microsoft\\Windows NT\\CurrentVersion")))
413                 return key.ReadString(_T("ProductName"), _T("Unknown OS"));
414         return _T("Unknown OS");
415 }
416
417
418 /** 
419  * @brief Parse Processor Information data to string.
420  * @return String describing Windows version.
421  */
422 String CConfigLog::GetProcessorInfo()
423 {
424         CRegKeyEx key;
425         String sProductName = _T("");
426         if (key.QueryRegMachine(_T("Hardware\\Description\\System\\CentralProcessor\\0")))
427                 sProductName = key.ReadString(_T("Identifier"), _T(""));
428         if (sProductName != _T(""))
429         {
430                 // This is the full identifier of the processor
431                 //      (e.g. "Intel64 Family 6 Model 158 Stepping 9")
432                 //      but we'll only keep the first word (e.g. "Intel64")
433                 int x = (int)sProductName.find_first_of(_T(" "));
434                 sProductName = sProductName.substr(0, x);
435         }
436
437
438         // Number of processors, Amount of memory
439         SYSTEM_INFO siSysInfo;
440         ::GetSystemInfo(&siSysInfo); 
441
442         MEMORYSTATUSEX GlobalMemoryBuffer = {sizeof (GlobalMemoryBuffer)};
443         ::GlobalMemoryStatusEx(&GlobalMemoryBuffer);
444         ULONG lInstalledMemory = (ULONG)(GlobalMemoryBuffer.ullTotalPhys / (1024*1024));
445
446         TCHAR buf[MAX_PATH];
447         swprintf_s(buf, MAX_PATH, _T("%u Logical Processors, %u MB Memory"), 
448                         siSysInfo.dwNumberOfProcessors, lInstalledMemory); 
449
450         return sProductName + _T(", ") + String(buf);
451 }
452         
453 /** 
454  * @brief Return string representation of build flags (for reporting in config log)
455  */
456 String CConfigLog::GetBuildFlags()
457 {
458         String flags;
459
460 #if defined WIN64
461         flags += _T(" WIN64 ");
462 #elif defined WIN32
463         flags += _T(" WIN32 ");
464 #endif
465
466 #if defined UNICODE
467         flags += _T(" UNICODE ");
468 #endif
469
470 #if defined _DEBUG
471         flags += _T(" _DEBUG ");
472 #endif
473
474 #if defined TEST_WINMERGE
475         flags += _T(" TEST_WINMERGE ");
476 #endif
477
478         return flags;
479 }
480
481 bool CConfigLog::WriteLogFile(String &sError)
482 {
483         CloseFile();
484
485         return DoFile(sError);
486 }
487
488 /// Write line to file (if writing configuration log)
489 void
490 CConfigLog::FileWriteString(const String& lpsz)
491 {
492         m_pfile->WriteString(lpsz);
493 }
494
495 /**
496  * @brief Close any open file
497  */
498 void
499 CConfigLog::CloseFile()
500 {
501         if (m_pfile->IsOpen())
502                 m_pfile->Close();
503 }
504