reactos/base/applications/rapps/available.cpp

632 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 2009 Dmitry Chapyshev (dmitry@reactos.org)
* Copyright 2015 Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com)
* Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
* Copyright 2020 He Yang (1160386205@qq.com)
*/
#include "rapps.h"
#include "available.h"
#include "misc.h"
#include "dialogs.h"
// CAvailableApplicationInfo
CAvailableApplicationInfo::CAvailableApplicationInfo(const ATL::CStringW& sFileNameParam, AvailableStrings& AvlbStrings)
: m_LicenseType(LICENSE_NONE), m_SizeBytes(0), m_sFileName(sFileNameParam),
m_IsInstalled(FALSE), m_HasLanguageInfo(FALSE), m_HasInstalledVersion(FALSE)
{
RetrieveGeneralInfo(AvlbStrings);
}
VOID CAvailableApplicationInfo::RefreshAppInfo(AvailableStrings& AvlbStrings)
{
if (m_szUrlDownload.IsEmpty())
{
RetrieveGeneralInfo(AvlbStrings);
}
}
// Lazily load general info from the file
VOID CAvailableApplicationInfo::RetrieveGeneralInfo(AvailableStrings& AvlbStrings)
{
m_Parser = new CConfigParser(m_sFileName);
// TODO: I temporarily use the file name (without suffix) as package name.
// It should be better to put this in a field of ini file.
// consider write a converter to do this and write a github action for rapps-db to ensure package_name is unique.
m_szPkgName = m_sFileName;
PathRemoveExtensionW(m_szPkgName.GetBuffer(MAX_PATH));
m_szPkgName.ReleaseBuffer();
m_Parser->GetInt(L"Category", m_Category);
if (!GetString(L"Name", m_szName)
|| !GetString(L"URLDownload", m_szUrlDownload))
{
delete m_Parser;
return;
}
GetString(L"RegName", m_szRegName);
GetString(L"Version", m_szVersion);
GetString(L"License", m_szLicense);
GetString(L"Description", m_szDesc);
GetString(L"URLSite", m_szUrlSite);
GetString(L"SHA1", m_szSHA1);
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 (!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);
}
else
{
// TODO: Does the filename contain anything stuff like ":" "<" ">" ?
// these stuff may lead to security issues
ATL::CStringW ScrnshotName = AvlbStrings.szAppsPath;
PathAppendW(ScrnshotName.GetBuffer(MAX_PATH), L"screenshots");
BOOL bSuccess = PathAppendNoDirEscapeW(ScrnshotName.GetBuffer(), ScrnshotLocation.GetString());
ScrnshotName.ReleaseBuffer();
if (bSuccess)
{
m_szScrnshotLocation.Add(ScrnshotName);
}
}
}
ATL::CStringW IconPath = AvlbStrings.szAppsPath;
PathAppendW(IconPath.GetBuffer(MAX_PATH), L"icons");
// TODO: are we going to support specify an URL for an icon ?
ATL::CStringW IconLocation;
if (GetString(L"Icon", IconLocation))
{
BOOL bSuccess = PathAppendNoDirEscapeW(IconPath.GetBuffer(), IconLocation.GetString());
IconPath.ReleaseBuffer();
if (!bSuccess)
{
IconPath.Empty();
}
}
else
{
// inifile.ico
PathAppendW(IconPath.GetBuffer(), m_szPkgName);
IconPath.ReleaseBuffer();
IconPath += L".ico";
}
if (!IconPath.IsEmpty())
{
if (PathFileExistsW(IconPath))
{
m_szIconLocation = IconPath;
}
}
RetrieveSize();
RetrieveLicenseType();
RetrieveLanguages();
RetrieveInstalledStatus();
if (m_IsInstalled)
{
RetrieveInstalledVersion();
}
delete m_Parser;
}
VOID CAvailableApplicationInfo::RetrieveInstalledStatus()
{
m_IsInstalled = ::GetInstalledVersion(NULL, m_szRegName)
|| ::GetInstalledVersion(NULL, m_szName);
}
VOID CAvailableApplicationInfo::RetrieveInstalledVersion()
{
ATL::CStringW szNameVersion;
szNameVersion = m_szName + L" " + m_szVersion;
m_HasInstalledVersion = ::GetInstalledVersion(&m_szInstalledVersion, m_szRegName)
|| ::GetInstalledVersion(&m_szInstalledVersion, m_szName)
|| ::GetInstalledVersion(&m_szInstalledVersion, szNameVersion);
}
VOID CAvailableApplicationInfo::RetrieveLanguages()
{
const WCHAR cDelimiter = L'|';
ATL::CStringW szBuffer;
// TODO: Get multiline parameter
if (!m_Parser->GetString(L"Languages", szBuffer))
{
m_HasLanguageInfo = FALSE;
return;
}
// Parse parameter string
ATL::CStringW m_szLocale;
INT iLCID;
for (INT i = 0; szBuffer[i] != UNICODE_NULL; ++i)
{
if (szBuffer[i] != cDelimiter && szBuffer[i] != L'\n')
{
m_szLocale += szBuffer[i];
}
else
{
if (StrToIntExW(m_szLocale.GetString(), STIF_DEFAULT, &iLCID))
{
m_LanguageLCIDs.Add(static_cast<LCID>(iLCID));
m_szLocale.Empty();
}
}
}
// For the text after delimiter
if (!m_szLocale.IsEmpty())
{
if (StrToIntExW(m_szLocale.GetString(), STIF_DEFAULT, &iLCID))
{
m_LanguageLCIDs.Add(static_cast<LCID>(iLCID));
}
}
m_HasLanguageInfo = TRUE;
}
VOID CAvailableApplicationInfo::RetrieveLicenseType()
{
INT IntBuffer;
m_Parser->GetInt(L"LicenseType", IntBuffer);
if (IsLicenseType(IntBuffer))
{
m_LicenseType = static_cast<LicenseType>(IntBuffer);
}
else
{
m_LicenseType = LICENSE_NONE;
}
}
VOID CAvailableApplicationInfo::RetrieveSize()
{
INT iSizeBytes;
if (!m_Parser->GetInt(L"SizeBytes", iSizeBytes))
{
// fall back to "Size" string
GetString(L"Size", m_szSize);
return;
}
m_SizeBytes = iSizeBytes;
StrFormatByteSizeW(iSizeBytes, m_szSize.GetBuffer(MAX_PATH), MAX_PATH);
m_szSize.ReleaseBuffer();
}
BOOL CAvailableApplicationInfo::FindInLanguages(LCID what) const
{
if (!m_HasLanguageInfo)
{
return FALSE;
}
//Find locale code in the list
const INT nLanguagesSize = m_LanguageLCIDs.GetSize();
for (INT i = 0; i < nLanguagesSize; ++i)
{
if (m_LanguageLCIDs[i] == what)
{
return TRUE;
}
}
return FALSE;
}
BOOL CAvailableApplicationInfo::HasLanguageInfo() const
{
return m_HasLanguageInfo;
}
BOOL CAvailableApplicationInfo::HasNativeLanguage() const
{
return FindInLanguages(GetUserDefaultLCID());
}
BOOL CAvailableApplicationInfo::HasEnglishLanguage() const
{
return FindInLanguages(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT));
}
BOOL CAvailableApplicationInfo::IsInstalled() const
{
return m_IsInstalled;
}
BOOL CAvailableApplicationInfo::HasInstalledVersion() const
{
return m_HasInstalledVersion;
}
BOOL CAvailableApplicationInfo::HasUpdate() const
{
return (m_szInstalledVersion.Compare(m_szVersion) < 0) ? TRUE : FALSE;
}
BOOL CAvailableApplicationInfo::RetrieveScrnshot(UINT Index,ATL::CStringW& ScrnshotLocation) const
{
if (Index >= (UINT)m_szScrnshotLocation.GetSize())
{
return FALSE;
}
ScrnshotLocation = m_szScrnshotLocation[Index];
return TRUE;
}
BOOL CAvailableApplicationInfo::RetrieveIcon(ATL::CStringW& IconLocation) const
{
if (m_szIconLocation.IsEmpty())
{
return FALSE;
}
IconLocation = m_szIconLocation;
return TRUE;
}
VOID CAvailableApplicationInfo::SetLastWriteTime(FILETIME* ftTime)
{
RtlCopyMemory(&m_ftCacheStamp, ftTime, sizeof(FILETIME));
}
inline BOOL CAvailableApplicationInfo::GetString(LPCWSTR lpKeyName, ATL::CStringW& ReturnedString)
{
if (!m_Parser->GetString(lpKeyName, ReturnedString))
{
ReturnedString.Empty();
return FALSE;
}
return TRUE;
}
// CAvailableApplicationInfo
// AvailableStrings
AvailableStrings::AvailableStrings()
{
//FIXME: maybe provide a fallback?
if (GetStorageDirectory(szPath))
{
szAppsPath = szPath;
PathAppendW(szAppsPath.GetBuffer(MAX_PATH), L"rapps");
szAppsPath.ReleaseBuffer();
szCabName = APPLICATION_DATABASE_NAME;
szCabDir = szPath;
szCabPath = szCabDir;
PathAppendW(szCabPath.GetBuffer(MAX_PATH), szCabName);
szCabPath.ReleaseBuffer();
szSearchPath = szAppsPath;
PathAppendW(szSearchPath.GetBuffer(MAX_PATH), L"*.txt");
szSearchPath.ReleaseBuffer();
}
}
// AvailableStrings
// CAvailableApps
AvailableStrings CAvailableApps::m_Strings;
CAvailableApps::CAvailableApps()
{
}
VOID CAvailableApps::FreeCachedEntries()
{
POSITION InfoListPosition = m_InfoList.GetHeadPosition();
/* loop and deallocate all the cached app infos in the list */
while (InfoListPosition)
{
CAvailableApplicationInfo* Info = m_InfoList.GetNext(InfoListPosition);
delete Info;
}
m_InfoList.RemoveAll();
}
static void DeleteWithWildcard(const CStringW& DirWithFilter)
{
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW FindFileData;
hFind = FindFirstFileW(DirWithFilter, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
return;
CStringW Dir = DirWithFilter;
PathRemoveFileSpecW(Dir.GetBuffer(MAX_PATH));
Dir.ReleaseBuffer();
do
{
ATL::CStringW szTmp = Dir + L"\\";
szTmp += FindFileData.cFileName;
if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
DeleteFileW(szTmp);
}
} while (FindNextFileW(hFind, &FindFileData) != 0);
FindClose(hFind);
}
VOID CAvailableApps::DeleteCurrentAppsDB()
{
// Delete icons
ATL::CStringW IconPath = m_Strings.szAppsPath;
PathAppendW(IconPath.GetBuffer(MAX_PATH), L"icons");
IconPath.ReleaseBuffer();
DeleteWithWildcard(IconPath + L"\\*.ico");
// Delete leftover screenshots
ATL::CStringW ScrnshotFolder = m_Strings.szAppsPath;
PathAppendW(ScrnshotFolder.GetBuffer(MAX_PATH), L"screenshots");
ScrnshotFolder.ReleaseBuffer();
DeleteWithWildcard(IconPath + L"\\*.tmp");
// Delete data base files (*.txt)
DeleteWithWildcard(m_Strings.szSearchPath);
RemoveDirectoryW(IconPath);
RemoveDirectoryW(ScrnshotFolder);
RemoveDirectoryW(m_Strings.szAppsPath);
RemoveDirectoryW(m_Strings.szPath);
}
BOOL CAvailableApps::UpdateAppsDB()
{
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW FindFileData;
if (!CreateDirectoryW(m_Strings.szPath, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
{
return FALSE;
}
//if there are some files in the db folder - we're good
hFind = FindFirstFileW(m_Strings.szSearchPath, &FindFileData);
if (hFind != INVALID_HANDLE_VALUE)
{
FindClose(hFind);
return TRUE;
}
DownloadApplicationsDB(SettingsInfo.bUseSource ? SettingsInfo.szSourceURL : APPLICATION_DATABASE_URL,
!SettingsInfo.bUseSource);
if (!ExtractFilesFromCab(m_Strings.szCabName,
m_Strings.szCabDir,
m_Strings.szAppsPath))
{
return FALSE;
}
DeleteFileW(m_Strings.szCabPath);
return TRUE;
}
BOOL CAvailableApps::ForceUpdateAppsDB()
{
DeleteCurrentAppsDB();
return UpdateAppsDB();
}
BOOL CAvailableApps::Enum(INT EnumType, AVAILENUMPROC lpEnumProc, PVOID param)
{
if (EnumType == ENUM_CAT_SELECTED)
{
CAvailableApplicationInfo *EnumAvlbInfo = NULL;
// enum all object in m_SelectedList and invoke callback
for(POSITION CurrentPosition = m_SelectedList.GetHeadPosition();
CurrentPosition && (EnumAvlbInfo = m_SelectedList.GetAt(CurrentPosition));
m_SelectedList.GetNext(CurrentPosition))
{
EnumAvlbInfo->RefreshAppInfo(m_Strings);
if (lpEnumProc)
lpEnumProc(EnumAvlbInfo, TRUE, param);
}
return TRUE;
}
else
{
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW FindFileData;
hFind = FindFirstFileW(m_Strings.szSearchPath.GetString(), &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
//no db yet
return FALSE;
}
do
{
// loop for all the cached entries
POSITION CurrentListPosition = m_InfoList.GetHeadPosition();
CAvailableApplicationInfo *Info = NULL;
while (CurrentListPosition != NULL)
{
POSITION LastListPosition = CurrentListPosition;
Info = m_InfoList.GetNext(CurrentListPosition);
// do we already have this entry in cache?
if (Info->m_sFileName == FindFileData.cFileName)
{
// is it current enough, or the file has been modified since our last time here?
if (CompareFileTime(&FindFileData.ftLastWriteTime, &Info->m_ftCacheStamp) == 1)
{
// recreate our cache, this is the slow path
m_InfoList.RemoveAt(LastListPosition);
// also remove this in selected list (if exist)
RemoveSelected(Info);
delete Info;
Info = NULL;
break;
}
else
{
// speedy path, compare directly, we already have the data
goto skip_if_cached;
}
}
}
// create a new entry
Info = new CAvailableApplicationInfo(FindFileData.cFileName, m_Strings);
// set a timestamp for the next time
Info->SetLastWriteTime(&FindFileData.ftLastWriteTime);
/* Check if we have the download URL */
if (Info->m_szUrlDownload.IsEmpty())
{
/* Can't use it, delete it */
delete Info;
continue;
}
m_InfoList.AddTail(Info);
skip_if_cached:
if (EnumType == Info->m_Category
|| EnumType == ENUM_ALL_AVAILABLE)
{
Info->RefreshAppInfo(m_Strings);
if (lpEnumProc)
{
if (m_SelectedList.Find(Info))
{
lpEnumProc(Info, TRUE, param);
}
else
{
lpEnumProc(Info, FALSE, param);
}
}
}
} while (FindNextFileW(hFind, &FindFileData));
FindClose(hFind);
return TRUE;
}
}
BOOL CAvailableApps::AddSelected(CAvailableApplicationInfo *AvlbInfo)
{
return m_SelectedList.AddTail(AvlbInfo) != 0;
}
BOOL CAvailableApps::RemoveSelected(CAvailableApplicationInfo *AvlbInfo)
{
POSITION Position = m_SelectedList.Find(AvlbInfo);
if (Position)
{
m_SelectedList.RemoveAt(Position);
return TRUE;
}
return FALSE;
}
VOID CAvailableApps::RemoveAllSelected()
{
m_SelectedList.RemoveAll();
return;
}
int CAvailableApps::GetSelectedCount()
{
return m_SelectedList.GetCount();
}
CAvailableApplicationInfo* CAvailableApps::FindAppByPkgName(const ATL::CStringW& szPkgName) const
{
if (m_InfoList.IsEmpty())
{
return NULL;
}
// linear search
POSITION CurrentListPosition = m_InfoList.GetHeadPosition();
CAvailableApplicationInfo* info;
while (CurrentListPosition != NULL)
{
info = m_InfoList.GetNext(CurrentListPosition);
if (info->m_szPkgName.CompareNoCase(szPkgName) == 0)
{
return info;
}
}
return NULL;
}
ATL::CSimpleArray<CAvailableApplicationInfo> CAvailableApps::FindAppsByPkgNameList(const ATL::CSimpleArray<ATL::CStringW> &PkgNameList) const
{
ATL::CSimpleArray<CAvailableApplicationInfo> result;
for (INT i = 0; i < PkgNameList.GetSize(); ++i)
{
CAvailableApplicationInfo* Info = FindAppByPkgName(PkgNameList[i]);
if (Info)
{
result.Add(*Info);
}
}
return result;
}
const ATL::CStringW& CAvailableApps::GetFolderPath() const
{
return m_Strings.szPath;
}
const ATL::CStringW& CAvailableApps::GetAppPath() const
{
return m_Strings.szAppsPath;
}
const ATL::CStringW& CAvailableApps::GetCabPath() const
{
return m_Strings.szCabPath;
}
// CAvailableApps