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