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