xref: /reactos/base/applications/rapps/appinfo.cpp (revision 961893a7)
1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Classes for working with available applications
5  * COPYRIGHT:   Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
6  *              Copyright 2020 He Yang (1160386205@qq.com)
7  *              Copyright 2021-2023 Mark Jansen <mark.jansen@reactos.org>
8  */
9 
10 #include "rapps.h"
11 #include "appview.h"
12 
13 CAppInfo::CAppInfo(const CStringW &Identifier, AppsCategories Category)
14     : szIdentifier(Identifier), iCategory(Category)
15 {
16 }
17 
18 CAppInfo::~CAppInfo()
19 {
20 }
21 
22 CAvailableApplicationInfo::CAvailableApplicationInfo(
23     CConfigParser *Parser,
24     const CStringW &PkgName,
25     AppsCategories Category,
26     const CPathW &BasePath)
27     : CAppInfo(PkgName, Category), m_Parser(Parser), m_ScrnshotRetrieved(false), m_LanguagesLoaded(false)
28 {
29     m_Parser->GetString(L"Name", szDisplayName);
30     m_Parser->GetString(L"Version", szDisplayVersion);
31     m_Parser->GetString(L"URLDownload", m_szUrlDownload);
32     m_Parser->GetString(L"Description", szComments);
33 
34     CPathW IconPath = BasePath;
35     IconPath += L"icons";
36 
37     CStringW IconName;
38     if (m_Parser->GetString(L"Icon", IconName))
39     {
40         IconPath += IconName;
41     }
42     else
43     {
44         // inifile.ico
45         IconPath += (szIdentifier + L".ico");
46     }
47 
48     if (PathFileExistsW(IconPath))
49     {
50         szDisplayIcon = (LPCWSTR)IconPath;
51     }
52 
53     INT iSizeBytes;
54 
55     if (m_Parser->GetInt(L"SizeBytes", iSizeBytes))
56     {
57         StrFormatByteSizeW(iSizeBytes, m_szSize.GetBuffer(MAX_PATH), MAX_PATH);
58         m_szSize.ReleaseBuffer();
59     }
60 
61     m_Parser->GetString(L"URLSite", m_szUrlSite);
62 }
63 
64 CAvailableApplicationInfo::~CAvailableApplicationInfo()
65 {
66     delete m_Parser;
67 }
68 
69 VOID
70 CAvailableApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit)
71 {
72     RichEdit->SetText(szDisplayName, CFE_BOLD);
73     InsertVersionInfo(RichEdit);
74     RichEdit->LoadAndInsertText(IDS_AINFO_LICENSE, LicenseString(), 0);
75     InsertLanguageInfo(RichEdit);
76 
77     RichEdit->LoadAndInsertText(IDS_AINFO_SIZE, m_szSize, 0);
78     RichEdit->LoadAndInsertText(IDS_AINFO_URLSITE, m_szUrlSite, CFE_LINK);
79     RichEdit->LoadAndInsertText(IDS_AINFO_DESCRIPTION, szComments, 0);
80     RichEdit->LoadAndInsertText(IDS_AINFO_URLDOWNLOAD, m_szUrlDownload, CFE_LINK);
81     RichEdit->LoadAndInsertText(IDS_AINFO_PACKAGE_NAME, szIdentifier, 0);
82 }
83 
84 int
85 CompareVersion(const CStringW &left, const CStringW &right)
86 {
87     int nLeft = 0, nRight = 0;
88 
89     while (true)
90     {
91         CStringW leftPart = left.Tokenize(L".", nLeft);
92         CStringW rightPart = right.Tokenize(L".", nRight);
93 
94         if (leftPart.IsEmpty() && rightPart.IsEmpty())
95             return 0;
96         if (leftPart.IsEmpty())
97             return -1;
98         if (rightPart.IsEmpty())
99             return 1;
100 
101         int leftVal, rightVal;
102 
103         if (!StrToIntExW(leftPart, STIF_DEFAULT, &leftVal))
104             leftVal = 0;
105         if (!StrToIntExW(rightPart, STIF_DEFAULT, &rightVal))
106             rightVal = 0;
107 
108         if (leftVal > rightVal)
109             return 1;
110         if (rightVal < leftVal)
111             return -1;
112     }
113 }
114 
115 VOID
116 CAvailableApplicationInfo::InsertVersionInfo(CAppRichEdit *RichEdit)
117 {
118     CStringW szRegName;
119     m_Parser->GetString(L"RegName", szRegName);
120 
121     BOOL bIsInstalled = ::GetInstalledVersion(NULL, szRegName) || ::GetInstalledVersion(NULL, szDisplayName);
122     if (bIsInstalled)
123     {
124         CStringW szInstalledVersion;
125         CStringW szNameVersion = szDisplayName + L" " + szDisplayVersion;
126         BOOL bHasInstalledVersion = ::GetInstalledVersion(&szInstalledVersion, szRegName) ||
127                                     ::GetInstalledVersion(&szInstalledVersion, szDisplayName) ||
128                                     ::GetInstalledVersion(&szInstalledVersion, szNameVersion);
129 
130         if (bHasInstalledVersion)
131         {
132             BOOL bHasUpdate = CompareVersion(szInstalledVersion, szDisplayVersion) < 0;
133             if (bHasUpdate)
134                 RichEdit->LoadAndInsertText(IDS_STATUS_UPDATE_AVAILABLE, CFE_ITALIC);
135             else
136                 RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC);
137 
138             RichEdit->LoadAndInsertText(IDS_AINFO_VERSION, szInstalledVersion, 0);
139         }
140         else
141         {
142             RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC);
143         }
144     }
145     else
146     {
147         RichEdit->LoadAndInsertText(IDS_STATUS_NOTINSTALLED, CFE_ITALIC);
148     }
149 
150     RichEdit->LoadAndInsertText(IDS_AINFO_AVAILABLEVERSION, szDisplayVersion, 0);
151 }
152 
153 CStringW
154 CAvailableApplicationInfo::LicenseString()
155 {
156     INT IntBuffer;
157     m_Parser->GetInt(L"LicenseType", IntBuffer);
158     CStringW szLicenseString;
159     m_Parser->GetString(L"License", szLicenseString);
160     LicenseType licenseType;
161 
162     if (IsLicenseType(IntBuffer))
163     {
164         licenseType = static_cast<LicenseType>(IntBuffer);
165     }
166     else
167     {
168         licenseType = LICENSE_NONE;
169     }
170 
171     CStringW szLicense;
172     switch (licenseType)
173     {
174         case LICENSE_OPENSOURCE:
175             szLicense.LoadStringW(IDS_LICENSE_OPENSOURCE);
176             break;
177         case LICENSE_FREEWARE:
178             szLicense.LoadStringW(IDS_LICENSE_FREEWARE);
179             break;
180         case LICENSE_TRIAL:
181             szLicense.LoadStringW(IDS_LICENSE_TRIAL);
182             break;
183         default:
184             return szLicenseString;
185     }
186 
187     return szLicense + L" (" + szLicenseString + L")";
188 }
189 
190 VOID
191 CAvailableApplicationInfo::InsertLanguageInfo(CAppRichEdit *RichEdit)
192 {
193     if (!m_LanguagesLoaded)
194     {
195         RetrieveLanguages();
196     }
197 
198     if (m_LanguageLCIDs.GetSize() == 0)
199     {
200         return;
201     }
202 
203     const INT nTranslations = m_LanguageLCIDs.GetSize();
204     CStringW szLangInfo;
205     CStringW szLoadedTextAvailability;
206     CStringW szLoadedAInfoText;
207 
208     szLoadedAInfoText.LoadStringW(IDS_AINFO_LANGUAGES);
209 
210     const LCID lcEnglish = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT);
211     if (m_LanguageLCIDs.Find(GetUserDefaultLCID()) >= 0)
212     {
213         szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_AVAILABLE_TRANSLATION);
214         if (nTranslations > 1)
215         {
216             CStringW buf;
217             buf.LoadStringW(IDS_LANGUAGE_MORE_PLACEHOLDER);
218             szLangInfo.Format(buf, nTranslations - 1);
219         }
220         else
221         {
222             szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE);
223             szLangInfo = L" (" + szLangInfo + L")";
224         }
225     }
226     else if (m_LanguageLCIDs.Find(lcEnglish) >= 0)
227     {
228         szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_ENGLISH_TRANSLATION);
229         if (nTranslations > 1)
230         {
231             CStringW buf;
232             buf.LoadStringW(IDS_LANGUAGE_AVAILABLE_PLACEHOLDER);
233             szLangInfo.Format(buf, nTranslations - 1);
234         }
235         else
236         {
237             szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE);
238             szLangInfo = L" (" + szLangInfo + L")";
239         }
240     }
241     else
242     {
243         szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_NO_TRANSLATION);
244     }
245 
246     RichEdit->InsertText(szLoadedAInfoText, CFE_BOLD);
247     RichEdit->InsertText(szLoadedTextAvailability, NULL);
248     RichEdit->InsertText(szLangInfo, CFE_ITALIC);
249 }
250 
251 VOID
252 CAvailableApplicationInfo::RetrieveLanguages()
253 {
254     m_LanguagesLoaded = true;
255 
256     CStringW szBuffer;
257     if (!m_Parser->GetString(L"Languages", szBuffer))
258     {
259         return;
260     }
261 
262     // Parse parameter string
263     int iIndex = 0;
264     while (true)
265     {
266         CStringW szLocale = szBuffer.Tokenize(L"|", iIndex);
267         if (szLocale.IsEmpty())
268             break;
269 
270         szLocale = L"0x" + szLocale;
271 
272         INT iLCID;
273         if (StrToIntExW(szLocale, STIF_SUPPORT_HEX, &iLCID))
274         {
275             m_LanguageLCIDs.Add(static_cast<LCID>(iLCID));
276         }
277     }
278 }
279 
280 BOOL
281 CAvailableApplicationInfo::Valid() const
282 {
283     return !szDisplayName.IsEmpty() && !m_szUrlDownload.IsEmpty();
284 }
285 
286 BOOL
287 CAvailableApplicationInfo::CanModify()
288 {
289     return FALSE;
290 }
291 
292 BOOL
293 CAvailableApplicationInfo::RetrieveIcon(CStringW &Path) const
294 {
295     Path = szDisplayIcon;
296     return !Path.IsEmpty();
297 }
298 
299 #define MAX_SCRNSHOT_NUM 16
300 BOOL
301 CAvailableApplicationInfo::RetrieveScreenshot(CStringW &Path)
302 {
303     if (!m_ScrnshotRetrieved)
304     {
305         static_assert(MAX_SCRNSHOT_NUM < 10000, "MAX_SCRNSHOT_NUM is too big");
306         for (int i = 0; i < MAX_SCRNSHOT_NUM; i++)
307         {
308             CStringW ScrnshotField;
309             ScrnshotField.Format(L"Screenshot%d", i + 1);
310             CStringW ScrnshotLocation;
311             if (!m_Parser->GetString(ScrnshotField, ScrnshotLocation))
312             {
313                 // We stop at the first screenshot not found,
314                 // so screenshots _have_ to be consecutive
315                 break;
316             }
317 
318             if (PathIsURLW(ScrnshotLocation.GetString()))
319             {
320                 m_szScrnshotLocation.Add(ScrnshotLocation);
321             }
322         }
323         m_ScrnshotRetrieved = true;
324     }
325 
326     if (m_szScrnshotLocation.GetSize() > 0)
327     {
328         Path = m_szScrnshotLocation[0];
329     }
330 
331     return !Path.IsEmpty();
332 }
333 
334 VOID
335 CAvailableApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const
336 {
337     Url = m_szUrlDownload;
338     m_Parser->GetString(L"SHA1", Sha1);
339     INT iSizeBytes;
340 
341     if (m_Parser->GetInt(L"SizeBytes", iSizeBytes))
342     {
343         SizeInBytes = (ULONG)iSizeBytes;
344     }
345     else
346     {
347         SizeInBytes = 0;
348     }
349 }
350 
351 VOID
352 CAvailableApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload)
353 {
354     License = LicenseString();
355     Size = m_szSize;
356     UrlSite = m_szUrlSite;
357     UrlDownload = m_szUrlDownload;
358 }
359 
360 BOOL
361 CAvailableApplicationInfo::UninstallApplication(BOOL bModify)
362 {
363     ATLASSERT(FALSE && "Should not be called");
364     return FALSE;
365 }
366 
367 CInstalledApplicationInfo::CInstalledApplicationInfo(
368     HKEY Key,
369     const CStringW &KeyName,
370     AppsCategories Category,
371     int KeyIndex)
372     : CAppInfo(KeyName, Category), m_hKey(Key), iKeyIndex(KeyIndex)
373 {
374     if (GetApplicationRegString(L"DisplayName", szDisplayName))
375     {
376         GetApplicationRegString(L"DisplayIcon", szDisplayIcon);
377         GetApplicationRegString(L"DisplayVersion", szDisplayVersion);
378         GetApplicationRegString(L"Comments", szComments);
379     }
380 }
381 
382 CInstalledApplicationInfo::~CInstalledApplicationInfo()
383 {
384 }
385 
386 VOID
387 CInstalledApplicationInfo::AddApplicationRegString(
388     CAppRichEdit *RichEdit,
389     UINT StringID,
390     const CStringW &String,
391     DWORD TextFlags)
392 {
393     CStringW Tmp;
394     if (GetApplicationRegString(String, Tmp))
395     {
396         RichEdit->InsertTextWithString(StringID, Tmp, TextFlags);
397     }
398 }
399 
400 VOID
401 CInstalledApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit)
402 {
403     RichEdit->SetText(szDisplayName, CFE_BOLD);
404     RichEdit->InsertText(L"\n", 0);
405 
406     RichEdit->InsertTextWithString(IDS_INFO_VERSION, szDisplayVersion, 0);
407     AddApplicationRegString(RichEdit, IDS_INFO_PUBLISHER, L"Publisher", 0);
408     AddApplicationRegString(RichEdit, IDS_INFO_REGOWNER, L"RegOwner", 0);
409     AddApplicationRegString(RichEdit, IDS_INFO_PRODUCTID, L"ProductID", 0);
410     AddApplicationRegString(RichEdit, IDS_INFO_HELPLINK, L"HelpLink", CFM_LINK);
411     AddApplicationRegString(RichEdit, IDS_INFO_HELPPHONE, L"HelpTelephone", 0);
412     AddApplicationRegString(RichEdit, IDS_INFO_README, L"Readme", 0);
413     AddApplicationRegString(RichEdit, IDS_INFO_CONTACT, L"Contact", 0);
414     AddApplicationRegString(RichEdit, IDS_INFO_UPDATEINFO, L"URLUpdateInfo", CFM_LINK);
415     AddApplicationRegString(RichEdit, IDS_INFO_INFOABOUT, L"URLInfoAbout", CFM_LINK);
416     RichEdit->InsertTextWithString(IDS_INFO_COMMENTS, szComments, 0);
417 
418     if (m_szInstallDate.IsEmpty())
419     {
420         RetrieveInstallDate();
421     }
422 
423     RichEdit->InsertTextWithString(IDS_INFO_INSTALLDATE, m_szInstallDate, 0);
424     AddApplicationRegString(RichEdit, IDS_INFO_INSTLOCATION, L"InstallLocation", 0);
425     AddApplicationRegString(RichEdit, IDS_INFO_INSTALLSRC, L"InstallSource", 0);
426 
427     if (m_szUninstallString.IsEmpty())
428     {
429         RetrieveUninstallStrings();
430     }
431 
432     RichEdit->InsertTextWithString(IDS_INFO_UNINSTALLSTR, m_szUninstallString, 0);
433     RichEdit->InsertTextWithString(IDS_INFO_MODIFYPATH, m_szModifyString, 0);
434 }
435 
436 VOID
437 CInstalledApplicationInfo::RetrieveInstallDate()
438 {
439     DWORD dwInstallTimeStamp;
440     SYSTEMTIME InstallLocalTime;
441     if (GetApplicationRegString(L"InstallDate", m_szInstallDate))
442     {
443         ZeroMemory(&InstallLocalTime, sizeof(InstallLocalTime));
444         // Check if we have 8 characters to parse the datetime.
445         // Maybe other formats exist as well?
446         m_szInstallDate = m_szInstallDate.Trim();
447         if (m_szInstallDate.GetLength() == 8)
448         {
449             InstallLocalTime.wYear = wcstol(m_szInstallDate.Left(4).GetString(), NULL, 10);
450             InstallLocalTime.wMonth = wcstol(m_szInstallDate.Mid(4, 2).GetString(), NULL, 10);
451             InstallLocalTime.wDay = wcstol(m_szInstallDate.Mid(6, 2).GetString(), NULL, 10);
452         }
453     }
454     // It might be a DWORD (Unix timestamp). try again.
455     else if (GetApplicationRegDword(L"InstallDate", &dwInstallTimeStamp))
456     {
457         FILETIME InstallFileTime;
458         SYSTEMTIME InstallSystemTime;
459 
460         UnixTimeToFileTime(dwInstallTimeStamp, &InstallFileTime);
461         FileTimeToSystemTime(&InstallFileTime, &InstallSystemTime);
462 
463         // convert to localtime
464         SystemTimeToTzSpecificLocalTime(NULL, &InstallSystemTime, &InstallLocalTime);
465     }
466 
467     // convert to readable date string
468     int cchTimeStrLen = GetDateFormatW(LOCALE_USER_DEFAULT, 0, &InstallLocalTime, NULL, 0, 0);
469 
470     GetDateFormatW(
471         LOCALE_USER_DEFAULT, // use default locale for current user
472         0, &InstallLocalTime, NULL, m_szInstallDate.GetBuffer(cchTimeStrLen), cchTimeStrLen);
473     m_szInstallDate.ReleaseBuffer();
474 }
475 
476 VOID
477 CInstalledApplicationInfo::RetrieveUninstallStrings()
478 {
479     DWORD dwWindowsInstaller = 0;
480     if (GetApplicationRegDword(L"WindowsInstaller", &dwWindowsInstaller) && dwWindowsInstaller)
481     {
482         // MSI has the same info in Uninstall / modify, so manually build it
483         m_szUninstallString.Format(L"msiexec /x%s", szIdentifier.GetString());
484     }
485     else
486     {
487         GetApplicationRegString(L"UninstallString", m_szUninstallString);
488     }
489     DWORD dwNoModify = 0;
490     if (!GetApplicationRegDword(L"NoModify", &dwNoModify))
491     {
492         CStringW Tmp;
493         if (GetApplicationRegString(L"NoModify", Tmp))
494         {
495             dwNoModify = Tmp.GetLength() > 0 ? (Tmp[0] == '1') : 0;
496         }
497         else
498         {
499             dwNoModify = 0;
500         }
501     }
502     if (!dwNoModify)
503     {
504         if (dwWindowsInstaller)
505         {
506             m_szModifyString.Format(L"msiexec /i%s", szIdentifier.GetString());
507         }
508         else
509         {
510             GetApplicationRegString(L"ModifyPath", m_szModifyString);
511         }
512     }
513 }
514 
515 BOOL
516 CInstalledApplicationInfo::Valid() const
517 {
518     return !szDisplayName.IsEmpty();
519 }
520 
521 BOOL
522 CInstalledApplicationInfo::CanModify()
523 {
524     if (m_szUninstallString.IsEmpty())
525     {
526         RetrieveUninstallStrings();
527     }
528 
529     return !m_szModifyString.IsEmpty();
530 }
531 
532 BOOL
533 CInstalledApplicationInfo::RetrieveIcon(CStringW &Path) const
534 {
535     Path = szDisplayIcon;
536     return !Path.IsEmpty();
537 }
538 
539 BOOL
540 CInstalledApplicationInfo::RetrieveScreenshot(CStringW & /*Path*/)
541 {
542     return FALSE;
543 }
544 
545 VOID
546 CInstalledApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const
547 {
548     ATLASSERT(FALSE && "Should not be called");
549 }
550 
551 VOID
552 CInstalledApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload)
553 {
554     ATLASSERT(FALSE && "Should not be called");
555 }
556 
557 BOOL
558 CInstalledApplicationInfo::UninstallApplication(BOOL bModify)
559 {
560     if (m_szUninstallString.IsEmpty())
561     {
562         RetrieveUninstallStrings();
563     }
564 
565     BOOL bSuccess = StartProcess(bModify ? m_szModifyString : m_szUninstallString, TRUE);
566 
567     if (bSuccess && !bModify)
568         WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_REMOVE, szDisplayName);
569 
570     return bSuccess;
571 }
572 
573 BOOL
574 CInstalledApplicationInfo::GetApplicationRegString(LPCWSTR lpKeyName, CStringW &String)
575 {
576     ULONG nChars = 0;
577     // Get the size
578     if (m_hKey.QueryStringValue(lpKeyName, NULL, &nChars) != ERROR_SUCCESS)
579     {
580         String.Empty();
581         return FALSE;
582     }
583 
584     LPWSTR Buffer = String.GetBuffer(nChars);
585     LONG lResult = m_hKey.QueryStringValue(lpKeyName, Buffer, &nChars);
586     if (nChars > 0 && Buffer[nChars - 1] == UNICODE_NULL)
587         nChars--;
588     String.ReleaseBuffer(nChars);
589 
590     if (lResult != ERROR_SUCCESS)
591     {
592         String.Empty();
593         return FALSE;
594     }
595 
596     if (String.Find('%') >= 0)
597     {
598         CStringW Tmp;
599         DWORD dwLen = ExpandEnvironmentStringsW(String, NULL, 0);
600         if (dwLen > 0)
601         {
602             BOOL bSuccess = ExpandEnvironmentStringsW(String, Tmp.GetBuffer(dwLen), dwLen) == dwLen;
603             Tmp.ReleaseBuffer(dwLen - 1);
604             if (bSuccess)
605             {
606                 String = Tmp;
607             }
608             else
609             {
610                 String.Empty();
611                 return FALSE;
612             }
613         }
614     }
615 
616     return TRUE;
617 }
618 
619 BOOL
620 CInstalledApplicationInfo::GetApplicationRegDword(LPCWSTR lpKeyName, DWORD *lpValue)
621 {
622     DWORD dwSize = sizeof(DWORD), dwType;
623     if (RegQueryValueExW(m_hKey, lpKeyName, NULL, &dwType, (LPBYTE)lpValue, &dwSize) != ERROR_SUCCESS ||
624         dwType != REG_DWORD)
625     {
626         return FALSE;
627     }
628 
629     return TRUE;
630 }
631