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