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