Files
haiku-beta6/src/apps/haikudepot/model/Model.cpp
T
Andrew Lindesay 1f3909ade1 HaikuDepot: Referencable Fixes
Some misuse of BReferencable / BReference are causing the
debugger to fire while debugging another issue.  This
should fix some of it.

Change-Id: I895e209afe9e350e35b111739140c85133107227
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2556
Reviewed-by: Adrien Destugues <[email protected]>
2020-05-03 09:47:43 +00:00

1137 lines
26 KiB
C++

/*
* Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2014, Axel Dörfler <[email protected]>.
* Copyright 2016-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "Model.h"
#include <ctime>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <Autolock.h>
#include <Catalog.h>
#include <Collator.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <KeyStore.h>
#include <Locale.h>
#include <LocaleRoster.h>
#include <Message.h>
#include <Path.h>
#include "HaikuDepotConstants.h"
#include "Logger.h"
#include "LocaleUtils.h"
#include "StorageUtils.h"
#include "RepositoryUrlUtils.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Model"
#define KEY_STORE_IDENTIFIER_PREFIX "hds.password."
// this prefix is added before the nickname in the keystore
// so that HDS username/password pairs can be identified.
static const char* kHaikuDepotKeyring = "HaikuDepot";
PackageFilter::~PackageFilter()
{
}
ModelListener::~ModelListener()
{
}
// #pragma mark - PackageFilters
class AnyFilter : public PackageFilter {
public:
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
return true;
}
};
class DepotFilter : public PackageFilter {
public:
DepotFilter(const DepotInfo& depot)
:
fDepot(depot)
{
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
// TODO: Maybe a PackageInfo ought to know the Depot it came from?
// But right now the same package could theoretically be provided
// from different depots and the filter would work correctly.
// Also the PackageList could actually contain references to packages
// instead of the packages as objects. The equal operator is quite
// expensive as is.
return fDepot.Packages().Contains(package);
}
const BString& Depot() const
{
return fDepot.Name();
}
private:
DepotInfo fDepot;
};
class CategoryFilter : public PackageFilter {
public:
CategoryFilter(const BString& category)
:
fCategory(category)
{
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
if (package.Get() == NULL)
return false;
const CategoryList& categories = package->Categories();
for (int i = categories.CountItems() - 1; i >= 0; i--) {
const CategoryRef& category = categories.ItemAtFast(i);
if (category.Get() == NULL)
continue;
if (category->Code() == fCategory)
return true;
}
return false;
}
const BString& Category() const
{
return fCategory;
}
private:
BString fCategory;
};
class ContainedInFilter : public PackageFilter {
public:
ContainedInFilter(const PackageList& packageList)
:
fPackageList(packageList)
{
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
return fPackageList.Contains(package);
}
private:
const PackageList& fPackageList;
};
class ContainedInEitherFilter : public PackageFilter {
public:
ContainedInEitherFilter(const PackageList& packageListA,
const PackageList& packageListB)
:
fPackageListA(packageListA),
fPackageListB(packageListB)
{
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
return fPackageListA.Contains(package)
|| fPackageListB.Contains(package);
}
private:
const PackageList& fPackageListA;
const PackageList& fPackageListB;
};
class NotContainedInFilter : public PackageFilter {
public:
NotContainedInFilter(const PackageList* packageList, ...)
{
va_list args;
va_start(args, packageList);
while (true) {
const PackageList* packageList = va_arg(args, const PackageList*);
if (packageList == NULL)
break;
fPackageLists.Add(packageList);
}
va_end(args);
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
if (package.Get() == NULL)
return false;
printf("TEST %s\n", package->Name().String());
for (int32 i = 0; i < fPackageLists.CountItems(); i++) {
if (fPackageLists.ItemAtFast(i)->Contains(package)) {
printf(" contained in %" B_PRId32 "\n", i);
return false;
}
}
return true;
}
private:
List<const PackageList*, true> fPackageLists;
};
class StateFilter : public PackageFilter {
public:
StateFilter(PackageState state)
:
fState(state)
{
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
return package->State() == NONE;
}
private:
PackageState fState;
};
class SearchTermsFilter : public PackageFilter {
public:
SearchTermsFilter(const BString& searchTerms)
{
// Separate the string into terms at spaces
int32 index = 0;
while (index < searchTerms.Length()) {
int32 nextSpace = searchTerms.FindFirst(" ", index);
if (nextSpace < 0)
nextSpace = searchTerms.Length();
if (nextSpace > index) {
BString term;
searchTerms.CopyInto(term, index, nextSpace - index);
term.ToLower();
fSearchTerms.Add(term);
}
index = nextSpace + 1;
}
}
virtual bool AcceptsPackage(const PackageInfoRef& package) const
{
if (package.Get() == NULL)
return false;
// Every search term must be found in one of the package texts
for (int32 i = fSearchTerms.CountItems() - 1; i >= 0; i--) {
const BString& term = fSearchTerms.ItemAtFast(i);
if (!_TextContains(package->Name(), term)
&& !_TextContains(package->Title(), term)
&& !_TextContains(package->Publisher().Name(), term)
&& !_TextContains(package->ShortDescription(), term)
&& !_TextContains(package->FullDescription(), term)) {
return false;
}
}
return true;
}
BString SearchTerms() const
{
BString searchTerms;
for (int32 i = 0; i < fSearchTerms.CountItems(); i++) {
const BString& term = fSearchTerms.ItemAtFast(i);
if (term.IsEmpty())
continue;
if (!searchTerms.IsEmpty())
searchTerms.Append(" ");
searchTerms.Append(term);
}
return searchTerms;
}
private:
bool _TextContains(BString text, const BString& string) const
{
text.ToLower();
int32 index = text.FindFirst(string);
return index >= 0;
}
private:
StringList fSearchTerms;
};
static inline bool
is_source_package(const PackageInfoRef& package)
{
const BString& packageName = package->Name();
return packageName.EndsWith("_source");
}
static inline bool
is_develop_package(const PackageInfoRef& package)
{
const BString& packageName = package->Name();
return packageName.EndsWith("_devel")
|| packageName.EndsWith("_debuginfo");
}
// #pragma mark - Model
static int32
PackageCategoryCompareFn(const CategoryRef& c1, const CategoryRef& c2)
{
BCollator* collator = LocaleUtils::GetSharedCollator();
int32 result = collator->Compare(c1->Name().String(),
c2->Name().String());
if (result == 0)
result = c1->Code().Compare(c2->Code());
return result;
}
Model::Model()
:
fDepots(),
fCategories(&PackageCategoryCompareFn, NULL),
fCategoryFilter(PackageFilterRef(new AnyFilter(), true)),
fDepotFilter(""),
fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)),
fPackageListViewMode(PROMINENT),
fShowAvailablePackages(true),
fShowInstalledPackages(true),
fShowSourcePackages(false),
fShowDevelopPackages(false)
{
}
Model::~Model()
{
}
LanguageModel&
Model::Language()
{
return fLanguageModel;
}
bool
Model::AddListener(const ModelListenerRef& listener)
{
return fListeners.Add(listener);
}
PackageList
Model::CreatePackageList() const
{
// Iterate all packages from all depots.
// If configured, restrict depot, filter by search terms, status, name ...
PackageList resultList;
for (int32 i = 0; i < fDepots.CountItems(); i++) {
const DepotInfo& depot = fDepots.ItemAtFast(i);
if (fDepotFilter.Length() > 0 && fDepotFilter != depot.Name())
continue;
const PackageList& packages = depot.Packages();
for (int32 j = 0; j < packages.CountItems(); j++) {
const PackageInfoRef& package = packages.ItemAtFast(j);
if (MatchesFilter(package))
resultList.Add(package);
}
}
return resultList;
}
bool
Model::MatchesFilter(const PackageInfoRef& package) const
{
return fCategoryFilter->AcceptsPackage(package)
&& fSearchTermsFilter->AcceptsPackage(package)
&& (fShowAvailablePackages || package->State() != NONE)
&& (fShowInstalledPackages || package->State() != ACTIVATED)
&& (fShowSourcePackages || !is_source_package(package))
&& (fShowDevelopPackages || !is_develop_package(package));
}
bool
Model::AddDepot(const DepotInfo& depot)
{
return fDepots.Add(depot);
}
bool
Model::HasDepot(const BString& name) const
{
return NULL != DepotForName(name);
}
const DepotInfo*
Model::DepotForName(const BString& name) const
{
for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
if (fDepots.ItemAtFast(i).Name() == name)
return &fDepots.ItemAtFast(i);
}
return NULL;
}
bool
Model::SyncDepot(const DepotInfo& depot)
{
for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
const DepotInfo& existingDepot = fDepots.ItemAtFast(i);
if (existingDepot.Name() == depot.Name()) {
DepotInfo mergedDepot(existingDepot);
mergedDepot.SyncPackages(depot.Packages());
fDepots.Replace(i, mergedDepot);
return true;
}
}
return false;
}
bool
Model::HasAnyProminentPackages()
{
for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
const DepotInfo& existingDepot = fDepots.ItemAtFast(i);
if (existingDepot.HasAnyProminentPackages())
return true;
}
return false;
}
void
Model::Clear()
{
fDepots.Clear();
}
void
Model::SetPackageState(const PackageInfoRef& package, PackageState state)
{
switch (state) {
default:
case NONE:
fInstalledPackages.Remove(package);
fActivatedPackages.Remove(package);
fUninstalledPackages.Remove(package);
break;
case INSTALLED:
if (!fInstalledPackages.Contains(package))
fInstalledPackages.Add(package);
fActivatedPackages.Remove(package);
fUninstalledPackages.Remove(package);
break;
case ACTIVATED:
if (!fInstalledPackages.Contains(package))
fInstalledPackages.Add(package);
if (!fActivatedPackages.Contains(package))
fActivatedPackages.Add(package);
fUninstalledPackages.Remove(package);
break;
case UNINSTALLED:
fInstalledPackages.Remove(package);
fActivatedPackages.Remove(package);
if (!fUninstalledPackages.Contains(package))
fUninstalledPackages.Add(package);
break;
}
package->SetState(state);
}
// #pragma mark - filters
void
Model::SetCategory(const BString& category)
{
PackageFilter* filter;
if (category.Length() == 0)
filter = new AnyFilter();
else
filter = new CategoryFilter(category);
fCategoryFilter.SetTo(filter, true);
}
BString
Model::Category() const
{
CategoryFilter* filter
= dynamic_cast<CategoryFilter*>(fCategoryFilter.Get());
if (filter == NULL)
return "";
return filter->Category();
}
void
Model::SetDepot(const BString& depot)
{
fDepotFilter = depot;
}
BString
Model::Depot() const
{
return fDepotFilter;
}
void
Model::SetSearchTerms(const BString& searchTerms)
{
PackageFilter* filter;
if (searchTerms.Length() == 0)
filter = new AnyFilter();
else
filter = new SearchTermsFilter(searchTerms);
fSearchTermsFilter.SetTo(filter, true);
}
BString
Model::SearchTerms() const
{
SearchTermsFilter* filter
= dynamic_cast<SearchTermsFilter*>(fSearchTermsFilter.Get());
if (filter == NULL)
return "";
return filter->SearchTerms();
}
void
Model::SetPackageListViewMode(package_list_view_mode mode)
{
fPackageListViewMode = mode;
}
void
Model::SetShowAvailablePackages(bool show)
{
fShowAvailablePackages = show;
}
void
Model::SetShowInstalledPackages(bool show)
{
fShowInstalledPackages = show;
}
void
Model::SetShowSourcePackages(bool show)
{
fShowSourcePackages = show;
}
void
Model::SetShowDevelopPackages(bool show)
{
fShowDevelopPackages = show;
}
// #pragma mark - information retrieval
/*! Initially only superficial data is loaded from the server into the data
model of the packages. When the package is viewed, additional data needs
to be populated including ratings. This method takes care of that.
*/
void
Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
{
// TODO: There should probably also be a way to "unpopulate" the
// package information. Maybe a cache of populated packages, so that
// packages loose their extra information after a certain amount of
// time when they have not been accessed/displayed in the UI. Otherwise
// HaikuDepot will consume more and more resources in the packages.
// Especially screen-shots will be a problem eventually.
{
BAutolock locker(&fLock);
bool alreadyPopulated = fPopulatedPackages.Contains(package);
if ((flags & POPULATE_FORCE) == 0 && alreadyPopulated)
return;
if (!alreadyPopulated)
fPopulatedPackages.Add(package);
}
if ((flags & POPULATE_CHANGELOG) != 0) {
_PopulatePackageChangelog(package);
}
if ((flags & POPULATE_USER_RATINGS) != 0) {
// Retrieve info from web-app
BMessage info;
BString packageName;
BString webAppRepositoryCode;
{
BAutolock locker(&fLock);
packageName = package->Name();
const DepotInfo* depot = DepotForName(package->DepotName());
if (depot != NULL)
webAppRepositoryCode = depot->WebAppRepositoryCode();
}
status_t status = fWebAppInterface
.RetreiveUserRatingsForPackageForDisplay(packageName,
webAppRepositoryCode, 0, PACKAGE_INFO_MAX_USER_RATINGS,
info);
if (status == B_OK) {
// Parse message
BMessage result;
BMessage items;
if (info.FindMessage("result", &result) == B_OK
&& result.FindMessage("items", &items) == B_OK) {
BAutolock locker(&fLock);
package->ClearUserRatings();
int32 index = 0;
while (true) {
BString name;
name << index++;
BMessage item;
if (items.FindMessage(name, &item) != B_OK)
break;
BString code;
if (item.FindString("code", &code) != B_OK) {
printf("corrupt user rating at index %" B_PRIi32 "\n",
index);
continue;
}
BString user;
BMessage userInfo;
if (item.FindMessage("user", &userInfo) != B_OK
|| userInfo.FindString("nickname", &user) != B_OK) {
printf("ignored user rating [%s] without a user "
"nickname\n", code.String());
continue;
}
// Extract basic info, all items are optional
BString languageCode;
BString comment;
double rating;
item.FindString("naturalLanguageCode", &languageCode);
item.FindString("comment", &comment);
if (item.FindDouble("rating", &rating) != B_OK)
rating = -1;
if (comment.Length() == 0 && rating == -1) {
printf("rating [%s] has no comment or rating so will be"
"ignored\n", code.String());
continue;
}
// For which version of the package was the rating?
BString major = "?";
BString minor = "?";
BString micro = "";
double revision = -1;
BString architectureCode = "";
BMessage version;
if (item.FindMessage("pkgVersion", &version) == B_OK) {
version.FindString("major", &major);
version.FindString("minor", &minor);
version.FindString("micro", &micro);
version.FindDouble("revision", &revision);
version.FindString("architectureCode",
&architectureCode);
}
BString versionString = major;
versionString << ".";
versionString << minor;
if (!micro.IsEmpty()) {
versionString << ".";
versionString << micro;
}
if (revision > 0) {
versionString << "-";
versionString << (int) revision;
}
if (!architectureCode.IsEmpty()) {
versionString << " " << STR_MDASH << " ";
versionString << architectureCode;
}
double createTimestamp;
item.FindDouble("createTimestamp", &createTimestamp);
// Add the rating to the PackageInfo
UserRating userRating = UserRating(UserInfo(user), rating,
comment, languageCode, versionString,
(uint64) createTimestamp);
package->AddUserRating(userRating);
if (Logger::IsDebugEnabled()) {
printf("rating [%s] retrieved from server\n",
code.String());
}
}
if (Logger::IsDebugEnabled()) {
printf("did retrieve %" B_PRIi32 " user ratings for [%s]\n",
index - 1, packageName.String());
}
} else {
_MaybeLogJsonRpcError(info, "retrieve user ratings");
}
} else {
printf("unable to retrieve user ratings\n");
}
}
if ((flags & POPULATE_SCREEN_SHOTS) != 0) {
ScreenshotInfoList screenshotInfos;
{
BAutolock locker(&fLock);
screenshotInfos = package->ScreenshotInfos();
package->ClearScreenshots();
}
for (int i = 0; i < screenshotInfos.CountItems(); i++) {
const ScreenshotInfo& info = screenshotInfos.ItemAtFast(i);
_PopulatePackageScreenshot(package, info, 320, false);
}
}
}
void
Model::_PopulatePackageChangelog(const PackageInfoRef& package)
{
BMessage info;
BString packageName;
{
BAutolock locker(&fLock);
packageName = package->Name();
}
status_t status = fWebAppInterface.GetChangelog(packageName, info);
if (status == B_OK) {
// Parse message
BMessage result;
BString content;
if (info.FindMessage("result", &result) == B_OK) {
if (result.FindString("content", &content) == B_OK
&& 0 != content.Length()) {
BAutolock locker(&fLock);
package->SetChangelog(content);
if (Logger::IsDebugEnabled()) {
fprintf(stdout, "changelog populated for [%s]\n",
packageName.String());
}
} else {
if (Logger::IsDebugEnabled()) {
fprintf(stdout, "no changelog present for [%s]\n",
packageName.String());
}
}
} else {
_MaybeLogJsonRpcError(info, "populate package changelog");
}
} else {
fprintf(stdout, "unable to obtain the changelog for the package"
" [%s]\n", packageName.String());
}
}
static void
model_remove_key_for_user(const BString& nickname)
{
if (nickname.IsEmpty())
return;
BKeyStore keyStore;
BPasswordKey key;
BString passwordIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
<< nickname;
status_t result = keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD,
passwordIdentifier, key);
switch (result) {
case B_OK:
result = keyStore.RemoveKey(kHaikuDepotKeyring, key);
if (result != B_OK) {
printf("! error occurred when removing password for nickname "
"[%s] : %s\n", nickname.String(), strerror(result));
}
break;
case B_ENTRY_NOT_FOUND:
return;
default:
printf("! error occurred when finding password for nickname "
"[%s] : %s\n", nickname.String(), strerror(result));
break;
}
}
void
Model::SetNickname(BString nickname)
{
BString password;
BString existingNickname = Nickname();
// this happens when the user is logging out. Best to remove the password
// stored for the existing user since it is no longer required.
if (!existingNickname.IsEmpty() && nickname.IsEmpty())
model_remove_key_for_user(existingNickname);
if (nickname.Length() > 0) {
BPasswordKey key;
BKeyStore keyStore;
BString passwordIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
<< nickname;
if (keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD,
passwordIdentifier, key) == B_OK) {
password = key.Password();
}
if (password.IsEmpty())
nickname = "";
}
SetAuthorization(nickname, password, false);
}
const BString&
Model::Nickname() const
{
return fWebAppInterface.Nickname();
}
void
Model::SetAuthorization(const BString& nickname, const BString& passwordClear,
bool storePassword)
{
BString existingNickname = Nickname();
if (storePassword) {
// no point continuing to store the password for the previous user.
if (!existingNickname.IsEmpty())
model_remove_key_for_user(existingNickname);
// adding a key that is already there does not seem to override the
// existing key so the old key needs to be removed first.
if (!nickname.IsEmpty())
model_remove_key_for_user(nickname);
if (!nickname.IsEmpty() && !passwordClear.IsEmpty()) {
BString keyIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
<< nickname;
BPasswordKey key(passwordClear, B_KEY_PURPOSE_WEB, keyIdentifier);
BKeyStore keyStore;
keyStore.AddKeyring(kHaikuDepotKeyring);
keyStore.AddKey(kHaikuDepotKeyring, key);
}
}
BAutolock locker(&fLock);
fWebAppInterface.SetAuthorization(UserCredentials(nickname, passwordClear));
if (nickname != existingNickname)
_NotifyAuthorizationChanged();
}
/*! When bulk repository data comes down from the server, it will
arrive as a json.gz payload. This is stored locally as a cache
and this method will provide the on-disk storage location for
this file.
*/
status_t
Model::DumpExportRepositoryDataPath(BPath& path) const
{
BString leaf;
leaf.SetToFormat("repository-all_%s.json.gz",
LanguageModel().PreferredLanguage()->Code());
return StorageUtils::LocalWorkingFilesPath(leaf, path);
}
/*! When the system downloads reference data (eg; categories) from the server
then the downloaded data is stored and cached at the path defined by this
method.
*/
status_t
Model::DumpExportReferenceDataPath(BPath& path) const
{
BString leaf;
leaf.SetToFormat("reference-all_%s.json.gz",
LanguageModel().PreferredLanguage()->Code());
return StorageUtils::LocalWorkingFilesPath(leaf, path);
}
status_t
Model::IconStoragePath(BPath& path) const
{
return StorageUtils::LocalWorkingDirectoryPath("__allicons", path);
}
status_t
Model::DumpExportPkgDataPath(BPath& path,
const BString& repositorySourceCode) const
{
BString leaf;
leaf.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(),
LanguageModel().PreferredLanguage()->Code());
return StorageUtils::LocalWorkingFilesPath(leaf, path);
}
void
Model::_PopulatePackageScreenshot(const PackageInfoRef& package,
const ScreenshotInfo& info, int32 scaledWidth, bool fromCacheOnly)
{
// See if there is a cached screenshot
BFile screenshotFile;
BPath screenshotCachePath;
status_t result = StorageUtils::LocalWorkingDirectoryPath(
"Screenshots", screenshotCachePath);
if (result != B_OK) {
printf("[!] unable to get the screenshot dir - unable to proceed");
return;
}
bool fileExists = false;
BString screenshotName(info.Code());
screenshotName << "@" << scaledWidth;
screenshotName << ".png";
time_t modifiedTime;
if (screenshotCachePath.Append(screenshotName) == B_OK) {
// Try opening the file in read-only mode, which will fail if its
// not a file or does not exist.
fileExists = screenshotFile.SetTo(screenshotCachePath.Path(),
B_READ_ONLY) == B_OK;
if (fileExists)
screenshotFile.GetModificationTime(&modifiedTime);
}
if (fileExists) {
time_t now;
time(&now);
if (fromCacheOnly || now - modifiedTime < 60 * 60) {
// Cache file is recent enough, just use it and return.
BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(screenshotFile),
true);
BAutolock locker(&fLock);
package->AddScreenshot(bitmapRef);
return;
}
}
if (fromCacheOnly)
return;
// Retrieve screenshot from web-app
BMallocIO buffer;
int32 scaledHeight = scaledWidth * info.Height() / info.Width();
status_t status = fWebAppInterface.RetrieveScreenshot(info.Code(),
scaledWidth, scaledHeight, &buffer);
if (status == B_OK) {
BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(buffer), true);
BAutolock locker(&fLock);
package->AddScreenshot(bitmapRef);
locker.Unlock();
if (screenshotFile.SetTo(screenshotCachePath.Path(),
B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) == B_OK) {
screenshotFile.Write(buffer.Buffer(), buffer.BufferLength());
}
} else {
fprintf(stderr, "Failed to retrieve screenshot for code '%s' "
"at %" B_PRIi32 "x%" B_PRIi32 ".\n", info.Code().String(),
scaledWidth, scaledHeight);
}
}
// #pragma mark - listener notification methods
void
Model::_NotifyAuthorizationChanged()
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
const ModelListenerRef& listener = fListeners.ItemAtFast(i);
if (listener.Get() != NULL)
listener->AuthorizationChanged();
}
}
void
Model::_NotifyCategoryListChanged()
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
const ModelListenerRef& listener = fListeners.ItemAtFast(i);
if (listener.Get() != NULL)
listener->CategoryListChanged();
}
}
/*! This method will find the stored 'DepotInfo' that correlates to the
supplied 'url' and will invoke the mapper function in order to get a
replacement for the 'DepotInfo'. The 'url' is a unique identifier
for the repository that holds across mirrors.
*/
void
Model::ReplaceDepotByUrl(const BString& URL, DepotMapper* depotMapper,
void* context)
{
for (int32 i = 0; i < fDepots.CountItems(); i++) {
DepotInfo depotInfo = fDepots.ItemAtFast(i);
if (RepositoryUrlUtils::EqualsNormalized(URL, depotInfo.URL())) {
BAutolock locker(&fLock);
fDepots.Replace(i, depotMapper->MapDepot(depotInfo, context));
}
}
}
void
Model::LogDepotsWithNoWebAppRepositoryCode() const
{
int32 i;
for (i = 0; i < fDepots.CountItems(); i++) {
const DepotInfo& depot = fDepots.ItemAt(i);
if (depot.WebAppRepositoryCode().Length() == 0) {
printf("depot [%s]", depot.Name().String());
if (depot.URL().Length() > 0)
printf(" (%s)", depot.URL().String());
printf(" correlates with no repository in the haiku"
"depot server system\n");
}
}
}
void
Model::_MaybeLogJsonRpcError(const BMessage &responsePayload,
const char *sourceDescription) const
{
BMessage error;
BString errorMessage;
double errorCode;
if (responsePayload.FindMessage("error", &error) == B_OK
&& error.FindString("message", &errorMessage) == B_OK
&& error.FindDouble("code", &errorCode) == B_OK) {
printf("[%s] --> error : [%s] (%f)\n", sourceDescription,
errorMessage.String(), errorCode);
} else {
printf("[%s] --> an undefined error has occurred\n", sourceDescription);
}
}
void
Model::AddCategories(const CategoryList& categories)
{
int32 i;
for (i = 0; i < categories.CountItems(); i++)
_AddCategory(categories.ItemAt(i));
_NotifyCategoryListChanged();
}
void
Model::_AddCategory(const CategoryRef& category)
{
int32 i;
for (i = 0; i < fCategories.CountItems(); i++) {
if (fCategories.ItemAt(i)->Code() == category->Code()) {
fCategories.Replace(i, category);
return;
}
}
fCategories.Add(category);
}