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