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