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& rightsList = info.CopyrightList(); 90 if (rightsList.CountStrings() > 0) 91 publisherName = rightsList.Last(); 92 if (!publisherName.IsEmpty()) 93 publisherName.Prepend("© "); 94 95 fPublisher = PublisherInfo(publisherName, publisherURL); 96 } 97 98 99 PackageInfo::PackageInfo(const BString& name, const BPackageVersion& version, 100 const PublisherInfo& publisher, const BString& shortDescription, 101 const BString& fullDescription, 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 fDidPopulateUserRatings(false), 114 fCachedRatingSummary(), 115 fProminence(0), 116 fScreenshotInfos(), 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 fDidPopulateUserRatings(other.fDidPopulateUserRatings), 147 fCachedRatingSummary(other.fCachedRatingSummary), 148 fProminence(other.fProminence), 149 fScreenshotInfos(other.fScreenshotInfos), 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 fDidPopulateUserRatings = fDidPopulateUserRatings; 182 fCachedRatingSummary = other.fCachedRatingSummary; 183 fProminence = other.fProminence; 184 fScreenshotInfos = other.fScreenshotInfos; 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 && fState == other.fState 219 && fFlags == other.fFlags 220 && fDownloadProgress == other.fDownloadProgress 221 && fSystemDependency == other.fSystemDependency 222 && fArchitecture == other.fArchitecture 223 && fLocalFilePath == other.fLocalFilePath 224 && fFileName == other.fFileName 225 && fSize == other.fSize 226 && fVersionCreateTimestamp == other.fVersionCreateTimestamp; 227 } 228 229 230 bool 231 PackageInfo::operator!=(const PackageInfo& other) const 232 { 233 return !(*this == other); 234 } 235 236 237 void 238 PackageInfo::SetTitle(const BString& title) 239 { 240 if (fTitle != title) { 241 fTitle = title; 242 _NotifyListeners(PKG_CHANGED_TITLE); 243 } 244 } 245 246 247 const BString& 248 PackageInfo::Title() const 249 { 250 return fTitle.Length() > 0 ? fTitle : fName; 251 } 252 253 254 void 255 PackageInfo::SetShortDescription(const BString& description) 256 { 257 if (fShortDescription != description) { 258 fShortDescription = description; 259 _NotifyListeners(PKG_CHANGED_SUMMARY); 260 } 261 } 262 263 264 void 265 PackageInfo::SetFullDescription(const BString& description) 266 { 267 if (fFullDescription != description) { 268 fFullDescription = description; 269 _NotifyListeners(PKG_CHANGED_DESCRIPTION); 270 } 271 } 272 273 274 void 275 PackageInfo::SetHasChangelog(bool value) 276 { 277 fHasChangelog = value; 278 } 279 280 281 void 282 PackageInfo::SetChangelog(const BString& changelog) 283 { 284 if (fChangelog != changelog) { 285 fChangelog = changelog; 286 _NotifyListeners(PKG_CHANGED_CHANGELOG); 287 } 288 } 289 290 291 bool 292 PackageInfo::IsSystemPackage() const 293 { 294 return (fFlags & BPackageKit::B_PACKAGE_FLAG_SYSTEM_PACKAGE) != 0; 295 } 296 297 298 int32 299 PackageInfo::CountCategories() const 300 { 301 return fCategories.size(); 302 } 303 304 305 CategoryRef 306 PackageInfo::CategoryAtIndex(int32 index) const 307 { 308 return fCategories[index]; 309 } 310 311 312 void 313 PackageInfo::ClearCategories() 314 { 315 if (!fCategories.empty()) { 316 fCategories.clear(); 317 _NotifyListeners(PKG_CHANGED_CATEGORIES); 318 } 319 } 320 321 322 bool 323 PackageInfo::AddCategory(const CategoryRef& category) 324 { 325 std::vector<CategoryRef>::const_iterator itInsertionPt 326 = std::lower_bound( 327 fCategories.begin(), 328 fCategories.end(), 329 category, 330 &IsPackageCategoryBefore); 331 332 if (itInsertionPt == fCategories.end()) { 333 fCategories.push_back(category); 334 _NotifyListeners(PKG_CHANGED_CATEGORIES); 335 return true; 336 } 337 return false; 338 } 339 340 341 void 342 PackageInfo::SetSystemDependency(bool isDependency) 343 { 344 fSystemDependency = isDependency; 345 } 346 347 348 void 349 PackageInfo::SetState(PackageState state) 350 { 351 if (fState != state) { 352 fState = state; 353 if (fState != DOWNLOADING) 354 fDownloadProgress = 0.0; 355 _NotifyListeners(PKG_CHANGED_STATE); 356 } 357 } 358 359 360 void 361 PackageInfo::AddInstallationLocation(int32 location) 362 { 363 fInstallationLocations.insert(location); 364 SetState(ACTIVATED); 365 // TODO: determine how to differentiate between installed and active. 366 } 367 368 369 void 370 PackageInfo::ClearInstallationLocations() 371 { 372 fInstallationLocations.clear(); 373 } 374 375 376 void 377 PackageInfo::SetDownloadProgress(float progress) 378 { 379 fState = DOWNLOADING; 380 fDownloadProgress = progress; 381 _NotifyListeners(PKG_CHANGED_STATE); 382 } 383 384 385 void 386 PackageInfo::SetLocalFilePath(const char* path) 387 { 388 fLocalFilePath = path; 389 } 390 391 392 bool 393 PackageInfo::IsLocalFile() const 394 { 395 return !fLocalFilePath.IsEmpty() && fInstallationLocations.empty(); 396 } 397 398 399 void 400 PackageInfo::ClearUserRatings() 401 { 402 if (!fUserRatings.empty()) { 403 fUserRatings.clear(); 404 _NotifyListeners(PKG_CHANGED_RATINGS); 405 } 406 } 407 408 409 bool 410 PackageInfo::DidPopulateUserRatings() const 411 { 412 return fDidPopulateUserRatings; 413 } 414 415 416 void 417 PackageInfo::SetDidPopulateUserRatings() 418 { 419 fDidPopulateUserRatings = true; 420 } 421 422 423 int32 424 PackageInfo::CountUserRatings() const 425 { 426 return fUserRatings.size(); 427 } 428 429 430 UserRatingRef 431 PackageInfo::UserRatingAtIndex(int32 index) const 432 { 433 return fUserRatings[index]; 434 } 435 436 437 void 438 PackageInfo::AddUserRating(const UserRatingRef& rating) 439 { 440 fUserRatings.push_back(rating); 441 _NotifyListeners(PKG_CHANGED_RATINGS); 442 } 443 444 445 void 446 PackageInfo::SetRatingSummary(const RatingSummary& summary) 447 { 448 if (fCachedRatingSummary == summary) 449 return; 450 451 fCachedRatingSummary = summary; 452 453 _NotifyListeners(PKG_CHANGED_RATINGS); 454 } 455 456 457 RatingSummary 458 PackageInfo::CalculateRatingSummary() const 459 { 460 if (fUserRatings.empty()) 461 return fCachedRatingSummary; 462 463 RatingSummary summary; 464 summary.ratingCount = fUserRatings.size(); 465 summary.averageRating = 0.0f; 466 int starRatingCount = sizeof(summary.ratingCountByStar) / sizeof(int); 467 for (int i = 0; i < starRatingCount; i++) 468 summary.ratingCountByStar[i] = 0; 469 470 if (summary.ratingCount <= 0) 471 return summary; 472 473 float ratingSum = 0.0f; 474 475 int ratingsSpecified = summary.ratingCount; 476 for (int i = 0; i < summary.ratingCount; i++) { 477 float rating = fUserRatings[i]->Rating(); 478 479 if (rating < 0.0f) 480 rating = -1.0f; 481 else if (rating > 5.0f) 482 rating = 5.0f; 483 484 if (rating >= 0.0f) 485 ratingSum += rating; 486 487 if (rating <= 0.0f) 488 ratingsSpecified--; // No rating specified by user 489 else if (rating <= 1.0f) 490 summary.ratingCountByStar[0]++; 491 else if (rating <= 2.0f) 492 summary.ratingCountByStar[1]++; 493 else if (rating <= 3.0f) 494 summary.ratingCountByStar[2]++; 495 else if (rating <= 4.0f) 496 summary.ratingCountByStar[3]++; 497 else if (rating <= 5.0f) 498 summary.ratingCountByStar[4]++; 499 } 500 501 if (ratingsSpecified > 1) 502 ratingSum /= ratingsSpecified; 503 504 summary.averageRating = ratingSum; 505 summary.ratingCount = ratingsSpecified; 506 507 return summary; 508 } 509 510 511 void 512 PackageInfo::SetProminence(int64 prominence) 513 { 514 if (fProminence != prominence) { 515 fProminence = prominence; 516 _NotifyListeners(PKG_CHANGED_PROMINENCE); 517 } 518 } 519 520 521 bool 522 PackageInfo::IsProminent() const 523 { 524 return HasProminence() && Prominence() <= PROMINANCE_ORDERING_PROMINENT_MAX; 525 } 526 527 528 void 529 PackageInfo::ClearScreenshotInfos() 530 { 531 if (!fScreenshotInfos.empty()) { 532 fScreenshotInfos.clear(); 533 _NotifyListeners(PKG_CHANGED_SCREENSHOTS); 534 } 535 } 536 537 538 int32 539 PackageInfo::CountScreenshotInfos() const 540 { 541 return fScreenshotInfos.size(); 542 } 543 544 545 ScreenshotInfoRef 546 PackageInfo::ScreenshotInfoAtIndex(int32 index) const 547 { 548 return fScreenshotInfos[index]; 549 } 550 551 552 void 553 PackageInfo::AddScreenshotInfo(const ScreenshotInfoRef& info) 554 { 555 fScreenshotInfos.push_back(info); 556 _NotifyListeners(PKG_CHANGED_SCREENSHOTS); 557 } 558 559 560 void 561 PackageInfo::SetSize(int64 size) 562 { 563 if (fSize != size) { 564 fSize = size; 565 _NotifyListeners(PKG_CHANGED_SIZE); 566 } 567 } 568 569 570 void 571 PackageInfo::SetViewed() 572 { 573 fViewed = true; 574 } 575 576 577 void 578 PackageInfo::SetVersionCreateTimestamp(uint64 value) 579 { 580 if (fVersionCreateTimestamp != value) { 581 fVersionCreateTimestamp = value; 582 _NotifyListeners(PKG_CHANGED_VERSION_CREATE_TIMESTAMP); 583 } 584 } 585 586 587 void 588 PackageInfo::SetDepotName(const BString& depotName) 589 { 590 if (fDepotName != depotName) { 591 fDepotName = depotName; 592 _NotifyListeners(PKG_CHANGED_DEPOT); 593 } 594 } 595 596 597 bool 598 PackageInfo::AddListener(const PackageInfoListenerRef& listener) 599 { 600 fListeners.push_back(listener); 601 return true; 602 } 603 604 605 void 606 PackageInfo::RemoveListener(const PackageInfoListenerRef& listener) 607 { 608 fListeners.erase(std::remove(fListeners.begin(), fListeners.end(), 609 listener), fListeners.end()); 610 } 611 612 613 void 614 PackageInfo::NotifyChangedIcon() 615 { 616 _NotifyListeners(PKG_CHANGED_ICON); 617 } 618 619 620 void 621 PackageInfo::StartCollatingChanges() 622 { 623 fIsCollatingChanges = true; 624 fCollatedChanges = 0; 625 } 626 627 628 void 629 PackageInfo::EndCollatingChanges() 630 { 631 if (fIsCollatingChanges && fCollatedChanges != 0) 632 _NotifyListenersImmediate(fCollatedChanges); 633 fIsCollatingChanges = false; 634 fCollatedChanges = 0; 635 } 636 637 638 void 639 PackageInfo::_NotifyListeners(uint32 changes) 640 { 641 if (fIsCollatingChanges) 642 fCollatedChanges |= changes; 643 else 644 _NotifyListenersImmediate(changes); 645 } 646 647 648 void 649 PackageInfo::_NotifyListenersImmediate(uint32 changes) 650 { 651 if (fListeners.empty()) 652 return; 653 654 // Clone list to avoid listeners detaching themselves in notifications 655 // to screw up the list while iterating it. 656 std::vector<PackageInfoListenerRef> listeners(fListeners); 657 PackageInfoEvent event(PackageInfoRef(this), changes); 658 659 std::vector<PackageInfoListenerRef>::iterator it; 660 for (it = listeners.begin(); it != listeners.end(); it++) { 661 const PackageInfoListenerRef listener = *it; 662 if (listener.IsSet()) 663 listener->PackageChanged(event); 664 } 665 } 666 667 668 const char* package_state_to_string(PackageState state) 669 { 670 switch (state) { 671 case NONE: 672 return "NONE"; 673 case INSTALLED: 674 return "INSTALLED"; 675 case DOWNLOADING: 676 return "DOWNLOADING"; 677 case ACTIVATED: 678 return "ACTIVATED"; 679 case UNINSTALLED: 680 return "UNINSTALLED"; 681 case PENDING: 682 return "PENDING"; 683 default: 684 debugger("unknown package state"); 685 return "???"; 686 } 687 } 688