f6e12b8a646f3ed4252a70714bedb0b6205ac5be
[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: Functions 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 ATL::CAtlList<PAPPLICATION_INFO> InfoList;
14
15 inline VOID
16 InsertTextAfterLoaded_RichEdit(UINT uStringID, const ATL::CStringW& szText, DWORD StringFlags, DWORD TextFlags)
17 {
18 ATL::CStringW szLoadedText;
19 if (!szText.IsEmpty() && szLoadedText.LoadStringW(hInst, uStringID))
20 {
21 InsertRichEditText(szLoadedText, StringFlags);
22 InsertRichEditText(szText, TextFlags);
23 }
24 }
25
26 inline VOID
27 InsertLoadedTextNewl_RichEdit(UINT uStringID, DWORD StringFlags)
28 {
29 ATL::CStringW szLoadedText;
30 if (szLoadedText.LoadStringW(hInst, uStringID))
31 {
32 InsertRichEditText(L"\n", 0);
33 InsertRichEditText(szLoadedText, StringFlags);
34 InsertRichEditText(L"\n", 0);
35 }
36 }
37
38 inline BOOL
39 GetString(LPCWSTR lpKeyName, ATL::CStringW& ReturnedString, const ATL::CStringW& cFileName)
40 {
41 if (!ParserGetString(lpKeyName, cFileName, ReturnedString))
42 {
43 ReturnedString.Empty();
44 return FALSE;
45 }
46 return TRUE;
47 }
48
49 //Check both registry keys in 64bit system
50 //TODO: check system type beforehand to avoid double checks?
51 inline BOOL
52 GetInstalledVersionEx(PAPPLICATION_INFO Info, ATL::CStringW* szVersion, REGSAM key)
53 {
54 return (!Info->szRegName.IsEmpty()
55 && (GetInstalledVersion_WowUser(szVersion, Info->szRegName, TRUE, key)
56 || GetInstalledVersion_WowUser(szVersion, Info->szRegName, FALSE, key)))
57 || (!Info->szName.IsEmpty()
58 && (GetInstalledVersion_WowUser(szVersion, Info->szName, TRUE, key)
59 || GetInstalledVersion_WowUser(szVersion, Info->szName, FALSE, key)));
60 }
61
62 inline BOOL
63 GetInstalledVersion(PAPPLICATION_INFO Info, ATL::CStringW* szVersion)
64 {
65 return GetInstalledVersionEx(Info, szVersion, KEY_WOW64_32KEY)
66 || GetInstalledVersionEx(Info, szVersion, KEY_WOW64_64KEY);
67 }
68
69 inline BOOL
70 IsAppInstalled(PAPPLICATION_INFO Info)
71 {
72 return GetInstalledVersion(Info, NULL);
73 }
74
75 ATL::CSimpleArray<ATL::CStringW>
76 ParserGetAppLanguages(const ATL::CStringW& szFileName)
77 {
78 const WCHAR cDelimiter = L'|';
79 ATL::CStringW szBuffer;
80
81 if (!ParserGetString(L"Languages", szFileName, szBuffer))
82 return CSimpleArray<ATL::CStringW>();
83
84 ATL::CStringW szLocale;
85 ATL::CSimpleArray<ATL::CStringW> arrLocalesResult;
86 for (INT i = 0; szBuffer[i] != UNICODE_NULL; ++i)
87 {
88 if (szBuffer[i] != cDelimiter)
89 {
90 szLocale += szBuffer[i];
91 } else {
92 arrLocalesResult.Add(szLocale);
93 szLocale.Empty();
94 }
95 }
96 // For the text after delimiter
97 if (!szLocale.IsEmpty())
98 {
99 arrLocalesResult.Add(szLocale);
100 }
101 return arrLocalesResult;
102 }
103
104 BOOL
105 HasNativeLanguage(PAPPLICATION_INFO Info)
106 {
107 if (!Info)
108 {
109 return FALSE;
110 }
111 //TODO: make the actual check
112 return TRUE;
113 }
114 BOOL
115 HasEnglishLanguage(PAPPLICATION_INFO Info)
116 {
117 if (!Info)
118 {
119 return FALSE;
120 }
121 //TODO: make the actual check
122 return TRUE;
123 }
124
125 LICENSE_TYPE
126 ParserGetLicenseType(const ATL::CStringW& szFileName)
127 {
128 INT IntBuffer = ParserGetInt(L"LicenseType", szFileName);
129 if (IntBuffer < 0 || IntBuffer > LICENSE_TYPE::Max)
130 {
131 return LICENSE_TYPE::None;
132 }
133 return (LICENSE_TYPE) IntBuffer;
134 }
135
136 LIST_ENTRY CachedEntriesHead = {&CachedEntriesHead, &CachedEntriesHead};
137 PLIST_ENTRY pCachedEntry = &CachedEntriesHead;
138
139 VOID
140 InsertVersionInfo_RichEdit(PAPPLICATION_INFO Info)
141 {
142 ATL::CStringW szVersion;
143 BOOL bIsInstalled = IsAppInstalled(Info),
144 bHasVersion = GetInstalledVersion(Info, &szVersion);
145
146 if (bIsInstalled)
147 {
148 if (bHasVersion)
149 {
150 if (Info->szVersion.Compare(szVersion) > 0)
151 InsertLoadedTextNewl_RichEdit(IDS_STATUS_UPDATE_AVAILABLE, CFE_ITALIC);
152 else
153 InsertLoadedTextNewl_RichEdit(IDS_STATUS_INSTALLED, CFE_ITALIC);
154
155 InsertTextAfterLoaded_RichEdit(IDS_AINFO_VERSION, szVersion, CFE_BOLD, 0);
156 }
157 else
158 {
159 InsertLoadedTextNewl_RichEdit(IDS_STATUS_INSTALLED, CFE_ITALIC);
160 }
161 }
162 else
163 {
164 InsertLoadedTextNewl_RichEdit(IDS_STATUS_NOTINSTALLED, CFE_ITALIC);
165 }
166
167 InsertTextAfterLoaded_RichEdit(IDS_AINFO_AVAILABLEVERSION, Info->szVersion, CFE_BOLD, 0);
168 }
169
170 VOID
171 InsertLicenseInfo_RichEdit(PAPPLICATION_INFO Info)
172 {
173 ATL::CStringW szLicense;
174 switch (Info->LicenseType)
175 {
176 case LICENSE_TYPE::OpenSource:
177 szLicense.LoadStringW(hInst, IDS_LICENSE_OPENSOURCE);
178 break;
179 case LICENSE_TYPE::Freeware:
180 szLicense.LoadStringW(hInst, IDS_LICENSE_FREEWARE);
181 break;
182 case LICENSE_TYPE::Trial:
183 szLicense.LoadStringW(hInst, IDS_LICENSE_TRIAL);
184 break;
185 default:
186 break;
187 }
188
189 if (szLicense.IsEmpty())
190 {
191 InsertTextAfterLoaded_RichEdit(IDS_AINFO_LICENSE, Info->szLicense, CFE_BOLD, 0);
192 }
193 else
194 {
195 szLicense.Format(L"(%ls)", Info->szLicense);
196 InsertTextAfterLoaded_RichEdit(IDS_AINFO_LICENSE, szLicense, CFE_BOLD, 0);
197 }
198
199 }
200
201 VOID
202 InsertLanguageInfo_RichEdit(PAPPLICATION_INFO Info)
203 {
204 const INT nTranslations = Info->Languages.GetSize();
205 ATL::CStringW szLangInfo;
206 ATL::CStringW szLoadedTextAvailability;
207 ATL::CStringW szLoadedAInfoText;
208 szLoadedAInfoText.LoadStringW(IDS_AINFO_LANGUAGES);
209
210 if(HasNativeLanguage(Info))
211 {
212 szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_AVAILABLE_TRANSLATION);
213 }
214 else if (HasEnglishLanguage(Info))
215 {
216 szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_ENGLISH_TRANSLATION);
217 }
218 else
219 {
220 szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_NO_TRANSLATION);
221 }
222
223 if (nTranslations > 1)
224 {
225 szLangInfo.Format(L" (+%d more)", nTranslations - 1);
226 }
227
228 InsertRichEditText(szLoadedAInfoText, CFE_BOLD);
229 InsertRichEditText(szLoadedTextAvailability, NULL);
230 InsertRichEditText(szLangInfo, CFE_ITALIC);
231 }
232
233 BOOL
234 ShowAvailableAppInfo(INT Index)
235 {
236 PAPPLICATION_INFO Info = (PAPPLICATION_INFO) ListViewGetlParam(Index);
237 if (!Info) return FALSE;
238
239 NewRichEditText(Info->szName, CFE_BOLD);
240 InsertVersionInfo_RichEdit(Info);
241 InsertLicenseInfo_RichEdit(Info);
242 InsertLanguageInfo_RichEdit(Info);
243
244 InsertTextAfterLoaded_RichEdit(IDS_AINFO_SIZE, Info->szSize, CFE_BOLD, 0);
245 InsertTextAfterLoaded_RichEdit(IDS_AINFO_URLSITE, Info->szUrlSite, CFE_BOLD, CFE_LINK);
246 InsertTextAfterLoaded_RichEdit(IDS_AINFO_DESCRIPTION, Info->szDesc, CFE_BOLD, 0);
247 InsertTextAfterLoaded_RichEdit(IDS_AINFO_URLDOWNLOAD, Info->szUrlDownload, CFE_BOLD, CFE_LINK);
248
249 return TRUE;
250 }
251
252 static BOOL
253 DeleteCurrentAppsDB(VOID)
254 {
255 HANDLE hFind = INVALID_HANDLE_VALUE;
256 WIN32_FIND_DATAW FindFileData;
257 ATL::CStringW szCabPath;
258 ATL::CStringW szSearchPath;
259 ATL::CStringW szPath;
260 BOOL result = TRUE;
261
262 if (!GetStorageDirectory(szPath))
263 return FALSE;
264
265 szCabPath = szPath + L"\\rappmgr.cab";
266 result = result && DeleteFileW(szCabPath.GetString());
267
268 szPath += L"\\rapps\\";
269 szSearchPath = szPath + L"*.txt";
270
271 hFind = FindFirstFileW(szSearchPath.GetString(), &FindFileData);
272
273 if (hFind == INVALID_HANDLE_VALUE)
274 return result;
275
276 ATL::CStringW szTmp;
277 do
278 {
279 szTmp = szPath + FindFileData.cFileName;
280 result = result && DeleteFileW(szTmp.GetString());
281 } while (FindNextFileW(hFind, &FindFileData) != 0);
282
283 FindClose(hFind);
284
285 return result;
286 }
287
288 BOOL
289 UpdateAppsDB(VOID)
290 {
291 ATL::CStringW szPath;
292 ATL::CStringW szAppsPath;
293 ATL::CStringW szCabPath;
294
295 if (!DeleteCurrentAppsDB())
296 return FALSE;
297
298 DownloadApplicationsDB(APPLICATION_DATABASE_URL);
299
300 if (!GetStorageDirectory(szPath))
301 return FALSE;
302
303 szCabPath = szPath + L"\\rappmgr.cab";
304 szAppsPath = szPath + L"\\rapps\\";
305
306 if (!ExtractFilesFromCab(szCabPath, szAppsPath))
307 {
308 return FALSE;
309 }
310
311 return TRUE;
312 }
313
314
315 BOOL
316 EnumAvailableApplications(INT EnumType, AVAILENUMPROC lpEnumProc)
317 {
318 HANDLE hFind = INVALID_HANDLE_VALUE;
319 WIN32_FIND_DATAW FindFileData;
320 ATL::CStringW szPath;
321 ATL::CStringW szAppsPath;
322 ATL::CStringW szCabPath;
323 PAPPLICATION_INFO Info;
324
325 if (!GetStorageDirectory(szPath))
326 return FALSE;
327
328 szCabPath = szPath + L"\\rappmgr.cab";
329 szPath += L"\\rapps\\";
330 szAppsPath = szPath;
331
332 if (!CreateDirectoryW(szPath.GetString(), NULL) &&
333 GetLastError() != ERROR_ALREADY_EXISTS)
334 {
335 return FALSE;
336 }
337
338 szPath += L"*.txt";
339 hFind = FindFirstFileW(szPath.GetString(), &FindFileData);
340
341 if (hFind == INVALID_HANDLE_VALUE)
342 {
343 if (GetFileAttributesW(szCabPath) == INVALID_FILE_ATTRIBUTES)
344 DownloadApplicationsDB(APPLICATION_DATABASE_URL);
345
346 ExtractFilesFromCab(szCabPath, szAppsPath);
347 hFind = FindFirstFileW(szPath, &FindFileData);
348
349 if (hFind == INVALID_HANDLE_VALUE)
350 return FALSE;
351 }
352
353 do
354 {
355 /* loop for all the cached entries */
356 POSITION CurrentListPosition = InfoList.GetHeadPosition();
357
358 while (CurrentListPosition != NULL)
359 {
360 POSITION LastListPosition = CurrentListPosition;
361 Info = InfoList.GetNext(CurrentListPosition);
362
363 /* do we already have this entry in cache? */
364 if (!Info->cFileName.Compare(FindFileData.cFileName))
365 {
366 /* is it current enough, or the file has been modified since our last time here? */
367 if (CompareFileTime(&FindFileData.ftLastWriteTime, &Info->ftCacheStamp) == 1)
368 {
369 /* recreate our cache, this is the slow path */
370 InfoList.RemoveAt(LastListPosition);
371 delete Info;
372 break;
373 }
374 else
375 {
376 /* speedy path, compare directly, we already have the data */
377 goto skip_if_cached;
378 }
379 }
380 }
381
382 /* create a new entry */
383 Info = new APPLICATION_INFO();
384
385 if (!Info)
386 break;
387
388 Info->Category = ParserGetInt(L"Category", FindFileData.cFileName);
389 Info->LicenseType = ParserGetLicenseType(FindFileData.cFileName);
390 Info->Languages = ParserGetAppLanguages(FindFileData.cFileName);
391
392 /* copy the cache-related fields for the next time */
393 Info->cFileName = FindFileData.cFileName;
394 RtlCopyMemory(&Info->ftCacheStamp, &FindFileData.ftLastWriteTime, sizeof(FILETIME));
395
396 /* add our cached entry to the cached list */
397 InfoList.AddTail(Info);
398
399 skip_if_cached:
400 if (Info->Category == FALSE)
401 continue;
402
403 if (EnumType != Info->Category && EnumType != ENUM_ALL_AVAILABLE)
404 continue;
405
406 /* if our cache hit was only partial, we need to parse
407 and lazily fill the rest of fields only when needed */
408
409 if (Info->szUrlDownload.IsEmpty())
410 {
411 if (!GetString(L"Name", Info->szName, FindFileData.cFileName)
412 || !GetString(L"URLDownload", Info->szUrlDownload, FindFileData.cFileName))
413 {
414 continue;
415 }
416
417 GetString(L"RegName", Info->szRegName, FindFileData.cFileName);
418 GetString(L"Version", Info->szVersion, FindFileData.cFileName);
419 GetString(L"License", Info->szLicense, FindFileData.cFileName);
420 GetString(L"Description", Info->szDesc, FindFileData.cFileName);
421 GetString(L"Size", Info->szSize, FindFileData.cFileName);
422 GetString(L"URLSite", Info->szUrlSite, FindFileData.cFileName);
423 GetString(L"CDPath", Info->szCDPath, FindFileData.cFileName);
424 GetString(L"Language", Info->szRegName, FindFileData.cFileName);
425 GetString(L"SHA1", Info->szSHA1, FindFileData.cFileName);
426 }
427
428 if (!lpEnumProc(Info))
429 break;
430
431 } while (FindNextFileW(hFind, &FindFileData) != 0);
432
433 FindClose(hFind);
434
435 return TRUE;
436 }
437
438 VOID FreeCachedAvailableEntries(VOID)
439 {
440 PAPPLICATION_INFO Info;
441 POSITION InfoListPosition = InfoList.GetHeadPosition();
442
443 /* loop and deallocate all the cached app infos in the list */
444 while (InfoListPosition)
445 {
446 Info = InfoList.GetAt(InfoListPosition);
447 InfoList.RemoveHead();
448 InfoListPosition = InfoList.GetHeadPosition();
449
450 delete Info;
451 }
452 }