1 /*
2 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2014, Axel Dörfler <axeld@pinc-software.de>.
4 * Copyright 2016-2024, Andrew Lindesay <apl@lindesay.co.nz>.
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
7 #include "Model.h"
8
9 #include <algorithm>
10 #include <ctime>
11 #include <vector>
12
13 #include <stdarg.h>
14 #include <time.h>
15
16 #include <Autolock.h>
17 #include <Catalog.h>
18 #include <Directory.h>
19 #include <Entry.h>
20 #include <File.h>
21 #include <KeyStore.h>
22 #include <Locale.h>
23 #include <LocaleRoster.h>
24 #include <Message.h>
25 #include <Path.h>
26
27 #include "HaikuDepotConstants.h"
28 #include "LocaleUtils.h"
29 #include "Logger.h"
30 #include "PackageUtils.h"
31 #include "StorageUtils.h"
32
33
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "Model"
36
37
38 #define KEY_STORE_IDENTIFIER_PREFIX "hds.password."
39 // this prefix is added before the nickname in the keystore
40 // so that HDS username/password pairs can be identified.
41
42 static const char* kHaikuDepotKeyring = "HaikuDepot";
43
44
~ModelListener()45 ModelListener::~ModelListener()
46 {
47 }
48
49
50 // #pragma mark - Model
51
52
Model()53 Model::Model()
54 :
55 fPreferredLanguage(LanguageRef(new Language(LANGUAGE_DEFAULT_ID, "English", true), true)),
56 fDepots(),
57 fCategories(),
58 fPackageListViewMode(PROMINENT),
59 fCanShareAnonymousUsageData(false)
60 {
61
62 // setup the language into a default state with a hard-coded language so
63 // that there is a language at all.
64 fLanguageRepository = new LanguageRepository();
65 fLanguageRepository->AddLanguage(fPreferredLanguage);
66
67 fPackageFilterModel = new PackageFilterModel();
68 fPackageScreenshotRepository = new PackageScreenshotRepository(
69 PackageScreenshotRepositoryListenerRef(this),
70 &fWebAppInterface);
71 }
72
73
~Model()74 Model::~Model()
75 {
76 delete fPackageFilterModel;
77 delete fPackageScreenshotRepository;
78 delete fLanguageRepository;
79 }
80
81
82 LanguageRepository*
Languages()83 Model::Languages()
84 {
85 return fLanguageRepository;
86 }
87
88
89 PackageFilterModel*
PackageFilter()90 Model::PackageFilter()
91 {
92 return fPackageFilterModel;
93 }
94
95
96 PackageIconRepository&
GetPackageIconRepository()97 Model::GetPackageIconRepository()
98 {
99 return fPackageIconRepository;
100 }
101
102
103 status_t
InitPackageIconRepository()104 Model::InitPackageIconRepository()
105 {
106 BPath tarPath;
107 status_t result = StorageUtils::IconTarPath(tarPath);
108 if (result == B_OK)
109 result = fPackageIconRepository.Init(tarPath);
110 return result;
111 }
112
113
114 PackageScreenshotRepository*
GetPackageScreenshotRepository()115 Model::GetPackageScreenshotRepository()
116 {
117 return fPackageScreenshotRepository;
118 }
119
120
121 void
AddListener(const ModelListenerRef & listener)122 Model::AddListener(const ModelListenerRef& listener)
123 {
124 fListeners.push_back(listener);
125 }
126
127
128 LanguageRef
PreferredLanguage() const129 Model::PreferredLanguage() const
130 {
131 return fPreferredLanguage;
132 }
133
134
135 void
SetPreferredLanguage(LanguageRef value)136 Model::SetPreferredLanguage(LanguageRef value)
137 {
138 if (value.IsSet()) {
139 if (fPreferredLanguage != value)
140 fPreferredLanguage = value;
141 }
142 }
143
144
145 // TODO; part of a wider change; cope with the package being in more than one
146 // depot
147 PackageInfoRef
PackageForName(const BString & name)148 Model::PackageForName(const BString& name)
149 {
150 std::vector<DepotInfoRef>::iterator it;
151 for (it = fDepots.begin(); it != fDepots.end(); it++) {
152 DepotInfoRef depotInfoRef = *it;
153 PackageInfoRef packageInfoRef = depotInfoRef->PackageByName(name);
154 if (packageInfoRef.Get() != NULL)
155 return packageInfoRef;
156 }
157 return PackageInfoRef();
158 }
159
160
161 void
MergeOrAddDepot(const DepotInfoRef & depot)162 Model::MergeOrAddDepot(const DepotInfoRef& depot)
163 {
164 BString depotName = depot->Name();
165 for(uint32 i = 0; i < fDepots.size(); i++) {
166 if (fDepots[i]->Name() == depotName) {
167 DepotInfoRef ersatzDepot(new DepotInfo(*(fDepots[i].Get())), true);
168 ersatzDepot->SyncPackagesFromDepot(depot);
169 fDepots[i] = ersatzDepot;
170 return;
171 }
172 }
173 fDepots.push_back(depot);
174 }
175
176
177 bool
HasDepot(const BString & name) const178 Model::HasDepot(const BString& name) const
179 {
180 return NULL != DepotForName(name).Get();
181 }
182
183
184 const DepotInfoRef
DepotForName(const BString & name) const185 Model::DepotForName(const BString& name) const
186 {
187 std::vector<DepotInfoRef>::const_iterator it;
188 for (it = fDepots.begin(); it != fDepots.end(); it++) {
189 DepotInfoRef aDepot = *it;
190 if (aDepot->Name() == name)
191 return aDepot;
192 }
193 return DepotInfoRef();
194 }
195
196
197 int32
CountDepots() const198 Model::CountDepots() const
199 {
200 return fDepots.size();
201 }
202
203
204 DepotInfoRef
DepotAtIndex(int32 index) const205 Model::DepotAtIndex(int32 index) const
206 {
207 return fDepots[index];
208 }
209
210
211 bool
HasAnyProminentPackages()212 Model::HasAnyProminentPackages()
213 {
214 std::vector<DepotInfoRef>::iterator it;
215 for (it = fDepots.begin(); it != fDepots.end(); it++) {
216 DepotInfoRef aDepot = *it;
217 if (aDepot->HasAnyProminentPackages())
218 return true;
219 }
220 return false;
221 }
222
223
224 void
Clear()225 Model::Clear()
226 {
227 GetPackageIconRepository().Clear();
228 fDepots.clear();
229 fPopulatedPackageNames.MakeEmpty();
230 }
231
232
233 void
SetStateForPackagesByName(BStringList & packageNames,PackageState state)234 Model::SetStateForPackagesByName(BStringList& packageNames, PackageState state)
235 {
236 for (int32 i = 0; i < packageNames.CountStrings(); i++) {
237 BString packageName = packageNames.StringAt(i);
238 PackageInfoRef packageInfo = PackageForName(packageName);
239
240 if (packageInfo.IsSet()) {
241 // TODO; make this immutable
242
243 PackageLocalInfoRef localInfo = PackageUtils::NewLocalInfo(packageInfo);
244 localInfo->SetState(state);
245 packageInfo->SetLocalInfo(localInfo);
246
247 HDINFO("did update package [%s] with state [%s]", packageName.String(),
248 PackageUtils::StateToString(state));
249 }
250 else {
251 HDINFO("was unable to find package [%s] so was not possible to set the state to [%s]",
252 packageName.String(), PackageUtils::StateToString(state));
253 }
254 }
255 }
256
257
258 void
SetPackageListViewMode(package_list_view_mode mode)259 Model::SetPackageListViewMode(package_list_view_mode mode)
260 {
261 fPackageListViewMode = mode;
262 }
263
264
265 void
SetCanShareAnonymousUsageData(bool value)266 Model::SetCanShareAnonymousUsageData(bool value)
267 {
268 fCanShareAnonymousUsageData = value;
269 }
270
271
272 // #pragma mark - information retrieval
273
274 /*! It may transpire that the package has no corresponding record on the
275 server side because the repository is not represented in the server.
276 In such a case, there is little point in communicating with the server
277 only to hear back that the package does not exist.
278 */
279
280 bool
CanPopulatePackage(const PackageInfoRef & package)281 Model::CanPopulatePackage(const PackageInfoRef& package)
282 {
283 const BString& depotName = package->DepotName();
284
285 if (depotName.IsEmpty())
286 return false;
287
288 const DepotInfoRef& depot = DepotForName(depotName);
289
290 if (depot.Get() == NULL)
291 return false;
292
293 return !depot->WebAppRepositoryCode().IsEmpty();
294 }
295
296
297 static void
model_remove_key_for_user(const BString & nickname)298 model_remove_key_for_user(const BString& nickname)
299 {
300 if (nickname.IsEmpty())
301 return;
302 BKeyStore keyStore;
303 BPasswordKey key;
304 BString passwordIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
305 << nickname;
306 status_t result = keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD,
307 passwordIdentifier, key);
308
309 switch (result) {
310 case B_OK:
311 result = keyStore.RemoveKey(kHaikuDepotKeyring, key);
312 if (result != B_OK) {
313 HDERROR("error occurred when removing password for nickname "
314 "[%s] : %s", nickname.String(), strerror(result));
315 }
316 break;
317 case B_ENTRY_NOT_FOUND:
318 return;
319 default:
320 HDERROR("error occurred when finding password for nickname "
321 "[%s] : %s", nickname.String(), strerror(result));
322 break;
323 }
324 }
325
326
327 void
SetNickname(BString nickname)328 Model::SetNickname(BString nickname)
329 {
330 BString password;
331 BString existingNickname = Nickname();
332
333 // this happens when the user is logging out. Best to remove the password
334 // stored for the existing user since it is no longer required.
335
336 if (!existingNickname.IsEmpty() && nickname.IsEmpty())
337 model_remove_key_for_user(existingNickname);
338
339 if (nickname.Length() > 0) {
340 BPasswordKey key;
341 BKeyStore keyStore;
342 BString passwordIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
343 << nickname;
344 if (keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD,
345 passwordIdentifier, key) == B_OK) {
346 password = key.Password();
347 }
348 if (password.IsEmpty())
349 nickname = "";
350 }
351
352 SetCredentials(nickname, password, false);
353 }
354
355
356 const BString&
Nickname()357 Model::Nickname()
358 {
359 return fWebAppInterface.Nickname();
360 }
361
362
363 void
SetCredentials(const BString & nickname,const BString & passwordClear,bool storePassword)364 Model::SetCredentials(const BString& nickname, const BString& passwordClear,
365 bool storePassword)
366 {
367 BString existingNickname = Nickname();
368
369 if (storePassword) {
370 // no point continuing to store the password for the previous user.
371
372 if (!existingNickname.IsEmpty())
373 model_remove_key_for_user(existingNickname);
374
375 // adding a key that is already there does not seem to override the
376 // existing key so the old key needs to be removed first.
377
378 if (!nickname.IsEmpty())
379 model_remove_key_for_user(nickname);
380
381 if (!nickname.IsEmpty() && !passwordClear.IsEmpty()) {
382 BString keyIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
383 << nickname;
384 BPasswordKey key(passwordClear, B_KEY_PURPOSE_WEB, keyIdentifier);
385 BKeyStore keyStore;
386 keyStore.AddKeyring(kHaikuDepotKeyring);
387 keyStore.AddKey(kHaikuDepotKeyring, key);
388 }
389 }
390
391 BAutolock locker(&fLock);
392 fWebAppInterface.SetCredentials(UserCredentials(nickname, passwordClear));
393
394 if (nickname != existingNickname)
395 _NotifyAuthorizationChanged();
396 }
397
398
399 // #pragma mark - listener notification methods
400
401
402 void
_NotifyAuthorizationChanged()403 Model::_NotifyAuthorizationChanged()
404 {
405 std::vector<ModelListenerRef>::const_iterator it;
406 for (it = fListeners.begin(); it != fListeners.end(); it++) {
407 const ModelListenerRef& listener = *it;
408 if (listener.IsSet())
409 listener->AuthorizationChanged();
410 }
411 }
412
413
414 void
_NotifyCategoryListChanged()415 Model::_NotifyCategoryListChanged()
416 {
417 std::vector<ModelListenerRef>::const_iterator it;
418 for (it = fListeners.begin(); it != fListeners.end(); it++) {
419 const ModelListenerRef& listener = *it;
420 if (listener.IsSet())
421 listener->CategoryListChanged();
422 }
423 }
424
425
426 void
_MaybeLogJsonRpcError(const BMessage & responsePayload,const char * sourceDescription) const427 Model::_MaybeLogJsonRpcError(const BMessage &responsePayload,
428 const char *sourceDescription) const
429 {
430 BMessage error;
431 BString errorMessage;
432 double errorCode;
433
434 if (responsePayload.FindMessage("error", &error) == B_OK
435 && error.FindString("message", &errorMessage) == B_OK
436 && error.FindDouble("code", &errorCode) == B_OK) {
437 HDERROR("[%s] --> error : [%s] (%f)", sourceDescription,
438 errorMessage.String(), errorCode);
439 } else
440 HDERROR("[%s] --> an undefined error has occurred", sourceDescription);
441 }
442
443
444 // #pragma mark - Rating Stabilities
445
446
447 int32
CountRatingStabilities() const448 Model::CountRatingStabilities() const
449 {
450 return fRatingStabilities.size();
451 }
452
453
454 RatingStabilityRef
RatingStabilityByCode(BString & code) const455 Model::RatingStabilityByCode(BString& code) const
456 {
457 std::vector<RatingStabilityRef>::const_iterator it;
458 for (it = fRatingStabilities.begin(); it != fRatingStabilities.end();
459 it++) {
460 RatingStabilityRef aRatingStability = *it;
461 if (aRatingStability->Code() == code)
462 return aRatingStability;
463 }
464 return RatingStabilityRef();
465 }
466
467
468 RatingStabilityRef
RatingStabilityAtIndex(int32 index) const469 Model::RatingStabilityAtIndex(int32 index) const
470 {
471 return fRatingStabilities[index];
472 }
473
474
475 void
AddRatingStabilities(std::vector<RatingStabilityRef> & values)476 Model::AddRatingStabilities(std::vector<RatingStabilityRef>& values)
477 {
478 std::vector<RatingStabilityRef>::const_iterator it;
479 for (it = values.begin(); it != values.end(); it++)
480 _AddRatingStability(*it);
481 }
482
483
484 void
_AddRatingStability(const RatingStabilityRef & value)485 Model::_AddRatingStability(const RatingStabilityRef& value)
486 {
487 std::vector<RatingStabilityRef>::const_iterator itInsertionPtConst
488 = std::lower_bound(
489 fRatingStabilities.begin(),
490 fRatingStabilities.end(),
491 value,
492 &IsRatingStabilityBefore);
493 std::vector<RatingStabilityRef>::iterator itInsertionPt =
494 fRatingStabilities.begin()
495 + (itInsertionPtConst - fRatingStabilities.begin());
496
497 if (itInsertionPt != fRatingStabilities.end()
498 && (*itInsertionPt)->Code() == value->Code()) {
499 itInsertionPt = fRatingStabilities.erase(itInsertionPt);
500 // replace the one with the same code.
501 }
502
503 fRatingStabilities.insert(itInsertionPt, value);
504 }
505
506
507 // #pragma mark - Categories
508
509
510 int32
CountCategories() const511 Model::CountCategories() const
512 {
513 return fCategories.size();
514 }
515
516
517 CategoryRef
CategoryByCode(BString & code) const518 Model::CategoryByCode(BString& code) const
519 {
520 std::vector<CategoryRef>::const_iterator it;
521 for (it = fCategories.begin(); it != fCategories.end(); it++) {
522 CategoryRef aCategory = *it;
523 if (aCategory->Code() == code)
524 return aCategory;
525 }
526 return CategoryRef();
527 }
528
529
530 CategoryRef
CategoryAtIndex(int32 index) const531 Model::CategoryAtIndex(int32 index) const
532 {
533 return fCategories[index];
534 }
535
536
537 void
AddCategories(std::vector<CategoryRef> & values)538 Model::AddCategories(std::vector<CategoryRef>& values)
539 {
540 std::vector<CategoryRef>::iterator it;
541 for (it = values.begin(); it != values.end(); it++)
542 _AddCategory(*it);
543 _NotifyCategoryListChanged();
544 }
545
546 /*! This will insert the category in order.
547 */
548
549 void
_AddCategory(const CategoryRef & category)550 Model::_AddCategory(const CategoryRef& category)
551 {
552 std::vector<CategoryRef>::const_iterator itInsertionPtConst
553 = std::lower_bound(
554 fCategories.begin(),
555 fCategories.end(),
556 category,
557 &IsPackageCategoryBefore);
558 std::vector<CategoryRef>::iterator itInsertionPt =
559 fCategories.begin() + (itInsertionPtConst - fCategories.begin());
560
561 if (itInsertionPt != fCategories.end()
562 && (*itInsertionPt)->Code() == category->Code()) {
563 itInsertionPt = fCategories.erase(itInsertionPt);
564 // replace the one with the same code.
565 }
566
567 fCategories.insert(itInsertionPt, category);
568 }
569
570
571 void
ScreenshotCached(const ScreenshotCoordinate & coord)572 Model::ScreenshotCached(const ScreenshotCoordinate& coord)
573 {
574 std::vector<ModelListenerRef>::const_iterator it;
575 for (it = fListeners.begin(); it != fListeners.end(); it++) {
576 const ModelListenerRef& listener = *it;
577 if (listener.IsSet())
578 listener->ScreenshotCached(coord);
579 }
580 }
581