reactos/base/applications/rapps/appinfo.cpp
Whindmar Saksit 4b03981846
[RAPPS] Improve LicenseType handling (#5809)
Tries now to map the "License" text set to "Freeware" to the
LICENSE_FREEWARE "LicenseType" so it is translated correctly (LoadString).

Fixes the following:

- If only the "License" field is set in the DB, nothing will change
  (this applies to 99% of the current entries in the DB).

- If both "LicenseType" and "License" are set, both will be used
  (no observable change in behavior): "Open Source (GPL v2)" etc.

- If only "LicenseType" is set, it will now display just the type
  "Freeware" instead of "Freeware ()".
  This is done only for "Freeware", because the others (the open source ones)
  have many variations. "OpenSource", "Open Source", "Open Source (GPL)" etc.
2023-11-04 22:08:10 +01:00

637 lines
17 KiB
C++

/*
* PROJECT: ReactOS Applications Manager
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Classes for working with available applications
* COPYRIGHT: Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
* Copyright 2020 He Yang (1160386205@qq.com)
* Copyright 2021-2023 Mark Jansen <mark.jansen@reactos.org>
*/
#include "rapps.h"
#include "appview.h"
CAppInfo::CAppInfo(const CStringW &Identifier, AppsCategories Category)
: szIdentifier(Identifier), iCategory(Category)
{
}
CAppInfo::~CAppInfo()
{
}
CAvailableApplicationInfo::CAvailableApplicationInfo(
CConfigParser *Parser,
const CStringW &PkgName,
AppsCategories Category,
const CPathW &BasePath)
: CAppInfo(PkgName, Category), m_Parser(Parser), m_ScrnshotRetrieved(false), m_LanguagesLoaded(false)
{
m_Parser->GetString(L"Name", szDisplayName);
m_Parser->GetString(L"Version", szDisplayVersion);
m_Parser->GetString(L"URLDownload", m_szUrlDownload);
m_Parser->GetString(L"Description", szComments);
CPathW IconPath = BasePath;
IconPath += L"icons";
CStringW IconName;
if (m_Parser->GetString(L"Icon", IconName))
{
IconPath += IconName;
}
else
{
// inifile.ico
IconPath += (szIdentifier + L".ico");
}
if (PathFileExistsW(IconPath))
{
szDisplayIcon = (LPCWSTR)IconPath;
}
INT iSizeBytes;
if (m_Parser->GetInt(L"SizeBytes", iSizeBytes))
{
StrFormatByteSizeW(iSizeBytes, m_szSize.GetBuffer(MAX_PATH), MAX_PATH);
m_szSize.ReleaseBuffer();
}
m_Parser->GetString(L"URLSite", m_szUrlSite);
}
CAvailableApplicationInfo::~CAvailableApplicationInfo()
{
delete m_Parser;
}
VOID
CAvailableApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit)
{
RichEdit->SetText(szDisplayName, CFE_BOLD);
InsertVersionInfo(RichEdit);
RichEdit->LoadAndInsertText(IDS_AINFO_LICENSE, LicenseString(), 0);
InsertLanguageInfo(RichEdit);
RichEdit->LoadAndInsertText(IDS_AINFO_SIZE, m_szSize, 0);
RichEdit->LoadAndInsertText(IDS_AINFO_URLSITE, m_szUrlSite, CFE_LINK);
RichEdit->LoadAndInsertText(IDS_AINFO_DESCRIPTION, szComments, 0);
RichEdit->LoadAndInsertText(IDS_AINFO_URLDOWNLOAD, m_szUrlDownload, CFE_LINK);
RichEdit->LoadAndInsertText(IDS_AINFO_PACKAGE_NAME, szIdentifier, 0);
}
int
CompareVersion(const CStringW &left, const CStringW &right)
{
int nLeft = 0, nRight = 0;
while (true)
{
CStringW leftPart = left.Tokenize(L".", nLeft);
CStringW rightPart = right.Tokenize(L".", nRight);
if (leftPart.IsEmpty() && rightPart.IsEmpty())
return 0;
if (leftPart.IsEmpty())
return -1;
if (rightPart.IsEmpty())
return 1;
int leftVal, rightVal;
if (!StrToIntExW(leftPart, STIF_DEFAULT, &leftVal))
leftVal = 0;
if (!StrToIntExW(rightPart, STIF_DEFAULT, &rightVal))
rightVal = 0;
if (leftVal > rightVal)
return 1;
if (rightVal < leftVal)
return -1;
}
}
VOID
CAvailableApplicationInfo::InsertVersionInfo(CAppRichEdit *RichEdit)
{
CStringW szRegName;
m_Parser->GetString(L"RegName", szRegName);
BOOL bIsInstalled = ::GetInstalledVersion(NULL, szRegName) || ::GetInstalledVersion(NULL, szDisplayName);
if (bIsInstalled)
{
CStringW szInstalledVersion;
CStringW szNameVersion = szDisplayName + L" " + szDisplayVersion;
BOOL bHasInstalledVersion = ::GetInstalledVersion(&szInstalledVersion, szRegName) ||
::GetInstalledVersion(&szInstalledVersion, szDisplayName) ||
::GetInstalledVersion(&szInstalledVersion, szNameVersion);
if (bHasInstalledVersion)
{
BOOL bHasUpdate = CompareVersion(szInstalledVersion, szDisplayVersion) < 0;
if (bHasUpdate)
RichEdit->LoadAndInsertText(IDS_STATUS_UPDATE_AVAILABLE, CFE_ITALIC);
else
RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC);
RichEdit->LoadAndInsertText(IDS_AINFO_VERSION, szInstalledVersion, 0);
}
else
{
RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC);
}
}
else
{
RichEdit->LoadAndInsertText(IDS_STATUS_NOTINSTALLED, CFE_ITALIC);
}
RichEdit->LoadAndInsertText(IDS_AINFO_AVAILABLEVERSION, szDisplayVersion, 0);
}
CStringW
CAvailableApplicationInfo::LicenseString()
{
INT IntBuffer;
m_Parser->GetInt(L"LicenseType", IntBuffer);
CStringW szLicenseString;
m_Parser->GetString(L"License", szLicenseString);
LicenseType licenseType;
if (IsKnownLicenseType(IntBuffer))
{
licenseType = static_cast<LicenseType>(IntBuffer);
}
else
{
licenseType = LICENSE_NONE;
if (szLicenseString.CompareNoCase(L"Freeware") == 0)
{
licenseType = LICENSE_FREEWARE;
szLicenseString = L"";
}
}
CStringW szLicense;
switch (licenseType)
{
case LICENSE_OPENSOURCE:
szLicense.LoadStringW(IDS_LICENSE_OPENSOURCE);
break;
case LICENSE_FREEWARE:
szLicense.LoadStringW(IDS_LICENSE_FREEWARE);
break;
case LICENSE_TRIAL:
szLicense.LoadStringW(IDS_LICENSE_TRIAL);
break;
default:
return szLicenseString;
}
if (!szLicenseString.IsEmpty())
szLicense += L" (" + szLicenseString + L")";
return szLicense;
}
VOID
CAvailableApplicationInfo::InsertLanguageInfo(CAppRichEdit *RichEdit)
{
if (!m_LanguagesLoaded)
{
RetrieveLanguages();
}
if (m_LanguageLCIDs.GetSize() == 0)
{
return;
}
const INT nTranslations = m_LanguageLCIDs.GetSize();
CStringW szLangInfo;
CStringW szLoadedTextAvailability;
CStringW szLoadedAInfoText;
szLoadedAInfoText.LoadStringW(IDS_AINFO_LANGUAGES);
const LCID lcEnglish = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT);
if (m_LanguageLCIDs.Find(GetUserDefaultLCID()) >= 0)
{
szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_AVAILABLE_TRANSLATION);
if (nTranslations > 1)
{
CStringW buf;
buf.LoadStringW(IDS_LANGUAGE_MORE_PLACEHOLDER);
szLangInfo.Format(buf, nTranslations - 1);
}
else
{
szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE);
szLangInfo = L" (" + szLangInfo + L")";
}
}
else if (m_LanguageLCIDs.Find(lcEnglish) >= 0)
{
szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_ENGLISH_TRANSLATION);
if (nTranslations > 1)
{
CStringW buf;
buf.LoadStringW(IDS_LANGUAGE_AVAILABLE_PLACEHOLDER);
szLangInfo.Format(buf, nTranslations - 1);
}
else
{
szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE);
szLangInfo = L" (" + szLangInfo + L")";
}
}
else
{
szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_NO_TRANSLATION);
}
RichEdit->InsertText(szLoadedAInfoText, CFE_BOLD);
RichEdit->InsertText(szLoadedTextAvailability, NULL);
RichEdit->InsertText(szLangInfo, CFE_ITALIC);
}
VOID
CAvailableApplicationInfo::RetrieveLanguages()
{
m_LanguagesLoaded = true;
CStringW szBuffer;
if (!m_Parser->GetString(L"Languages", szBuffer))
{
return;
}
// Parse parameter string
int iIndex = 0;
while (true)
{
CStringW szLocale = szBuffer.Tokenize(L"|", iIndex);
if (szLocale.IsEmpty())
break;
szLocale = L"0x" + szLocale;
INT iLCID;
if (StrToIntExW(szLocale, STIF_SUPPORT_HEX, &iLCID))
{
m_LanguageLCIDs.Add(static_cast<LCID>(iLCID));
}
}
}
BOOL
CAvailableApplicationInfo::Valid() const
{
return !szDisplayName.IsEmpty() && !m_szUrlDownload.IsEmpty();
}
BOOL
CAvailableApplicationInfo::CanModify()
{
return FALSE;
}
BOOL
CAvailableApplicationInfo::RetrieveIcon(CStringW &Path) const
{
Path = szDisplayIcon;
return !Path.IsEmpty();
}
#define MAX_SCRNSHOT_NUM 16
BOOL
CAvailableApplicationInfo::RetrieveScreenshot(CStringW &Path)
{
if (!m_ScrnshotRetrieved)
{
static_assert(MAX_SCRNSHOT_NUM < 10000, "MAX_SCRNSHOT_NUM is too big");
for (int i = 0; i < MAX_SCRNSHOT_NUM; i++)
{
CStringW ScrnshotField;
ScrnshotField.Format(L"Screenshot%d", i + 1);
CStringW ScrnshotLocation;
if (!m_Parser->GetString(ScrnshotField, ScrnshotLocation))
{
// We stop at the first screenshot not found,
// so screenshots _have_ to be consecutive
break;
}
if (PathIsURLW(ScrnshotLocation.GetString()))
{
m_szScrnshotLocation.Add(ScrnshotLocation);
}
}
m_ScrnshotRetrieved = true;
}
if (m_szScrnshotLocation.GetSize() > 0)
{
Path = m_szScrnshotLocation[0];
}
return !Path.IsEmpty();
}
VOID
CAvailableApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const
{
Url = m_szUrlDownload;
m_Parser->GetString(L"SHA1", Sha1);
INT iSizeBytes;
if (m_Parser->GetInt(L"SizeBytes", iSizeBytes))
{
SizeInBytes = (ULONG)iSizeBytes;
}
else
{
SizeInBytes = 0;
}
}
VOID
CAvailableApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload)
{
License = LicenseString();
Size = m_szSize;
UrlSite = m_szUrlSite;
UrlDownload = m_szUrlDownload;
}
BOOL
CAvailableApplicationInfo::UninstallApplication(BOOL bModify)
{
ATLASSERT(FALSE && "Should not be called");
return FALSE;
}
CInstalledApplicationInfo::CInstalledApplicationInfo(
HKEY Key,
const CStringW &KeyName,
AppsCategories Category,
int KeyIndex)
: CAppInfo(KeyName, Category), m_hKey(Key), iKeyIndex(KeyIndex)
{
if (GetApplicationRegString(L"DisplayName", szDisplayName))
{
GetApplicationRegString(L"DisplayIcon", szDisplayIcon);
GetApplicationRegString(L"DisplayVersion", szDisplayVersion);
GetApplicationRegString(L"Comments", szComments);
}
}
CInstalledApplicationInfo::~CInstalledApplicationInfo()
{
}
VOID
CInstalledApplicationInfo::AddApplicationRegString(
CAppRichEdit *RichEdit,
UINT StringID,
const CStringW &String,
DWORD TextFlags)
{
CStringW Tmp;
if (GetApplicationRegString(String, Tmp))
{
RichEdit->InsertTextWithString(StringID, Tmp, TextFlags);
}
}
VOID
CInstalledApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit)
{
RichEdit->SetText(szDisplayName, CFE_BOLD);
RichEdit->InsertText(L"\n", 0);
RichEdit->InsertTextWithString(IDS_INFO_VERSION, szDisplayVersion, 0);
AddApplicationRegString(RichEdit, IDS_INFO_PUBLISHER, L"Publisher", 0);
AddApplicationRegString(RichEdit, IDS_INFO_REGOWNER, L"RegOwner", 0);
AddApplicationRegString(RichEdit, IDS_INFO_PRODUCTID, L"ProductID", 0);
AddApplicationRegString(RichEdit, IDS_INFO_HELPLINK, L"HelpLink", CFM_LINK);
AddApplicationRegString(RichEdit, IDS_INFO_HELPPHONE, L"HelpTelephone", 0);
AddApplicationRegString(RichEdit, IDS_INFO_README, L"Readme", 0);
AddApplicationRegString(RichEdit, IDS_INFO_CONTACT, L"Contact", 0);
AddApplicationRegString(RichEdit, IDS_INFO_UPDATEINFO, L"URLUpdateInfo", CFM_LINK);
AddApplicationRegString(RichEdit, IDS_INFO_INFOABOUT, L"URLInfoAbout", CFM_LINK);
RichEdit->InsertTextWithString(IDS_INFO_COMMENTS, szComments, 0);
if (m_szInstallDate.IsEmpty())
{
RetrieveInstallDate();
}
RichEdit->InsertTextWithString(IDS_INFO_INSTALLDATE, m_szInstallDate, 0);
AddApplicationRegString(RichEdit, IDS_INFO_INSTLOCATION, L"InstallLocation", 0);
AddApplicationRegString(RichEdit, IDS_INFO_INSTALLSRC, L"InstallSource", 0);
if (m_szUninstallString.IsEmpty())
{
RetrieveUninstallStrings();
}
RichEdit->InsertTextWithString(IDS_INFO_UNINSTALLSTR, m_szUninstallString, 0);
RichEdit->InsertTextWithString(IDS_INFO_MODIFYPATH, m_szModifyString, 0);
}
VOID
CInstalledApplicationInfo::RetrieveInstallDate()
{
DWORD dwInstallTimeStamp;
SYSTEMTIME InstallLocalTime;
if (GetApplicationRegString(L"InstallDate", m_szInstallDate))
{
ZeroMemory(&InstallLocalTime, sizeof(InstallLocalTime));
// Check if we have 8 characters to parse the datetime.
// Maybe other formats exist as well?
m_szInstallDate = m_szInstallDate.Trim();
if (m_szInstallDate.GetLength() == 8)
{
InstallLocalTime.wYear = wcstol(m_szInstallDate.Left(4).GetString(), NULL, 10);
InstallLocalTime.wMonth = wcstol(m_szInstallDate.Mid(4, 2).GetString(), NULL, 10);
InstallLocalTime.wDay = wcstol(m_szInstallDate.Mid(6, 2).GetString(), NULL, 10);
}
}
// It might be a DWORD (Unix timestamp). try again.
else if (GetApplicationRegDword(L"InstallDate", &dwInstallTimeStamp))
{
FILETIME InstallFileTime;
SYSTEMTIME InstallSystemTime;
UnixTimeToFileTime(dwInstallTimeStamp, &InstallFileTime);
FileTimeToSystemTime(&InstallFileTime, &InstallSystemTime);
// convert to localtime
SystemTimeToTzSpecificLocalTime(NULL, &InstallSystemTime, &InstallLocalTime);
}
// convert to readable date string
int cchTimeStrLen = GetDateFormatW(LOCALE_USER_DEFAULT, 0, &InstallLocalTime, NULL, 0, 0);
GetDateFormatW(
LOCALE_USER_DEFAULT, // use default locale for current user
0, &InstallLocalTime, NULL, m_szInstallDate.GetBuffer(cchTimeStrLen), cchTimeStrLen);
m_szInstallDate.ReleaseBuffer();
}
VOID
CInstalledApplicationInfo::RetrieveUninstallStrings()
{
DWORD dwWindowsInstaller = 0;
if (GetApplicationRegDword(L"WindowsInstaller", &dwWindowsInstaller) && dwWindowsInstaller)
{
// MSI has the same info in Uninstall / modify, so manually build it
m_szUninstallString.Format(L"msiexec /x%s", szIdentifier.GetString());
}
else
{
GetApplicationRegString(L"UninstallString", m_szUninstallString);
}
DWORD dwNoModify = 0;
if (!GetApplicationRegDword(L"NoModify", &dwNoModify))
{
CStringW Tmp;
if (GetApplicationRegString(L"NoModify", Tmp))
{
dwNoModify = Tmp.GetLength() > 0 ? (Tmp[0] == '1') : 0;
}
else
{
dwNoModify = 0;
}
}
if (!dwNoModify)
{
if (dwWindowsInstaller)
{
m_szModifyString.Format(L"msiexec /i%s", szIdentifier.GetString());
}
else
{
GetApplicationRegString(L"ModifyPath", m_szModifyString);
}
}
}
BOOL
CInstalledApplicationInfo::Valid() const
{
return !szDisplayName.IsEmpty();
}
BOOL
CInstalledApplicationInfo::CanModify()
{
if (m_szUninstallString.IsEmpty())
{
RetrieveUninstallStrings();
}
return !m_szModifyString.IsEmpty();
}
BOOL
CInstalledApplicationInfo::RetrieveIcon(CStringW &Path) const
{
Path = szDisplayIcon;
return !Path.IsEmpty();
}
BOOL
CInstalledApplicationInfo::RetrieveScreenshot(CStringW & /*Path*/)
{
return FALSE;
}
VOID
CInstalledApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const
{
ATLASSERT(FALSE && "Should not be called");
}
VOID
CInstalledApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload)
{
ATLASSERT(FALSE && "Should not be called");
}
BOOL
CInstalledApplicationInfo::UninstallApplication(BOOL bModify)
{
if (m_szUninstallString.IsEmpty())
{
RetrieveUninstallStrings();
}
BOOL bSuccess = StartProcess(bModify ? m_szModifyString : m_szUninstallString, TRUE);
if (bSuccess && !bModify)
WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_REMOVE, szDisplayName);
return bSuccess;
}
BOOL
CInstalledApplicationInfo::GetApplicationRegString(LPCWSTR lpKeyName, CStringW &String)
{
ULONG nChars = 0;
// Get the size
if (m_hKey.QueryStringValue(lpKeyName, NULL, &nChars) != ERROR_SUCCESS)
{
String.Empty();
return FALSE;
}
LPWSTR Buffer = String.GetBuffer(nChars);
LONG lResult = m_hKey.QueryStringValue(lpKeyName, Buffer, &nChars);
if (nChars > 0 && Buffer[nChars - 1] == UNICODE_NULL)
nChars--;
String.ReleaseBuffer(nChars);
if (lResult != ERROR_SUCCESS)
{
String.Empty();
return FALSE;
}
if (String.Find('%') >= 0)
{
CStringW Tmp;
DWORD dwLen = ExpandEnvironmentStringsW(String, NULL, 0);
if (dwLen > 0)
{
BOOL bSuccess = ExpandEnvironmentStringsW(String, Tmp.GetBuffer(dwLen), dwLen) == dwLen;
Tmp.ReleaseBuffer(dwLen - 1);
if (bSuccess)
{
String = Tmp;
}
else
{
String.Empty();
return FALSE;
}
}
}
return TRUE;
}
BOOL
CInstalledApplicationInfo::GetApplicationRegDword(LPCWSTR lpKeyName, DWORD *lpValue)
{
DWORD dwSize = sizeof(DWORD), dwType;
if (RegQueryValueExW(m_hKey, lpKeyName, NULL, &dwType, (LPBYTE)lpValue, &dwSize) != ERROR_SUCCESS ||
dwType != REG_DWORD)
{
return FALSE;
}
return TRUE;
}