xref: /haiku/src/apps/haikudepot/packagemodel/PackageInfo.cpp (revision 82bfaa954dcfd90582fb2c1a0e918971eea57091)
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