xref: /reactos/base/applications/rapps/appinfo.cpp (revision 10e7643c)
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 (IsKnownLicenseType(IntBuffer))
163     {
164         licenseType = static_cast<LicenseType>(IntBuffer);
165     }
166     else
167     {
168         licenseType = LICENSE_NONE;
169         if (szLicenseString.CompareNoCase(L"Freeware") == 0)
170         {
171             licenseType = LICENSE_FREEWARE;
172             szLicenseString = L"";
173         }
174     }
175 
176     CStringW szLicense;
177     switch (licenseType)
178     {
179         case LICENSE_OPENSOURCE:
180             szLicense.LoadStringW(IDS_LICENSE_OPENSOURCE);
181             break;
182         case LICENSE_FREEWARE:
183             szLicense.LoadStringW(IDS_LICENSE_FREEWARE);
184             break;
185         case LICENSE_TRIAL:
186             szLicense.LoadStringW(IDS_LICENSE_TRIAL);
187             break;
188         default:
189             return szLicenseString;
190     }
191 
192     if (!szLicenseString.IsEmpty())
193         szLicense += L" (" + szLicenseString + L")";
194     return szLicense;
195 }
196 
197 VOID
198 CAvailableApplicationInfo::InsertLanguageInfo(CAppRichEdit *RichEdit)
199 {
200     if (!m_LanguagesLoaded)
201     {
202         RetrieveLanguages();
203     }
204 
205     if (m_LanguageLCIDs.GetSize() == 0)
206     {
207         return;
208     }
209 
210     const INT nTranslations = m_LanguageLCIDs.GetSize();
211     CStringW szLangInfo;
212     CStringW szLoadedTextAvailability;
213     CStringW szLoadedAInfoText;
214 
215     szLoadedAInfoText.LoadStringW(IDS_AINFO_LANGUAGES);
216 
217     const LCID lcEnglish = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT);
218     if (m_LanguageLCIDs.Find(GetUserDefaultLCID()) >= 0)
219     {
220         szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_AVAILABLE_TRANSLATION);
221         if (nTranslations > 1)
222         {
223             CStringW buf;
224             buf.LoadStringW(IDS_LANGUAGE_MORE_PLACEHOLDER);
225             szLangInfo.Format(buf, nTranslations - 1);
226         }
227         else
228         {
229             szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE);
230             szLangInfo = L" (" + szLangInfo + L")";
231         }
232     }
233     else if (m_LanguageLCIDs.Find(lcEnglish) >= 0)
234     {
235         szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_ENGLISH_TRANSLATION);
236         if (nTranslations > 1)
237         {
238             CStringW buf;
239             buf.LoadStringW(IDS_LANGUAGE_AVAILABLE_PLACEHOLDER);
240             szLangInfo.Format(buf, nTranslations - 1);
241         }
242         else
243         {
244             szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE);
245             szLangInfo = L" (" + szLangInfo + L")";
246         }
247     }
248     else
249     {
250         szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_NO_TRANSLATION);
251     }
252 
253     RichEdit->InsertText(szLoadedAInfoText, CFE_BOLD);
254     RichEdit->InsertText(szLoadedTextAvailability, NULL);
255     RichEdit->InsertText(szLangInfo, CFE_ITALIC);
256 }
257 
258 VOID
259 CAvailableApplicationInfo::RetrieveLanguages()
260 {
261     m_LanguagesLoaded = true;
262 
263     CStringW szBuffer;
264     if (!m_Parser->GetString(L"Languages", szBuffer))
265     {
266         return;
267     }
268 
269     // Parse parameter string
270     int iIndex = 0;
271     while (true)
272     {
273         CStringW szLocale = szBuffer.Tokenize(L"|", iIndex);
274         if (szLocale.IsEmpty())
275             break;
276 
277         szLocale = L"0x" + szLocale;
278 
279         INT iLCID;
280         if (StrToIntExW(szLocale, STIF_SUPPORT_HEX, &iLCID))
281         {
282             m_LanguageLCIDs.Add(static_cast<LCID>(iLCID));
283         }
284     }
285 }
286 
287 BOOL
288 CAvailableApplicationInfo::Valid() const
289 {
290     return !szDisplayName.IsEmpty() && !m_szUrlDownload.IsEmpty();
291 }
292 
293 BOOL
294 CAvailableApplicationInfo::CanModify()
295 {
296     return FALSE;
297 }
298 
299 BOOL
300 CAvailableApplicationInfo::RetrieveIcon(CStringW &Path) const
301 {
302     Path = szDisplayIcon;
303     return !Path.IsEmpty();
304 }
305 
306 #define MAX_SCRNSHOT_NUM 16
307 BOOL
308 CAvailableApplicationInfo::RetrieveScreenshot(CStringW &Path)
309 {
310     if (!m_ScrnshotRetrieved)
311     {
312         static_assert(MAX_SCRNSHOT_NUM < 10000, "MAX_SCRNSHOT_NUM is too big");
313         for (int i = 0; i < MAX_SCRNSHOT_NUM; i++)
314         {
315             CStringW ScrnshotField;
316             ScrnshotField.Format(L"Screenshot%d", i + 1);
317             CStringW ScrnshotLocation;
318             if (!m_Parser->GetString(ScrnshotField, ScrnshotLocation))
319             {
320                 // We stop at the first screenshot not found,
321                 // so screenshots _have_ to be consecutive
322                 break;
323             }
324 
325             if (PathIsURLW(ScrnshotLocation.GetString()))
326             {
327                 m_szScrnshotLocation.Add(ScrnshotLocation);
328             }
329         }
330         m_ScrnshotRetrieved = true;
331     }
332 
333     if (m_szScrnshotLocation.GetSize() > 0)
334     {
335         Path = m_szScrnshotLocation[0];
336     }
337 
338     return !Path.IsEmpty();
339 }
340 
341 VOID
342 CAvailableApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const
343 {
344     Url = m_szUrlDownload;
345     m_Parser->GetString(L"SHA1", Sha1);
346     INT iSizeBytes;
347 
348     if (m_Parser->GetInt(L"SizeBytes", iSizeBytes))
349     {
350         SizeInBytes = (ULONG)iSizeBytes;
351     }
352     else
353     {
354         SizeInBytes = 0;
355     }
356 }
357 
358 VOID
359 CAvailableApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload)
360 {
361     License = LicenseString();
362     Size = m_szSize;
363     UrlSite = m_szUrlSite;
364     UrlDownload = m_szUrlDownload;
365 }
366 
367 BOOL
368 CAvailableApplicationInfo::UninstallApplication(BOOL bModify)
369 {
370     ATLASSERT(FALSE && "Should not be called");
371     return FALSE;
372 }
373 
374 CInstalledApplicationInfo::CInstalledApplicationInfo(
375     HKEY Key,
376     const CStringW &KeyName,
377     AppsCategories Category,
378     int KeyIndex)
379     : CAppInfo(KeyName, Category), m_hKey(Key), iKeyIndex(KeyIndex)
380 {
381     if (GetApplicationRegString(L"DisplayName", szDisplayName))
382     {
383         GetApplicationRegString(L"DisplayIcon", szDisplayIcon);
384         GetApplicationRegString(L"DisplayVersion", szDisplayVersion);
385         GetApplicationRegString(L"Comments", szComments);
386     }
387 }
388 
389 CInstalledApplicationInfo::~CInstalledApplicationInfo()
390 {
391 }
392 
393 VOID
394 CInstalledApplicationInfo::AddApplicationRegString(
395     CAppRichEdit *RichEdit,
396     UINT StringID,
397     const CStringW &String,
398     DWORD TextFlags)
399 {
400     CStringW Tmp;
401     if (GetApplicationRegString(String, Tmp))
402     {
403         RichEdit->InsertTextWithString(StringID, Tmp, TextFlags);
404     }
405 }
406 
407 VOID
408 CInstalledApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit)
409 {
410     RichEdit->SetText(szDisplayName, CFE_BOLD);
411     RichEdit->InsertText(L"\n", 0);
412 
413     RichEdit->InsertTextWithString(IDS_INFO_VERSION, szDisplayVersion, 0);
414     AddApplicationRegString(RichEdit, IDS_INFO_PUBLISHER, L"Publisher", 0);
415     AddApplicationRegString(RichEdit, IDS_INFO_REGOWNER, L"RegOwner", 0);
416     AddApplicationRegString(RichEdit, IDS_INFO_PRODUCTID, L"ProductID", 0);
417     AddApplicationRegString(RichEdit, IDS_INFO_HELPLINK, L"HelpLink", CFM_LINK);
418     AddApplicationRegString(RichEdit, IDS_INFO_HELPPHONE, L"HelpTelephone", 0);
419     AddApplicationRegString(RichEdit, IDS_INFO_README, L"Readme", 0);
420     AddApplicationRegString(RichEdit, IDS_INFO_CONTACT, L"Contact", 0);
421     AddApplicationRegString(RichEdit, IDS_INFO_UPDATEINFO, L"URLUpdateInfo", CFM_LINK);
422     AddApplicationRegString(RichEdit, IDS_INFO_INFOABOUT, L"URLInfoAbout", CFM_LINK);
423     RichEdit->InsertTextWithString(IDS_INFO_COMMENTS, szComments, 0);
424 
425     if (m_szInstallDate.IsEmpty())
426     {
427         RetrieveInstallDate();
428     }
429 
430     RichEdit->InsertTextWithString(IDS_INFO_INSTALLDATE, m_szInstallDate, 0);
431     AddApplicationRegString(RichEdit, IDS_INFO_INSTLOCATION, L"InstallLocation", 0);
432     AddApplicationRegString(RichEdit, IDS_INFO_INSTALLSRC, L"InstallSource", 0);
433 
434     if (m_szUninstallString.IsEmpty())
435     {
436         RetrieveUninstallStrings();
437     }
438 
439     RichEdit->InsertTextWithString(IDS_INFO_UNINSTALLSTR, m_szUninstallString, 0);
440     RichEdit->InsertTextWithString(IDS_INFO_MODIFYPATH, m_szModifyString, 0);
441 }
442 
443 VOID
444 CInstalledApplicationInfo::RetrieveInstallDate()
445 {
446     DWORD dwInstallTimeStamp;
447     SYSTEMTIME InstallLocalTime;
448     if (GetApplicationRegString(L"InstallDate", m_szInstallDate))
449     {
450         ZeroMemory(&InstallLocalTime, sizeof(InstallLocalTime));
451         // Check if we have 8 characters to parse the datetime.
452         // Maybe other formats exist as well?
453         m_szInstallDate = m_szInstallDate.Trim();
454         if (m_szInstallDate.GetLength() == 8)
455         {
456             InstallLocalTime.wYear = wcstol(m_szInstallDate.Left(4).GetString(), NULL, 10);
457             InstallLocalTime.wMonth = wcstol(m_szInstallDate.Mid(4, 2).GetString(), NULL, 10);
458             InstallLocalTime.wDay = wcstol(m_szInstallDate.Mid(6, 2).GetString(), NULL, 10);
459         }
460     }
461     // It might be a DWORD (Unix timestamp). try again.
462     else if (GetApplicationRegDword(L"InstallDate", &dwInstallTimeStamp))
463     {
464         FILETIME InstallFileTime;
465         SYSTEMTIME InstallSystemTime;
466 
467         UnixTimeToFileTime(dwInstallTimeStamp, &InstallFileTime);
468         FileTimeToSystemTime(&InstallFileTime, &InstallSystemTime);
469 
470         // convert to localtime
471         SystemTimeToTzSpecificLocalTime(NULL, &InstallSystemTime, &InstallLocalTime);
472     }
473 
474     // convert to readable date string
475     int cchTimeStrLen = GetDateFormatW(LOCALE_USER_DEFAULT, 0, &InstallLocalTime, NULL, 0, 0);
476 
477     GetDateFormatW(
478         LOCALE_USER_DEFAULT, // use default locale for current user
479         0, &InstallLocalTime, NULL, m_szInstallDate.GetBuffer(cchTimeStrLen), cchTimeStrLen);
480     m_szInstallDate.ReleaseBuffer();
481 }
482 
483 VOID
484 CInstalledApplicationInfo::RetrieveUninstallStrings()
485 {
486     DWORD dwWindowsInstaller = 0;
487     if (GetApplicationRegDword(L"WindowsInstaller", &dwWindowsInstaller) && dwWindowsInstaller)
488     {
489         // MSI has the same info in Uninstall / modify, so manually build it
490         m_szUninstallString.Format(L"msiexec /x%s", szIdentifier.GetString());
491     }
492     else
493     {
494         GetApplicationRegString(L"UninstallString", m_szUninstallString);
495     }
496     DWORD dwNoModify = 0;
497     if (!GetApplicationRegDword(L"NoModify", &dwNoModify))
498     {
499         CStringW Tmp;
500         if (GetApplicationRegString(L"NoModify", Tmp))
501         {
502             dwNoModify = Tmp.GetLength() > 0 ? (Tmp[0] == '1') : 0;
503         }
504         else
505         {
506             dwNoModify = 0;
507         }
508     }
509     if (!dwNoModify)
510     {
511         if (dwWindowsInstaller)
512         {
513             m_szModifyString.Format(L"msiexec /i%s", szIdentifier.GetString());
514         }
515         else
516         {
517             GetApplicationRegString(L"ModifyPath", m_szModifyString);
518         }
519     }
520 }
521 
522 BOOL
523 CInstalledApplicationInfo::Valid() const
524 {
525     return !szDisplayName.IsEmpty();
526 }
527 
528 BOOL
529 CInstalledApplicationInfo::CanModify()
530 {
531     if (m_szUninstallString.IsEmpty())
532     {
533         RetrieveUninstallStrings();
534     }
535 
536     return !m_szModifyString.IsEmpty();
537 }
538 
539 BOOL
540 CInstalledApplicationInfo::RetrieveIcon(CStringW &Path) const
541 {
542     Path = szDisplayIcon;
543     return !Path.IsEmpty();
544 }
545 
546 BOOL
547 CInstalledApplicationInfo::RetrieveScreenshot(CStringW & /*Path*/)
548 {
549     return FALSE;
550 }
551 
552 VOID
553 CInstalledApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const
554 {
555     ATLASSERT(FALSE && "Should not be called");
556 }
557 
558 VOID
559 CInstalledApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload)
560 {
561     ATLASSERT(FALSE && "Should not be called");
562 }
563 
564 BOOL
565 CInstalledApplicationInfo::UninstallApplication(BOOL bModify)
566 {
567     if (m_szUninstallString.IsEmpty())
568     {
569         RetrieveUninstallStrings();
570     }
571 
572     BOOL bSuccess = StartProcess(bModify ? m_szModifyString : m_szUninstallString, TRUE);
573 
574     if (bSuccess && !bModify)
575         WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_REMOVE, szDisplayName);
576 
577     return bSuccess;
578 }
579 
580 BOOL
581 CInstalledApplicationInfo::GetApplicationRegString(LPCWSTR lpKeyName, CStringW &String)
582 {
583     ULONG nChars = 0;
584     // Get the size
585     if (m_hKey.QueryStringValue(lpKeyName, NULL, &nChars) != ERROR_SUCCESS)
586     {
587         String.Empty();
588         return FALSE;
589     }
590 
591     LPWSTR Buffer = String.GetBuffer(nChars);
592     LONG lResult = m_hKey.QueryStringValue(lpKeyName, Buffer, &nChars);
593     if (nChars > 0 && Buffer[nChars - 1] == UNICODE_NULL)
594         nChars--;
595     String.ReleaseBuffer(nChars);
596 
597     if (lResult != ERROR_SUCCESS)
598     {
599         String.Empty();
600         return FALSE;
601     }
602 
603     if (String.Find('%') >= 0)
604     {
605         CStringW Tmp;
606         DWORD dwLen = ExpandEnvironmentStringsW(String, NULL, 0);
607         if (dwLen > 0)
608         {
609             BOOL bSuccess = ExpandEnvironmentStringsW(String, Tmp.GetBuffer(dwLen), dwLen) == dwLen;
610             Tmp.ReleaseBuffer(dwLen - 1);
611             if (bSuccess)
612             {
613                 String = Tmp;
614             }
615             else
616             {
617                 String.Empty();
618                 return FALSE;
619             }
620         }
621     }
622 
623     return TRUE;
624 }
625 
626 BOOL
627 CInstalledApplicationInfo::GetApplicationRegDword(LPCWSTR lpKeyName, DWORD *lpValue)
628 {
629     DWORD dwSize = sizeof(DWORD), dwType;
630     if (RegQueryValueExW(m_hKey, lpKeyName, NULL, &dwType, (LPBYTE)lpValue, &dwSize) != ERROR_SUCCESS ||
631         dwType != REG_DWORD)
632     {
633         return FALSE;
634     }
635 
636     return TRUE;
637 }
638