From f85e030047e96fc3ae57af2fff35e6059d71ea77 Mon Sep 17 00:00:00 2001 From: Andrew Lindesay Date: Sun, 28 Apr 2019 22:17:34 +0200 Subject: [PATCH] HaikuDepot: Load Languages from HDS The HaikuDepot application has, thus far had its own hard-coded list of languages that the user is able to choose when (a) creating a new account or (b) creating a user-rating. This change will mean that those languages are loaded from the HDS server dynamically and in this way the user can choose from the full list. There have also been improvements to the way in which the languages are displayed in the menu as well. Change-Id: If7cb7b87f348ca59d503d276a22444e72d0e6168 Reviewed-on: https://review.haiku-os.org/c/1425 Reviewed-by: Adrien Destugues --- src/apps/haikudepot/HaikuDepotConstants.h | 10 +- src/apps/haikudepot/Jamfile | 62 ++++++- .../haikudepot/build/jam/HdsSchemaGenRules | 23 +++ .../build/scripts/jsonschema2cppmodel.py | 22 ++- src/apps/haikudepot/model/LanguageModel.cpp | 168 ++++++++++++++++++ src/apps/haikudepot/model/LanguageModel.h | 47 +++++ src/apps/haikudepot/model/Model.cpp | 91 +++++----- src/apps/haikudepot/model/Model.h | 19 +- src/apps/haikudepot/model/PackageInfo.cpp | 37 +++- src/apps/haikudepot/model/PackageInfo.h | 27 ++- .../server/ProcessCoordinatorFactory.cpp | 12 +- .../ServerReferenceDataUpdateProcess.cpp | 152 ++++++++++++++++ .../server/ServerReferenceDataUpdateProcess.h | 55 ++++++ .../ServerRepositoryDataUpdateProcess.cpp | 7 +- .../haikudepot/server/WebAppInterface.cpp | 17 +- src/apps/haikudepot/server/WebAppInterface.h | 6 +- .../server/schema/dumpexportreference.json | 72 ++++++++ src/apps/haikudepot/ui/PackageInfoView.cpp | 28 +-- src/apps/haikudepot/ui/RatePackageWindow.cpp | 42 ++--- src/apps/haikudepot/ui/RatePackageWindow.h | 4 +- src/apps/haikudepot/ui/UserLoginWindow.cpp | 38 ++-- src/apps/haikudepot/ui/UserLoginWindow.h | 3 +- src/apps/haikudepot/util/AppUtils.cpp | 2 +- .../haikudepot/util/LanguageMenuUtils.cpp | 137 ++++++++++++++ src/apps/haikudepot/util/LanguageMenuUtils.h | 43 +++++ 25 files changed, 950 insertions(+), 174 deletions(-) create mode 100644 src/apps/haikudepot/model/LanguageModel.cpp create mode 100644 src/apps/haikudepot/model/LanguageModel.h create mode 100644 src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp create mode 100644 src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h create mode 100644 src/apps/haikudepot/server/schema/dumpexportreference.json create mode 100644 src/apps/haikudepot/util/LanguageMenuUtils.cpp create mode 100644 src/apps/haikudepot/util/LanguageMenuUtils.h diff --git a/src/apps/haikudepot/HaikuDepotConstants.h b/src/apps/haikudepot/HaikuDepotConstants.h index d251f69c20..9671451c7e 100644 --- a/src/apps/haikudepot/HaikuDepotConstants.h +++ b/src/apps/haikudepot/HaikuDepotConstants.h @@ -1,5 +1,5 @@ /* - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef HAIKU_DEPOT_CONSTANTS_H @@ -18,7 +18,8 @@ enum { MSG_SERVER_DATA_CHANGED = 'svdc', MSG_ALERT_SIMPLE_ERROR = 'nser', MSG_DID_ADD_USER_RATING = 'adur', - MSG_DID_UPDATE_USER_RATING = 'upur' + MSG_DID_UPDATE_USER_RATING = 'upur', + MSG_LANGUAGE_SELECTED = 'lngs' }; @@ -54,4 +55,9 @@ enum { RSRC_ARROW_RIGHT = 550, }; + +#define LANGUAGE_DEFAULT_CODE "en" +#define LANGUAGE_DEFAULT Language(LANGUAGE_DEFAULT_CODE, "English", true) + + #endif // HAIKU_DEPOT_CONSTANTS_H \ No newline at end of file diff --git a/src/apps/haikudepot/Jamfile b/src/apps/haikudepot/Jamfile index 2a04c7ba49..875f9fec7b 100644 --- a/src/apps/haikudepot/Jamfile +++ b/src/apps/haikudepot/Jamfile @@ -16,12 +16,19 @@ local dumpExportRepositoryBulkListerTargetDirectory = local dumpExportPkgBulkListenerTargetDirectory = [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportpkgbulklistener ] ; +local dumpExportReferenceListenerTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) + dumpexportreferencelistener ] ; + local dumpExportPkgModelTargetDirectory = [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportpkgmodel ] ; local dumpExportRepositoryModelTargetDirectory = [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) dumpexportrepositorymodel ] ; +local dumpExportReferenceModelTargetDirectory = + [ FDirName $(HAIKUDEPOT_GENERATED_SOURCES_DIRECTORY) + dumpexportreferencemodel ] ; # During the build process, some sources are generated into a directory. In # order to maintain a timestamp on that generation process, a dummy file is @@ -34,10 +41,15 @@ local dumpExportRepositoryBulkListerTargetFile = [ FDirName $(dumpExportRepositoryBulkListerTargetDirectory) $(dummyFile) ] ; local dumpExportPkgBulkListenerTargetFile = [ FDirName $(dumpExportPkgBulkListenerTargetDirectory) $(dummyFile) ] ; +local dumpExportReferenceListenerTargetFile = + [ FDirName $(dumpExportReferenceListenerTargetDirectory) $(dummyFile) ] ; + local dumpExportPkgModelTargetFile = [ FDirName $(dumpExportPkgModelTargetDirectory) $(dummyFile) ] ; local dumpExportRepositoryModelTargetFile = [ FDirName $(dumpExportRepositoryModelTargetDirectory) $(dummyFile) ] ; +local dumpExportReferenceModelTargetFile = + [ FDirName $(dumpExportReferenceModelTargetDirectory) $(dummyFile) ] ; # source directories local sourceDirs = @@ -57,10 +69,14 @@ for sourceDir in $(sourceDirs) { } SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src servers package ] ; + SEARCH_SOURCE += $(dumpExportRepositoryBulkListerTargetDirectory) ; SEARCH_SOURCE += $(dumpExportPkgBulkListenerTargetDirectory) ; +SEARCH_SOURCE += $(dumpExportReferenceListenerTargetDirectory) ; + SEARCH_SOURCE += $(dumpExportPkgModelTargetDirectory) ; SEARCH_SOURCE += $(dumpExportRepositoryModelTargetDirectory) ; +SEARCH_SOURCE += $(dumpExportReferenceModelTargetDirectory) ; local textDocumentSources = # edits_generic @@ -101,6 +117,7 @@ local applicationSources = FilterView.cpp LocalIconStore.cpp JobStateListener.cpp + LanguageModel.cpp LinkView.cpp LinkedBitmapView.cpp Logger.cpp @@ -138,6 +155,7 @@ local applicationSources = ServerHelper.cpp ServerSettings.cpp ServerPkgDataUpdateProcess.cpp + ServerReferenceDataUpdateProcess.cpp ServerRepositoryDataUpdateProcess.cpp ServerIconExportUpdateProcess.cpp StandardMetaDataJsonEventListener.cpp @@ -151,6 +169,7 @@ local applicationSources = # util AppUtils.cpp DataIOUtils.cpp + LanguageMenuUtils.cpp RepositoryUrlUtils.cpp StorageUtils.cpp ToFileUrlProtocolListener.cpp @@ -173,6 +192,14 @@ local generatedRepositoryModelSourceFiles = DumpExportRepositorySourceMirror.cpp ; +local generatedReferenceModelSourceFiles = + DumpExportReference.cpp + DumpExportReferenceNaturalLanguage.cpp + DumpExportReferencePkgCategory.cpp + DumpExportReferenceUserRatingStability.cpp + DumpExportReferenceCountry.cpp +; + local generatedPkgParserSourceFiles = DumpExportPkgJsonListener.cpp ; @@ -181,10 +208,19 @@ local generatedRepositoryParserSourceFiles = DumpExportRepositoryJsonListener.cpp ; +local generatedReferenceParserSourceFiles = + DumpExportReferenceJsonListener.cpp +; + Application HaikuDepot - : $(applicationSources) $(textDocumentSources) - $(generatedPkgModelSourceFiles) $(generatedRepositoryModelSourceFiles) - $(generatedPkgParserSourceFiles) $(generatedRepositoryParserSourceFiles) + : $(applicationSources) + $(textDocumentSources) + $(generatedPkgModelSourceFiles) + $(generatedRepositoryModelSourceFiles) + $(generatedReferenceModelSourceFiles) + $(generatedPkgParserSourceFiles) + $(generatedRepositoryParserSourceFiles) + $(generatedReferenceParserSourceFiles) : be package bnetapi translation libcolumnlistview.a shared [ TargetLibstdc++ ] [ TargetLibsupc++ ] localestub : HaikuDepot.rdef @@ -225,12 +261,18 @@ HdsSchemaGenModel $(dumpExportPkgModelTargetFile) HdsSchemaGenModel $(dumpExportRepositoryModelTargetFile) : dumpexportrepository.json : jsonschema2cppmodel.py ; +HdsSchemaGenModel $(dumpExportReferenceModelTargetFile) + : dumpexportreference.json : jsonschema2cppmodel.py ; + HdsSchemaGenBulkParser $(dumpExportRepositoryBulkListerTargetFile) : dumpexportrepository.json : jsonschema2cppparser.py ; HdsSchemaGenBulkParser $(dumpExportPkgBulkListenerTargetFile) : dumpexportpkg.json : jsonschema2cppparser.py ; +HdsSchemaGenParser $(dumpExportReferenceListenerTargetFile) + : dumpexportreference.json : jsonschema2cppparser.py ; + HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedPkgParserSourceFiles) ] [ FGristFiles $(generatedPkgParserSourceFiles:S=.h) ] @@ -241,6 +283,11 @@ HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] : $(dumpExportRepositoryBulkListerTargetFile) ; +HdsSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(generatedReferenceParserSourceFiles) ] + [ FGristFiles $(generatedReferenceParserSourceFiles:S=.h) ] + : $(dumpExportReferenceListenerTargetFile) ; + HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedRepositoryModelSourceFiles) ] [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] @@ -251,13 +298,20 @@ HdsSchemaGenAppSrcDependsOnGeneration [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] : $(dumpExportPkgModelTargetFile) ; +HdsSchemaGenAppSrcDependsOnGeneration + [ FGristFiles $(generatedReferenceModelSourceFiles) ] + [ FGristFiles $(generatedReferenceModelSourceFiles:S=.h) ] + : $(dumpExportReferenceModelTargetFile) ; + # This will ensure that if any of the generated files' header files change, then # the application should be re-built. Depends [ FGristFiles $(applicationSources:S=.o) ] : [ FGristFiles $(generatedPkgParserSourceFiles:S=.h) ] + [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] [ FGristFiles $(generatedRepositoryParserSourceFiles:S=.h) ] [ FGristFiles $(generatedRepositoryModelSourceFiles:S=.h) ] - [ FGristFiles $(generatedPkgModelSourceFiles:S=.h) ] + [ FGristFiles $(generatedReferenceParserSourceFiles:S=.h) ] + [ FGristFiles $(generatedReferenceModelSourceFiles:S=.h) ] ; \ No newline at end of file diff --git a/src/apps/haikudepot/build/jam/HdsSchemaGenRules b/src/apps/haikudepot/build/jam/HdsSchemaGenRules index 8c3bf8c1d5..96872b4bb2 100644 --- a/src/apps/haikudepot/build/jam/HdsSchemaGenRules +++ b/src/apps/haikudepot/build/jam/HdsSchemaGenRules @@ -72,6 +72,29 @@ actions HdsSchemaGenBulkParser1 touch $(1) } +# pragma mark - Non-Bulk Parsing Class Generation + +# 1 : the dummy file in the class generation directory (target) +# 2 : the JSON schema file +# 3 : the Python script to use + +rule HdsSchemaGenParser +{ + SEARCH on $(2) = [ FDirName $(SUBDIR) server schema ] ; + SEARCH on $(3) = [ FDirName $(SUBDIR) build scripts ] ; + + Clean $(1:D) ; + Depends $(1) : $(2) $(3) ; + HdsSchemaGenParser1 $(1) : $(2) $(3) $(1:D) ; +} + +actions HdsSchemaGenParser1 +{ + mkdir -p $(2[3]) + $(HOST_PYTHON) $(2[2]) -i $(2[1]) --outputdirectory $(2[3]) + touch $(1) +} + # pragma mark - Registering Generated Classes # Because a number of .cpp and .h files will be generated from a single python diff --git a/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py b/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py index ce4b453a43..8eb54ad943 100644 --- a/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py +++ b/src/apps/haikudepot/build/scripts/jsonschema2cppmodel.py @@ -152,7 +152,27 @@ def writescalaraccessors(outputfile, cppclassname, cppname, cppmembername, cppty string.Template(""" ${cpptype} ${cppclassname}::${cppname}() -{ +{""").substitute(dict)) + + if cpptype == jscom.CPP_TYPE_BOOLEAN: + outputfile.write(string.Template(""" + if (${cppname}IsNull()) + return false; +""").substitute(dict)) + + if cpptype == jscom.CPP_TYPE_INTEGER: + outputfile.write(string.Template(""" + if (${cppname}IsNull()) + return 0; +""").substitute(dict)) + + if cpptype == jscom.CPP_TYPE_NUMBER: + outputfile.write(string.Template(""" + if (${cppname}IsNull()) + return 0.0; +""").substitute(dict)) + + outputfile.write(string.Template(""" return *${cppmembername}; } diff --git a/src/apps/haikudepot/model/LanguageModel.cpp b/src/apps/haikudepot/model/LanguageModel.cpp new file mode 100644 index 0000000000..30263cd2c6 --- /dev/null +++ b/src/apps/haikudepot/model/LanguageModel.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#include "LanguageModel.h" + +#include +#include +#include + +#include "HaikuDepotConstants.h" +#include "Logger.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"); + } + + BString name1; + BString name2; + + if (l1->GetName(name1) == B_OK && l2->GetName(name2) == B_OK) + return collator.Compare(name1.String(), name2.String()); + + return collator.Compare(l1->Code(), l2->Code()); +} + + +LanguageModel::LanguageModel() + : + fSupportedLanguages(LanguagesCompareFn, NULL), + fPreferredLanguage(LANGUAGE_DEFAULT) +{ + const Language defaultLanguage = _DeriveDefaultLanguage(); + fSupportedLanguages.Add(LanguageRef( + new Language(defaultLanguage))); + _SetPreferredLanguage(defaultLanguage); +} + + +LanguageModel::~LanguageModel() +{ +} + + +void +LanguageModel::AddSupportedLanguages(const LanguageList& languages) +{ + for (int32 i = 0; i < languages.CountItems(); i++) { + const LanguageRef language = languages.ItemAt(i); + int32 index = IndexOfSupportedLanguage(language->Code()); + + if (index == -1) + fSupportedLanguages.Add(language); + else + fSupportedLanguages.Replace(index, language); + } + + // it could be that the preferred language does not exist in the + // list. In this case it is necessary to choose one from the list. + _SetPreferredLanguage(_DeriveDefaultLanguage()); +} + + +void +LanguageModel::_SetPreferredLanguage(const Language& language) +{ + fPreferredLanguage = language; + if(Logger::IsDebugEnabled()) + printf("set preferred language [%s]\n", language.Code()); +} + + +int32 +LanguageModel::IndexOfSupportedLanguage(const BString& languageCode) const +{ + for (int32 i = 0; i < fSupportedLanguages.CountItems(); i++) { + if (fSupportedLanguages.ItemAt(i)->Code() == languageCode) + return i; + } + + return -1; +} + + +/*! This will derive the default language. If there are no other + possible languages configured then the default language will be + assumed to exist. Otherwise if there is a set of possible languages + then this method will ensure that the default language is in that + set. +*/ + +Language +LanguageModel::_DeriveDefaultLanguage() const +{ + Language defaultLanguage = _DeriveSystemDefaultLanguage(); + + if(Logger::IsDebugEnabled()) { + printf("derived system default language [%s]\n", + defaultLanguage.Code()); + } + + // if there are no supported languages; as is the case to start with as the + // application starts, the default language from the system is used anyway. + // The data queried in HDS will handle the case where the language is not + // 'known' at the HDS end so it doesn't matter if it is invalid. + + if (fSupportedLanguages.IsEmpty()) + return defaultLanguage; + + // if there are supported languages defined then the preferred language + // needs to be one of the supported ones. + + Language* foundSupportedLanguage = _FindSupportedLanguage( + defaultLanguage.Code()); + + if (foundSupportedLanguage == NULL) { + printf("unable to find the language [%s] - looking for app default", + defaultLanguage.Code()); + foundSupportedLanguage = _FindSupportedLanguage( + LANGUAGE_DEFAULT.Code()); + } + + if (foundSupportedLanguage == NULL) { + printf("unable to find the app default language - using the first " + "supported language"); + foundSupportedLanguage = fSupportedLanguages.ItemAt(0); + } + + return Language(*foundSupportedLanguage); +} + + +/*static*/ Language +LanguageModel::_DeriveSystemDefaultLanguage() +{ + BLocaleRoster* localeRoster = BLocaleRoster::Default(); + if (localeRoster != NULL) { + BMessage preferredLanguages; + if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) { + BString language; + BString languageIso; + if (preferredLanguages.FindString( + "language", 0, &language) == B_OK) { + language.CopyInto(languageIso, 0, 2); + return Language(languageIso, languageIso, true); + } + } + } + + return LANGUAGE_DEFAULT; +} + + +Language* +LanguageModel::_FindSupportedLanguage(const BString& code) const +{ + int32 index = IndexOfSupportedLanguage(code); + if (-1 == index) + return NULL; + return fSupportedLanguages.ItemAt(index); +} \ No newline at end of file diff --git a/src/apps/haikudepot/model/LanguageModel.h b/src/apps/haikudepot/model/LanguageModel.h new file mode 100644 index 0000000000..5010332d32 --- /dev/null +++ b/src/apps/haikudepot/model/LanguageModel.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef LANGUAGE_MODEL_H +#define LANGUAGE_MODEL_H + + +#include + +#include "List.h" +#include "PackageInfo.h" + + +typedef BReference LanguageRef; +typedef List LanguageList; + + +class LanguageModel { +public: + LanguageModel(); + virtual ~LanguageModel(); + + const LanguageList& SupportedLanguages() const + { return fSupportedLanguages; } + void AddSupportedLanguages( + const LanguageList& languages); + int32 IndexOfSupportedLanguage( + const BString& languageCode) const; + + const Language& PreferredLanguage() const + { return fPreferredLanguage; } + +private: + static Language _DeriveSystemDefaultLanguage(); + Language _DeriveDefaultLanguage() const; + Language* _FindSupportedLanguage( + const BString& code) const; + void _SetPreferredLanguage(const Language& language); + +private: + LanguageList fSupportedLanguages; + Language fPreferredLanguage; +}; + + +#endif // LANGUAGE_MODEL_H \ No newline at end of file diff --git a/src/apps/haikudepot/model/Model.cpp b/src/apps/haikudepot/model/Model.cpp index f6ee6a675f..7f583f6348 100644 --- a/src/apps/haikudepot/model/Model.cpp +++ b/src/apps/haikudepot/model/Model.cpp @@ -1,7 +1,7 @@ /* * Copyright 2013-2014, Stephan Aßmus . * Copyright 2014, Axel Dörfler . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -353,7 +353,6 @@ Model::Model() fDepotFilter(""), fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)), fIsFeaturedFilter(), - fShowFeaturedPackages(true), fShowAvailablePackages(true), fShowInstalledPackages(true), @@ -378,38 +377,6 @@ Model::Model() // get the defined categories and their translated names. // This should then be used instead of hard-coded // categories and translations in the app. - - fPreferredLanguage = "en"; - BLocaleRoster* localeRoster = BLocaleRoster::Default(); - if (localeRoster != NULL) { - BMessage preferredLanguages; - if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) { - BString language; - if (preferredLanguages.FindString("language", 0, &language) == B_OK) - language.CopyInto(fPreferredLanguage, 0, 2); - } - } - - // TODO: Fetch this from the web-app. - fSupportedLanguages.Add("en"); - fSupportedLanguages.Add("es"); - fSupportedLanguages.Add("de"); - fSupportedLanguages.Add("fr"); - fSupportedLanguages.Add("it"); - fSupportedLanguages.Add("ja"); - fSupportedLanguages.Add("pt"); - fSupportedLanguages.Add("ru"); - fSupportedLanguages.Add("sk"); - fSupportedLanguages.Add("zh"); - - if (!fSupportedLanguages.Contains(fPreferredLanguage)) { - // Force the preferred language to one of the currently supported - // ones, until the web application supports all ISO language codes. - printf("User preferred language '%s' not currently supported, " - "defaulting to 'en'.\n", fPreferredLanguage.String()); - fPreferredLanguage = "en"; - } - fWebAppInterface.SetPreferredLanguage(fPreferredLanguage); } @@ -418,6 +385,13 @@ Model::~Model() } +LanguageModel& +Model::Language() +{ + return fLanguageModel; +} + + bool Model::AddListener(const ModelListenerRef& listener) { @@ -903,6 +877,30 @@ Model::SetAuthorization(const BString& username, const BString& password, } +status_t +Model::_LocalDataPath(const BString leaf, BPath& path) const +{ + BString leafAssembled(leaf); + leafAssembled.ReplaceAll("%languageCode%", + LanguageModel().PreferredLanguage().Code()); + + BPath repoDataPath; + + if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK + && repoDataPath.Append("HaikuDepot") == B_OK + && create_directory(repoDataPath.Path(), 0777) == B_OK + && repoDataPath.Append(leafAssembled) == B_OK) { + path.SetTo(repoDataPath.Path()); + return B_OK; + } + + path.Unset(); + fprintf(stdout, "unable to find the user cache file for [%s] data", + leaf.String()); + return B_ERROR; +} + + /*! 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 @@ -912,20 +910,19 @@ Model::SetAuthorization(const BString& username, const BString& password, status_t Model::DumpExportRepositoryDataPath(BPath& path) const { - BPath repoDataPath; + return _LocalDataPath("repository-all_%languageCode%.json.gz", path); +} - if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK - && repoDataPath.Append("HaikuDepot") == B_OK - && create_directory(repoDataPath.Path(), 0777) == B_OK - && repoDataPath.Append("repository-all_en.json.gz") == B_OK) { - path.SetTo(repoDataPath.Path()); - return B_OK; - } - path.Unset(); - fprintf(stdout, "unable to find the user cache file for repositories'" - " data"); - return B_ERROR; +/*! 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 +{ + return _LocalDataPath("reference-all_%languageCode%.json.gz", path); } @@ -956,7 +953,7 @@ Model::DumpExportPkgDataPath(BPath& path, BString leafName; leafName.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(), - fPreferredLanguage.String()); + LanguageModel().PreferredLanguage().Code()); if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK && repoDataPath.Append("HaikuDepot") == B_OK diff --git a/src/apps/haikudepot/model/Model.h b/src/apps/haikudepot/model/Model.h index 3799c408f3..553f48614a 100644 --- a/src/apps/haikudepot/model/Model.h +++ b/src/apps/haikudepot/model/Model.h @@ -1,6 +1,6 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef MODEL_H @@ -10,6 +10,7 @@ #include #include "AbstractProcess.h" +#include "LanguageModel.h" #include "LocalIconStore.h" #include "PackageInfo.h" #include "WebAppInterface.h" @@ -63,6 +64,8 @@ public: Model(); virtual ~Model(); + LanguageModel& Language(); + BLocker* Lock() { return &fLock; } @@ -150,12 +153,6 @@ public: void PopulatePackage(const PackageInfoRef& package, uint32 flags); - const StringList& SupportedLanguages() const - { return fSupportedLanguages; } - - const BString& PreferredLanguage() const - { return fPreferredLanguage; } - void SetUsername(BString username); const BString& Username() const; void SetAuthorization(const BString& username, @@ -171,6 +168,7 @@ public: void* context); status_t IconStoragePath(BPath& path) const; + status_t DumpExportReferenceDataPath(BPath& path) const; status_t DumpExportRepositoryDataPath(BPath& path) const; status_t DumpExportPkgDataPath(BPath& path, const BString& repositorySourceCode) const; @@ -178,6 +176,9 @@ public: void LogDepotsWithNoWebAppRepositoryCode() const; private: + status_t _LocalDataPath(const BString leaf, + BPath& path) const; + void _MaybeLogJsonRpcError( const BMessage &responsePayload, const char *sourceDescription) const; @@ -245,9 +246,7 @@ private: bool fShowSourcePackages; bool fShowDevelopPackages; - StringList fSupportedLanguages; - BString fPreferredLanguage; - + LanguageModel fLanguageModel; WebAppInterface fWebAppInterface; ModelListenerList fListeners; diff --git a/src/apps/haikudepot/model/PackageInfo.cpp b/src/apps/haikudepot/model/PackageInfo.cpp index e0ba889dde..e1cade50db 100644 --- a/src/apps/haikudepot/model/PackageInfo.cpp +++ b/src/apps/haikudepot/model/PackageInfo.cpp @@ -1,7 +1,7 @@ /* * Copyright 2013-2014, Stephan Aßmus . * Copyright 2013, Rene Gollent . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -15,6 +15,41 @@ #include +// #pragma mark - Language + + +Language::Language(const char* language, const BString& serverName, + bool isPopular) + : + BLanguage(language), + fServerName(serverName), + fIsPopular(isPopular) +{ +} + + +Language::Language(const Language& other) + : + BLanguage(other.Code()), + fServerName(other.fServerName), + fIsPopular(other.fIsPopular) +{ +} + + +status_t +Language::GetName(BString& name, + const BLanguage* displayLanguage) const +{ + status_t result = BLanguage::GetName(name, displayLanguage); + + if (result == B_OK && (name.IsEmpty() || name == Code())) + name.SetTo(fServerName); + + return result; +} + + // #pragma mark - UserInfo diff --git a/src/apps/haikudepot/model/PackageInfo.h b/src/apps/haikudepot/model/PackageInfo.h index 263d052e4a..c2c8ddb35b 100644 --- a/src/apps/haikudepot/model/PackageInfo.h +++ b/src/apps/haikudepot/model/PackageInfo.h @@ -1,6 +1,6 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef PACKAGE_INFO_H @@ -9,6 +9,7 @@ #include +#include #include #include @@ -21,6 +22,30 @@ class BPath; +/*! This class represents a language that is supported by the Haiku + Depot Server system. This may differ from the set of languages + that are supported in the platform itself. +*/ + +class Language : public BReferenceable, public BLanguage { +public: + Language(const char* language, + const BString& serverName, + bool isPopular); + Language(const Language& other); + + status_t GetName(BString& name, + const BLanguage* displayLanguage = NULL + ) const; + bool IsPopular() const + { return fIsPopular; } + +private: + BString fServerName; + bool fIsPopular; +}; + + class UserInfo { public: UserInfo(); diff --git a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp index f5a544c524..eeeec2f614 100644 --- a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp +++ b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -21,6 +21,7 @@ #include "ServerHelper.h" #include "ServerIconExportUpdateProcess.h" #include "ServerPkgDataUpdateProcess.h" +#include "ServerReferenceDataUpdateProcess.h" #include "ServerRepositoryDataUpdateProcess.h" #include "ServerSettings.h" @@ -62,6 +63,11 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator( serverRepositoryDataUpdate->AddPredecessor(localPkgDataLoad); processCoordinator->AddNode(serverRepositoryDataUpdate); + ProcessNode *serverReferenceDataUpdate = + new ProcessNode(new ServerReferenceDataUpdateProcess(model, + serverProcessOptions)); + processCoordinator->AddNode(serverReferenceDataUpdate); + // create a process for each of the repositories that are configured on the // local system. Later, only those that have a web-app repository server // code will be actually processed, but this means that the creation of the @@ -74,9 +80,11 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator( if (repoNamesResult == B_OK) { for (int32 i = 0; i < repoNames.CountStrings(); i++) { ProcessNode* processNode = new ProcessNode( - new ServerPkgDataUpdateProcess(model->PreferredLanguage(), + new ServerPkgDataUpdateProcess( + model->Language().PreferredLanguage().Code(), repoNames.StringAt(i), model, serverProcessOptions)); processNode->AddPredecessor(serverRepositoryDataUpdate); + processNode->AddPredecessor(serverReferenceDataUpdate); processCoordinator->AddNode(processNode); } } else { diff --git a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp new file mode 100644 index 0000000000..ceaa4849ea --- /dev/null +++ b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ + + +#include "ServerReferenceDataUpdateProcess.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "ServerSettings.h" +#include "StorageUtils.h" +#include "Logger.h" +#include "DumpExportReference.h" +#include "DumpExportReferenceNaturalLanguage.h" +#include "DumpExportReferencePkgCategory.h" +#include "DumpExportReferenceUserRatingStability.h" +#include "DumpExportReferenceCountry.h" +#include "DumpExportReferenceJsonListener.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "ServerReferenceDataUpdateProcess" + + +ServerReferenceDataUpdateProcess::ServerReferenceDataUpdateProcess( + Model* model, + uint32 serverProcessOptions) + : + AbstractSingleFileServerProcess(serverProcessOptions), + fModel(model) +{ +} + + +ServerReferenceDataUpdateProcess::~ServerReferenceDataUpdateProcess() +{ +} + + +const char* +ServerReferenceDataUpdateProcess::Name() const +{ + return "ServerReferenceDataUpdateProcess"; +} + + +const char* +ServerReferenceDataUpdateProcess::Description() const +{ + return B_TRANSLATE("Synchronizing reference data from server"); +} + + +BString +ServerReferenceDataUpdateProcess::UrlPathComponent() +{ + BString result; + result.SetToFormat("/__reference/all-%s.json.gz", + fModel->Language().PreferredLanguage().Code()); + return result; +} + + +status_t +ServerReferenceDataUpdateProcess::GetLocalPath(BPath& path) const +{ + return fModel->DumpExportReferenceDataPath(path); +} + + +status_t +ServerReferenceDataUpdateProcess::ProcessLocalData() +{ + SingleDumpExportReferenceJsonListener* listener = + new SingleDumpExportReferenceJsonListener(); + + BPath localPath; + status_t result = GetLocalPath(localPath); + + if (result != B_OK) + return result; + + result = ParseJsonFromFileWithListener(listener, localPath); + + if (result != B_OK) + return result; + + result = listener->ErrorStatus(); + + if (result != B_OK) + return result; + + return _ProcessData(listener->Target()); +} + + +status_t +ServerReferenceDataUpdateProcess::_ProcessData(DumpExportReference* data) +{ + // more to come here later... + return _ProcessNaturalLanguages(data); +} + + +status_t +ServerReferenceDataUpdateProcess::_ProcessNaturalLanguages( + DumpExportReference* data) +{ + printf("[%s] will populate %" B_PRId32 " natural languages\n", + Name(), data->CountNaturalLanguages()); + + LanguageList result; + + for (int32 i = 0; i < data->CountNaturalLanguages(); i++) { + DumpExportReferenceNaturalLanguage* naturalLanguage = + data->NaturalLanguagesItemAt(i); + result.Add(LanguageRef(new Language( + *(naturalLanguage->Code()), + *(naturalLanguage->Name()), + naturalLanguage->IsPopular()))); + } + + fModel->Language().AddSupportedLanguages(result); + + printf("[%s] did add %" B_PRId32 " supported languages\n", + Name(), result.CountItems()); + + return B_OK; +} + + +status_t +ServerReferenceDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const +{ + return GetLocalPath(path); +} + + +void +ServerReferenceDataUpdateProcess::GetStandardMetaDataJsonPath( + BString& jsonPath) const +{ + jsonPath.SetTo("$.info"); +} diff --git a/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h new file mode 100644 index 0000000000..fe051b6efa --- /dev/null +++ b/src/apps/haikudepot/server/ServerReferenceDataUpdateProcess.h @@ -0,0 +1,55 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef SERVER_REFERENCE_DATA_UPDATE_PROCESS_H +#define SERVER_REFERENCE_DATA_UPDATE_PROCESS_H + + +#include "AbstractSingleFileServerProcess.h" + +#include +#include +#include +#include + +#include "Model.h" +#include "PackageInfo.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "ServerReferenceDataUpdateProcess" + +class DumpExportReference; + +class ServerReferenceDataUpdateProcess : public AbstractSingleFileServerProcess +{ +public: + + ServerReferenceDataUpdateProcess( + Model* model, uint32 serverProcessOptions); + virtual ~ServerReferenceDataUpdateProcess(); + + const char* Name() const; + const char* Description() const; + +protected: + status_t GetStandardMetaDataPath(BPath& path) const; + void GetStandardMetaDataJsonPath( + BString& jsonPath) const; + + BString UrlPathComponent(); + status_t ProcessLocalData(); + status_t GetLocalPath(BPath& path) const; + +private: + status_t _ProcessData(DumpExportReference* data); + status_t _ProcessNaturalLanguages( + DumpExportReference* data); + +private: + Model* fModel; + +}; + +#endif // SERVER_REFERENCE_DATA_UPDATE_PROCESS_H diff --git a/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp index cc4b78e109..ebc6ffa9f3 100644 --- a/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp +++ b/src/apps/haikudepot/server/ServerRepositoryDataUpdateProcess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018, Andrew Lindesay . + * Copyright 2017-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -177,7 +177,10 @@ ServerRepositoryDataUpdateProcess::Description() const BString ServerRepositoryDataUpdateProcess::UrlPathComponent() { - return BString("/__repository/all-en.json.gz"); + BString result; + result.SetToFormat("/__repository/all-%s.json.gz", + fModel->Language().PreferredLanguage().Code()); + return result; } diff --git a/src/apps/haikudepot/server/WebAppInterface.cpp b/src/apps/haikudepot/server/WebAppInterface.cpp index c03c8d8293..30231264a3 100644 --- a/src/apps/haikudepot/server/WebAppInterface.cpp +++ b/src/apps/haikudepot/server/WebAppInterface.cpp @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -271,8 +271,6 @@ enum { WebAppInterface::WebAppInterface() - : - fLanguage("en") { } @@ -280,8 +278,7 @@ WebAppInterface::WebAppInterface() WebAppInterface::WebAppInterface(const WebAppInterface& other) : fUsername(other.fUsername), - fPassword(other.fPassword), - fLanguage(other.fLanguage) + fPassword(other.fPassword) { } @@ -296,11 +293,8 @@ WebAppInterface::operator=(const WebAppInterface& other) { if (this == &other) return *this; - fUsername = other.fUsername; fPassword = other.fPassword; - fLanguage = other.fLanguage; - return *this; } @@ -314,13 +308,6 @@ WebAppInterface::SetAuthorization(const BString& username, } -void -WebAppInterface::SetPreferredLanguage(const BString& language) -{ - fLanguage = language; -} - - status_t WebAppInterface::GetChangelog(const BString& packageName, BMessage& message) { diff --git a/src/apps/haikudepot/server/WebAppInterface.h b/src/apps/haikudepot/server/WebAppInterface.h index 543aac1557..4b99fa231f 100644 --- a/src/apps/haikudepot/server/WebAppInterface.h +++ b/src/apps/haikudepot/server/WebAppInterface.h @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef WEB_APP_INTERFACE_H @@ -51,9 +51,6 @@ public: const BString& Username() const { return fUsername; } - void SetPreferredLanguage(const BString& language); - void SetArchitecture(const BString& architecture); - status_t GetChangelog( const BString& packageName, BMessage& message); @@ -130,7 +127,6 @@ private: private: BString fUsername; BString fPassword; - BString fLanguage; static int fRequestIndex; }; diff --git a/src/apps/haikudepot/server/schema/dumpexportreference.json b/src/apps/haikudepot/server/schema/dumpexportreference.json new file mode 100644 index 0000000000..08eb684863 --- /dev/null +++ b/src/apps/haikudepot/server/schema/dumpexportreference.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://depot.haiku-os.org/schema/reference/dumpreference.json", + "title": "Reference", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReference", + "type": "object", + "properties": { + "naturalLanguages": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferenceNaturalLanguage", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "isPopular": { + "type": "boolean" + } + } + } + }, + "pkgCategories": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferencePkgCategory", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "userRatingStabilities": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferenceUserRatingStability", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "countries": { + "type": "array", + "items": { + "type": "object", + "javaType": "org.haiku.haikudepotserver.reference.model.dumpexport.DumpExportReferenceCountry", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/apps/haikudepot/ui/PackageInfoView.cpp b/src/apps/haikudepot/ui/PackageInfoView.cpp index b93bc82648..d7733bd371 100644 --- a/src/apps/haikudepot/ui/PackageInfoView.cpp +++ b/src/apps/haikudepot/ui/PackageInfoView.cpp @@ -1,6 +1,6 @@ /* * Copyright 2013-2014, Stephan Aßmus . - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -1050,8 +1050,6 @@ public: .Add(scrollView, 1.0f) .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) ; - - _InitPreferredLanguages(); } virtual ~UserRatingsView() @@ -1113,33 +1111,9 @@ public: } } -private: - void _InitPreferredLanguages() - { - fPreferredLanguages.Clear(); - - BLocaleRoster* localeRoster = BLocaleRoster::Default(); - if (localeRoster == NULL) - return; - - BMessage preferredLanguages; - if (localeRoster->GetPreferredLanguages(&preferredLanguages) != B_OK) - return; - - BString language; - int32 index = 0; - while (preferredLanguages.FindString("language", index++, - &language) == B_OK) { - BString languageCode; - language.CopyInto(languageCode, 0, 2); - fPreferredLanguages.Add(languageCode); - } - } - private: BGroupLayout* fRatingContainerLayout; RatingSummaryView* fRatingSummaryView; - StringList fPreferredLanguages; }; diff --git a/src/apps/haikudepot/ui/RatePackageWindow.cpp b/src/apps/haikudepot/ui/RatePackageWindow.cpp index 975a82cf3d..aff723798c 100644 --- a/src/apps/haikudepot/ui/RatePackageWindow.cpp +++ b/src/apps/haikudepot/ui/RatePackageWindow.cpp @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2016-2018, Andrew Lindesay . + * Copyright 2016-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -22,6 +22,7 @@ #include #include "HaikuDepotConstants.h" +#include "LanguageMenuUtils.h" #include "MarkupParser.h" #include "RatingView.h" #include "ServerHelper.h" @@ -37,7 +38,6 @@ enum { MSG_SEND = 'send', MSG_PACKAGE_RATED = 'rpkg', MSG_STABILITY_SELECTED = 'stbl', - MSG_LANGUAGE_SELECTED = 'lngs', MSG_RATING_ACTIVE_CHANGED = 'rtac', MSG_RATING_DETERMINATE_CHANGED = 'rdch' }; @@ -180,19 +180,6 @@ add_stabilities_to_menu(const StabilityRatingList& stabilities, BMenu* menu) } -static void -add_languages_to_menu(const StringList& languages, BMenu* menu) -{ - for (int i = 0; i < languages.CountItems(); i++) { - const BString& language = languages.ItemAtFast(i); - BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED); - message->AddString("code", language); - BMenuItem* item = new BMenuItem(language, message); - menu->AddItem(item); - } -} - - RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, Model& model) : @@ -204,7 +191,7 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, fTextEditor(new TextEditor(), true), fRating(RATING_NONE), fRatingDeterminate(false), - fCommentLanguage(fModel.PreferredLanguage()), + fCommentLanguageCode(fModel.Language().PreferredLanguage().Code()), fWorkerThread(-1) { AddToSubset(parent); @@ -260,13 +247,12 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, fCommentLanguageField = new BMenuField("language", B_TRANSLATE("Comment language:"), languagesMenu); - add_languages_to_menu(fModel.SupportedLanguages(), languagesMenu); + LanguageMenuUtils::AddLanguagesToMenu( + fModel.Language().SupportedLanguages(), + languagesMenu); languagesMenu->SetTargetForItems(this); - - BMenuItem* defaultItem = languagesMenu->ItemAt( - fModel.SupportedLanguages().IndexOf(fCommentLanguage)); - if (defaultItem != NULL) - defaultItem->SetMarked(true); + LanguageMenuUtils::MarkLanguageInMenu(fCommentLanguageCode, + languagesMenu); fRatingActiveCheckBox = new BCheckBox("rating active", B_TRANSLATE("Other users can see this rating"), @@ -348,7 +334,7 @@ RatePackageWindow::MessageReceived(BMessage* message) break; case MSG_LANGUAGE_SELECTED: - message->FindString("code", &fCommentLanguage); + message->FindString("code", &fCommentLanguageCode); break; case MSG_RATING_DETERMINATE_CHANGED: @@ -514,11 +500,9 @@ RatePackageWindow::_RelayServerDataToUI(BMessage& response) item->SetMarked(true); } if (response.FindString("naturalLanguageCode", - &fCommentLanguage) == B_OK) { - BMenuItem* item = fCommentLanguageField->Menu()->ItemAt( - fModel.SupportedLanguages().IndexOf(fCommentLanguage)); - if (item != NULL) - item->SetMarked(true); + &fCommentLanguageCode) == B_OK) { + LanguageMenuUtils::MarkLanguageInMenu( + fCommentLanguageCode, fCommentLanguageField->Menu()); } double rating; if (response.FindDouble("rating", &rating) == B_OK) { @@ -649,7 +633,7 @@ RatePackageWindow::_SendRatingThread() int rating = (int)fRating; BString stability = fStability; BString comment = fRatingText->Text(); - BString languageCode = fCommentLanguage; + BString languageCode = fCommentLanguageCode; BString ratingID = fRatingID; bool active = fRatingActive; diff --git a/src/apps/haikudepot/ui/RatePackageWindow.h b/src/apps/haikudepot/ui/RatePackageWindow.h index 04ca5f0183..bd6e2c5393 100644 --- a/src/apps/haikudepot/ui/RatePackageWindow.h +++ b/src/apps/haikudepot/ui/RatePackageWindow.h @@ -1,6 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . - * Copyright 2018, Andrew Lindesay . + * Copyright 2018-2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef RATE_PACKAGE_WINDOW_H @@ -56,7 +56,7 @@ private: bool fRatingDeterminate; BString fStability; StabilityRatingList fStabilityCodes; - BString fCommentLanguage; + BString fCommentLanguageCode; BString fRatingID; bool fRatingActive; PackageInfoRef fPackage; diff --git a/src/apps/haikudepot/ui/UserLoginWindow.cpp b/src/apps/haikudepot/ui/UserLoginWindow.cpp index 29263a1d05..a225540bc5 100644 --- a/src/apps/haikudepot/ui/UserLoginWindow.cpp +++ b/src/apps/haikudepot/ui/UserLoginWindow.cpp @@ -1,5 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . + * Copyright 2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -21,6 +22,8 @@ #include #include "BitmapView.h" +#include "HaikuDepotConstants.h" +#include "LanguageMenuUtils.h" #include "Model.h" #include "TabView.h" #include "WebAppInterface.h" @@ -34,31 +37,17 @@ enum { MSG_SEND = 'send', MSG_TAB_SELECTED = 'tbsl', MSG_CAPTCHA_OBTAINED = 'cpob', - MSG_VALIDATE_FIELDS = 'vldt', - MSG_LANGUAGE_SELECTED = 'lngs', + MSG_VALIDATE_FIELDS = 'vldt' }; -static void -add_languages_to_menu(const StringList& languages, BMenu* menu) -{ - for (int i = 0; i < languages.CountItems(); i++) { - const BString& language = languages.ItemAtFast(i); - BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED); - message->AddString("code", language); - BMenuItem* item = new BMenuItem(language, message); - menu->AddItem(item); - } -} - - UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model) : BWindow(frame, B_TRANSLATE("Log in"), B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE | B_NOT_ZOOMABLE), - fPreferredLanguage(model.PreferredLanguage()), + fPreferredLanguageCode(model.Language().PreferredLanguage().Code()), fModel(model), fMode(NONE), fWorkerThread(-1) @@ -83,14 +72,15 @@ UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model) fLanguageCodeField = new BMenuField("language", B_TRANSLATE("Preferred language:"), languagesMenu); - add_languages_to_menu(fModel.SupportedLanguages(), languagesMenu); + LanguageMenuUtils::AddLanguagesToMenu( + fModel.Language().SupportedLanguages(), + languagesMenu); languagesMenu->SetTargetForItems(this); - BMenuItem* defaultItem = languagesMenu->ItemAt( - fModel.SupportedLanguages().IndexOf(fPreferredLanguage)); - if (defaultItem != NULL) - defaultItem->SetMarked(true); - + printf("using preferred language code [%s]\n", + fPreferredLanguageCode.String()); + LanguageMenuUtils::MarkLanguageInMenu(fPreferredLanguageCode, + languagesMenu); fEmailField = new BTextControl(B_TRANSLATE("Email address:"), "", NULL); fCaptchaView = new BitmapView("captcha view"); @@ -218,7 +208,7 @@ UserLoginWindow::MessageReceived(BMessage* message) break; case MSG_LANGUAGE_SELECTED: - message->FindString("code", &fPreferredLanguage); + message->FindString("code", &fPreferredLanguageCode); break; default: @@ -623,7 +613,7 @@ UserLoginWindow::_CreateAccountThread() BString email(fEmailField->Text()); BString captchaToken(fCaptchaToken); BString captchaResponse(fCaptchaResultField->Text()); - BString languageCode(fPreferredLanguage); + BString languageCode(fPreferredLanguageCode); Unlock(); diff --git a/src/apps/haikudepot/ui/UserLoginWindow.h b/src/apps/haikudepot/ui/UserLoginWindow.h index 6d529e717e..ee52d452ca 100644 --- a/src/apps/haikudepot/ui/UserLoginWindow.h +++ b/src/apps/haikudepot/ui/UserLoginWindow.h @@ -1,5 +1,6 @@ /* * Copyright 2014, Stephan Aßmus . + * Copyright 2019, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef USER_LOGIN_WINDOW_H @@ -84,7 +85,7 @@ private: BString fCaptchaToken; BitmapRef fCaptchaImage; - BString fPreferredLanguage; + BString fPreferredLanguageCode; Model& fModel; diff --git a/src/apps/haikudepot/util/AppUtils.cpp b/src/apps/haikudepot/util/AppUtils.cpp index 54933eff1c..79bdfff77a 100644 --- a/src/apps/haikudepot/util/AppUtils.cpp +++ b/src/apps/haikudepot/util/AppUtils.cpp @@ -9,10 +9,10 @@ #include #include -#include #include "HaikuDepotConstants.h" + /*! This method can be called to pop up an error in the user interface; typically in a background thread. */ diff --git a/src/apps/haikudepot/util/LanguageMenuUtils.cpp b/src/apps/haikudepot/util/LanguageMenuUtils.cpp new file mode 100644 index 0000000000..f22e468aeb --- /dev/null +++ b/src/apps/haikudepot/util/LanguageMenuUtils.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#include "LanguageMenuUtils.h" + +#include + +#include +#include +#include + +#include "HaikuDepotConstants.h" +#include "Logger.h" + + +/*! This method will add the supplied languages to the menu. It will + add first the popular languages, followed by a separator and then + other less popular languages. +*/ + +/* static */ void +LanguageMenuUtils::AddLanguagesToMenu( + const LanguageList& languages, BMenu* menu) +{ + if (languages.IsEmpty()) + printf("there are no languages defined\n"); + + int32 addedPopular = LanguageMenuUtils::_AddLanguagesToMenu( + languages, menu, true); + + if (addedPopular > 0) + menu->AddSeparatorItem(); + + int32 addedNonPopular = LanguageMenuUtils::_AddLanguagesToMenu( + languages, menu, false); + + if (Logger::IsDebugEnabled()) { + printf("did add %" B_PRId32 " popular languages and %" B_PRId32 + " non-popular languages to a menu\n", addedPopular, + addedNonPopular); + } +} + + +/* static */ void +LanguageMenuUtils::MarkLanguageInMenu( + const BString& languageCode, BMenu* menu) { + if (menu->CountItems() == 0) { + debugger("menu contains no items; not able to set the " + "language"); + return; + } + + int32 index = LanguageMenuUtils::_IndexOfLanguageInMenu( + languageCode, menu); + + if (index == -1) { + printf("unable to find the language [%s] in the menu\n", + languageCode.String()); + menu->ItemAt(0)->SetMarked(true); + } + else + menu->ItemAt(index)->SetMarked(true); +} + + +/* static */ void +LanguageMenuUtils::_AddLanguageToMenu( + const BString& code, const BString& name, BMenu* menu) +{ + BMessage* message = new BMessage(MSG_LANGUAGE_SELECTED); + message->AddString("code", code); + BMenuItem* item = new BMenuItem(name, message); + menu->AddItem(item); +} + + +/* static */ void +LanguageMenuUtils::_AddLanguageToMenu( + const LanguageRef& language, BMenu* menu) +{ + BString name; + if (language->GetName(name) != B_OK || name.IsEmpty()) + name.SetTo("???"); + LanguageMenuUtils::_AddLanguageToMenu(language->Code(), name, menu); +} + + +/* static */ int32 +LanguageMenuUtils::_AddLanguagesToMenu(const LanguageList& languages, + BMenu* menu, bool isPopular) +{ + int32 count = 0; + + for (int32 i = 0; i < languages.CountItems(); i++) { + const LanguageRef language = languages.ItemAtFast(i); + + if (language->IsPopular() == isPopular) { + LanguageMenuUtils::_AddLanguageToMenu(language, menu); + count++; + } + } + + return count; +} + + +/* static */ status_t +LanguageMenuUtils::_GetLanguageAtIndexInMenu(BMenu* menu, int32 index, + BString* result) +{ + BMessage *itemMessage = menu->ItemAt(index)->Message(); + + if (itemMessage == NULL) + return B_ERROR; + + return itemMessage->FindString("code", result); +} + + +/* static */ int32 +LanguageMenuUtils::_IndexOfLanguageInMenu( + const BString& languageCode, BMenu* menu) +{ + BString itemLanguageCode; + for (int32 i = 0; i < menu->CountItems(); i++) { + if (_GetLanguageAtIndexInMenu( + menu, i, &itemLanguageCode) == B_OK) { + if (itemLanguageCode == languageCode) { + return i; + } + } + } + + return -1; +} \ No newline at end of file diff --git a/src/apps/haikudepot/util/LanguageMenuUtils.h b/src/apps/haikudepot/util/LanguageMenuUtils.h new file mode 100644 index 0000000000..420d711af8 --- /dev/null +++ b/src/apps/haikudepot/util/LanguageMenuUtils.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef LANGUAGE_MENU_UTILS_H +#define LANGUAGE_MENU_UTILS_H + + +#include + +#include "Model.h" +#include "PackageInfo.h" + + +class LanguageMenuUtils { + +public: + static void AddLanguagesToMenu( + const LanguageList& languages, + BMenu* menu); + static void MarkLanguageInMenu( + const BString& languageCode, + BMenu* menu); + +private: + static int32 _IndexOfLanguageInMenu( + const BString& languageCode, + BMenu* menu); + static status_t _GetLanguageAtIndexInMenu(BMenu* menu, + int32 index, BString* result); + static int32 _AddLanguagesToMenu( + const LanguageList& languages, + BMenu* menu, bool isPopular); + static void _AddLanguageToMenu( + const LanguageRef& language, + BMenu* menu); + static void _AddLanguageToMenu( + const BString& code, + const BString& name, BMenu* menu); +}; + + +#endif // LANGUAGE_MENU_UTILS_H