1 /* 2 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2013, Rene Gollent <rene@gollent.com>. 4 * Copyright 2016-2024, Andrew Lindesay <apl@lindesay.co.nz>. 5 * All rights reserved. Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "PackageInfo.h" 10 11 #include <algorithm> 12 13 #include <package/PackageDefs.h> 14 #include <package/PackageFlags.h> 15 16 #include "HaikuDepotConstants.h" 17 #include "Logger.h" 18 19 20 // #pragma mark - PackageInfo 21 22 23 PackageInfo::PackageInfo() 24 : 25 fName(), 26 fTitle(), 27 fVersion(), 28 fPublisher(), 29 fShortDescription(), 30 fFullDescription(), 31 fHasChangelog(false), 32 fChangelog(), 33 fUserRatings(), 34 fDidPopulateUserRatings(false), 35 fCachedRatingSummary(), 36 fProminence(0), 37 fScreenshotInfos(), 38 fState(NONE), 39 fDownloadProgress(0.0), 40 fFlags(0), 41 fSystemDependency(false), 42 fArchitecture(), 43 fLocalFilePath(), 44 fFileName(), 45 fSize(0), 46 fDepotName(""), 47 fViewed(false), 48 fIsCollatingChanges(false), 49 fCollatedChanges(0), 50 fVersionCreateTimestamp(0) 51 { 52 } 53 54 55 PackageInfo::PackageInfo(const BPackageInfo& info) 56 : 57 fName(info.Name()), 58 fTitle(), 59 fVersion(info.Version()), 60 fPublisher(), 61 fShortDescription(info.Summary()), 62 fFullDescription(info.Description()), 63 fHasChangelog(false), 64 fChangelog(), 65 fUserRatings(), 66 fDidPopulateUserRatings(false), 67 fCachedRatingSummary(), 68 fProminence(0), 69 fScreenshotInfos(), 70 fState(NONE), 71 fDownloadProgress(0.0), 72 fFlags(info.Flags()), 73 fSystemDependency(false), 74 fArchitecture(info.ArchitectureName()), 75 fLocalFilePath(), 76 fFileName(info.FileName()), 77 fSize(0), // TODO: Retrieve local file size 78 fDepotName(""), 79 fViewed(false), 80 fIsCollatingChanges(false), 81 fCollatedChanges(0), 82 fVersionCreateTimestamp(0) 83 { 84 BString publisherURL; 85 if (info.URLList().CountStrings() > 0) 86 publisherURL = info.URLList().StringAt(0); 87 88 BString publisherName = info.Vendor(); 89 const BStringList& copyrightList = info.CopyrightList(); 90 if (!copyrightList.IsEmpty()) { 91 publisherName = ""; 92 93 for (int32 i = 0; i < copyrightList.CountStrings(); i++) { 94 if (!publisherName.IsEmpty()) 95 publisherName << ", "; 96 publisherName << copyrightList.StringAt(i); 97 } 98 } 99 if (!publisherName.IsEmpty()) 100 publisherName.Prepend("© "); 101 102 fPublisher = PublisherInfo(publisherName, publisherURL); 103 } 104 105 106 PackageInfo::PackageInfo(const BString& name, const BPackageVersion& version, 107 const PublisherInfo& publisher, const BString& shortDescription, 108 const BString& fullDescription, int32 flags, const char* architecture) 109 : 110 fName(name), 111 fTitle(), 112 fVersion(version), 113 fPublisher(publisher), 114 fShortDescription(shortDescription), 115 fFullDescription(fullDescription), 116 fHasChangelog(false), 117 fChangelog(), 118 fCategories(), 119 fUserRatings(), 120 fDidPopulateUserRatings(false), 121 fCachedRatingSummary(), 122 fProminence(0), 123 fScreenshotInfos(), 124 fState(NONE), 125 fDownloadProgress(0.0), 126 fFlags(flags), 127 fSystemDependency(false), 128 fArchitecture(architecture), 129 fLocalFilePath(), 130 fFileName(), 131 fSize(0), 132 fDepotName(""), 133 fViewed(false), 134 fIsCollatingChanges(false), 135 fCollatedChanges(0), 136 fVersionCreateTimestamp(0) 137 { 138 } 139 140 141 PackageInfo::PackageInfo(const PackageInfo& other) 142 : 143 fName(other.fName), 144 fTitle(other.fTitle), 145 fVersion(other.fVersion), 146 fPublisher(other.fPublisher), 147 fShortDescription(other.fShortDescription), 148 fFullDescription(other.fFullDescription), 149 fHasChangelog(other.fHasChangelog), 150 fChangelog(other.fChangelog), 151 fCategories(other.fCategories), 152 fUserRatings(other.fUserRatings), 153 fDidPopulateUserRatings(other.fDidPopulateUserRatings), 154 fCachedRatingSummary(other.fCachedRatingSummary), 155 fProminence(other.fProminence), 156 fScreenshotInfos(other.fScreenshotInfos), 157 fState(other.fState), 158 fInstallationLocations(other.fInstallationLocations), 159 fDownloadProgress(other.fDownloadProgress), 160 fFlags(other.fFlags), 161 fSystemDependency(other.fSystemDependency), 162 fArchitecture(other.fArchitecture), 163 fLocalFilePath(other.fLocalFilePath), 164 fFileName(other.fFileName), 165 fSize(other.fSize), 166 fDepotName(other.fDepotName), 167 fViewed(other.fViewed), 168 fIsCollatingChanges(false), 169 fCollatedChanges(0), 170 fVersionCreateTimestamp(other.fVersionCreateTimestamp) 171 { 172 } 173 174 175 PackageInfo& 176 PackageInfo::operator=(const PackageInfo& other) 177 { 178 fName = other.fName; 179 fTitle = other.fTitle; 180 fVersion = other.fVersion; 181 fPublisher = other.fPublisher; 182 fShortDescription = other.fShortDescription; 183 fFullDescription = other.fFullDescription; 184 fHasChangelog = other.fHasChangelog; 185 fChangelog = other.fChangelog; 186 fCategories = other.fCategories; 187 fUserRatings = other.fUserRatings; 188 fDidPopulateUserRatings = fDidPopulateUserRatings; 189 fCachedRatingSummary = other.fCachedRatingSummary; 190 fProminence = other.fProminence; 191 fScreenshotInfos = other.fScreenshotInfos; 192 fState = other.fState; 193 fInstallationLocations = other.fInstallationLocations; 194 fDownloadProgress = other.fDownloadProgress; 195 fFlags = other.fFlags; 196 fSystemDependency = other.fSystemDependency; 197 fArchitecture = other.fArchitecture; 198 fLocalFilePath = other.fLocalFilePath; 199 fFileName = other.fFileName; 200 fSize = other.fSize; 201 fDepotName = other.fDepotName; 202 fViewed = other.fViewed; 203 fVersionCreateTimestamp = other.fVersionCreateTimestamp; 204 205 return *this; 206 } 207 208 209 bool 210 PackageInfo::operator==(const PackageInfo& other) const 211 { 212 return fName == other.fName 213 && fTitle == other.fTitle 214 && fVersion == other.fVersion 215 && fPublisher == other.fPublisher 216 && fShortDescription == other.fShortDescription 217 && fFullDescription == other.fFullDescription 218 && fHasChangelog == other.fHasChangelog 219 && fChangelog == other.fChangelog 220 && fCategories == other.fCategories 221 && fUserRatings == other.fUserRatings 222 && fCachedRatingSummary == other.fCachedRatingSummary 223 && fProminence == other.fProminence 224 && fScreenshotInfos == other.fScreenshotInfos 225 && fState == other.fState 226 && fFlags == other.fFlags 227 && fDownloadProgress == other.fDownloadProgress 228 && fSystemDependency == other.fSystemDependency 229 && fArchitecture == other.fArchitecture 230 && fLocalFilePath == other.fLocalFilePath 231 && fFileName == other.fFileName 232 && fSize == other.fSize 233 && fVersionCreateTimestamp == other.fVersionCreateTimestamp; 234 } 235 236 237 bool 238 PackageInfo::operator!=(const PackageInfo& other) const 239 { 240 return !(*this == other); 241 } 242 243 244 void 245 PackageInfo::SetTitle(const BString& title) 246 { 247 if (fTitle != title) { 248 fTitle = title; 249 _NotifyListeners(PKG_CHANGED_TITLE); 250 } 251 } 252 253 254 const BString& 255 PackageInfo::Title() const 256 { 257 return fTitle.Length() > 0 ? fTitle : fName; 258 } 259 260 261 void 262 PackageInfo::SetShortDescription(const BString& description) 263 { 264 if (fShortDescription != description) { 265 fShortDescription = description; 266 _NotifyListeners(PKG_CHANGED_SUMMARY); 267 } 268 } 269 270 271 void 272 PackageInfo::SetFullDescription(const BString& description) 273 { 274 if (fFullDescription != description) { 275 fFullDescription = description; 276 _NotifyListeners(PKG_CHANGED_DESCRIPTION); 277 } 278 } 279 280 281 void 282 PackageInfo::SetHasChangelog(bool value) 283 { 284 fHasChangelog = value; 285 } 286 287 288 void 289 PackageInfo::SetChangelog(const BString& changelog) 290 { 291 if (fChangelog != changelog) { 292 fChangelog = changelog; 293 _NotifyListeners(PKG_CHANGED_CHANGELOG); 294 } 295 } 296 297 298 bool 299 PackageInfo::IsSystemPackage() const 300 { 301 return (fFlags & BPackageKit::B_PACKAGE_FLAG_SYSTEM_PACKAGE) != 0; 302 } 303 304 305 int32 306 PackageInfo::CountCategories() const 307 { 308 return fCategories.size(); 309 } 310 311 312 CategoryRef 313 PackageInfo::CategoryAtIndex(int32 index) const 314 { 315 return fCategories[index]; 316 } 317 318 319 void 320 PackageInfo::ClearCategories() 321 { 322 if (!fCategories.empty()) { 323 fCategories.clear(); 324 _NotifyListeners(PKG_CHANGED_CATEGORIES); 325 } 326 } 327 328 329 bool 330 PackageInfo::AddCategory(const CategoryRef& category) 331 { 332 std::vector<CategoryRef>::const_iterator itInsertionPt 333 = std::lower_bound( 334 fCategories.begin(), 335 fCategories.end(), 336 category, 337 &IsPackageCategoryBefore); 338 339 if (itInsertionPt == fCategories.end()) { 340 fCategories.push_back(category); 341 _NotifyListeners(PKG_CHANGED_CATEGORIES); 342 return true; 343 } 344 return false; 345 } 346 347 348 void 349 PackageInfo::SetSystemDependency(bool isDependency) 350 { 351 fSystemDependency = isDependency; 352 } 353 354 355 void 356 PackageInfo::SetState(PackageState state) 357 { 358 if (fState != state) { 359 fState = state; 360 if (fState != DOWNLOADING) 361 fDownloadProgress = 0.0; 362 _NotifyListeners(PKG_CHANGED_STATE); 363 } 364 } 365 366 367 void 368 PackageInfo::AddInstallationLocation(int32 location) 369 { 370 fInstallationLocations.insert(location); 371 SetState(ACTIVATED); 372 // TODO: determine how to differentiate between installed and active. 373 } 374 375 376 void 377 PackageInfo::ClearInstallationLocations() 378 { 379 fInstallationLocations.clear(); 380 } 381 382 383 void 384 PackageInfo::SetDownloadProgress(float progress) 385 { 386 fState = DOWNLOADING; 387 fDownloadProgress = progress; 388 _NotifyListeners(PKG_CHANGED_STATE); 389 } 390 391 392 void 393 PackageInfo::SetLocalFilePath(const char* path) 394 { 395 fLocalFilePath = path; 396 } 397 398 399 bool 400 PackageInfo::IsLocalFile() const 401 { 402 return !fLocalFilePath.IsEmpty() && fInstallationLocations.empty(); 403 } 404 405 406 void 407 PackageInfo::ClearUserRatings() 408 { 409 if (!fUserRatings.empty()) { 410 fUserRatings.clear(); 411 _NotifyListeners(PKG_CHANGED_RATINGS); 412 } 413 } 414 415 416 bool 417 PackageInfo::DidPopulateUserRatings() const 418 { 419 return fDidPopulateUserRatings; 420 } 421 422 423 void 424 PackageInfo::SetDidPopulateUserRatings() 425 { 426 fDidPopulateUserRatings = true; 427 } 428 429 430 int32 431 PackageInfo::CountUserRatings() const 432 { 433 return fUserRatings.size(); 434 } 435 436 437 UserRatingRef 438 PackageInfo::UserRatingAtIndex(int32 index) const 439 { 440 return fUserRatings[index]; 441 } 442 443 444 void 445 PackageInfo::AddUserRating(const UserRatingRef& rating) 446 { 447 fUserRatings.push_back(rating); 448 _NotifyListeners(PKG_CHANGED_RATINGS); 449 } 450 451 452 void 453 PackageInfo::SetRatingSummary(const RatingSummary& summary) 454 { 455 if (fCachedRatingSummary == summary) 456 return; 457 458 fCachedRatingSummary = summary; 459 460 _NotifyListeners(PKG_CHANGED_RATINGS); 461 } 462 463 464 RatingSummary 465 PackageInfo::CalculateRatingSummary() const 466 { 467 if (fUserRatings.empty()) 468 return fCachedRatingSummary; 469 470 RatingSummary summary; 471 summary.ratingCount = fUserRatings.size(); 472 summary.averageRating = 0.0f; 473 int starRatingCount = sizeof(summary.ratingCountByStar) / sizeof(int); 474 for (int i = 0; i < starRatingCount; i++) 475 summary.ratingCountByStar[i] = 0; 476 477 if (summary.ratingCount <= 0) 478 return summary; 479 480 float ratingSum = 0.0f; 481 482 int ratingsSpecified = summary.ratingCount; 483 for (int i = 0; i < summary.ratingCount; i++) { 484 float rating = fUserRatings[i]->Rating(); 485 486 if (rating < 0.0f) 487 rating = -1.0f; 488 else if (rating > 5.0f) 489 rating = 5.0f; 490 491 if (rating >= 0.0f) 492 ratingSum += rating; 493 494 if (rating <= 0.0f) 495 ratingsSpecified--; // No rating specified by user 496 else if (rating <= 1.0f) 497 summary.ratingCountByStar[0]++; 498 else if (rating <= 2.0f) 499 summary.ratingCountByStar[1]++; 500 else if (rating <= 3.0f) 501 summary.ratingCountByStar[2]++; 502 else if (rating <= 4.0f) 503 summary.ratingCountByStar[3]++; 504 else if (rating <= 5.0f) 505 summary.ratingCountByStar[4]++; 506 } 507 508 if (ratingsSpecified > 1) 509 ratingSum /= ratingsSpecified; 510 511 summary.averageRating = ratingSum; 512 summary.ratingCount = ratingsSpecified; 513 514 return summary; 515 } 516 517 518 void 519 PackageInfo::SetProminence(int64 prominence) 520 { 521 if (fProminence != prominence) { 522 fProminence = prominence; 523 _NotifyListeners(PKG_CHANGED_PROMINENCE); 524 } 525 } 526 527 528 bool 529 PackageInfo::IsProminent() const 530 { 531 return HasProminence() && Prominence() <= PROMINANCE_ORDERING_PROMINENT_MAX; 532 } 533 534 535 void 536 PackageInfo::ClearScreenshotInfos() 537 { 538 if (!fScreenshotInfos.empty()) { 539 fScreenshotInfos.clear(); 540 _NotifyListeners(PKG_CHANGED_SCREENSHOTS); 541 } 542 } 543 544 545 int32 546 PackageInfo::CountScreenshotInfos() const 547 { 548 return fScreenshotInfos.size(); 549 } 550 551 552 ScreenshotInfoRef 553 PackageInfo::ScreenshotInfoAtIndex(int32 index) const 554 { 555 return fScreenshotInfos[index]; 556 } 557 558 559 void 560 PackageInfo::AddScreenshotInfo(const ScreenshotInfoRef& info) 561 { 562 fScreenshotInfos.push_back(info); 563 _NotifyListeners(PKG_CHANGED_SCREENSHOTS); 564 } 565 566 567 void 568 PackageInfo::SetSize(int64 size) 569 { 570 if (fSize != size) { 571 fSize = size; 572 _NotifyListeners(PKG_CHANGED_SIZE); 573 } 574 } 575 576 577 void 578 PackageInfo::SetViewed() 579 { 580 fViewed = true; 581 } 582 583 584 void 585 PackageInfo::SetVersionCreateTimestamp(uint64 value) 586 { 587 if (fVersionCreateTimestamp != value) { 588 fVersionCreateTimestamp = value; 589 _NotifyListeners(PKG_CHANGED_VERSION_CREATE_TIMESTAMP); 590 } 591 } 592 593 594 void 595 PackageInfo::SetDepotName(const BString& depotName) 596 { 597 if (fDepotName != depotName) { 598 fDepotName = depotName; 599 _NotifyListeners(PKG_CHANGED_DEPOT); 600 } 601 } 602 603 604 bool 605 PackageInfo::AddListener(const PackageInfoListenerRef& listener) 606 { 607 fListeners.push_back(listener); 608 return true; 609 } 610 611 612 void 613 PackageInfo::RemoveListener(const PackageInfoListenerRef& listener) 614 { 615 fListeners.erase(std::remove(fListeners.begin(), fListeners.end(), 616 listener), fListeners.end()); 617 } 618 619 620 void 621 PackageInfo::NotifyChangedIcon() 622 { 623 _NotifyListeners(PKG_CHANGED_ICON); 624 } 625 626 627 void 628 PackageInfo::StartCollatingChanges() 629 { 630 fIsCollatingChanges = true; 631 fCollatedChanges = 0; 632 } 633 634 635 void 636 PackageInfo::EndCollatingChanges() 637 { 638 if (fIsCollatingChanges && fCollatedChanges != 0) 639 _NotifyListenersImmediate(fCollatedChanges); 640 fIsCollatingChanges = false; 641 fCollatedChanges = 0; 642 } 643 644 645 void 646 PackageInfo::_NotifyListeners(uint32 changes) 647 { 648 if (fIsCollatingChanges) 649 fCollatedChanges |= changes; 650 else 651 _NotifyListenersImmediate(changes); 652 } 653 654 655 void 656 PackageInfo::_NotifyListenersImmediate(uint32 changes) 657 { 658 if (fListeners.empty()) 659 return; 660 661 // Clone list to avoid listeners detaching themselves in notifications 662 // to screw up the list while iterating it. 663 std::vector<PackageInfoListenerRef> listeners(fListeners); 664 PackageInfoEvent event(PackageInfoRef(this), changes); 665 666 std::vector<PackageInfoListenerRef>::iterator it; 667 for (it = listeners.begin(); it != listeners.end(); it++) { 668 const PackageInfoListenerRef listener = *it; 669 if (listener.IsSet()) 670 listener->PackageChanged(event); 671 } 672 } 673 674 675 const char* package_state_to_string(PackageState state) 676 { 677 switch (state) { 678 case NONE: 679 return "NONE"; 680 case INSTALLED: 681 return "INSTALLED"; 682 case DOWNLOADING: 683 return "DOWNLOADING"; 684 case ACTIVATED: 685 return "ACTIVATED"; 686 case UNINSTALLED: 687 return "UNINSTALLED"; 688 case PENDING: 689 return "PENDING"; 690 default: 691 debugger("unknown package state"); 692 return "???"; 693 } 694 } 695