Model.cpp revision 3b17d8dd
16d367ba4SStephan Aßmus/*
25bd5f161SStephan Aßmus * Copyright 2013-2014, Stephan A��mus <superstippi@gmx.de>.
3634c2c68SAxel Dörfler * Copyright 2014, Axel D��rfler <axeld@pinc-software.de>.
480a272eeSAndrew Lindesay * Copyright 2016-2018, Andrew Lindesay <apl@lindesay.co.nz>.
56d367ba4SStephan Aßmus * All rights reserved. Distributed under the terms of the MIT License.
66d367ba4SStephan Aßmus */
76d367ba4SStephan Aßmus
86d367ba4SStephan Aßmus#include "Model.h"
96d367ba4SStephan Aßmus
10726db742SStephan Aßmus#include <ctime>
11634c2c68SAxel Dörfler#include <stdarg.h>
126d367ba4SStephan Aßmus#include <stdio.h>
13726db742SStephan Aßmus#include <time.h>
146d367ba4SStephan Aßmus
157eec822dSStephan Aßmus#include <Autolock.h>
16f75ec10dSStephan Aßmus#include <Catalog.h>
17726db742SStephan Aßmus#include <Directory.h>
18726db742SStephan Aßmus#include <Entry.h>
19726db742SStephan Aßmus#include <File.h>
2089ec84a4SStephan Aßmus#include <KeyStore.h>
216e48c81bSStephan Aßmus#include <LocaleRoster.h>
22e3fdc894SStephan Aßmus#include <Message.h>
23726db742SStephan Aßmus#include <Path.h>
24f75ec10dSStephan Aßmus
25f0665db4SAndrew Lindesay#include "Logger.h"
26f0665db4SAndrew Lindesay#include "StorageUtils.h"
273b17d8ddSAndrew Lindesay#include "RepositoryUrlUtils.h"
28f0665db4SAndrew Lindesay
29f75ec10dSStephan Aßmus
30f75ec10dSStephan Aßmus#undef B_TRANSLATION_CONTEXT
31f75ec10dSStephan Aßmus#define B_TRANSLATION_CONTEXT "Model"
32f75ec10dSStephan Aßmus
336d367ba4SStephan Aßmus
3489ec84a4SStephan Aßmusstatic const char* kHaikuDepotKeyring = "HaikuDepot";
3589ec84a4SStephan Aßmus
3689ec84a4SStephan Aßmus
378f03a0f9SStephan AßmusPackageFilter::~PackageFilter()
388f03a0f9SStephan Aßmus{
398f03a0f9SStephan Aßmus}
404abd2b71SStephan Aßmus
414abd2b71SStephan Aßmus
428f03a0f9SStephan AßmusModelListener::~ModelListener()
434abd2b71SStephan Aßmus{
444abd2b71SStephan Aßmus}
454abd2b71SStephan Aßmus
464abd2b71SStephan Aßmus
478f03a0f9SStephan Aßmus// #pragma mark - PackageFilters
488f03a0f9SStephan Aßmus
498f03a0f9SStephan Aßmus
504abd2b71SStephan Aßmusclass AnyFilter : public PackageFilter {
514abd2b71SStephan Aßmuspublic:
52779d8213SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
534abd2b71SStephan Aßmus	{
544abd2b71SStephan Aßmus		return true;
554abd2b71SStephan Aßmus	}
564abd2b71SStephan Aßmus};
574abd2b71SStephan Aßmus
584abd2b71SStephan Aßmus
594abd2b71SStephan Aßmusclass DepotFilter : public PackageFilter {
604abd2b71SStephan Aßmuspublic:
614abd2b71SStephan Aßmus	DepotFilter(const DepotInfo& depot)
624abd2b71SStephan Aßmus		:
634abd2b71SStephan Aßmus		fDepot(depot)
644abd2b71SStephan Aßmus	{
654abd2b71SStephan Aßmus	}
661f67148fSRene Gollent
67779d8213SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
684abd2b71SStephan Aßmus	{
694abd2b71SStephan Aßmus		// TODO: Maybe a PackageInfo ought to know the Depot it came from?
704abd2b71SStephan Aßmus		// But right now the same package could theoretically be provided
714abd2b71SStephan Aßmus		// from different depots and the filter would work correctly.
724abd2b71SStephan Aßmus		// Also the PackageList could actually contain references to packages
734abd2b71SStephan Aßmus		// instead of the packages as objects. The equal operator is quite
744abd2b71SStephan Aßmus		// expensive as is.
75634c2c68SAxel Dörfler		return fDepot.Packages().Contains(package);
764abd2b71SStephan Aßmus	}
771f67148fSRene Gollent
780c169aceSStephan Aßmus	const BString& Depot() const
790c169aceSStephan Aßmus	{
800c169aceSStephan Aßmus		return fDepot.Name();
810c169aceSStephan Aßmus	}
820c169aceSStephan Aßmus
834abd2b71SStephan Aßmusprivate:
844abd2b71SStephan Aßmus	DepotInfo	fDepot;
854abd2b71SStephan Aßmus};
864abd2b71SStephan Aßmus
874abd2b71SStephan Aßmus
8842479955SStephan Aßmusclass CategoryFilter : public PackageFilter {
8942479955SStephan Aßmuspublic:
9042479955SStephan Aßmus	CategoryFilter(const BString& category)
9142479955SStephan Aßmus		:
9242479955SStephan Aßmus		fCategory(category)
9342479955SStephan Aßmus	{
9442479955SStephan Aßmus	}
951f67148fSRene Gollent
96779d8213SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
9742479955SStephan Aßmus	{
98779d8213SStephan Aßmus		if (package.Get() == NULL)
99779d8213SStephan Aßmus			return false;
100634c2c68SAxel Dörfler
101779d8213SStephan Aßmus		const CategoryList& categories = package->Categories();
10242479955SStephan Aßmus		for (int i = categories.CountItems() - 1; i >= 0; i--) {
10342479955SStephan Aßmus			const CategoryRef& category = categories.ItemAtFast(i);
10442479955SStephan Aßmus			if (category.Get() == NULL)
10542479955SStephan Aßmus				continue;
10642479955SStephan Aßmus			if (category->Name() == fCategory)
10742479955SStephan Aßmus				return true;
10842479955SStephan Aßmus		}
10942479955SStephan Aßmus		return false;
11042479955SStephan Aßmus	}
11142479955SStephan Aßmus
1120c169aceSStephan Aßmus	const BString& Category() const
1130c169aceSStephan Aßmus	{
1140c169aceSStephan Aßmus		return fCategory;
1150c169aceSStephan Aßmus	}
1160c169aceSStephan Aßmus
11742479955SStephan Aßmusprivate:
11842479955SStephan Aßmus	BString		fCategory;
11942479955SStephan Aßmus};
12042479955SStephan Aßmus
12142479955SStephan Aßmus
1223a6ccc85SStephan Aßmusclass ContainedInFilter : public PackageFilter {
1233a6ccc85SStephan Aßmuspublic:
1243a6ccc85SStephan Aßmus	ContainedInFilter(const PackageList& packageList)
1253a6ccc85SStephan Aßmus		:
1263a6ccc85SStephan Aßmus		fPackageList(packageList)
1273a6ccc85SStephan Aßmus	{
1283a6ccc85SStephan Aßmus	}
1291f67148fSRene Gollent
130779d8213SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
1313a6ccc85SStephan Aßmus	{
1323a6ccc85SStephan Aßmus		return fPackageList.Contains(package);
1333a6ccc85SStephan Aßmus	}
1343a6ccc85SStephan Aßmus
1353a6ccc85SStephan Aßmusprivate:
1363a6ccc85SStephan Aßmus	const PackageList&	fPackageList;
1373a6ccc85SStephan Aßmus};
1383a6ccc85SStephan Aßmus
1393a6ccc85SStephan Aßmus
1403a6ccc85SStephan Aßmusclass ContainedInEitherFilter : public PackageFilter {
1413a6ccc85SStephan Aßmuspublic:
1423a6ccc85SStephan Aßmus	ContainedInEitherFilter(const PackageList& packageListA,
1433a6ccc85SStephan Aßmus		const PackageList& packageListB)
1443a6ccc85SStephan Aßmus		:
1453a6ccc85SStephan Aßmus		fPackageListA(packageListA),
1463a6ccc85SStephan Aßmus		fPackageListB(packageListB)
1473a6ccc85SStephan Aßmus	{
1483a6ccc85SStephan Aßmus	}
1491f67148fSRene Gollent
150779d8213SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
1513a6ccc85SStephan Aßmus	{
1523a6ccc85SStephan Aßmus		return fPackageListA.Contains(package)
1533a6ccc85SStephan Aßmus			|| fPackageListB.Contains(package);
1543a6ccc85SStephan Aßmus	}
1553a6ccc85SStephan Aßmus
1563a6ccc85SStephan Aßmusprivate:
1573a6ccc85SStephan Aßmus	const PackageList&	fPackageListA;
1583a6ccc85SStephan Aßmus	const PackageList&	fPackageListB;
1593a6ccc85SStephan Aßmus};
1603a6ccc85SStephan Aßmus
1613a6ccc85SStephan Aßmus
162634c2c68SAxel Dörflerclass NotContainedInFilter : public PackageFilter {
163634c2c68SAxel Dörflerpublic:
164634c2c68SAxel Dörfler	NotContainedInFilter(const PackageList* packageList, ...)
165634c2c68SAxel Dörfler	{
166634c2c68SAxel Dörfler		va_list args;
167634c2c68SAxel Dörfler		va_start(args, packageList);
168634c2c68SAxel Dörfler		while (true) {
169634c2c68SAxel Dörfler			const PackageList* packageList = va_arg(args, const PackageList*);
170634c2c68SAxel Dörfler			if (packageList == NULL)
171634c2c68SAxel Dörfler				break;
172634c2c68SAxel Dörfler			fPackageLists.Add(packageList);
173634c2c68SAxel Dörfler		}
174634c2c68SAxel Dörfler		va_end(args);
175634c2c68SAxel Dörfler	}
176634c2c68SAxel Dörfler
177634c2c68SAxel Dörfler	virtual bool AcceptsPackage(const PackageInfoRef& package) const
178634c2c68SAxel Dörfler	{
179be298474SAndrew Lindesay		if (package.Get() == NULL)
180634c2c68SAxel Dörfler			return false;
181a10ad54cSJonathan Schleifer
18272992391SStephan Aßmus		printf("TEST %s\n", package->Name().String());
183a10ad54cSJonathan Schleifer
184634c2c68SAxel Dörfler		for (int32 i = 0; i < fPackageLists.CountItems(); i++) {
185634c2c68SAxel Dörfler			if (fPackageLists.ItemAtFast(i)->Contains(package)) {
186998f4043SJonathan Schleifer				printf("  contained in %" B_PRId32 "\n", i);
187634c2c68SAxel Dörfler				return false;
188634c2c68SAxel Dörfler			}
189634c2c68SAxel Dörfler		}
190634c2c68SAxel Dörfler		return true;
191634c2c68SAxel Dörfler	}
192634c2c68SAxel Dörfler
193634c2c68SAxel Dörflerprivate:
194634c2c68SAxel Dörfler	List<const PackageList*, true>	fPackageLists;
195634c2c68SAxel Dörfler};
196634c2c68SAxel Dörfler
197634c2c68SAxel Dörfler
198634c2c68SAxel Dörflerclass StateFilter : public PackageFilter {
199634c2c68SAxel Dörflerpublic:
200634c2c68SAxel Dörfler	StateFilter(PackageState state)
201634c2c68SAxel Dörfler		:
202634c2c68SAxel Dörfler		fState(state)
203634c2c68SAxel Dörfler	{
204634c2c68SAxel Dörfler	}
205634c2c68SAxel Dörfler
206634c2c68SAxel Dörfler	virtual bool AcceptsPackage(const PackageInfoRef& package) const
207634c2c68SAxel Dörfler	{
208634c2c68SAxel Dörfler		return package->State() == NONE;
209634c2c68SAxel Dörfler	}
210634c2c68SAxel Dörfler
211634c2c68SAxel Dörflerprivate:
212634c2c68SAxel Dörfler	PackageState	fState;
213634c2c68SAxel Dörfler};
214634c2c68SAxel Dörfler
215634c2c68SAxel Dörfler
21609d44cecSStephan Aßmusclass SearchTermsFilter : public PackageFilter {
21709d44cecSStephan Aßmuspublic:
21809d44cecSStephan Aßmus	SearchTermsFilter(const BString& searchTerms)
21909d44cecSStephan Aßmus	{
22009d44cecSStephan Aßmus		// Separate the string into terms at spaces
22109d44cecSStephan Aßmus		int32 index = 0;
22209d44cecSStephan Aßmus		while (index < searchTerms.Length()) {
22309d44cecSStephan Aßmus			int32 nextSpace = searchTerms.FindFirst(" ", index);
22409d44cecSStephan Aßmus			if (nextSpace < 0)
22509d44cecSStephan Aßmus				nextSpace = searchTerms.Length();
22609d44cecSStephan Aßmus			if (nextSpace > index) {
22709d44cecSStephan Aßmus				BString term;
22809d44cecSStephan Aßmus				searchTerms.CopyInto(term, index, nextSpace - index);
22909d44cecSStephan Aßmus				term.ToLower();
23009d44cecSStephan Aßmus				fSearchTerms.Add(term);
23109d44cecSStephan Aßmus			}
23209d44cecSStephan Aßmus			index = nextSpace + 1;
23309d44cecSStephan Aßmus		}
23409d44cecSStephan Aßmus	}
2351f67148fSRene Gollent
236779d8213SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
23709d44cecSStephan Aßmus	{
238779d8213SStephan Aßmus		if (package.Get() == NULL)
239779d8213SStephan Aßmus			return false;
24009d44cecSStephan Aßmus		// Every search term must be found in one of the package texts
24109d44cecSStephan Aßmus		for (int32 i = fSearchTerms.CountItems() - 1; i >= 0; i--) {
24209d44cecSStephan Aßmus			const BString& term = fSearchTerms.ItemAtFast(i);
24372992391SStephan Aßmus			if (!_TextContains(package->Name(), term)
24472992391SStephan Aßmus				&& !_TextContains(package->Title(), term)
245779d8213SStephan Aßmus				&& !_TextContains(package->Publisher().Name(), term)
246779d8213SStephan Aßmus				&& !_TextContains(package->ShortDescription(), term)
247779d8213SStephan Aßmus				&& !_TextContains(package->FullDescription(), term)) {
24809d44cecSStephan Aßmus				return false;
24909d44cecSStephan Aßmus			}
25009d44cecSStephan Aßmus		}
25109d44cecSStephan Aßmus		return true;
25209d44cecSStephan Aßmus	}
25309d44cecSStephan Aßmus
2540c169aceSStephan Aßmus	BString SearchTerms() const
2550c169aceSStephan Aßmus	{
2560c169aceSStephan Aßmus		BString searchTerms;
2570c169aceSStephan Aßmus		for (int32 i = 0; i < fSearchTerms.CountItems(); i++) {
2580c169aceSStephan Aßmus			const BString& term = fSearchTerms.ItemAtFast(i);
2590c169aceSStephan Aßmus			if (term.IsEmpty())
2600c169aceSStephan Aßmus				continue;
2610c169aceSStephan Aßmus			if (!searchTerms.IsEmpty())
2620c169aceSStephan Aßmus				searchTerms.Append(" ");
2630c169aceSStephan Aßmus			searchTerms.Append(term);
2640c169aceSStephan Aßmus		}
2650c169aceSStephan Aßmus		return searchTerms;
2660c169aceSStephan Aßmus	}
2670c169aceSStephan Aßmus
26809d44cecSStephan Aßmusprivate:
26909d44cecSStephan Aßmus	bool _TextContains(BString text, const BString& string) const
27009d44cecSStephan Aßmus	{
27109d44cecSStephan Aßmus		text.ToLower();
27209d44cecSStephan Aßmus		int32 index = text.FindFirst(string);
27309d44cecSStephan Aßmus		return index >= 0;
27409d44cecSStephan Aßmus	}
27509d44cecSStephan Aßmus
27609d44cecSStephan Aßmusprivate:
2770c169aceSStephan Aßmus	StringList fSearchTerms;
27809d44cecSStephan Aßmus};
27909d44cecSStephan Aßmus
28009d44cecSStephan Aßmus
28112107be2SStephan Aßmusclass IsFeaturedFilter : public PackageFilter {
28212107be2SStephan Aßmuspublic:
28312107be2SStephan Aßmus	IsFeaturedFilter()
28412107be2SStephan Aßmus	{
28512107be2SStephan Aßmus	}
28612107be2SStephan Aßmus
28712107be2SStephan Aßmus	virtual bool AcceptsPackage(const PackageInfoRef& package) const
28812107be2SStephan Aßmus	{
28912107be2SStephan Aßmus		return package.Get() != NULL && package->IsProminent();
29012107be2SStephan Aßmus	}
29112107be2SStephan Aßmus};
29212107be2SStephan Aßmus
29312107be2SStephan Aßmus
294870554acSStephan Aßmusstatic inline bool
295870554acSStephan Aßmusis_source_package(const PackageInfoRef& package)
296870554acSStephan Aßmus{
29772992391SStephan Aßmus	const BString& packageName = package->Name();
298870554acSStephan Aßmus	return packageName.EndsWith("_source");
299870554acSStephan Aßmus}
300870554acSStephan Aßmus
301870554acSStephan Aßmus
302870554acSStephan Aßmusstatic inline bool
303870554acSStephan Aßmusis_develop_package(const PackageInfoRef& package)
304870554acSStephan Aßmus{
30572992391SStephan Aßmus	const BString& packageName = package->Name();
30674e41611SJulian Harnath	return packageName.EndsWith("_devel")
30774e41611SJulian Harnath		|| packageName.EndsWith("_debuginfo");
308870554acSStephan Aßmus}
309870554acSStephan Aßmus
310870554acSStephan Aßmus
3114abd2b71SStephan Aßmus// #pragma mark - Model
3124abd2b71SStephan Aßmus
3134abd2b71SStephan Aßmus
3146d367ba4SStephan AßmusModel::Model()
3156d367ba4SStephan Aßmus	:
316f75ec10dSStephan Aßmus	fDepots(),
317f75ec10dSStephan Aßmus
318f75ec10dSStephan Aßmus	fCategoryAudio(new PackageCategory(
319f75ec10dSStephan Aßmus		BitmapRef(),
320f75ec10dSStephan Aßmus		B_TRANSLATE("Audio"), "audio"), true),
3214a02fc4fSStephan Aßmus	fCategoryBusiness(new PackageCategory(
322f75ec10dSStephan Aßmus		BitmapRef(),
3234a02fc4fSStephan Aßmus		B_TRANSLATE("Business"), "business"), true),
3244a02fc4fSStephan Aßmus	fCategoryDevelopment(new PackageCategory(
3254a02fc4fSStephan Aßmus		BitmapRef(),
3264a02fc4fSStephan Aßmus		B_TRANSLATE("Development"), "development"), true),
3274a02fc4fSStephan Aßmus	fCategoryEducation(new PackageCategory(
3284a02fc4fSStephan Aßmus		BitmapRef(),
3294a02fc4fSStephan Aßmus		B_TRANSLATE("Education"), "education"), true),
3304a02fc4fSStephan Aßmus	fCategoryGames(new PackageCategory(
3314a02fc4fSStephan Aßmus		BitmapRef(),
3324a02fc4fSStephan Aßmus		B_TRANSLATE("Games"), "games"), true),
333f75ec10dSStephan Aßmus	fCategoryGraphics(new PackageCategory(
334f75ec10dSStephan Aßmus		BitmapRef(),
335f75ec10dSStephan Aßmus		B_TRANSLATE("Graphics"), "graphics"), true),
3364a02fc4fSStephan Aßmus	fCategoryInternetAndNetwork(new PackageCategory(
3374a02fc4fSStephan Aßmus		BitmapRef(),
3384a02fc4fSStephan Aßmus		B_TRANSLATE("Internet & Network"), "internetandnetwork"), true),
339f75ec10dSStephan Aßmus	fCategoryProductivity(new PackageCategory(
340f75ec10dSStephan Aßmus		BitmapRef(),
341f75ec10dSStephan Aßmus		B_TRANSLATE("Productivity"), "productivity"), true),
3424a02fc4fSStephan Aßmus	fCategoryScienceAndMathematics(new PackageCategory(
343f75ec10dSStephan Aßmus		BitmapRef(),
3444a02fc4fSStephan Aßmus		B_TRANSLATE("Science & Mathematics"), "scienceandmathematics"), true),
3454a02fc4fSStephan Aßmus	fCategorySystemAndUtilities(new PackageCategory(
346f75ec10dSStephan Aßmus		BitmapRef(),
3474a02fc4fSStephan Aßmus		B_TRANSLATE("System & Utilities"), "systemandutilities"), true),
3484a02fc4fSStephan Aßmus	fCategoryVideo(new PackageCategory(
3494abd2b71SStephan Aßmus		BitmapRef(),
3504a02fc4fSStephan Aßmus		B_TRANSLATE("Video"), "video"), true),
3514abd2b71SStephan Aßmus
3524abd2b71SStephan Aßmus	fCategoryFilter(PackageFilterRef(new AnyFilter(), true)),
35309d44cecSStephan Aßmus	fDepotFilter(""),
354870554acSStephan Aßmus	fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)),
35512107be2SStephan Aßmus	fIsFeaturedFilter(),
356634c2c68SAxel Dörfler
3574fade61fSStephan Aßmus	fShowFeaturedPackages(true),
358ab172803SStephan Aßmus	fShowAvailablePackages(true),
3594fade61fSStephan Aßmus	fShowInstalledPackages(true),
360870554acSStephan Aßmus	fShowSourcePackages(false),
3613094fef3SAndrew Lindesay	fShowDevelopPackages(false)
3626d367ba4SStephan Aßmus{
36312107be2SStephan Aßmus	_UpdateIsFeaturedFilter();
36412107be2SStephan Aßmus
365633ed4c4SStephan Aßmus	// Don't forget to add new categories to this list:
3664a02fc4fSStephan Aßmus	fCategories.Add(fCategoryGames);
3674a02fc4fSStephan Aßmus	fCategories.Add(fCategoryBusiness);
368633ed4c4SStephan Aßmus	fCategories.Add(fCategoryAudio);
369633ed4c4SStephan Aßmus	fCategories.Add(fCategoryVideo);
370633ed4c4SStephan Aßmus	fCategories.Add(fCategoryGraphics);
3714a02fc4fSStephan Aßmus	fCategories.Add(fCategoryEducation);
372633ed4c4SStephan Aßmus	fCategories.Add(fCategoryProductivity);
3734a02fc4fSStephan Aßmus	fCategories.Add(fCategorySystemAndUtilities);
3744a02fc4fSStephan Aßmus	fCategories.Add(fCategoryInternetAndNetwork);
375633ed4c4SStephan Aßmus	fCategories.Add(fCategoryDevelopment);
3764a02fc4fSStephan Aßmus	fCategories.Add(fCategoryScienceAndMathematics);
3772b411c02SStephan Aßmus	// TODO: The server will eventually support an API to
3782b411c02SStephan Aßmus	// get the defined categories and their translated names.
3792b411c02SStephan Aßmus	// This should then be used instead of hard-coded
3802b411c02SStephan Aßmus	// categories and translations in the app.
381d07ca069SStephan Aßmus
382d07ca069SStephan Aßmus	fPreferredLanguage = "en";
383d07ca069SStephan Aßmus	BLocaleRoster* localeRoster = BLocaleRoster::Default();
384d07ca069SStephan Aßmus	if (localeRoster != NULL) {
385d07ca069SStephan Aßmus		BMessage preferredLanguages;
386d07ca069SStephan Aßmus		if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) {
387d07ca069SStephan Aßmus			BString language;
388d07ca069SStephan Aßmus			if (preferredLanguages.FindString("language", 0, &language) == B_OK)
389d07ca069SStephan Aßmus				language.CopyInto(fPreferredLanguage, 0, 2);
390d07ca069SStephan Aßmus		}
391d07ca069SStephan Aßmus	}
3924ff3d17aSStephan Aßmus
3934ff3d17aSStephan Aßmus	// TODO: Fetch this from the web-app.
3944ff3d17aSStephan Aßmus	fSupportedLanguages.Add("en");
3954ff3d17aSStephan Aßmus	fSupportedLanguages.Add("de");
3964ff3d17aSStephan Aßmus	fSupportedLanguages.Add("fr");
3974ff3d17aSStephan Aßmus	fSupportedLanguages.Add("ja");
3984ff3d17aSStephan Aßmus	fSupportedLanguages.Add("es");
3994ff3d17aSStephan Aßmus	fSupportedLanguages.Add("zh");
4004ff3d17aSStephan Aßmus	fSupportedLanguages.Add("pt");
4014ff3d17aSStephan Aßmus	fSupportedLanguages.Add("ru");
4024ff3d17aSStephan Aßmus
4034ff3d17aSStephan Aßmus	if (!fSupportedLanguages.Contains(fPreferredLanguage)) {
40412f29d44SStephan Aßmus		// Force the preferred language to one of the currently supported
40512f29d44SStephan Aßmus		// ones, until the web application supports all ISO language codes.
4064ff3d17aSStephan Aßmus		printf("User preferred language '%s' not currently supported, "
4074ff3d17aSStephan Aßmus			"defaulting to 'en'.", fPreferredLanguage.String());
40812f29d44SStephan Aßmus		fPreferredLanguage = "en";
40912f29d44SStephan Aßmus	}
41028075ee4SStephan Aßmus	fWebAppInterface.SetPreferredLanguage(fPreferredLanguage);
4116d367ba4SStephan Aßmus}
4126d367ba4SStephan Aßmus
4136d367ba4SStephan Aßmus
4145bd5f161SStephan AßmusModel::~Model()
4155bd5f161SStephan Aßmus{
4165bd5f161SStephan Aßmus}
4175bd5f161SStephan Aßmus
4185bd5f161SStephan Aßmus
4198f03a0f9SStephan Aßmusbool
4208f03a0f9SStephan AßmusModel::AddListener(const ModelListenerRef& listener)
4218f03a0f9SStephan Aßmus{
4228f03a0f9SStephan Aßmus	return fListeners.Add(listener);
4238f03a0f9SStephan Aßmus}
4248f03a0f9SStephan Aßmus
4258f03a0f9SStephan Aßmus
426706edd80SStephan AßmusPackageList
4276d367ba4SStephan AßmusModel::CreatePackageList() const
4286d367ba4SStephan Aßmus{
429ab172803SStephan Aßmus	// Iterate all packages from all depots.
430ab172803SStephan Aßmus	// If configured, restrict depot, filter by search terms, status, name ...
431706edd80SStephan Aßmus	PackageList resultList;
4326d367ba4SStephan Aßmus
4336d367ba4SStephan Aßmus	for (int32 i = 0; i < fDepots.CountItems(); i++) {
43409d44cecSStephan Aßmus		const DepotInfo& depot = fDepots.ItemAtFast(i);
4351f67148fSRene Gollent
43609d44cecSStephan Aßmus		if (fDepotFilter.Length() > 0 && fDepotFilter != depot.Name())
43709d44cecSStephan Aßmus			continue;
4381f67148fSRene Gollent
43909d44cecSStephan Aßmus		const PackageList& packages = depot.Packages();
4406d367ba4SStephan Aßmus
441706edd80SStephan Aßmus		for (int32 j = 0; j < packages.CountItems(); j++) {
442779d8213SStephan Aßmus			const PackageInfoRef& package = packages.ItemAtFast(j);
44339f49f70SJulian Harnath			if (MatchesFilter(package))
4444abd2b71SStephan Aßmus				resultList.Add(package);
4456d367ba4SStephan Aßmus		}
4466d367ba4SStephan Aßmus	}
4476d367ba4SStephan Aßmus
4486d367ba4SStephan Aßmus	return resultList;
4496d367ba4SStephan Aßmus}
4506d367ba4SStephan Aßmus
4516d367ba4SStephan Aßmus
45239f49f70SJulian Harnathbool
45339f49f70SJulian HarnathModel::MatchesFilter(const PackageInfoRef& package) const
45439f49f70SJulian Harnath{
45539f49f70SJulian Harnath	return fCategoryFilter->AcceptsPackage(package)
45639f49f70SJulian Harnath			&& fSearchTermsFilter->AcceptsPackage(package)
45739f49f70SJulian Harnath			&& fIsFeaturedFilter->AcceptsPackage(package)
45839f49f70SJulian Harnath			&& (fShowAvailablePackages || package->State() != NONE)
45939f49f70SJulian Harnath			&& (fShowInstalledPackages || package->State() != ACTIVATED)
46039f49f70SJulian Harnath			&& (fShowSourcePackages || !is_source_package(package))
46139f49f70SJulian Harnath			&& (fShowDevelopPackages || !is_develop_package(package));
46239f49f70SJulian Harnath}
46339f49f70SJulian Harnath
46439f49f70SJulian Harnath
4656d367ba4SStephan Aßmusbool
4666d367ba4SStephan AßmusModel::AddDepot(const DepotInfo& depot)
4676d367ba4SStephan Aßmus{
4686d367ba4SStephan Aßmus	return fDepots.Add(depot);
4696d367ba4SStephan Aßmus}
4706d367ba4SStephan Aßmus
47142479955SStephan Aßmus
4727b2c59b9SStephan Aßmusbool
4737b2c59b9SStephan AßmusModel::HasDepot(const BString& name) const
474ebb1a01cSAndrew Lindesay{
475ebb1a01cSAndrew Lindesay	return NULL != DepotForName(name);
476ebb1a01cSAndrew Lindesay}
477ebb1a01cSAndrew Lindesay
478ebb1a01cSAndrew Lindesay
479ebb1a01cSAndrew Lindesayconst DepotInfo*
480ebb1a01cSAndrew LindesayModel::DepotForName(const BString& name) const
4817b2c59b9SStephan Aßmus{
4827b2c59b9SStephan Aßmus	for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
4837b2c59b9SStephan Aßmus		if (fDepots.ItemAtFast(i).Name() == name)
484ebb1a01cSAndrew Lindesay			return &fDepots.ItemAtFast(i);
4857b2c59b9SStephan Aßmus	}
486ebb1a01cSAndrew Lindesay	return NULL;
4877b2c59b9SStephan Aßmus}
4887b2c59b9SStephan Aßmus
4897b2c59b9SStephan Aßmus
4907b2c59b9SStephan Aßmusbool
4917b2c59b9SStephan AßmusModel::SyncDepot(const DepotInfo& depot)
4927b2c59b9SStephan Aßmus{
4937b2c59b9SStephan Aßmus	for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
4947b2c59b9SStephan Aßmus		const DepotInfo& existingDepot = fDepots.ItemAtFast(i);
4957b2c59b9SStephan Aßmus		if (existingDepot.Name() == depot.Name()) {
4967b2c59b9SStephan Aßmus			DepotInfo mergedDepot(existingDepot);
4977b2c59b9SStephan Aßmus			mergedDepot.SyncPackages(depot.Packages());
4987b2c59b9SStephan Aßmus			fDepots.Replace(i, mergedDepot);
4997b2c59b9SStephan Aßmus			return true;
5007b2c59b9SStephan Aßmus		}
5017b2c59b9SStephan Aßmus	}
5027b2c59b9SStephan Aßmus	return false;
5037b2c59b9SStephan Aßmus}
5047b2c59b9SStephan Aßmus
5057b2c59b9SStephan Aßmus
5062c287f5bSRene Gollentvoid
5072c287f5bSRene GollentModel::Clear()
5082c287f5bSRene Gollent{
5092c287f5bSRene Gollent	fDepots.Clear();
5102c287f5bSRene Gollent}
5112c287f5bSRene Gollent
5122c287f5bSRene Gollent
513706edd80SStephan Aßmusvoid
514779d8213SStephan AßmusModel::SetPackageState(const PackageInfoRef& package, PackageState state)
515706edd80SStephan Aßmus{
516706edd80SStephan Aßmus	switch (state) {
517706edd80SStephan Aßmus		default:
518706edd80SStephan Aßmus		case NONE:
519706edd80SStephan Aßmus			fInstalledPackages.Remove(package);
520706edd80SStephan Aßmus			fActivatedPackages.Remove(package);
521706edd80SStephan Aßmus			fUninstalledPackages.Remove(package);
522706edd80SStephan Aßmus			break;
523706edd80SStephan Aßmus		case INSTALLED:
524706edd80SStephan Aßmus			if (!fInstalledPackages.Contains(package))
525706edd80SStephan Aßmus				fInstalledPackages.Add(package);
526706edd80SStephan Aßmus			fActivatedPackages.Remove(package);
527706edd80SStephan Aßmus			fUninstalledPackages.Remove(package);
528706edd80SStephan Aßmus			break;
529706edd80SStephan Aßmus		case ACTIVATED:
530706edd80SStephan Aßmus			if (!fInstalledPackages.Contains(package))
531706edd80SStephan Aßmus				fInstalledPackages.Add(package);
532706edd80SStephan Aßmus			if (!fActivatedPackages.Contains(package))
533706edd80SStephan Aßmus				fActivatedPackages.Add(package);
534706edd80SStephan Aßmus			fUninstalledPackages.Remove(package);
535706edd80SStephan Aßmus			break;
536706edd80SStephan Aßmus		case UNINSTALLED:
537706edd80SStephan Aßmus			fInstalledPackages.Remove(package);
538706edd80SStephan Aßmus			fActivatedPackages.Remove(package);
539ab172803SStephan Aßmus			if (!fUninstalledPackages.Contains(package))
540ab172803SStephan Aßmus				fUninstalledPackages.Add(package);
541706edd80SStephan Aßmus			break;
542706edd80SStephan Aßmus	}
543b4c8d2ffSRene Gollent
544b4c8d2ffSRene Gollent	package->SetState(state);
545706edd80SStephan Aßmus}
546706edd80SStephan Aßmus
547706edd80SStephan Aßmus
54809d44cecSStephan Aßmus// #pragma mark - filters
54909d44cecSStephan Aßmus
55009d44cecSStephan Aßmus
55142479955SStephan Aßmusvoid
55242479955SStephan AßmusModel::SetCategory(const BString& category)
55342479955SStephan Aßmus{
5543a6ccc85SStephan Aßmus	PackageFilter* filter;
5551f67148fSRene Gollent
55642479955SStephan Aßmus	if (category.Length() == 0)
5573a6ccc85SStephan Aßmus		filter = new AnyFilter();
55842479955SStephan Aßmus	else
5593a6ccc85SStephan Aßmus		filter = new CategoryFilter(category);
5603a6ccc85SStephan Aßmus
5613a6ccc85SStephan Aßmus	fCategoryFilter.SetTo(filter, true);
56242479955SStephan Aßmus}
56342479955SStephan Aßmus
56409d44cecSStephan Aßmus
5650c169aceSStephan AßmusBString
5660c169aceSStephan AßmusModel::Category() const
5670c169aceSStephan Aßmus{
5680c169aceSStephan Aßmus	CategoryFilter* filter
5690c169aceSStephan Aßmus		= dynamic_cast<CategoryFilter*>(fCategoryFilter.Get());
5700c169aceSStephan Aßmus	if (filter == NULL)
5710c169aceSStephan Aßmus		return "";
5720c169aceSStephan Aßmus	return filter->Category();
5730c169aceSStephan Aßmus}
5740c169aceSStephan Aßmus
5750c169aceSStephan Aßmus
57609d44cecSStephan Aßmusvoid
57709d44cecSStephan AßmusModel::SetDepot(const BString& depot)
57809d44cecSStephan Aßmus{
57909d44cecSStephan Aßmus	fDepotFilter = depot;
58009d44cecSStephan Aßmus}
58109d44cecSStephan Aßmus
58209d44cecSStephan Aßmus
5830c169aceSStephan AßmusBString
5840c169aceSStephan AßmusModel::Depot() const
5850c169aceSStephan Aßmus{
5860c169aceSStephan Aßmus	return fDepotFilter;
5870c169aceSStephan Aßmus}
5880c169aceSStephan Aßmus
5890c169aceSStephan Aßmus
59009d44cecSStephan Aßmusvoid
59109d44cecSStephan AßmusModel::SetSearchTerms(const BString& searchTerms)
59209d44cecSStephan Aßmus{
59309d44cecSStephan Aßmus	PackageFilter* filter;
5941f67148fSRene Gollent
59509d44cecSStephan Aßmus	if (searchTerms.Length() == 0)
59609d44cecSStephan Aßmus		filter = new AnyFilter();
59709d44cecSStephan Aßmus	else
59809d44cecSStephan Aßmus		filter = new SearchTermsFilter(searchTerms);
59909d44cecSStephan Aßmus
60009d44cecSStephan Aßmus	fSearchTermsFilter.SetTo(filter, true);
60112107be2SStephan Aßmus	_UpdateIsFeaturedFilter();
60209d44cecSStephan Aßmus}
60309d44cecSStephan Aßmus
6047eec822dSStephan Aßmus
6050c169aceSStephan AßmusBString
6060c169aceSStephan AßmusModel::SearchTerms() const
6070c169aceSStephan Aßmus{
6080c169aceSStephan Aßmus	SearchTermsFilter* filter
6090c169aceSStephan Aßmus		= dynamic_cast<SearchTermsFilter*>(fSearchTermsFilter.Get());
6100c169aceSStephan Aßmus	if (filter == NULL)
6110c169aceSStephan Aßmus		return "";
6120c169aceSStephan Aßmus	return filter->SearchTerms();
6130c169aceSStephan Aßmus}
6140c169aceSStephan Aßmus
6150c169aceSStephan Aßmus
6164fade61fSStephan Aßmusvoid
6174fade61fSStephan AßmusModel::SetShowFeaturedPackages(bool show)
6184fade61fSStephan Aßmus{
6194fade61fSStephan Aßmus	fShowFeaturedPackages = show;
62012107be2SStephan Aßmus	_UpdateIsFeaturedFilter();
6214fade61fSStephan Aßmus}
6224fade61fSStephan Aßmus
6234fade61fSStephan Aßmus
624ab172803SStephan Aßmusvoid
625ab172803SStephan AßmusModel::SetShowAvailablePackages(bool show)
626ab172803SStephan Aßmus{
627ab172803SStephan Aßmus	fShowAvailablePackages = show;
628ab172803SStephan Aßmus}
629ab172803SStephan Aßmus
630ab172803SStephan Aßmus
631ab172803SStephan Aßmusvoid
632ab172803SStephan AßmusModel::SetShowInstalledPackages(bool show)
633ab172803SStephan Aßmus{
634ab172803SStephan Aßmus	fShowInstalledPackages = show;
635ab172803SStephan Aßmus}
636ab172803SStephan Aßmus
637ab172803SStephan Aßmus
638870554acSStephan Aßmusvoid
639870554acSStephan AßmusModel::SetShowSourcePackages(bool show)
640870554acSStephan Aßmus{
641870554acSStephan Aßmus	fShowSourcePackages = show;
642870554acSStephan Aßmus}
643870554acSStephan Aßmus
644870554acSStephan Aßmus
645870554acSStephan Aßmusvoid
646870554acSStephan AßmusModel::SetShowDevelopPackages(bool show)
647870554acSStephan Aßmus{
648870554acSStephan Aßmus	fShowDevelopPackages = show;
649870554acSStephan Aßmus}
650870554acSStephan Aßmus
651870554acSStephan Aßmus
65219c15fecSAndrew Lindesay// #pragma mark - information retrieval
6537eec822dSStephan Aßmus
6547eec822dSStephan Aßmus
655a9edb9bfSAndrew Lindesay/*! Initially only superficial data is loaded from the server into the data
656a9edb9bfSAndrew Lindesay    model of the packages.  When the package is viewed, additional data needs
657a9edb9bfSAndrew Lindesay    to be populated including ratings.  This method takes care of that.
658a9edb9bfSAndrew Lindesay*/
659a9edb9bfSAndrew Lindesay
6607eec822dSStephan Aßmusvoid
661a60a23f8SStephan AßmusModel::PopulatePackage(const PackageInfoRef& package, uint32 flags)
6627eec822dSStephan Aßmus{
6637eec822dSStephan Aßmus	// TODO: There should probably also be a way to "unpopulate" the
6647eec822dSStephan Aßmus	// package information. Maybe a cache of populated packages, so that
6657eec822dSStephan Aßmus	// packages loose their extra information after a certain amount of
6667eec822dSStephan Aßmus	// time when they have not been accessed/displayed in the UI. Otherwise
6677eec822dSStephan Aßmus	// HaikuDepot will consume more and more resources in the packages.
6687eec822dSStephan Aßmus	// Especially screen-shots will be a problem eventually.
66948045f5aSStephan Aßmus	{
67048045f5aSStephan Aßmus		BAutolock locker(&fLock);
67189e4ba17SStephan Aßmus		bool alreadyPopulated = fPopulatedPackages.Contains(package);
67289e4ba17SStephan Aßmus		if ((flags & POPULATE_FORCE) == 0 && alreadyPopulated)
67348045f5aSStephan Aßmus			return;
67489e4ba17SStephan Aßmus		if (!alreadyPopulated)
67589e4ba17SStephan Aßmus			fPopulatedPackages.Add(package);
67648045f5aSStephan Aßmus	}
6777eec822dSStephan Aßmus
67880a272eeSAndrew Lindesay	if ((flags & POPULATE_CHANGELOG) != 0) {
67980a272eeSAndrew Lindesay		_PopulatePackageChangelog(package);
68080a272eeSAndrew Lindesay	}
68180a272eeSAndrew Lindesay
6827ff94fd0SStephan Aßmus	if ((flags & POPULATE_USER_RATINGS) != 0) {
6837ff94fd0SStephan Aßmus		// Retrieve info from web-app
6847ff94fd0SStephan Aßmus		BMessage info;
68548045f5aSStephan Aßmus
68648045f5aSStephan Aßmus		BString packageName;
68787084745SMichael Lotz		BString architecture;
68848045f5aSStephan Aßmus		{
68948045f5aSStephan Aßmus			BAutolock locker(&fLock);
69072992391SStephan Aßmus			packageName = package->Name();
69148045f5aSStephan Aßmus			architecture = package->Architecture();
69248045f5aSStephan Aßmus		}
69387084745SMichael Lotz
69428075ee4SStephan Aßmus		status_t status = fWebAppInterface.RetrieveUserRatings(packageName,
69548045f5aSStephan Aßmus			architecture, 0, 50, info);
6967ff94fd0SStephan Aßmus		if (status == B_OK) {
6977ff94fd0SStephan Aßmus			// Parse message
6987ff94fd0SStephan Aßmus			BMessage result;
6997ff94fd0SStephan Aßmus			BMessage items;
7007ff94fd0SStephan Aßmus			if (info.FindMessage("result", &result) == B_OK
7017ff94fd0SStephan Aßmus				&& result.FindMessage("items", &items) == B_OK) {
70248045f5aSStephan Aßmus
70348045f5aSStephan Aßmus				BAutolock locker(&fLock);
7047eb09bfaSStephan Aßmus				package->ClearUserRatings();
70548045f5aSStephan Aßmus
706a9edb9bfSAndrew Lindesay				int32 index = 0;
7077ff94fd0SStephan Aßmus				while (true) {
7087ff94fd0SStephan Aßmus					BString name;
7097ff94fd0SStephan Aßmus					name << index++;
71087084745SMichael Lotz
7117ff94fd0SStephan Aßmus					BMessage item;
7127ff94fd0SStephan Aßmus					if (items.FindMessage(name, &item) != B_OK)
7137ff94fd0SStephan Aßmus						break;
7147ff94fd0SStephan Aßmus
715a9edb9bfSAndrew Lindesay					BString code;
716a9edb9bfSAndrew Lindesay					if (item.FindString("code", &code) != B_OK) {
717a9edb9bfSAndrew Lindesay						printf("corrupt user rating at index %" B_PRIi32 "\n",
718a9edb9bfSAndrew Lindesay							index);
719a9edb9bfSAndrew Lindesay						continue;
720a9edb9bfSAndrew Lindesay					}
721a9edb9bfSAndrew Lindesay
7227ff94fd0SStephan Aßmus					BString user;
7237ff94fd0SStephan Aßmus					BMessage userInfo;
7247ff94fd0SStephan Aßmus					if (item.FindMessage("user", &userInfo) != B_OK
7257ff94fd0SStephan Aßmus						|| userInfo.FindString("nickname", &user) != B_OK) {
726a9edb9bfSAndrew Lindesay						printf("ignored user rating [%s] without a user "
727a9edb9bfSAndrew Lindesay							"nickname\n", code.String());
7287ff94fd0SStephan Aßmus						continue;
7297ff94fd0SStephan Aßmus					}
7307ff94fd0SStephan Aßmus
73136a9b557SStephan Aßmus					// Extract basic info, all items are optional
7327ff94fd0SStephan Aßmus					BString languageCode;
7337ff94fd0SStephan Aßmus					BString comment;
7347ff94fd0SStephan Aßmus					double rating;
73536a9b557SStephan Aßmus					item.FindString("naturalLanguageCode", &languageCode);
73636a9b557SStephan Aßmus					item.FindString("comment", &comment);
73736a9b557SStephan Aßmus					if (item.FindDouble("rating", &rating) != B_OK)
73836a9b557SStephan Aßmus						rating = -1;
73936a9b557SStephan Aßmus					if (comment.Length() == 0 && rating == -1) {
740a9edb9bfSAndrew Lindesay						printf("rating [%s] has no comment or rating so will be"
741a9edb9bfSAndrew Lindesay							"ignored\n", code.String());
7427ff94fd0SStephan Aßmus						continue;
7437ff94fd0SStephan Aßmus					}
74436a9b557SStephan Aßmus
7457ff94fd0SStephan Aßmus					// For which version of the package was the rating?
7467ff94fd0SStephan Aßmus					BString major = "?";
7477ff94fd0SStephan Aßmus					BString minor = "?";
7487ff94fd0SStephan Aßmus					BString micro = "";
749a9edb9bfSAndrew Lindesay					double revision = -1;
7507ff94fd0SStephan Aßmus					BMessage version;
7517ff94fd0SStephan Aßmus					if (item.FindMessage("pkgVersion", &version) == B_OK) {
7527ff94fd0SStephan Aßmus						version.FindString("major", &major);
7537ff94fd0SStephan Aßmus						version.FindString("minor", &minor);
7547ff94fd0SStephan Aßmus						version.FindString("micro", &micro);
755a9edb9bfSAndrew Lindesay						version.FindDouble("revision", &revision);
7567ff94fd0SStephan Aßmus					}
7577ff94fd0SStephan Aßmus					BString versionString = major;
7587ff94fd0SStephan Aßmus					versionString << ".";
7597ff94fd0SStephan Aßmus					versionString << minor;
7607ff94fd0SStephan Aßmus					if (micro.Length() > 0) {
7617ff94fd0SStephan Aßmus						versionString << ".";
7627ff94fd0SStephan Aßmus						versionString << micro;
7637ff94fd0SStephan Aßmus					}
764a9edb9bfSAndrew Lindesay					if (revision > 0) {
765a9edb9bfSAndrew Lindesay						versionString << "-";
766a9edb9bfSAndrew Lindesay						versionString << (int) revision;
767a9edb9bfSAndrew Lindesay					}
768a9edb9bfSAndrew Lindesay
769a9edb9bfSAndrew Lindesay					BDateTime createTimestamp;
770a9edb9bfSAndrew Lindesay					double createTimestampMillisF;
771a9edb9bfSAndrew Lindesay					if (item.FindDouble("createTimestamp",
772a9edb9bfSAndrew Lindesay						&createTimestampMillisF) == B_OK) {
773a9edb9bfSAndrew Lindesay						double createTimestampSecsF =
774a9edb9bfSAndrew Lindesay							createTimestampMillisF / 1000.0;
775a9edb9bfSAndrew Lindesay						time_t createTimestampSecs =
776a9edb9bfSAndrew Lindesay							(time_t) createTimestampSecsF;
777a9edb9bfSAndrew Lindesay						createTimestamp.SetTime_t(createTimestampSecs);
778a9edb9bfSAndrew Lindesay					}
779a9edb9bfSAndrew Lindesay
7807ff94fd0SStephan Aßmus					// Add the rating to the PackageInfo
781a9edb9bfSAndrew Lindesay					UserRating userRating = UserRating(UserInfo(user), rating,
782a9edb9bfSAndrew Lindesay						comment, languageCode, versionString, 0, 0,
783a9edb9bfSAndrew Lindesay						createTimestamp);
784a9edb9bfSAndrew Lindesay					package->AddUserRating(userRating);
785a9edb9bfSAndrew Lindesay
786a9edb9bfSAndrew Lindesay					if (Logger::IsDebugEnabled()) {
787a9edb9bfSAndrew Lindesay						printf("rating [%s] retrieved from server\n",
788a9edb9bfSAndrew Lindesay							code.String());
789a9edb9bfSAndrew Lindesay					}
790a9edb9bfSAndrew Lindesay				}
791a9edb9bfSAndrew Lindesay
792a9edb9bfSAndrew Lindesay				if (Logger::IsDebugEnabled()) {
793a9edb9bfSAndrew Lindesay					printf("did retrieve %" B_PRIi32 " user ratings for [%s]\n",
794a9edb9bfSAndrew Lindesay						index - 1, packageName.String());
7957ff94fd0SStephan Aßmus				}
79680a272eeSAndrew Lindesay			} else {
79780a272eeSAndrew Lindesay				_MaybeLogJsonRpcError(info, "retrieve user ratings");
7987ff94fd0SStephan Aßmus			}
799a9edb9bfSAndrew Lindesay		} else {
800a9edb9bfSAndrew Lindesay			printf("unable to retrieve user ratings\n");
801a60a23f8SStephan Aßmus		}
8027eec822dSStephan Aßmus	}
80387038da7SStephan Aßmus
80487038da7SStephan Aßmus	if ((flags & POPULATE_SCREEN_SHOTS) != 0) {
80587038da7SStephan Aßmus		ScreenshotInfoList screenshotInfos;
80687038da7SStephan Aßmus		{
80787038da7SStephan Aßmus			BAutolock locker(&fLock);
80887038da7SStephan Aßmus			screenshotInfos = package->ScreenshotInfos();
8097eb09bfaSStephan Aßmus			package->ClearScreenshots();
81087038da7SStephan Aßmus		}
81187038da7SStephan Aßmus		for (int i = 0; i < screenshotInfos.CountItems(); i++) {
81287038da7SStephan Aßmus			const ScreenshotInfo& info = screenshotInfos.ItemAtFast(i);
813e0881f21SStephan Aßmus			_PopulatePackageScreenshot(package, info, 320, false);
81487038da7SStephan Aßmus		}
81587038da7SStephan Aßmus	}
8167eec822dSStephan Aßmus}
8175bd5f161SStephan Aßmus
8185bd5f161SStephan Aßmus
81980a272eeSAndrew Lindesayvoid
82080a272eeSAndrew LindesayModel::_PopulatePackageChangelog(const PackageInfoRef& package)
82180a272eeSAndrew Lindesay{
82280a272eeSAndrew Lindesay	BMessage info;
82380a272eeSAndrew Lindesay	BString packageName;
82480a272eeSAndrew Lindesay
82580a272eeSAndrew Lindesay	{
82680a272eeSAndrew Lindesay		BAutolock locker(&fLock);
82780a272eeSAndrew Lindesay		packageName = package->Name();
82880a272eeSAndrew Lindesay	}
82980a272eeSAndrew Lindesay
83080a272eeSAndrew Lindesay	status_t status = fWebAppInterface.GetChangelog(packageName, info);
83180a272eeSAndrew Lindesay
83280a272eeSAndrew Lindesay	if (status == B_OK) {
83380a272eeSAndrew Lindesay		// Parse message
83480a272eeSAndrew Lindesay		BMessage result;
83580a272eeSAndrew Lindesay		BString content;
83680a272eeSAndrew Lindesay		if (info.FindMessage("result", &result) == B_OK) {
83780a272eeSAndrew Lindesay			if (result.FindString("content", &content) == B_OK
83880a272eeSAndrew Lindesay				&& 0 != content.Length()) {
83980a272eeSAndrew Lindesay				BAutolock locker(&fLock);
84080a272eeSAndrew Lindesay				package->SetChangelog(content);
84180a272eeSAndrew Lindesay				if (Logger::IsDebugEnabled()) {
84280a272eeSAndrew Lindesay					fprintf(stdout, "changelog populated for [%s]\n",
84380a272eeSAndrew Lindesay						packageName.String());
84480a272eeSAndrew Lindesay				}
84580a272eeSAndrew Lindesay			} else {
84680a272eeSAndrew Lindesay				if (Logger::IsDebugEnabled()) {
84780a272eeSAndrew Lindesay					fprintf(stdout, "no changelog present for [%s]\n",
84880a272eeSAndrew Lindesay						packageName.String());
84980a272eeSAndrew Lindesay				}
85080a272eeSAndrew Lindesay			}
85180a272eeSAndrew Lindesay		} else {
85280a272eeSAndrew Lindesay			_MaybeLogJsonRpcError(info, "populate package changelog");
85380a272eeSAndrew Lindesay		}
85480a272eeSAndrew Lindesay	} else {
85580a272eeSAndrew Lindesay		fprintf(stdout, "unable to obtain the changelog for the package"
856a9edb9bfSAndrew Lindesay			" [%s]\n", packageName.String());
85780a272eeSAndrew Lindesay	}
85880a272eeSAndrew Lindesay}
85980a272eeSAndrew Lindesay
86080a272eeSAndrew Lindesay
86128075ee4SStephan Aßmusvoid
8628f03a0f9SStephan AßmusModel::SetUsername(BString username)
86389ec84a4SStephan Aßmus{
8648f03a0f9SStephan Aßmus	BString password;
8658f03a0f9SStephan Aßmus	if (username.Length() > 0) {
8668f03a0f9SStephan Aßmus		BPasswordKey key;
8678f03a0f9SStephan Aßmus		BKeyStore keyStore;
8688f03a0f9SStephan Aßmus		if (keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD, username,
8698f03a0f9SStephan Aßmus				key) == B_OK) {
8708f03a0f9SStephan Aßmus			password = key.Password();
8718f03a0f9SStephan Aßmus		} else {
8728f03a0f9SStephan Aßmus			username = "";
8738f03a0f9SStephan Aßmus		}
87489ec84a4SStephan Aßmus	}
8758f03a0f9SStephan Aßmus	SetAuthorization(username, password, false);
8768f03a0f9SStephan Aßmus}
8778f03a0f9SStephan Aßmus
8788f03a0f9SStephan Aßmus
8798f03a0f9SStephan Aßmusconst BString&
8808f03a0f9SStephan AßmusModel::Username() const
8818f03a0f9SStephan Aßmus{
8828f03a0f9SStephan Aßmus	return fWebAppInterface.Username();
88389ec84a4SStephan Aßmus}
88489ec84a4SStephan Aßmus
88589ec84a4SStephan Aßmus
88689ec84a4SStephan Aßmusvoid
88789ec84a4SStephan AßmusModel::SetAuthorization(const BString& username, const BString& password,
88889ec84a4SStephan Aßmus	bool storePassword)
88928075ee4SStephan Aßmus{
8908f03a0f9SStephan Aßmus	if (storePassword && username.Length() > 0 && password.Length() > 0) {
89189ec84a4SStephan Aßmus		BPasswordKey key(password, B_KEY_PURPOSE_WEB, username);
89289ec84a4SStephan Aßmus		BKeyStore keyStore;
89389ec84a4SStephan Aßmus		keyStore.AddKeyring(kHaikuDepotKeyring);
89489ec84a4SStephan Aßmus		keyStore.AddKey(kHaikuDepotKeyring, key);
89589ec84a4SStephan Aßmus	}
89689ec84a4SStephan Aßmus
89728075ee4SStephan Aßmus	BAutolock locker(&fLock);
89828075ee4SStephan Aßmus	fWebAppInterface.SetAuthorization(username, password);
8998f03a0f9SStephan Aßmus
9008f03a0f9SStephan Aßmus	_NotifyAuthorizationChanged();
90128075ee4SStephan Aßmus}
90228075ee4SStephan Aßmus
90328075ee4SStephan Aßmus
9043d528c4aSAndrew Lindesay/*! When bulk repository data comes down from the server, it will
9053d528c4aSAndrew Lindesay    arrive as a json.gz payload.  This is stored locally as a cache
9063d528c4aSAndrew Lindesay    and this method will provide the on-disk storage location for
9073d528c4aSAndrew Lindesay    this file.
9083d528c4aSAndrew Lindesay*/
90928075ee4SStephan Aßmus
9103d528c4aSAndrew Lindesaystatus_t
9113094fef3SAndrew LindesayModel::DumpExportRepositoryDataPath(BPath& path) const
912ebb1a01cSAndrew Lindesay{
9133d528c4aSAndrew Lindesay	BPath repoDataPath;
914ebb1a01cSAndrew Lindesay
9153d528c4aSAndrew Lindesay	if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK
9163d528c4aSAndrew Lindesay		&& repoDataPath.Append("HaikuDepot") == B_OK
9173d528c4aSAndrew Lindesay		&& create_directory(repoDataPath.Path(), 0777) == B_OK
9183d528c4aSAndrew Lindesay		&& repoDataPath.Append("repository-all_en.json.gz") == B_OK) {
9193d528c4aSAndrew Lindesay		path.SetTo(repoDataPath.Path());
9203d528c4aSAndrew Lindesay		return B_OK;
9213d528c4aSAndrew Lindesay	}
922ebb1a01cSAndrew Lindesay
9233d528c4aSAndrew Lindesay	path.Unset();
9243d528c4aSAndrew Lindesay	fprintf(stdout, "unable to find the user cache file for repositories'"
9253d528c4aSAndrew Lindesay		" data");
9263d528c4aSAndrew Lindesay	return B_ERROR;
9273d528c4aSAndrew Lindesay}
928ebb1a01cSAndrew Lindesay
929ebb1a01cSAndrew Lindesay
930c23b641cSAndrew Lindesaystatus_t
9313094fef3SAndrew LindesayModel::IconStoragePath(BPath& path) const
9323094fef3SAndrew Lindesay{
9333094fef3SAndrew Lindesay	BPath iconStoragePath;
9343094fef3SAndrew Lindesay
9353094fef3SAndrew Lindesay	if (find_directory(B_USER_CACHE_DIRECTORY, &iconStoragePath) == B_OK
9363094fef3SAndrew Lindesay		&& iconStoragePath.Append("HaikuDepot") == B_OK
9373094fef3SAndrew Lindesay		&& iconStoragePath.Append("__allicons") == B_OK
9383094fef3SAndrew Lindesay		&& create_directory(iconStoragePath.Path(), 0777) == B_OK) {
9393094fef3SAndrew Lindesay		path.SetTo(iconStoragePath.Path());
9403094fef3SAndrew Lindesay		return B_OK;
9413094fef3SAndrew Lindesay	}
9423094fef3SAndrew Lindesay
9433094fef3SAndrew Lindesay	path.Unset();
9443094fef3SAndrew Lindesay	fprintf(stdout, "unable to find the user cache directory for icons");
9453094fef3SAndrew Lindesay	return B_ERROR;
9463094fef3SAndrew Lindesay}
9473094fef3SAndrew Lindesay
9483094fef3SAndrew Lindesay
9493094fef3SAndrew Lindesaystatus_t
9503094fef3SAndrew LindesayModel::DumpExportPkgDataPath(BPath& path,
951c23b641cSAndrew Lindesay	const BString& repositorySourceCode) const
952c23b641cSAndrew Lindesay{
953c23b641cSAndrew Lindesay	BPath repoDataPath;
954c23b641cSAndrew Lindesay	BString leafName;
955c23b641cSAndrew Lindesay
956c23b641cSAndrew Lindesay	leafName.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(),
957c23b641cSAndrew Lindesay		fPreferredLanguage.String());
958c23b641cSAndrew Lindesay
959c23b641cSAndrew Lindesay	if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK
960c23b641cSAndrew Lindesay		&& repoDataPath.Append("HaikuDepot") == B_OK
961c23b641cSAndrew Lindesay		&& create_directory(repoDataPath.Path(), 0777) == B_OK
962c23b641cSAndrew Lindesay		&& repoDataPath.Append(leafName.String()) == B_OK) {
963c23b641cSAndrew Lindesay		path.SetTo(repoDataPath.Path());
964c23b641cSAndrew Lindesay		return B_OK;
965c23b641cSAndrew Lindesay	}
966c23b641cSAndrew Lindesay
967c23b641cSAndrew Lindesay	path.Unset();
968c23b641cSAndrew Lindesay	fprintf(stdout, "unable to find the user cache file for pkgs' data");
969c23b641cSAndrew Lindesay	return B_ERROR;
970c23b641cSAndrew Lindesay}
971c23b641cSAndrew Lindesay
972c23b641cSAndrew Lindesay
97312107be2SStephan Aßmusvoid
97412107be2SStephan AßmusModel::_UpdateIsFeaturedFilter()
97512107be2SStephan Aßmus{
97612107be2SStephan Aßmus	if (fShowFeaturedPackages && SearchTerms().IsEmpty())
97712107be2SStephan Aßmus		fIsFeaturedFilter = PackageFilterRef(new IsFeaturedFilter(), true);
97887084745SMichael Lotz	else
97912107be2SStephan Aßmus		fIsFeaturedFilter = PackageFilterRef(new AnyFilter(), true);
98012107be2SStephan Aßmus}
98112107be2SStephan Aßmus
98212107be2SStephan Aßmus
98387038da7SStephan Aßmusvoid
98487038da7SStephan AßmusModel::_PopulatePackageScreenshot(const PackageInfoRef& package,
98587038da7SStephan Aßmus	const ScreenshotInfo& info, int32 scaledWidth, bool fromCacheOnly)
98687038da7SStephan Aßmus{
98787038da7SStephan Aßmus	// See if there is a cached screenshot
98887038da7SStephan Aßmus	BFile screenshotFile;
98987038da7SStephan Aßmus	BPath screenshotCachePath;
99087038da7SStephan Aßmus	bool fileExists = false;
99187038da7SStephan Aßmus	BString screenshotName(info.Code());
99287038da7SStephan Aßmus	screenshotName << "@" << scaledWidth;
99387038da7SStephan Aßmus	screenshotName << ".png";
99487038da7SStephan Aßmus	time_t modifiedTime;
99587038da7SStephan Aßmus	if (find_directory(B_USER_CACHE_DIRECTORY, &screenshotCachePath) == B_OK
99687038da7SStephan Aßmus		&& screenshotCachePath.Append("HaikuDepot/Screenshots") == B_OK
99787038da7SStephan Aßmus		&& create_directory(screenshotCachePath.Path(), 0777) == B_OK
99887038da7SStephan Aßmus		&& screenshotCachePath.Append(screenshotName) == B_OK) {
99987038da7SStephan Aßmus		// Try opening the file in read-only mode, which will fail if its
100087038da7SStephan Aßmus		// not a file or does not exist.
100187038da7SStephan Aßmus		fileExists = screenshotFile.SetTo(screenshotCachePath.Path(),
100287038da7SStephan Aßmus			B_READ_ONLY) == B_OK;
100387038da7SStephan Aßmus		if (fileExists)
100487038da7SStephan Aßmus			screenshotFile.GetModificationTime(&modifiedTime);
100587038da7SStephan Aßmus	}
100687038da7SStephan Aßmus
100787038da7SStephan Aßmus	if (fileExists) {
100887038da7SStephan Aßmus		time_t now;
100987038da7SStephan Aßmus		time(&now);
101087038da7SStephan Aßmus		if (fromCacheOnly || now - modifiedTime < 60 * 60) {
101187038da7SStephan Aßmus			// Cache file is recent enough, just use it and return.
101287038da7SStephan Aßmus			BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(screenshotFile),
101387038da7SStephan Aßmus				true);
101487038da7SStephan Aßmus			BAutolock locker(&fLock);
101587038da7SStephan Aßmus			package->AddScreenshot(bitmapRef);
101687038da7SStephan Aßmus			return;
101787038da7SStephan Aßmus		}
101887038da7SStephan Aßmus	}
101987038da7SStephan Aßmus
102087038da7SStephan Aßmus	if (fromCacheOnly)
102187038da7SStephan Aßmus		return;
102287038da7SStephan Aßmus
102387038da7SStephan Aßmus	// Retrieve screenshot from web-app
102487038da7SStephan Aßmus	BMallocIO buffer;
102587038da7SStephan Aßmus
102687038da7SStephan Aßmus	int32 scaledHeight = scaledWidth * info.Height() / info.Width();
102787038da7SStephan Aßmus
102828075ee4SStephan Aßmus	status_t status = fWebAppInterface.RetrieveScreenshot(info.Code(),
102987038da7SStephan Aßmus		scaledWidth, scaledHeight, &buffer);
103087038da7SStephan Aßmus	if (status == B_OK) {
103187038da7SStephan Aßmus		BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(buffer), true);
103287038da7SStephan Aßmus		BAutolock locker(&fLock);
103387038da7SStephan Aßmus		package->AddScreenshot(bitmapRef);
103487038da7SStephan Aßmus		locker.Unlock();
103587038da7SStephan Aßmus		if (screenshotFile.SetTo(screenshotCachePath.Path(),
103687038da7SStephan Aßmus				B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) == B_OK) {
103787038da7SStephan Aßmus			screenshotFile.Write(buffer.Buffer(), buffer.BufferLength());
103887038da7SStephan Aßmus		}
1039e0881f21SStephan Aßmus	} else {
1040e0881f21SStephan Aßmus		fprintf(stderr, "Failed to retrieve screenshot for code '%s' "
1041e0881f21SStephan Aßmus			"at %" B_PRIi32 "x%" B_PRIi32 ".\n", info.Code().String(),
1042e0881f21SStephan Aßmus			scaledWidth, scaledHeight);
104387038da7SStephan Aßmus	}
104487038da7SStephan Aßmus}
104587038da7SStephan Aßmus
104687038da7SStephan Aßmus
10478f03a0f9SStephan Aßmus// #pragma mark - listener notification methods
10488f03a0f9SStephan Aßmus
10498f03a0f9SStephan Aßmus
10508f03a0f9SStephan Aßmusvoid
10518f03a0f9SStephan AßmusModel::_NotifyAuthorizationChanged()
10528f03a0f9SStephan Aßmus{
10538f03a0f9SStephan Aßmus	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
10548f03a0f9SStephan Aßmus		const ModelListenerRef& listener = fListeners.ItemAtFast(i);
10558f03a0f9SStephan Aßmus		if (listener.Get() != NULL)
10568f03a0f9SStephan Aßmus			listener->AuthorizationChanged();
10578f03a0f9SStephan Aßmus	}
10588f03a0f9SStephan Aßmus}
10593094fef3SAndrew Lindesay
10603094fef3SAndrew Lindesay
10613094fef3SAndrew Lindesayvoid
10623094fef3SAndrew LindesayModel::ForAllDepots(void (*func)(const DepotInfo& depot, void* context),
10633094fef3SAndrew Lindesay	void* context)
10643094fef3SAndrew Lindesay{
10653094fef3SAndrew Lindesay	for (int32 i = 0; i < fDepots.CountItems(); i++) {
10663094fef3SAndrew Lindesay		DepotInfo depotInfo = fDepots.ItemAtFast(i);
10673094fef3SAndrew Lindesay		func(depotInfo, context);
10683094fef3SAndrew Lindesay	}
10693094fef3SAndrew Lindesay}
10703094fef3SAndrew Lindesay
1071