[RAPPS] Improvements & multiple selections
[reactos.git] / reactos / base / applications / rapps / available.cpp
1 /*
2 * PROJECT: ReactOS Applications Manager
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: base/applications/rapps/available.cpp
5 * PURPOSE: Classes for working with available applications
6 * PROGRAMMERS: Dmitry Chapyshev (dmitry@reactos.org)
7 * Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com)
8 * Alexander Shaposhnikov (chaez.san@gmail.com)
9 */
10
11 #include "rapps.h"
12
13 // CAvailableApplicationInfo
14
15 CAvailableApplicationInfo::CAvailableApplicationInfo(const ATL::CStringW& sFileNameParam)
16 : m_Parser(sFileNameParam)
17 {
18 LicenseType = LICENSE_TYPE::None;
19 sFileName = sFileNameParam;
20
21 RetrieveGeneralInfo();
22 }
23
24 VOID CAvailableApplicationInfo::RefreshAppInfo()
25 {
26 if (szUrlDownload.IsEmpty())
27 {
28 RetrieveGeneralInfo();
29 }
30 }
31
32 VOID CAvailableApplicationInfo::RetrieveGeneralInfo()
33 {
34 Category = m_Parser.GetInt(L"Category");
35
36 if (!GetString(L"Name", szName)
37 || !GetString(L"URLDownload", szUrlDownload))
38 {
39 return;
40 }
41
42 GetString(L"RegName", szRegName);
43 GetString(L"Version", szVersion);
44 GetString(L"License", szLicense);
45 GetString(L"Description", szDesc);
46 GetString(L"Size", szSize);
47 GetString(L"URLSite", szUrlSite);
48 GetString(L"CDPath", szCDPath);
49 GetString(L"Language", szRegName);
50 GetString(L"SHA1", szSHA1);
51
52 RetrieveLicenseType();
53 RetrieveLanguages();
54 RetrieveInstalledStatus();
55 if (m_IsInstalled)
56 {
57 RetrieveInstalledVersion();
58 }
59 }
60
61 VOID CAvailableApplicationInfo::RetrieveInstalledStatus()
62 {
63 m_IsInstalled = ::GetInstalledVersion(NULL, szRegName)
64 || ::GetInstalledVersion(NULL, szName);
65 }
66
67 VOID CAvailableApplicationInfo::RetrieveInstalledVersion()
68 {
69 m_HasInstalledVersion = ::GetInstalledVersion(&szInstalledVersion, szRegName)
70 || ::GetInstalledVersion(&szInstalledVersion, szName);
71 }
72
73 VOID CAvailableApplicationInfo::RetrieveLanguages()
74 {
75 const WCHAR cDelimiter = L'|';
76 ATL::CStringW szBuffer;
77
78 // TODO: Get multiline parameter
79 if (!m_Parser.GetString(L"Languages", szBuffer))
80 {
81 m_HasLanguageInfo = FALSE;
82 return;
83 }
84
85 // Parse parameter string
86 ATL::CStringW m_szLocale;
87 int iLCID;
88 for (INT i = 0; szBuffer[i] != UNICODE_NULL; ++i)
89 {
90 if (szBuffer[i] != cDelimiter && szBuffer[i] != L'\n')
91 {
92 m_szLocale += szBuffer[i];
93 }
94 else
95 {
96 if (StrToIntExW(m_szLocale.GetString(), STIF_DEFAULT, &iLCID))
97 {
98 Languages.Add(static_cast<LCID>(iLCID));
99 m_szLocale.Empty();
100 }
101 }
102 }
103
104 // For the text after delimiter
105 if (!m_szLocale.IsEmpty())
106 {
107 if (StrToIntExW(m_szLocale.GetString(), STIF_DEFAULT, &iLCID))
108 {
109 Languages.Add(static_cast<LCID>(iLCID));
110 }
111 }
112
113 m_HasLanguageInfo = TRUE;
114 }
115
116 VOID CAvailableApplicationInfo::RetrieveLicenseType()
117 {
118 INT IntBuffer = m_Parser.GetInt(L"LicenseType");
119
120 if (IntBuffer < 0 || IntBuffer > LICENSE_TYPE::Max)
121 {
122 LicenseType = LICENSE_TYPE::None;
123 }
124 else
125 {
126 LicenseType = (LICENSE_TYPE) IntBuffer;
127 }
128 }
129
130 BOOL CAvailableApplicationInfo::FindInLanguages(LCID what) const
131 {
132 if (!m_HasLanguageInfo)
133 {
134 return FALSE;
135 }
136
137 //Find locale code in the list
138 const INT nLanguagesSize = Languages.GetSize();
139 for (INT i = 0; i < nLanguagesSize; ++i)
140 {
141 if (Languages[i] == what)
142 {
143 return TRUE;
144 }
145 }
146
147 return FALSE;
148 }
149
150 BOOL CAvailableApplicationInfo::HasLanguageInfo() const
151 {
152 return m_HasLanguageInfo;
153 }
154
155 BOOL CAvailableApplicationInfo::HasNativeLanguage() const
156 {
157 return FindInLanguages(GetUserDefaultLCID());
158 }
159
160 BOOL CAvailableApplicationInfo::HasEnglishLanguage() const
161 {
162 return FindInLanguages(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT));
163 }
164
165 BOOL CAvailableApplicationInfo::IsInstalled() const
166 {
167 return m_IsInstalled;
168 }
169
170 BOOL CAvailableApplicationInfo::HasInstalledVersion() const
171 {
172 return m_HasInstalledVersion;
173 }
174
175 BOOL CAvailableApplicationInfo::HasUpdate() const
176 {
177 return (szInstalledVersion.Compare(szVersion) < 0) ? TRUE : FALSE;
178 }
179
180 VOID CAvailableApplicationInfo::SetLastWriteTime(FILETIME* ftTime)
181 {
182 RtlCopyMemory(&ftCacheStamp, ftTime, sizeof(FILETIME));
183 }
184
185 inline BOOL CAvailableApplicationInfo::GetString(LPCWSTR lpKeyName, ATL::CStringW& ReturnedString)
186 {
187 if (!m_Parser.GetString(lpKeyName, ReturnedString))
188 {
189 ReturnedString.Empty();
190 return FALSE;
191 }
192 return TRUE;
193 }
194 // CAvailableApplicationInfo
195
196 // CAvailableApps
197 CAvailableApps::CAvailableApps()
198 {
199 //set all paths
200 if (GetStorageDirectory(m_szPath))
201 {
202 m_szAppsPath = m_szPath + L"\\rapps\\";
203 m_szCabPath = m_szPath + L"\\rappmgr.cab";
204 m_szSearchPath = m_szAppsPath + L"*.txt";
205 }
206 }
207
208 VOID CAvailableApps::FreeCachedEntries()
209 {
210 POSITION InfoListPosition = m_InfoList.GetHeadPosition();
211
212 /* loop and deallocate all the cached app infos in the list */
213 while (InfoListPosition)
214 {
215 CAvailableApplicationInfo* Info = m_InfoList.GetAt(InfoListPosition);
216 m_InfoList.RemoveHead();
217 delete Info;
218
219 InfoListPosition = m_InfoList.GetHeadPosition();
220 }
221 }
222
223 BOOL CAvailableApps::DeleteCurrentAppsDB()
224 {
225 HANDLE hFind = INVALID_HANDLE_VALUE;
226 WIN32_FIND_DATAW FindFileData;
227 BOOL result = TRUE;
228
229 if (m_szPath.IsEmpty())
230 return FALSE;
231
232 result = result && DeleteFileW(m_szCabPath.GetString());
233 hFind = FindFirstFileW(m_szSearchPath.GetString(), &FindFileData);
234
235 if (hFind == INVALID_HANDLE_VALUE)
236 return result;
237
238 ATL::CStringW szTmp;
239 do
240 {
241 szTmp = m_szPath + FindFileData.cFileName;
242 result = result && DeleteFileW(szTmp.GetString());
243 } while (FindNextFileW(hFind, &FindFileData) != 0);
244
245 FindClose(hFind);
246
247 return result;
248 }
249
250 BOOL CAvailableApps::UpdateAppsDB()
251 {
252 if (!DeleteCurrentAppsDB())
253 return FALSE;
254
255 DownloadApplicationsDB(APPLICATION_DATABASE_URL);
256
257 if (m_szPath.IsEmpty())
258 return FALSE;
259
260 if (!ExtractFilesFromCab(m_szCabPath, m_szAppsPath))
261 {
262 return FALSE;
263 }
264
265 return TRUE;
266 }
267
268 BOOL CAvailableApps::EnumAvailableApplications(INT EnumType, AVAILENUMPROC lpEnumProc)
269 {
270 HANDLE hFind = INVALID_HANDLE_VALUE;
271 WIN32_FIND_DATAW FindFileData;
272
273 if (!CreateDirectoryW(m_szPath.GetString(), NULL) &&
274 GetLastError() != ERROR_ALREADY_EXISTS)
275 {
276 return FALSE;
277 }
278
279 hFind = FindFirstFileW(m_szSearchPath.GetString(), &FindFileData);
280
281 if (hFind == INVALID_HANDLE_VALUE)
282 {
283 if (GetFileAttributesW(m_szCabPath) == INVALID_FILE_ATTRIBUTES)
284 DownloadApplicationsDB(APPLICATION_DATABASE_URL);
285
286 ExtractFilesFromCab(m_szCabPath, m_szAppsPath);
287 hFind = FindFirstFileW(m_szSearchPath.GetString(), &FindFileData);
288
289 if (hFind == INVALID_HANDLE_VALUE)
290 return FALSE;
291 }
292
293 do
294 {
295 // loop for all the cached entries
296 POSITION CurrentListPosition = m_InfoList.GetHeadPosition();
297 CAvailableApplicationInfo* Info = NULL;
298
299 while (CurrentListPosition != NULL)
300 {
301 POSITION LastListPosition = CurrentListPosition;
302 Info = m_InfoList.GetNext(CurrentListPosition);
303
304 // do we already have this entry in cache?
305 if (Info->sFileName == FindFileData.cFileName)
306 {
307 // is it current enough, or the file has been modified since our last time here?
308 if (CompareFileTime(&FindFileData.ftLastWriteTime, &Info->ftCacheStamp) == 1)
309 {
310 // recreate our cache, this is the slow path
311 m_InfoList.RemoveAt(LastListPosition);
312
313 delete Info;
314 Info = nullptr;
315 break;
316 }
317 else
318 {
319 // speedy path, compare directly, we already have the data
320 goto skip_if_cached;
321 }
322 }
323 }
324
325 // create a new entry
326 Info = new CAvailableApplicationInfo(FindFileData.cFileName);
327
328 // set a timestamp for the next time
329 Info->SetLastWriteTime(&FindFileData.ftLastWriteTime);
330 m_InfoList.AddTail(Info);
331
332 skip_if_cached:
333 if (Info->Category == FALSE)
334 continue;
335
336 if (EnumType != Info->Category && EnumType != ENUM_ALL_AVAILABLE)
337 continue;
338
339 Info->RefreshAppInfo();
340
341 if (!lpEnumProc(static_cast<PAPPLICATION_INFO>(Info), m_szAppsPath.GetString()))
342 break;
343
344 } while (FindNextFileW(hFind, &FindFileData) != 0);
345
346 FindClose(hFind);
347 return TRUE;
348 }
349
350 const ATL::CStringW & CAvailableApps::GetFolderPath()
351 {
352 return m_szPath;
353 }
354
355 const ATL::CStringW & CAvailableApps::GetAppPath()
356 {
357 return m_szAppsPath;
358 }
359
360 const ATL::CStringW & CAvailableApps::GetCabPath()
361 {
362 return m_szCabPath;
363 }
364
365 const LPCWSTR CAvailableApps::GetFolderPathString()
366 {
367 return m_szPath.GetString();
368 }
369
370 const LPCWSTR CAvailableApps::GetAppPathString()
371 {
372 return m_szPath.GetString();
373 }
374
375 const LPCWSTR CAvailableApps::GetCabPathString()
376 {
377 return m_szPath.GetString();
378 }
379 // CAvailableApps
380
381 // CConfigParser
382 ATL::CStringW CConfigParser::m_szLocaleID;
383 ATL::CStringW CConfigParser::m_szCachedINISectionLocale;
384 ATL::CStringW CConfigParser::m_szCachedINISectionLocaleNeutral;
385
386 CConfigParser::CConfigParser(const ATL::CStringW& FileName) : szConfigPath(GetINIFullPath(FileName))
387 {
388 // we don't have cached section strings for the current system language, create them, lazy
389 CacheINILocaleLazy();
390 }
391
392 ATL::CStringW CConfigParser::GetINIFullPath(const ATL::CStringW& FileName)
393 {
394 ATL::CStringW szDir;
395 ATL::CStringW szBuffer;
396
397 GetStorageDirectory(szDir);
398 szBuffer.Format(L"%ls\\rapps\\%ls", szDir, FileName);
399
400 return szBuffer;
401 }
402
403 VOID CConfigParser::CacheINILocaleLazy()
404 {
405 if (m_szLocaleID.IsEmpty())
406 {
407 // TODO: Set default locale if call fails
408 // find out what is the current system lang code (e.g. "0a") and append it to SectionLocale
409 GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_ILANGUAGE,
410 m_szLocaleID.GetBuffer(m_cchLocaleSize), m_cchLocaleSize);
411
412 m_szLocaleID.ReleaseBuffer();
413 m_szCachedINISectionLocale = L"Section." + m_szLocaleID;
414
415 // turn "Section.0c0a" into "Section.0a", keeping just the neutral lang part
416 m_szCachedINISectionLocaleNeutral = m_szCachedINISectionLocale + m_szLocaleID.Right(2);
417 }
418 }
419
420 const ATL::CStringW& CConfigParser::GetLocale()
421 {
422 CacheINILocaleLazy();
423 return m_szLocaleID;
424 }
425
426 INT CConfigParser::GetLocaleSize()
427 {
428 return m_cchLocaleSize;
429 }
430
431 UINT CConfigParser::GetString(const ATL::CStringW& KeyName, ATL::CStringW& ResultString)
432 {
433 DWORD dwResult;
434
435 LPWSTR ResultStringBuffer = ResultString.GetBuffer(MAX_PATH);
436 // 1st - find localized strings (e.g. "Section.0c0a")
437 dwResult = GetPrivateProfileStringW(m_szCachedINISectionLocale.GetString(),
438 KeyName.GetString(),
439 NULL,
440 ResultStringBuffer,
441 MAX_PATH,
442 szConfigPath.GetString());
443
444 if (!dwResult)
445 {
446 // 2nd - if they weren't present check for neutral sub-langs/ generic translations (e.g. "Section.0a")
447 dwResult = GetPrivateProfileStringW(m_szCachedINISectionLocaleNeutral.GetString(),
448 KeyName.GetString(),
449 NULL,
450 ResultStringBuffer,
451 MAX_PATH,
452 szConfigPath.GetString());
453 if (!dwResult)
454 {
455 // 3rd - if they weren't present fallback to standard english strings (just "Section")
456 dwResult = GetPrivateProfileStringW(L"Section",
457 KeyName.GetString(),
458 NULL,
459 ResultStringBuffer,
460 MAX_PATH,
461 szConfigPath.GetString());
462 }
463 }
464
465 ResultString.ReleaseBuffer();
466 return (dwResult != 0 ? TRUE : FALSE);
467 }
468
469 UINT CConfigParser::GetInt(const ATL::CStringW& KeyName)
470 {
471 ATL::CStringW Buffer;
472
473 // grab the text version of our entry
474 if (!GetString(KeyName, Buffer))
475 return FALSE;
476
477 if (Buffer.IsEmpty())
478 return FALSE;
479
480 // convert it to an actual integer
481 int result = StrToIntW(Buffer.GetString());
482
483 return (UINT) (result <= 0) ? 0 : result;
484 }
485 // CConfigParser