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