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
22PackageInfo::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
53PackageInfo::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
96PackageInfo::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
131PackageInfo::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
164PackageInfo&
165PackageInfo::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
197bool
198PackageInfo::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
225bool
226PackageInfo::operator!=(const PackageInfo& other) const
227{
228	return !(*this == other);
229}
230
231
232void
233PackageInfo::SetTitle(const BString& title)
234{
235	if (fTitle != title) {
236		fTitle = title;
237		_NotifyListeners(PKG_CHANGED_TITLE);
238	}
239}
240
241
242const BString&
243PackageInfo::Title() const
244{
245	return fTitle.Length() > 0 ? fTitle : fName;
246}
247
248
249void
250PackageInfo::SetShortDescription(const BString& description)
251{
252	if (fShortDescription != description) {
253		fShortDescription = description;
254		_NotifyListeners(PKG_CHANGED_SUMMARY);
255	}
256}
257
258
259void
260PackageInfo::SetFullDescription(const BString& description)
261{
262	if (fFullDescription != description) {
263		fFullDescription = description;
264		_NotifyListeners(PKG_CHANGED_DESCRIPTION);
265	}
266}
267
268
269void
270PackageInfo::SetHasChangelog(bool value)
271{
272	fHasChangelog = value;
273}
274
275
276void
277PackageInfo::SetChangelog(const BString& changelog)
278{
279	if (fChangelog != changelog) {
280		fChangelog = changelog;
281		_NotifyListeners(PKG_CHANGED_CHANGELOG);
282	}
283}
284
285
286bool
287PackageInfo::IsSystemPackage() const
288{
289	return (fFlags & BPackageKit::B_PACKAGE_FLAG_SYSTEM_PACKAGE) != 0;
290}
291
292
293int32
294PackageInfo::CountCategories() const
295{
296	return fCategories.size();
297}
298
299
300CategoryRef
301PackageInfo::CategoryAtIndex(int32 index) const
302{
303	return fCategories[index];
304}
305
306
307void
308PackageInfo::ClearCategories()
309{
310	if (!fCategories.empty()) {
311		fCategories.clear();
312		_NotifyListeners(PKG_CHANGED_CATEGORIES);
313	}
314}
315
316
317bool
318PackageInfo::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
336void
337PackageInfo::SetSystemDependency(bool isDependency)
338{
339	fSystemDependency = isDependency;
340}
341
342
343void
344PackageInfo::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
355void
356PackageInfo::AddInstallationLocation(int32 location)
357{
358	fInstallationLocations.insert(location);
359	SetState(ACTIVATED);
360		// TODO: determine how to differentiate between installed and active.
361}
362
363
364void
365PackageInfo::ClearInstallationLocations()
366{
367	fInstallationLocations.clear();
368}
369
370
371void
372PackageInfo::SetDownloadProgress(float progress)
373{
374	fState = DOWNLOADING;
375	fDownloadProgress = progress;
376	_NotifyListeners(PKG_CHANGED_STATE);
377}
378
379
380void
381PackageInfo::SetLocalFilePath(const char* path)
382{
383	fLocalFilePath = path;
384}
385
386
387bool
388PackageInfo::IsLocalFile() const
389{
390	return !fLocalFilePath.IsEmpty() && fInstallationLocations.empty();
391}
392
393
394void
395PackageInfo::ClearUserRatings()
396{
397	if (!fUserRatings.empty()) {
398		fUserRatings.clear();
399		_NotifyListeners(PKG_CHANGED_RATINGS);
400	}
401}
402
403
404int32
405PackageInfo::CountUserRatings() const
406{
407	return fUserRatings.size();
408}
409
410
411UserRatingRef
412PackageInfo::UserRatingAtIndex(int32 index) const
413{
414	return fUserRatings[index];
415}
416
417
418void
419PackageInfo::AddUserRating(const UserRatingRef& rating)
420{
421	fUserRatings.push_back(rating);
422	_NotifyListeners(PKG_CHANGED_RATINGS);
423}
424
425
426void
427PackageInfo::SetRatingSummary(const RatingSummary& summary)
428{
429	if (fCachedRatingSummary == summary)
430		return;
431
432	fCachedRatingSummary = summary;
433
434	_NotifyListeners(PKG_CHANGED_RATINGS);
435}
436
437
438RatingSummary
439PackageInfo::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
492void
493PackageInfo::SetProminence(int64 prominence)
494{
495	if (fProminence != prominence) {
496		fProminence = prominence;
497		_NotifyListeners(PKG_CHANGED_PROMINENCE);
498	}
499}
500
501
502bool
503PackageInfo::IsProminent() const
504{
505	return HasProminence() && Prominence() <= PROMINANCE_ORDERING_PROMINENT_MAX;
506}
507
508
509void
510PackageInfo::ClearScreenshotInfos()
511{
512	if (!fScreenshotInfos.empty()) {
513		fScreenshotInfos.clear();
514		_NotifyListeners(PKG_CHANGED_SCREENSHOTS);
515	}
516}
517
518
519int32
520PackageInfo::CountScreenshotInfos() const
521{
522	return fScreenshotInfos.size();
523}
524
525
526ScreenshotInfoRef
527PackageInfo::ScreenshotInfoAtIndex(int32 index) const
528{
529	return fScreenshotInfos[index];
530}
531
532
533void
534PackageInfo::AddScreenshotInfo(const ScreenshotInfoRef& info)
535{
536	fScreenshotInfos.push_back(info);
537	_NotifyListeners(PKG_CHANGED_SCREENSHOTS);
538}
539
540
541void
542PackageInfo::SetSize(int64 size)
543{
544	if (fSize != size) {
545		fSize = size;
546		_NotifyListeners(PKG_CHANGED_SIZE);
547	}
548}
549
550
551void
552PackageInfo::SetViewed()
553{
554	fViewed = true;
555}
556
557
558void
559PackageInfo::SetVersionCreateTimestamp(uint64 value)
560{
561	if (fVersionCreateTimestamp != value) {
562		fVersionCreateTimestamp = value;
563		_NotifyListeners(PKG_CHANGED_VERSION_CREATE_TIMESTAMP);
564	}
565}
566
567
568void
569PackageInfo::SetDepotName(const BString& depotName)
570{
571	if (fDepotName != depotName) {
572		fDepotName = depotName;
573		_NotifyListeners(PKG_CHANGED_DEPOT);
574	}
575}
576
577
578bool
579PackageInfo::AddListener(const PackageInfoListenerRef& listener)
580{
581	fListeners.push_back(listener);
582	return true;
583}
584
585
586void
587PackageInfo::RemoveListener(const PackageInfoListenerRef& listener)
588{
589	fListeners.erase(std::remove(fListeners.begin(), fListeners.end(),
590		listener), fListeners.end());
591}
592
593
594void
595PackageInfo::NotifyChangedIcon()
596{
597	_NotifyListeners(PKG_CHANGED_ICON);
598}
599
600
601void
602PackageInfo::StartCollatingChanges()
603{
604	fIsCollatingChanges = true;
605	fCollatedChanges = 0;
606}
607
608
609void
610PackageInfo::EndCollatingChanges()
611{
612	if (fIsCollatingChanges && fCollatedChanges != 0)
613		_NotifyListenersImmediate(fCollatedChanges);
614	fIsCollatingChanges = false;
615	fCollatedChanges = 0;
616}
617
618
619void
620PackageInfo::_NotifyListeners(uint32 changes)
621{
622	if (fIsCollatingChanges)
623		fCollatedChanges |= changes;
624	else
625		_NotifyListenersImmediate(changes);
626}
627
628
629void
630PackageInfo::_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
649const 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