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