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