HaikuDepot: Categories from Server

The list of categories for packages is currently
hard-coded into the HaikuDepot desktop
application.  This change will change that so that
the list is obtained from the HaikuDepot Server
system and is always up to date with the server's
list of categories.

Change-Id: I757732f4d771e1599d6ad9c85cd65905640de928
Reviewed-on: https://review.haiku-os.org/c/1478
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Andrew Lindesay
2019-05-25 11:10:50 +00:00
parent f259502c90
commit 89b16bb4d2
14 changed files with 259 additions and 161 deletions
+1
View File
@@ -173,6 +173,7 @@ local applicationSources =
AppUtils.cpp
DataIOUtils.cpp
LanguageMenuUtils.cpp
LocaleUtils.cpp
RepositoryUrlUtils.cpp
StorageUtils.cpp
ToFileUrlProtocolListener.cpp
+7 -9
View File
@@ -10,25 +10,23 @@
#include "HaikuDepotConstants.h"
#include "Logger.h"
#include "LocaleUtils.h"
static int32
LanguagesCompareFn(const LanguageRef& l1, const LanguageRef& l2)
{
const BLocale* locale = BLocaleRoster::Default()->GetDefaultLocale();
BCollator collator;
if (B_OK != locale->GetCollator(&collator)) {
debugger("unable to get the locale's collator");
}
BCollator* collator = LocaleUtils::GetSharedCollator();
BString name1;
BString name2;
int32 result = 0;
if (l1->GetName(name1) == B_OK && l2->GetName(name2) == B_OK)
return collator.Compare(name1.String(), name2.String());
result = collator->Compare(name1.String(), name2.String());
if (0 == result)
result = strcmp(l1->Code(), l2->Code());
return collator.Compare(l1->Code(), l2->Code());
return result;
}
+54 -53
View File
@@ -14,15 +14,18 @@
#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 "Logger.h"
#include "LocaleUtils.h"
#include "StorageUtils.h"
#include "RepositoryUrlUtils.h"
@@ -103,7 +106,7 @@ public:
const CategoryRef& category = categories.ItemAtFast(i);
if (category.Get() == NULL)
continue;
if (category->Name() == fCategory)
if (category->Code() == fCategory)
return true;
}
return false;
@@ -311,44 +314,22 @@ is_develop_package(const PackageInfoRef& package)
// #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(),
fCategoryAudio(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Audio"), "audio"), true),
fCategoryBusiness(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Business"), "business"), true),
fCategoryDevelopment(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Development"), "development"), true),
fCategoryEducation(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Education"), "education"), true),
fCategoryGames(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Games"), "games"), true),
fCategoryGraphics(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Graphics"), "graphics"), true),
fCategoryInternetAndNetwork(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Internet & Network"), "internetandnetwork"), true),
fCategoryProductivity(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Productivity"), "productivity"), true),
fCategoryScienceAndMathematics(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Science & Mathematics"), "scienceandmathematics"), true),
fCategorySystemAndUtilities(new PackageCategory(
BitmapRef(),
B_TRANSLATE("System & Utilities"), "systemandutilities"), true),
fCategoryVideo(new PackageCategory(
BitmapRef(),
B_TRANSLATE("Video"), "video"), true),
fCategories(&PackageCategoryCompareFn, NULL),
fCategoryFilter(PackageFilterRef(new AnyFilter(), true)),
fDepotFilter(""),
fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)),
@@ -360,23 +341,6 @@ Model::Model()
fShowDevelopPackages(false)
{
_UpdateIsFeaturedFilter();
// Don't forget to add new categories to this list:
fCategories.Add(fCategoryGames);
fCategories.Add(fCategoryBusiness);
fCategories.Add(fCategoryAudio);
fCategories.Add(fCategoryVideo);
fCategories.Add(fCategoryGraphics);
fCategories.Add(fCategoryEducation);
fCategories.Add(fCategoryProductivity);
fCategories.Add(fCategorySystemAndUtilities);
fCategories.Add(fCategoryInternetAndNetwork);
fCategories.Add(fCategoryDevelopment);
fCategories.Add(fCategoryScienceAndMathematics);
// TODO: The server will eventually support an API to
// get the defined categories and their translated names.
// This should then be used instead of hard-coded
// categories and translations in the app.
}
@@ -1057,6 +1021,18 @@ Model::_NotifyAuthorizationChanged()
}
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
@@ -1117,3 +1093,28 @@ Model::_MaybeLogJsonRpcError(const BMessage &responsePayload,
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);
}
+4 -37
View File
@@ -37,6 +37,7 @@ public:
virtual ~ModelListener();
virtual void AuthorizationChanged() = 0;
virtual void CategoryListChanged() = 0;
};
@@ -86,30 +87,7 @@ public:
void Clear();
// Access to global categories
const CategoryRef& CategoryAudio() const
{ return fCategoryAudio; }
const CategoryRef& CategoryBusiness() const
{ return fCategoryBusiness; }
const CategoryRef& CategoryDevelopment() const
{ return fCategoryDevelopment; }
const CategoryRef& CategoryEducation() const
{ return fCategoryEducation; }
const CategoryRef& CategoryInternetAndNetwork() const
{ return fCategoryInternetAndNetwork; }
const CategoryRef& CategoryGames() const
{ return fCategoryGames; }
const CategoryRef& CategoryGraphics() const
{ return fCategoryGraphics; }
const CategoryRef& CategoryProductivity() const
{ return fCategoryProductivity; }
const CategoryRef& CategoryScienceAndMathematics() const
{ return fCategoryScienceAndMathematics; }
const CategoryRef& CategorySystemAndUtilities() const
{ return fCategorySystemAndUtilities; }
const CategoryRef& CategoryVideo() const
{ return fCategoryVideo; }
void AddCategories(const CategoryList& categories);
const CategoryList& Categories() const
{ return fCategories; }
@@ -176,6 +154,7 @@ public:
void LogDepotsWithNoWebAppRepositoryCode() const;
private:
void _AddCategory(const CategoryRef& category);
status_t _LocalDataPath(const BString leaf,
BPath& path) const;
@@ -207,25 +186,13 @@ private:
bool ignoreAge, time_t maxAge) const;
void _NotifyAuthorizationChanged();
void _NotifyCategoryListChanged();
private:
BLocker fLock;
DepotList fDepots;
CategoryRef fCategoryAudio;
CategoryRef fCategoryBusiness;
CategoryRef fCategoryDevelopment;
CategoryRef fCategoryEducation;
CategoryRef fCategoryGames;
CategoryRef fCategoryGraphics;
CategoryRef fCategoryInternetAndNetwork;
CategoryRef fCategoryProductivity;
CategoryRef fCategoryScienceAndMathematics;
CategoryRef fCategorySystemAndUtilities;
CategoryRef fCategoryVideo;
// TODO: Dynamic categories retrieved from web-app
CategoryList fCategories;
PackageList fInstalledPackages;
+6 -13
View File
@@ -371,19 +371,16 @@ PublisherInfo::operator!=(const PublisherInfo& other) const
PackageCategory::PackageCategory()
:
BReferenceable(),
fIcon(),
fLabel(),
fCode(),
fName()
{
}
PackageCategory::PackageCategory(const BitmapRef& icon, const BString& label,
const BString& name)
PackageCategory::PackageCategory(const BString& code, const BString& name)
:
BReferenceable(),
fIcon(icon),
fLabel(label),
fCode(code),
fName(name)
{
}
@@ -392,8 +389,7 @@ PackageCategory::PackageCategory(const BitmapRef& icon, const BString& label,
PackageCategory::PackageCategory(const PackageCategory& other)
:
BReferenceable(),
fIcon(other.fIcon),
fLabel(other.fLabel),
fCode(other.fCode),
fName(other.fName)
{
}
@@ -402,8 +398,7 @@ PackageCategory::PackageCategory(const PackageCategory& other)
PackageCategory&
PackageCategory::operator=(const PackageCategory& other)
{
fIcon = other.fIcon;
fLabel = other.fLabel;
fCode = other.fCode;
fName = other.fName;
return *this;
}
@@ -412,9 +407,7 @@ PackageCategory::operator=(const PackageCategory& other)
bool
PackageCategory::operator==(const PackageCategory& other) const
{
return fIcon == other.fIcon
&& fLabel == other.fLabel
&& fName == other.fName;
return fCode == other.fCode && fName == other.fName;
}
+4 -8
View File
@@ -192,8 +192,7 @@ private:
class PackageCategory : public BReferenceable {
public:
PackageCategory();
PackageCategory(const BitmapRef& icon,
const BString& label,
PackageCategory(const BString& code,
const BString& name);
PackageCategory(const PackageCategory& other);
@@ -201,15 +200,12 @@ public:
bool operator==(const PackageCategory& other) const;
bool operator!=(const PackageCategory& other) const;
const BitmapRef& Icon() const
{ return fIcon; }
const BString& Label() const
{ return fLabel; }
const BString& Code() const
{ return fCode; }
const BString& Name() const
{ return fName; }
private:
BitmapRef fIcon;
BString fLabel;
BString fCode;
BString fName;
};
@@ -86,8 +86,8 @@ PackageFillingPkgListener::~PackageFillingPkgListener()
// TODO; performance could be improved by not needing the linear search
inline int32
PackageFillingPkgListener::IndexOfCategoryByName(
const BString& name) const
PackageFillingPkgListener::IndexOfCategoryByCode(
const BString& code) const
{
int32 i;
int32 categoryCount = fCategories.CountItems();
@@ -95,7 +95,7 @@ PackageFillingPkgListener::IndexOfCategoryByName(
for (i = 0; i < categoryCount; i++) {
const CategoryRef categoryRef = fCategories.ItemAtFast(i);
if (categoryRef->Name() == name)
if (categoryRef->Code() == code)
return i;
}
@@ -140,7 +140,7 @@ PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package,
for (i = 0; i < countPkgCategories; i++) {
BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code();
int categoryIndex = IndexOfCategoryByName(*(categoryCode));
int categoryIndex = IndexOfCategoryByCode(*(categoryCode));
if (categoryIndex == -1) {
printf("unable to find the category for [%s]\n",
@@ -107,8 +107,12 @@ ServerReferenceDataUpdateProcess::ProcessLocalData()
status_t
ServerReferenceDataUpdateProcess::_ProcessData(DumpExportReference* data)
{
// more to come here later...
return _ProcessNaturalLanguages(data);
status_t result = B_OK;
if (result == B_OK)
result = _ProcessNaturalLanguages(data);
if (result == B_OK)
result = _ProcessPkgCategories(data);
return result;
}
@@ -124,10 +128,13 @@ ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages(
for (int32 i = 0; i < data->CountNaturalLanguages(); i++) {
DumpExportReferenceNaturalLanguage* naturalLanguage =
data->NaturalLanguagesItemAt(i);
result.Add(LanguageRef(new Language(
*(naturalLanguage->Code()),
*(naturalLanguage->Name()),
naturalLanguage->IsPopular())));
result.Add(LanguageRef(
new Language(
*(naturalLanguage->Code()),
*(naturalLanguage->Name()),
naturalLanguage->IsPopular()
),
true));
}
{
@@ -142,6 +149,35 @@ ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages(
}
status_t
ServerReferenceDataUpdateProcess::_ProcessPkgCategories(
DumpExportReference* data)
{
printf("[%s] will populate %" B_PRId32 " pkg categories\n",
Name(), data->CountPkgCategories());
CategoryList result;
for (int32 i = 0; i < data->CountPkgCategories(); i++) {
DumpExportReferencePkgCategory* pkgCategory =
data->PkgCategoriesItemAt(i);
result.Add(CategoryRef(
new PackageCategory(
*(pkgCategory->Code()),
*(pkgCategory->Name())
),
true));
}
{
AutoLocker<BLocker> locker(fModel->Lock());
fModel->AddCategories(result);
}
return B_OK;
}
status_t
ServerReferenceDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const
{
@@ -46,6 +46,8 @@ private:
status_t _ProcessData(DumpExportReference* data);
status_t _ProcessNaturalLanguages(
DumpExportReference* data);
status_t _ProcessPkgCategories(
DumpExportReference* data);
private:
Model* fModel;
+44 -21
View File
@@ -1,5 +1,6 @@
/*
* Copyright 2013, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -8,6 +9,7 @@
#include <algorithm>
#include <stdio.h>
#include <AutoLocker.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <LayoutBuilder.h>
@@ -31,8 +33,8 @@ add_categories_to_menu(const CategoryList& categories, BMenu* menu)
for (int i = 0; i < categories.CountItems(); i++) {
const CategoryRef& category = categories.ItemAtFast(i);
BMessage* message = new BMessage(MSG_CATEGORY_SELECTED);
message->AddString("name", category->Name());
BMenuItem* item = new BMenuItem(category->Label(), message);
message->AddString("code", category->Code());
BMenuItem* item = new BMenuItem(category->Name(), message);
menu->AddItem(item);
}
}
@@ -88,7 +90,6 @@ FilterView::AttachedToWindow()
{
fShowField->Menu()->SetTargetForItems(Window());
fSearchTermsText->SetTarget(this);
fSearchTermsText->MakeFocus();
}
@@ -113,7 +114,7 @@ FilterView::MessageReceived(BMessage* message)
void
FilterView::AdoptModel(const Model& model)
FilterView::AdoptModel(Model& model)
{
// Adopt categories
BMenu* showMenu = fShowField->Menu();
@@ -122,25 +123,47 @@ FilterView::AdoptModel(const Model& model)
showMenu->AddItem(new BMenuItem(B_TRANSLATE("All categories"),
new BMessage(MSG_CATEGORY_SELECTED)));
showMenu->AddItem(new BSeparatorItem());
AutoLocker<BLocker> locker(model.Lock());
CategoryList categories = model.Categories();
add_categories_to_menu(model.Categories(), showMenu);
bool foundSelectedCategory = false;
for (int32 i = 0; i < showMenu->CountItems(); i++) {
BMenuItem* item = showMenu->ItemAt(i);
BMessage* message = item->Message();
if (message == NULL)
continue;
BString category;
if (message->FindString("name", &category) == B_OK
&& model.Category() == category) {
item->SetMarked(true);
foundSelectedCategory = true;
break;
}
if (!categories.IsEmpty()) {
showMenu->AddItem(new BSeparatorItem());
add_categories_to_menu(categories, showMenu);
}
if (!foundSelectedCategory)
showMenu->SetEnabled(!categories.IsEmpty());
if (!_SelectCategoryCode(showMenu, model.Category()))
showMenu->ItemAt(0)->SetMarked(true);
}
/*! Tries to mark the menu item that corresponds to the supplied
category code. If the supplied code was found and the item marked
then the method will return true.
*/
/*static*/ bool
FilterView::_SelectCategoryCode(BMenu* menu, const BString& code)
{
for (int32 i = 0; i < menu->CountItems(); i++) {
BMenuItem* item = menu->ItemAt(i);
if (_MatchesCategoryCode(item, code)) {
item->SetMarked(true);
return true;
}
}
return false;
}
/*static*/ bool
FilterView::_MatchesCategoryCode(BMenuItem* item, const BString& code)
{
BMessage* message = item->Message();
if (message == NULL)
return false;
BString itemCode;
message->FindString("code", &itemCode);
return itemCode == code;
}
+10 -1
View File
@@ -1,5 +1,6 @@
/*
* Copyright 2013, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef FILTER_VIEW_H
@@ -9,7 +10,9 @@
class BCheckBox;
class BMenu;
class BMenuField;
class BMenuItem;
class BTextControl;
class Model;
@@ -29,7 +32,13 @@ public:
virtual void AttachedToWindow();
virtual void MessageReceived(BMessage* message);
void AdoptModel(const Model& model);
void AdoptModel(Model& model);
private:
static bool _SelectCategoryCode(BMenu* menu,
const BString& code);
static bool _MatchesCategoryCode(BMenuItem* item,
const BString& code);
private:
BMenuField* fShowField;
+20 -9
View File
@@ -3,7 +3,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent, rene@gollent.com.
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2016-2018, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2019, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -63,6 +63,7 @@ enum {
MSG_LOG_IN = 'lgin',
MSG_LOG_OUT = 'lgot',
MSG_AUTHORIZATION_CHANGED = 'athc',
MSG_CATEGORIES_LIST_CHANGED = 'clic',
MSG_PACKAGE_CHANGED = 'pchd',
MSG_WORK_STATUS_CHANGE = 'wsch',
MSG_WORK_STATUS_CLEAR = 'wscl',
@@ -95,9 +96,9 @@ struct RefreshWorkerParameters {
};
class MessageModelListener : public ModelListener {
class MainWindowModelListener : public ModelListener {
public:
MessageModelListener(const BMessenger& messenger)
MainWindowModelListener(const BMessenger& messenger)
:
fMessenger(messenger)
{
@@ -109,6 +110,12 @@ public:
fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
}
virtual void CategoryListChanged()
{
if (fMessenger.IsValid())
fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED);
}
private:
BMessenger fMessenger;
};
@@ -123,7 +130,7 @@ MainWindow::MainWindow(const BMessage& settings)
fUserMenu(NULL),
fLogInItem(NULL),
fLogOutItem(NULL),
fModelListener(new MessageModelListener(BMessenger(this)), true),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fBulkLoadProcessCoordinator(NULL),
fSinglePackageMode(false)
{
@@ -221,7 +228,7 @@ MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
fUserMenu(NULL),
fLogInItem(NULL),
fLogOutItem(NULL),
fModelListener(new MessageModelListener(BMessenger(this)), true),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fBulkLoadProcessCoordinator(NULL),
fSinglePackageMode(true)
{
@@ -331,6 +338,10 @@ MainWindow::MessageReceived(BMessage* message)
_UpdateAuthorization();
break;
case MSG_CATEGORIES_LIST_CHANGED:
fFilterView->AdoptModel(fModel);
break;
case MSG_SHOW_FEATURED_PACKAGES:
// check to see if we aren't already on the current tab
if (fListTabs->Selection() ==
@@ -421,12 +432,12 @@ MainWindow::MessageReceived(BMessage* message)
case MSG_CATEGORY_SELECTED:
{
BString name;
if (message->FindString("name", &name) != B_OK)
name = "";
BString code;
if (message->FindString("code", &code) != B_OK)
code = "";
{
BAutolock locker(fModel.Lock());
fModel.SetCategory(name);
fModel.SetCategory(code);
}
_AdoptModel();
break;
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "LocaleUtils.h"
#include <Collator.h>
#include <Locale.h>
#include <LocaleRoster.h>
BCollator* LocaleUtils::sSharedCollator = NULL;
/*static*/ BCollator*
LocaleUtils::GetSharedCollator()
{
if (sSharedCollator == NULL) {
GetCollator(sSharedCollator);
}
return sSharedCollator;
}
/*static*/ void
LocaleUtils::GetCollator(BCollator* collator)
{
const BLocale* locale = BLocaleRoster::Default()->GetDefaultLocale();
if (B_OK != locale->GetCollator(collator)) {
debugger("unable to get the locale's collator");
exit(EXIT_FAILURE);
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef LOCALE_UTILS_H
#define LOCALE_UTILS_H
class BCollator;
class LocaleUtils {
public:
static BCollator* GetSharedCollator();
private:
static void GetCollator(BCollator* collator);
private:
static BCollator* sSharedCollator;
};
#endif // LOCALE_UTILS_H