HaikuDepot: Check User Auth on Start

The user might have changed their authentication details
on the server and the client won't detect this until
they go to do something.  Instead, if possible, check
this as the client starts.  Also check that the user has
agreed to the current user usage conditions.

As a side-effect this generalizes the logic for process
coordination in the main window and also fixes some bugs
in the main window's progress display as the application
starts.

Relates to #15209

Change-Id: I4c9620648819ecd14fb095e4cb2c66fe7b2a0920
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2467
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Andrew Lindesay
2020-04-15 07:10:10 +00:00
parent 5cfca119fb
commit 10cd325cfa
20 changed files with 1242 additions and 116 deletions
+9 -6
View File
@@ -6,6 +6,7 @@
#define HAIKU_DEPOT_CONSTANTS_H
enum {
MSG_BULK_LOAD_DONE = 'mmwd',
MSG_MAIN_WINDOW_CLOSED = 'mwcl',
MSG_PACKAGE_SELECTED = 'pkgs',
MSG_PACKAGE_WORKER_BUSY = 'pkwb',
@@ -23,7 +24,9 @@ enum {
MSG_VIEW_LATEST_USER_USAGE_CONDITIONS = 'vluc',
MSG_VIEW_USERS_USER_USAGE_CONDITIONS = 'vuuc',
MSG_USER_USAGE_CONDITIONS_DATA = 'uucd',
MSG_USER_USAGE_CONDITIONS_ERROR = 'uuce'
MSG_USER_USAGE_CONDITIONS_ERROR = 'uuce',
MSG_USER_USAGE_CONDITIONS_NOT_LATEST = 'uucl',
MSG_LOG_OUT = 'lgot',
};
@@ -34,11 +37,11 @@ enum {
#define RGB_COLOR_WHITE (rgb_color) { 255, 255, 255, 255 }
#define HD_ERROR_BASE (B_ERRORS_END + 1)
#define HD_NETWORK_INACCESSIBLE (HD_ERROR_BASE + 1)
#define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2)
#define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3)
#define HD_ERR_NO_DATA (HD_ERROR_BASE + 4)
#define HD_ERROR_BASE (B_ERRORS_END + 1)
#define HD_NETWORK_INACCESSIBLE (HD_ERROR_BASE + 1)
#define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2)
#define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3)
#define HD_ERR_NO_DATA (HD_ERROR_BASE + 4)
#define REPOSITORY_NAME_SYSTEM "system"
+2
View File
@@ -146,6 +146,7 @@ local applicationSources =
ScreenshotWindow.cpp
ScrollableGroupView.cpp
SharedBitmap.cpp
ToLatestUserUsageConditionsWindow.cpp
UserCredentials.cpp
UserDetail.cpp
UserLoginWindow.cpp
@@ -172,6 +173,7 @@ local applicationSources =
ServerIconExportUpdateProcess.cpp
StandardMetaDataJsonEventListener.cpp
StandardMetaData.cpp
UserDetailVerifierProcess.cpp
WebAppInterface.cpp
# tar
+73 -10
View File
@@ -35,6 +35,10 @@
#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";
@@ -804,20 +808,61 @@ Model::_PopulatePackageChangelog(const PackageInfoRef& package)
}
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;
if (keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD, nickname,
key) == B_OK) {
BString passwordIdentifier = BString(KEY_STORE_IDENTIFIER_PREFIX)
<< nickname;
if (keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD,
passwordIdentifier, key) == B_OK) {
password = key.Password();
} else {
nickname = "";
}
if (password.IsEmpty())
nickname = "";
}
SetAuthorization(nickname, password, false);
}
@@ -833,17 +878,35 @@ void
Model::SetAuthorization(const BString& nickname, const BString& passwordClear,
bool storePassword)
{
if (storePassword && nickname.Length() > 0 && passwordClear.Length() > 0) {
BPasswordKey key(passwordClear, B_KEY_PURPOSE_WEB, nickname);
BKeyStore keyStore;
keyStore.AddKeyring(kHaikuDepotKeyring);
keyStore.AddKey(kHaikuDepotKeyring, key);
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));
_NotifyAuthorizationChanged();
if (nickname != existingNickname)
_NotifyAuthorizationChanged();
}
@@ -76,9 +76,13 @@ ProcessCoordinatorState::ErrorStatus() const
// #pragma mark - ProcessCoordinator implementation
ProcessCoordinator::ProcessCoordinator(ProcessCoordinatorListener* listener)
ProcessCoordinator::ProcessCoordinator(const char* name,
ProcessCoordinatorListener* listener,
BMessage* message)
:
fName(name),
fListener(listener),
fMessage(message),
fWasStopped(false)
{
}
@@ -92,6 +96,7 @@ ProcessCoordinator::~ProcessCoordinator()
node->Process()->SetListener(NULL);
delete node;
}
delete fMessage;
}
@@ -147,6 +152,10 @@ ProcessCoordinator::Stop()
node->StopProcess();
}
}
if (fListener != NULL) {
ProcessCoordinatorState state = _CreateStatus();
fListener->CoordinatorChanged(state);
}
}
@@ -175,6 +184,20 @@ ProcessCoordinator::Progress()
}
const BString&
ProcessCoordinator::Name() const
{
return fName;
}
BMessage*
ProcessCoordinator::Message() const
{
return fMessage;
}
BString
ProcessCoordinator::_CreateStatusMessage()
{
@@ -74,7 +74,9 @@ public:
class ProcessCoordinator : public AbstractProcessListener {
public:
ProcessCoordinator(
ProcessCoordinatorListener* listener);
const char* name,
ProcessCoordinatorListener* listener,
BMessage* message = NULL);
virtual ~ProcessCoordinator();
void AddNode(ProcessNode* nodes);
@@ -91,6 +93,9 @@ public:
float Progress();
const BString& Name() const;
BMessage* Message() const;
private:
bool _IsRunning(ProcessNode* node);
void _CoordinateAndCallListener();
@@ -103,11 +108,14 @@ private:
void _StopSuccessorNodesToErroredOrStoppedNodes();
void _StopSuccessorNodes(ProcessNode* node);
private:
BString fName;
BLocker fLock;
List<ProcessNode*, true>
fNodes;
ProcessCoordinatorListener*
fListener;
BMessage* fMessage;
bool fWasStopped;
};
@@ -13,6 +13,7 @@
#include <package/PackageRoster.h>
#include "AbstractServerProcess.h"
#include "HaikuDepotConstants.h"
#include "LocalPkgDataLoadProcess.h"
#include "LocalRepositoryUpdateProcess.h"
#include "Model.h"
@@ -26,11 +27,28 @@
#include "ServerRepositoryDataUpdateProcess.h"
#include "ServerSettings.h"
#include "StorageUtils.h"
#include "UserDetailVerifierProcess.h"
using namespace BPackageKit;
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator(
UserDetailVerifierListener* userDetailVerifierListener,
ProcessCoordinatorListener* processCoordinatorListener,
Model* model)
{
ProcessCoordinator* processCoordinator = new ProcessCoordinator(
"UserDetailVerifier",
processCoordinatorListener);
ProcessNode* userDetailVerifier = new ProcessNode(
new UserDetailVerifierProcess(model, userDetailVerifierListener));
processCoordinator->AddNode(userDetailVerifier);
return processCoordinator;
}
/* static */ ProcessCoordinator*
ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
PackageInfoListener *packageInfoListener,
@@ -41,7 +59,8 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
uint32 serverProcessOptions = _CalculateServerProcessOptions();
BAutolock locker(model->Lock());
ProcessCoordinator* processCoordinator = new ProcessCoordinator(
processCoordinatorListener);
"BulkLoad",
processCoordinatorListener, new BMessage(MSG_BULK_LOAD_DONE));
ProcessNode *localRepositoryUpdate =
new ProcessNode(new LocalRepositoryUpdateProcess(model,
@@ -13,6 +13,7 @@ class Model;
class PackageInfoListener;
class ProcessCoordinator;
class ProcessCoordinatorListener;
class UserDetailVerifierListener;
/*! This class is able to create ProcessCoordinators that are loaded-up with
Processes that together complete some larger job.
@@ -25,6 +26,13 @@ public:
ProcessCoordinatorListener*
processCoordinatorListener,
Model* model, bool forceLocalUpdate);
static ProcessCoordinator* CreateUserDetailVerifierCoordinator(
UserDetailVerifierListener*
userDetailVerifierListener,
ProcessCoordinatorListener*
processCoordinatorListener,
Model* model);
private:
static uint32 _CalculateServerProcessOptions();
@@ -0,0 +1,141 @@
/*
* Copyright 2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "UserDetailVerifierProcess.h"
#include <AutoLocker.h>
#include <Catalog.h>
#include <Window.h>
#include "AppUtils.h"
#include "HaikuDepotConstants.h"
#include "Logger.h"
#include "ServerHelper.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "UserDetailVerifierProcess"
UserDetailVerifierProcess::UserDetailVerifierProcess(Model* model,
UserDetailVerifierListener* listener)
:
fModel(model),
fListener(listener)
{
}
UserDetailVerifierProcess::~UserDetailVerifierProcess()
{
}
const char*
UserDetailVerifierProcess::Name() const
{
return "UserDetailVerifierProcess";
}
const char*
UserDetailVerifierProcess::Description() const
{
return B_TRANSLATE("Checking user details");
}
status_t
UserDetailVerifierProcess::RunInternal()
{
status_t result = B_OK;
if (_ShouldVerify()) {
UserDetail userDetail;
result = _TryFetchUserDetail(userDetail);
switch (result) {
case B_PERMISSION_DENIED:
fListener->UserCredentialsFailed();
result = B_OK;
break;
case B_OK:
if (!userDetail.Agreement().IsLatest()) {
printf("! the user has not agreed to the latest user usage"
" conditions.\n");
fListener->UserUsageConditionsNotLatest(userDetail);
}
break;
default:
break;
}
}
return result;
}
bool
UserDetailVerifierProcess::_ShouldVerify()
{
if (!ServerHelper::IsNetworkAvailable()) {
printf("no network --> will not verify user\n");
return false;
}
{
AutoLocker<BLocker> locker(fModel->Lock());
if (fModel->Nickname().IsEmpty()) {
printf("no nickname --> will not verify user\n");
return false;
}
}
return true;
}
status_t
UserDetailVerifierProcess::_TryFetchUserDetail(UserDetail& userDetail)
{
WebAppInterface interface = fModel->GetWebAppInterface();
BMessage userDetailResponse;
status_t result;
result = interface.RetrieveCurrentUserDetail(userDetailResponse);
if (result != B_OK) {
printf("a problem has arisen retrieving the current user detail: %s\n",
strerror(result));
}
if (result == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(userDetailResponse);
switch (errorCode) {
case ERROR_CODE_NONE:
break;
case ERROR_CODE_AUTHORIZATIONFAILURE:
result = B_PERMISSION_DENIED;
break;
default:
printf("! a problem has arisen retrieving the current user "
"detail for user [%s]: jrpc error code %" B_PRId32 "\n",
fModel->Nickname().String(), errorCode);
result = B_ERROR;
break;
}
}
if (result == B_OK) {
// now we have the user details by showing that an authentication has
// worked, it is now necessary to check to see that the user has agreed
// to the most recent user-usage conditions.
result = interface.UnpackUserDetail(userDetailResponse, userDetail);
if (result != B_OK)
printf("! it was not possible to unpack the user details.\n");
}
return result;
}
@@ -0,0 +1,53 @@
/*
* Copyright 2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef USER_DETAIL_VERIFIER_PROCESS_H
#define USER_DETAIL_VERIFIER_PROCESS_H
#include <String.h>
#include "AbstractProcess.h"
#include "Model.h"
class UserDetailVerifierListener {
public:
virtual void UserUsageConditionsNotLatest(
const UserDetail& userDetail) = 0;
virtual void UserCredentialsFailed() = 0;
};
/*! This service has the purpose of querying the application server (HDS)
for details of the authenticated user. This will check that the user
has the correct username / password and also that the user has agreed
to the current terms and conditions.
*/
class UserDetailVerifierProcess : public AbstractProcess {
public:
UserDetailVerifierProcess(
Model* model,
UserDetailVerifierListener* listener);
virtual ~UserDetailVerifierProcess();
virtual const char* Name() const;
virtual const char* Description() const;
protected:
virtual status_t RunInternal();
private:
status_t _TryFetchUserDetail(UserDetail& userDetail);
bool _ShouldVerify();
private:
Model* fModel;
UserDetailVerifierListener*
fListener;
};
#endif // USER_DETAIL_VERIFIER_PROCESS_H
@@ -437,6 +437,37 @@ WebAppInterface::RetrieveUserUsageConditions(const BString& code,
}
status_t
WebAppInterface::AgreeUserUsageConditions(const BString& code,
BMessage& responsePayload)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"agreeUserUsageConditions");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
requestEnvelopeWriter.WriteString(code.String());
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(fCredentials.Nickname());
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
// now fetch this information into an object.
return _SendJsonRequest("user", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
responsePayload);
}
status_t
WebAppInterface::_RetrieveUserUsageConditionsMeta(const BString& code,
BMessage& message)
@@ -100,6 +100,9 @@ public:
const BString& code,
UserUsageConditions& conditions);
status_t AgreeUserUsageConditions(const BString& code,
BMessage& responsePayload);
status_t RetrieveScreenshot(
const BString& code,
int32 width, int32 height,
+267 -73
View File
@@ -3,7 +3,7 @@
* Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2013, Rene Gollent, [email protected].
* Copyright 2013, Ingo Weinhold, [email protected].
* Copyright 2016-2019, Andrew Lindesay <[email protected]>.
* Copyright 2016-2020, Andrew Lindesay <[email protected]>.
* Copyright 2017, Julian Harnath <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -47,6 +47,7 @@
#include "RatePackageWindow.h"
#include "support.h"
#include "ScreenshotWindow.h"
#include "ToLatestUserUsageConditionsWindow.h"
#include "UserLoginWindow.h"
#include "UserUsageConditionsWindow.h"
#include "WorkStatusView.h"
@@ -57,12 +58,10 @@
enum {
MSG_BULK_LOAD_DONE = 'mmwd',
MSG_REFRESH_REPOS = 'mrrp',
MSG_MANAGE_REPOS = 'mmrp',
MSG_SOFTWARE_UPDATER = 'mswu',
MSG_LOG_IN = 'lgin',
MSG_LOG_OUT = 'lgot',
MSG_AUTHORIZATION_CHANGED = 'athc',
MSG_CATEGORIES_LIST_CHANGED = 'clic',
MSG_PACKAGE_CHANGED = 'pchd',
@@ -76,6 +75,7 @@ enum {
MSG_SHOW_DEVELOP_PACKAGES = 'sdvl'
};
#define KEY_ERROR_STATUS "errorStatus"
using namespace BPackageKit;
using namespace BPackageKit::BManager::BPrivate;
@@ -133,9 +133,12 @@ MainWindow::MainWindow(const BMessage& settings)
fLogOutItem(NULL),
fUsersUserUsageConditionsMenuItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fBulkLoadProcessCoordinator(NULL),
fCoordinator(NULL),
fSinglePackageMode(false)
{
if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
debugger("unable to create the process coordinator semaphore");
BMenuBar* menuBar = new BMenuBar("Main Menu");
_BuildMenu(menuBar);
@@ -144,7 +147,6 @@ MainWindow::MainWindow(const BMessage& settings)
set_small_font(userMenuBar);
userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
menuBar->MaxSize().height));
_UpdateAuthorization();
fFilterView = new FilterView();
fFeaturedPackagesView = new FeaturedPackagesView();
@@ -207,6 +209,7 @@ MainWindow::MainWindow(const BMessage& settings)
fListTabs->Select(1);
_RestoreNickname(settings);
_UpdateAuthorization();
_RestoreWindowFrame(settings);
atomic_set(&fPackagesToShowListID, 0);
@@ -233,9 +236,12 @@ MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
fLogOutItem(NULL),
fUsersUserUsageConditionsMenuItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fBulkLoadProcessCoordinator(NULL),
fCoordinator(NULL),
fSinglePackageMode(true)
{
if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
debugger("unable to create the process coordinator semaphore");
fFilterView = new FilterView();
fPackageListView = new PackageListView(fModel.Lock());
fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
@@ -259,6 +265,10 @@ MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
MainWindow::~MainWindow()
{
_SpinUntilProcessCoordinatorComplete();
delete_sem(fCoordinatorRunningSem);
fCoordinatorRunningSem = 0;
BPackageRoster().StopWatching(this);
delete_sem(fPendingActionsSem);
@@ -290,7 +300,7 @@ MainWindow::QuitRequested()
be_app->PostMessage(&message);
_StopBulkLoad();
_StopProcessCoordinators();
return true;
}
@@ -301,8 +311,14 @@ MainWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_BULK_LOAD_DONE:
_BulkLoadCompleteReceived();
{
int64 errorStatus64;
if (message->FindInt64(KEY_ERROR_STATUS, &errorStatus64) == B_OK)
_BulkLoadCompleteReceived((status_t) errorStatus64);
else
printf("! expected [%s] value in message\n", KEY_ERROR_STATUS);
break;
}
case B_SIMPLE_DATA:
case B_REFS_RECEIVED:
// TODO: ?
@@ -318,6 +334,10 @@ MainWindow::MessageReceived(BMessage* message)
_StartBulkLoad(true);
break;
case MSG_WORK_STATUS_CLEAR:
_HandleWorkStatusClear();
break;
case MSG_WORK_STATUS_CHANGE:
_HandleWorkStatusChangeMessageReceived(message);
break;
@@ -347,6 +367,7 @@ MainWindow::MessageReceived(BMessage* message)
break;
case MSG_AUTHORIZATION_CHANGED:
_StartUserVerify();
_UpdateAuthorization();
break;
@@ -529,8 +550,10 @@ MainWindow::MessageReceived(BMessage* message)
}
}
if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0
&& fCoordinator == NULL) {
fWorkStatusView->PackageStatusChanged(ref);
}
}
break;
}
@@ -619,6 +642,18 @@ MainWindow::MessageReceived(BMessage* message)
break;
}
case MSG_USER_USAGE_CONDITIONS_NOT_LATEST:
{
BMessage userDetailMsg;
if (message->FindMessage("userDetail", &userDetailMsg) != B_OK) {
debugger("expected the [userDetail] data to be carried in the "
"message.");
}
UserDetail userDetail(&userDetailMsg);
_HandleUserUsageConditionsNotLatest(userDetail);
break;
}
default:
BWindow::MessageReceived(message);
break;
@@ -904,68 +939,25 @@ MainWindow::_ClearPackage()
}
void
MainWindow::_StopBulkLoad()
{
AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
if (fBulkLoadProcessCoordinator != NULL) {
printf("will stop full update process coordinator\n");
fBulkLoadProcessCoordinator->Stop();
}
}
void
MainWindow::_StartBulkLoad(bool force)
{
AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
if (fBulkLoadProcessCoordinator == NULL) {
fBulkLoadProcessCoordinator
= ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
this,
// PackageInfoListener
this,
// ProcessCoordinatorListener
&fModel, force);
fBulkLoadProcessCoordinator->Start();
fRefreshRepositoriesItem->SetEnabled(false);
}
}
/*! This method is called when there is some change in the bulk load process.
A change may mean that a new process has started / stopped etc... or it
may mean that the entire coordinator has finished.
*/
void
MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
{
AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
if (fBulkLoadProcessCoordinator == coordinatorState.Coordinator()) {
if (!coordinatorState.IsRunning())
_BulkLoadProcessCoordinatorFinished(coordinatorState);
else {
_NotifyWorkStatusChange(coordinatorState.Message(),
coordinatorState.Progress());
// show the progress to the user.
}
} else {
if (Logger::IsInfoEnabled()) {
printf("unknown process coordinator changed\n");
}
}
fRefreshRepositoriesItem->SetEnabled(false);
ProcessCoordinator* bulkLoadCoordinator =
ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
this,
// PackageInfoListener
this,
// ProcessCoordinatorListener
&fModel, force);
_AddProcessCoordinator(bulkLoadCoordinator);
}
void
MainWindow::_BulkLoadProcessCoordinatorFinished(
ProcessCoordinatorState& coordinatorState)
MainWindow::_BulkLoadCompleteReceived(status_t errorStatus)
{
if (coordinatorState.ErrorStatus() != B_OK) {
if (errorStatus != B_OK) {
AppUtils::NotifySimpleError(
B_TRANSLATE("Package update error"),
B_TRANSLATE("While updating package data, a problem has arisen "
@@ -975,22 +967,25 @@ MainWindow::_BulkLoadProcessCoordinatorFinished(
"logs."
ALERT_MSG_LOGS_USER_GUIDE));
}
BMessenger messenger(this);
messenger.SendMessage(MSG_BULK_LOAD_DONE);
// it is safe to delete the coordinator here because it is already known
// that all of the processes have completed and their threads will have
// exited safely by this point.
delete fBulkLoadProcessCoordinator;
fBulkLoadProcessCoordinator = NULL;
fRefreshRepositoriesItem->SetEnabled(true);
_AdoptModel();
_UpdateAvailableRepositories();
}
void
MainWindow::_BulkLoadCompleteReceived()
MainWindow::_NotifyWorkStatusClear()
{
_AdoptModel();
_UpdateAvailableRepositories();
BMessage message(MSG_WORK_STATUS_CLEAR);
this->PostMessage(&message, this);
}
void
MainWindow::_HandleWorkStatusClear()
{
fWorkStatusView->SetText("");
fWorkStatusView->SetIdle();
}
@@ -1204,6 +1199,21 @@ MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
}
void
MainWindow::_StartUserVerify()
{
if (!fModel.Nickname().IsEmpty()) {
_AddProcessCoordinator(
ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator(
this,
// UserDetailVerifierListener
this,
// ProcessCoordinatorListener
&fModel) );
}
}
void
MainWindow::_UpdateAuthorization()
{
@@ -1368,4 +1378,188 @@ MainWindow::_ViewUserUsageConditions(
UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
fModel, mode);
window->Show();
}
void
MainWindow::UserCredentialsFailed()
{
BString message = B_TRANSLATE("The password previously "
"supplied for the user [%Nickname%] is not currently "
"valid. The user will be logged-out of this application "
"and you should login again with your updated password.");
message.ReplaceAll("%Nickname%", fModel.Nickname());
AppUtils::NotifySimpleError(B_TRANSLATE("Login issue"),
message);
{
AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetNickname("");
}
}
/*! \brief This method is invoked from the UserDetailVerifierProcess on a
background thread. For this reason it lodges a message into itself
which can then be handled on the main thread.
*/
void
MainWindow::UserUsageConditionsNotLatest(const UserDetail& userDetail)
{
BMessage message(MSG_USER_USAGE_CONDITIONS_NOT_LATEST);
BMessage detailsMessage;
if (userDetail.Archive(&detailsMessage, true) != B_OK
|| message.AddMessage("userDetail", &detailsMessage) != B_OK) {
printf("!! unable to archive the user detail into a message\n");
}
else
BMessenger(this).SendMessage(&message);
}
void
MainWindow::_HandleUserUsageConditionsNotLatest(
const UserDetail& userDetail)
{
ToLatestUserUsageConditionsWindow* window =
new ToLatestUserUsageConditionsWindow(this, fModel, userDetail);
window->Show();
}
void
MainWindow::_AddProcessCoordinator(ProcessCoordinator* item)
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
if (fCoordinator == NULL) {
if (acquire_sem(fCoordinatorRunningSem) != B_OK)
debugger("unable to acquire the process coordinator sem");
if (Logger::IsInfoEnabled()) {
printf("adding and starting a process coordinator [%s]\n",
item->Name().String());
}
fCoordinator = item;
fCoordinator->Start();
}
else {
if (Logger::IsInfoEnabled()) {
printf("adding process coordinator [%s] to the queue\n",
item->Name().String());
}
fCoordinatorQueue.push(item);
}
}
void
MainWindow::_SpinUntilProcessCoordinatorComplete()
{
while (true) {
if (acquire_sem(fCoordinatorRunningSem) != B_OK)
debugger("unable to acquire the process coordinator sem");
if (release_sem(fCoordinatorRunningSem) != B_OK)
debugger("unable to release the process coordinator sem");
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
if (fCoordinator == NULL)
return;
}
}
}
void
MainWindow::_StopProcessCoordinators()
{
if (Logger::IsInfoEnabled())
printf("will stop all process coordinators\n");
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
while (!fCoordinatorQueue.empty()) {
ProcessCoordinator *processCoordinator = fCoordinatorQueue.front();
if (Logger::IsInfoEnabled()) {
printf("will drop queued process coordinator [%s]\n",
processCoordinator->Name().String());
}
fCoordinatorQueue.pop();
delete processCoordinator;
}
if (fCoordinator != NULL) {
fCoordinator->Stop();
}
}
if (Logger::IsInfoEnabled())
printf("will wait until the process coordinator has stopped\n");
_SpinUntilProcessCoordinatorComplete();
if (Logger::IsInfoEnabled())
printf("did stop all process coordinators\n");
}
/*! This method is called when there is some change in the bulk load process
or other process coordinator.
A change may mean that a new process has started / stopped etc... or it
may mean that the entire coordinator has finished.
*/
void
MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
{
AutoLocker<BLocker> lock(&fCoordinatorLock);
if (fCoordinator == coordinatorState.Coordinator()) {
if (!coordinatorState.IsRunning()) {
if (release_sem(fCoordinatorRunningSem) != B_OK)
debugger("unable to release the process coordinator sem");
if (Logger::IsInfoEnabled()) {
printf("process coordinator [%s] did complete\n",
fCoordinator->Name().String());
}
// complete the last one that just finished
BMessage* message = fCoordinator->Message();
if (message != NULL) {
BMessenger messenger(this);
message->AddInt64(KEY_ERROR_STATUS,
(int64) fCoordinator->ErrorStatus());
messenger.SendMessage(message);
}
delete fCoordinator;
fCoordinator = NULL;
// now schedule the next one.
if (!fCoordinatorQueue.empty()) {
if (acquire_sem(fCoordinatorRunningSem) != B_OK)
debugger("unable to acquire the process coordinator sem");
fCoordinator = fCoordinatorQueue.front();
if (Logger::IsInfoEnabled()) {
printf("starting next process coordinator [%s]\n",
fCoordinator->Name().String());
}
fCoordinatorQueue.pop();
fCoordinator->Start();
}
else {
_NotifyWorkStatusClear();
}
}
else {
_NotifyWorkStatusChange(coordinatorState.Message(),
coordinatorState.Progress());
// show the progress to the user.
}
} else {
if (Logger::IsInfoEnabled())
printf("! unknown process coordinator changed\n");
}
}
+31 -9
View File
@@ -2,7 +2,7 @@
* Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2013, Rene Gollent <[email protected]>.
* Copyright 2017, Julian Harnath <[email protected]>.
* Copyright 2017-2019, Andrew Lindesay <[email protected]>.
* Copyright 2017-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef MAIN_WINDOW_H
@@ -10,6 +10,8 @@
#include <Window.h>
#include <queue>
#include "HaikuDepotConstants.h"
#include "Model.h"
#include "PackageAction.h"
@@ -17,6 +19,8 @@
#include "ProcessCoordinator.h"
#include "PackageInfoListener.h"
#include "TabView.h"
#include "UserDetail.h"
#include "UserDetailVerifierProcess.h"
class BCardLayout;
@@ -33,7 +37,8 @@ class WorkStatusView;
class MainWindow : public BWindow, private PackageInfoListener,
private PackageActionHandler, public ProcessCoordinatorListener {
private PackageActionHandler, public ProcessCoordinatorListener,
public UserDetailVerifierListener {
public:
MainWindow(const BMessage& settings);
MainWindow(const BMessage& settings,
@@ -49,6 +54,11 @@ public:
// ProcessCoordinatorListener
virtual void CoordinatorChanged(
ProcessCoordinatorState& coordinatorState);
// UserDetailVerifierProcessListener
virtual void UserCredentialsFailed();
virtual void UserUsageConditionsNotLatest(
const UserDetail& userDetail);
private:
// PackageInfoListener
virtual void PackageChanged(
@@ -61,9 +71,11 @@ private:
virtual Model* GetModel();
private:
void _BulkLoadProcessCoordinatorFinished(
ProcessCoordinatorState&
processCoordinatorState);
void _AddProcessCoordinator(
ProcessCoordinator* item);
void _StopProcessCoordinators();
void _SpinUntilProcessCoordinatorComplete();
bool _SelectedPackageHasWebAppRepositoryCode();
void _BuildMenu(BMenuBar* menuBar);
@@ -80,9 +92,11 @@ private:
void _ClearPackage();
void _PopulatePackageAsync(bool forcePopulate);
void _StopBulkLoad();
void _StartBulkLoad(bool force = false);
void _BulkLoadCompleteReceived();
void _BulkLoadCompleteReceived(status_t errorStatus);
void _NotifyWorkStatusClear();
void _HandleWorkStatusClear();
void _NotifyWorkStatusChange(const BString& text,
float progress);
@@ -96,6 +110,7 @@ private:
void _OpenLoginWindow(
const BMessage& onSuccessMessage);
void _StartUserVerify();
void _UpdateAuthorization();
void _UpdateAvailableRepositories();
void _RatePackage();
@@ -104,6 +119,9 @@ private:
void _ViewUserUsageConditions(
UserUsageConditionsSelectionMode mode);
void _HandleUserUsageConditionsNotLatest(
const UserDetail& userDetail);
private:
FilterView* fFilterView;
TabView* fListTabs;
@@ -131,8 +149,12 @@ private:
Model fModel;
ModelListenerRef fModelListener;
PackageList fVisiblePackages;
ProcessCoordinator* fBulkLoadProcessCoordinator;
BLocker fBulkLoadProcessCoordinatorLock;
std::queue<ProcessCoordinator*>
fCoordinatorQueue;
ProcessCoordinator* fCoordinator;
BLocker fCoordinatorLock;
sem_id fCoordinatorRunningSem;
bool fSinglePackageMode;
@@ -0,0 +1,448 @@
/*
* Copyright 2020, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ToLatestUserUsageConditionsWindow.h"
#include <Alert.h>
#include <Autolock.h>
#include <AutoLocker.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <LayoutBuilder.h>
#include <Locker.h>
#include <SeparatorView.h>
#include <TextView.h>
#include "AppUtils.h"
#include "LinkView.h"
#include "LocaleUtils.h"
#include "Logger.h"
#include "Model.h"
#include "UserUsageConditionsWindow.h"
#include "ServerHelper.h"
#include "WebAppInterface.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ToLatestUserUsageConditionsWindow"
#define PLACEHOLDER_TEXT B_UTF8_ELLIPSIS
#define WINDOW_FRAME BRect(0, 0, 500, 280)
#define KEY_USER_USAGE_CONDITIONS "userUsageConditions"
#define NO_PRIOR_MESSAGE_TEXT "The user [%Nickname%] has authenticated, but " \
"before proceeding, you are required to agree to the most recent usage " \
"conditions."
#define PRIOR_MESSAGE_TEXT "The user \"%Nickname%\" has previously agreed to " \
"usage conditions, but the usage conditions have been updated since. " \
"The updated usage conditions now need to be agreed to."
enum {
MSG_AGREE = 'agre',
MSG_AGREE_FAILED = 'agfa',
MSG_AGREE_MINIMUM_AGE_TOGGLE = 'amat',
MSG_AGREE_USER_USAGE_CONDITIONS_TOGGLE = 'auct'
};
ToLatestUserUsageConditionsWindow::ToLatestUserUsageConditionsWindow(
BWindow* parent,
Model& model, const UserDetail& userDetail)
:
BWindow(WINDOW_FRAME, B_TRANSLATE("Update usage conditions"),
B_FLOATING_WINDOW_LOOK, B_MODAL_SUBSET_WINDOW_FEEL,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
| B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_NOT_CLOSABLE ),
fModel(model),
fUserDetail(userDetail),
fWorkerThread(-1),
fQuitRequestedDuringWorkerThread(false),
fMutableControlsEnabled(false)
{
AddToSubset(parent);
_InitUiControls();
// some layout magic happening here. If the checkboxes are put directly
// into the main vertical-group then the window tries to shrink to the
// preferred size of the checkboxes. To avoid this, a grid is used to house
// the checkboxes and a fake extra grid column is added to the right of the
// checkboxes which prevents the window from reducing in size to meet the
// checkboxes.
BLayoutBuilder::Group<>(this, B_VERTICAL)
.AddGrid()
.Add(fMessageTextView, 0, 0, 2)
.Add(fConfirmMinimumAgeCheckBox, 0, 1, 1)
.Add(fConfirmUserUsageConditionsCheckBox, 0, 2, 1)
.Add(fUserUsageConditionsLink, 0, 3, 2)
.End()
.Add(new BSeparatorView(B_HORIZONTAL))
// rule off
.AddGroup(B_HORIZONTAL, 1)
.AddGlue()
.Add(fLogoutButton)
.Add(fAgreeButton)
.End()
.Add(fWorkerIndicator, 1)
.SetInsets(B_USE_WINDOW_INSETS);
CenterOnScreen();
_FetchData();
// start a new thread to pull down the user usage conditions data.
}
ToLatestUserUsageConditionsWindow::~ToLatestUserUsageConditionsWindow()
{
BAutolock locker(&fLock);
if (fWorkerThread >= 0)
wait_for_thread(fWorkerThread, NULL);
}
void
ToLatestUserUsageConditionsWindow::_InitUiControls()
{
fMessageTextView = new BTextView("message text view");
fMessageTextView->AdoptSystemColors();
fMessageTextView->MakeEditable(false);
fMessageTextView->MakeSelectable(false);
BString message;
if (fUserDetail.Agreement().Code().IsEmpty())
message = B_TRANSLATE(NO_PRIOR_MESSAGE_TEXT);
else
message = B_TRANSLATE(PRIOR_MESSAGE_TEXT);
message.ReplaceAll("%Nickname%", fUserDetail.Nickname());
fMessageTextView->SetText(message);
fConfirmMinimumAgeCheckBox = new BCheckBox("confirm minimum age",
PLACEHOLDER_TEXT,
// is filled in when the user usage conditions data is available
new BMessage(MSG_AGREE_MINIMUM_AGE_TOGGLE));
fConfirmUserUsageConditionsCheckBox = new BCheckBox(
"confirm usage conditions",
B_TRANSLATE("I agree to the usage conditions"),
new BMessage(MSG_AGREE_USER_USAGE_CONDITIONS_TOGGLE));
fUserUsageConditionsLink = new LinkView("usage conditions view",
B_TRANSLATE("View the usage conditions"),
new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
fUserUsageConditionsLink->SetTarget(this);
fLogoutButton = new BButton("logout", B_TRANSLATE("Logout"),
new BMessage(MSG_LOG_OUT));
fAgreeButton = new BButton("agree", B_TRANSLATE("Agree"),
new BMessage(MSG_AGREE));
fWorkerIndicator = new BarberPole("fetch data worker indicator");
BSize workerIndicatorSize;
workerIndicatorSize.SetHeight(20);
fWorkerIndicator->SetExplicitSize(workerIndicatorSize);
fMutableControlsEnabled = false;
_EnableMutableControls();
}
void
ToLatestUserUsageConditionsWindow::_EnableMutableControls()
{
bool ageChecked = fConfirmMinimumAgeCheckBox->Value() == 1;
bool conditionsChecked = fConfirmUserUsageConditionsCheckBox->Value() == 1;
fUserUsageConditionsLink->SetEnabled(fMutableControlsEnabled);
fAgreeButton->SetEnabled(fMutableControlsEnabled && ageChecked
&& conditionsChecked);
fLogoutButton->SetEnabled(fMutableControlsEnabled);
fConfirmUserUsageConditionsCheckBox->SetEnabled(fMutableControlsEnabled);
fConfirmMinimumAgeCheckBox->SetEnabled(fMutableControlsEnabled);
}
void
ToLatestUserUsageConditionsWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_USER_USAGE_CONDITIONS_DATA:
{
BMessage userUsageConditionsMessage;
message->FindMessage(KEY_USER_USAGE_CONDITIONS,
&userUsageConditionsMessage);
UserUsageConditions userUsageConditions(
&userUsageConditionsMessage);
_DisplayData(userUsageConditions);
fWorkerIndicator->Stop();
break;
}
case MSG_LOG_OUT:
_HandleLogout();
break;
case MSG_AGREE:
_HandleAgree();
break;
case MSG_AGREE_FAILED:
_HandleAgreeFailed();
break;
case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
_HandleViewUserUsageConditions();
break;
case MSG_AGREE_MINIMUM_AGE_TOGGLE:
case MSG_AGREE_USER_USAGE_CONDITIONS_TOGGLE:
_EnableMutableControls();
break;
default:
BWindow::MessageReceived(message);
break;
}
}
bool
ToLatestUserUsageConditionsWindow::QuitRequested()
{
BAutolock locker(&fLock);
if (fWorkerThread >= 0) {
if (Logger::IsDebugEnabled())
printf("quit requested while worker thread is operating -- will "
"try again once the worker thread has completed\n");
fQuitRequestedDuringWorkerThread = true;
return false;
}
return true;
}
void
ToLatestUserUsageConditionsWindow::_SetWorkerThread(thread_id thread)
{
if (thread >= 0) {
fWorkerThread = thread;
resume_thread(fWorkerThread);
} else {
fWorkerThread = -1;
if (fQuitRequestedDuringWorkerThread)
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
fQuitRequestedDuringWorkerThread = false;
}
}
void
ToLatestUserUsageConditionsWindow::_SetWorkerThreadLocked(thread_id thread)
{
BAutolock locker(&fLock);
_SetWorkerThread(thread);
}
/*! This method is called on the main thread in order to initiate the background
processing to obtain the user usage conditions data. It will take
responsibility for coordinating the creation of the thread and starting the
thread etc...
*/
void
ToLatestUserUsageConditionsWindow::_FetchData()
{
{
BAutolock locker(&fLock);
if (-1 != fWorkerThread) {
debugger("illegal state - attempt to fetch, but thread in "
"progress");
}
}
thread_id thread = spawn_thread(&_FetchDataThreadEntry,
"Fetch usage conditions data", B_NORMAL_PRIORITY, this);
if (thread >= 0) {
fWorkerIndicator->Start();
_SetWorkerThreadLocked(thread);
} else {
debugger("unable to start a thread to fetch the user usage "
"conditions.");
}
}
/*! This method is called from the thread; it is
the entry-point for the background processing to obtain the user usage
conditions.
*/
/*static*/ int32
ToLatestUserUsageConditionsWindow::_FetchDataThreadEntry(void* data)
{
ToLatestUserUsageConditionsWindow* win
= reinterpret_cast<ToLatestUserUsageConditionsWindow*>(data);
win->_FetchDataPerform();
return 0;
}
/*! This method will perform the task of obtaining data about the user usage
conditions.
*/
void
ToLatestUserUsageConditionsWindow::_FetchDataPerform()
{
UserUsageConditions conditions;
WebAppInterface interface = fModel.GetWebAppInterface();
if (interface.RetrieveUserUsageConditions("", conditions) == B_OK) {
BMessage userUsageConditionsMessage;
conditions.Archive(&userUsageConditionsMessage, true);
BMessage dataMessage(MSG_USER_USAGE_CONDITIONS_DATA);
dataMessage.AddMessage(KEY_USER_USAGE_CONDITIONS,
&userUsageConditionsMessage);
BMessenger(this).SendMessage(&dataMessage);
} else {
_NotifyFetchProblem();
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
}
_SetWorkerThreadLocked(-1);
}
void
ToLatestUserUsageConditionsWindow::_NotifyFetchProblem()
{
AppUtils::NotifySimpleError(
B_TRANSLATE("Usage conditions download problem"),
B_TRANSLATE("An error has arisen downloading the usage "
"conditions. Check the log for details and try again. "
ALERT_MSG_LOGS_USER_GUIDE));
}
void
ToLatestUserUsageConditionsWindow::_Agree()
{
{
BAutolock locker(&fLock);
if (-1 != fWorkerThread) {
debugger("illegal state - attempt to agree, but thread in "
"progress");
}
}
fMutableControlsEnabled = false;
_EnableMutableControls();
thread_id thread = spawn_thread(&_AgreeThreadEntry,
"Agree usage conditions", B_NORMAL_PRIORITY, this);
if (thread >= 0) {
fWorkerIndicator->Start();
_SetWorkerThreadLocked(thread);
} else {
debugger("unable to start a thread to fetch the user usage "
"conditions.");
}
}
/*static*/ int32
ToLatestUserUsageConditionsWindow::_AgreeThreadEntry(void* data)
{
ToLatestUserUsageConditionsWindow* win
= reinterpret_cast<ToLatestUserUsageConditionsWindow*>(data);
win->_AgreePerform();
return 0;
}
void
ToLatestUserUsageConditionsWindow::_AgreePerform()
{
BMessenger messenger(this);
BMessage responsePayload;
WebAppInterface webApp = fModel.GetWebAppInterface();
status_t result = webApp.AgreeUserUsageConditions(
fUserUsageConditions.Code(), responsePayload);
if (result != B_OK) {
ServerHelper::NotifyTransportError(result);
messenger.SendMessage(MSG_AGREE_FAILED);
} else {
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(
responsePayload);
if (errorCode == ERROR_CODE_NONE) {
AppUtils::NotifySimpleError(
B_TRANSLATE("Usage conditions agreed"),
B_TRANSLATE("The current usage conditions have been agreed "
"to."));
messenger.SendMessage(B_QUIT_REQUESTED);
}
else {
AutoLocker<BLocker> locker(fModel.Lock());
ServerHelper::NotifyServerJsonRpcError(responsePayload);
messenger.SendMessage(MSG_AGREE_FAILED);
}
}
_SetWorkerThreadLocked(-1);
}
void
ToLatestUserUsageConditionsWindow::_HandleAgreeFailed()
{
fWorkerIndicator->Stop();
fMutableControlsEnabled = true;
_EnableMutableControls();
}
void
ToLatestUserUsageConditionsWindow::_DisplayData(
const UserUsageConditions& userUsageConditions)
{
fUserUsageConditions = userUsageConditions;
fConfirmMinimumAgeCheckBox->SetLabel(
LocaleUtils::CreateTranslatedIAmMinimumAgeSlug(
fUserUsageConditions.MinimumAge()));
fMutableControlsEnabled = true;
_EnableMutableControls();
}
void
ToLatestUserUsageConditionsWindow::_HandleViewUserUsageConditions()
{
if (!fUserUsageConditions.Code().IsEmpty()) {
UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
fModel, fUserUsageConditions);
window->Show();
}
}
void
ToLatestUserUsageConditionsWindow::_HandleLogout()
{
AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetNickname("");
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
}
void
ToLatestUserUsageConditionsWindow::_HandleAgree()
{
bool ageChecked = fConfirmMinimumAgeCheckBox->Value() == 1;
bool conditionsChecked = fConfirmUserUsageConditionsCheckBox->Value() == 1;
// precondition that the user has checked both of the checkboxes.
if (!ageChecked || !conditionsChecked)
debugger("the user has not agreed to the age and conditions");
_Agree();
}
@@ -0,0 +1,77 @@
/*
* Copyright 2019-2020, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef TO_LATEST_USER_USAGE_CONDITIONS_WINDOW_H
#define TO_LATEST_USER_USAGE_CONDITIONS_WINDOW_H
#include <Locker.h>
#include <Messenger.h>
#include <Window.h>
#include "BarberPole.h"
#include "HaikuDepotConstants.h"
#include "UserDetail.h"
#include "UserUsageConditions.h"
class BButton;
class BCheckBox;
class BTextView;
class LinkView;
class Model;
class ToLatestUserUsageConditionsWindow : public BWindow {
public:
ToLatestUserUsageConditionsWindow(
BWindow* parent,
Model& model, const UserDetail& userDetail);
virtual ~ToLatestUserUsageConditionsWindow();
virtual void MessageReceived(BMessage* message);
virtual bool QuitRequested();
private:
void _EnableMutableControls();
void _InitUiControls();
void _DisplayData(const UserUsageConditions&
userUsageConditions);
void _HandleViewUserUsageConditions();
void _HandleLogout();
void _HandleAgree();
void _HandleAgreeFailed();
void _SetWorkerThread(thread_id thread);
void _SetWorkerThreadLocked(thread_id thread);
void _FetchData();
static int32 _FetchDataThreadEntry(void* data);
void _FetchDataPerform();
void _NotifyFetchProblem();
void _Agree();
static int32 _AgreeThreadEntry(void* data);
void _AgreePerform();
private:
UserUsageConditions fUserUsageConditions;
Model& fModel;
UserDetail fUserDetail;
BTextView* fMessageTextView;
BButton* fLogoutButton;
BButton* fAgreeButton;
BCheckBox* fConfirmMinimumAgeCheckBox;
BCheckBox* fConfirmUserUsageConditionsCheckBox;
LinkView* fUserUsageConditionsLink;
BarberPole* fWorkerIndicator;
BLocker fLock;
thread_id fWorkerThread;
bool fQuitRequestedDuringWorkerThread;
bool fMutableControlsEnabled;
};
#endif // TO_LATEST_USER_USAGE_CONDITIONS_WINDOW_H
+5 -8
View File
@@ -1,6 +1,6 @@
/*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2019-2020, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -29,6 +29,7 @@
#include "HaikuDepotConstants.h"
#include "LanguageMenuUtils.h"
#include "LinkView.h"
#include "LocaleUtils.h"
#include "Logger.h"
#include "Model.h"
#include "ServerHelper.h"
@@ -942,13 +943,9 @@ UserLoginWindow::_SetUserUsageConditions(
fUserUsageConditions = userUsageConditions;
if (fUserUsageConditions != NULL) {
BString minimumAgeString;
minimumAgeString.SetToFormat("%" B_PRId8,
fUserUsageConditions->MinimumAge());
BString label = B_TRANSLATE(
"I am %MinimumAgeYears% years of age or older");
label.ReplaceAll("%MinimumAgeYears%", minimumAgeString);
fConfirmMinimumAgeCheckBox->SetLabel(label);
fConfirmMinimumAgeCheckBox->SetLabel(
LocaleUtils::CreateTranslatedIAmMinimumAgeSlug(
fUserUsageConditions->MinimumAge()));
} else {
fConfirmMinimumAgeCheckBox->SetLabel(PLACEHOLDER_TEXT);
fConfirmMinimumAgeCheckBox->SetValue(0);
@@ -104,7 +104,7 @@ UserUsageConditionsWindow::UserUsageConditionsWindow(
fWorkerIndicator = new BarberPole("fetch data worker indicator");
BSize workerIndicatorSize;
workerIndicatorSize.SetHeight(20);
fWorkerIndicator->SetExplicitMinSize(workerIndicatorSize);
fWorkerIndicator->SetExplicitSize(workerIndicatorSize);
fIntroductionTextView = new BTextView("introduction text view");
fIntroductionTextView->AdoptSystemColors();
+15 -6
View File
@@ -1,5 +1,6 @@
/*
* Copyright 2017 Julian Harnath <julian.harnath@rwth-aachen.de>
* Copyright 2020 Andrew Lindesay <apl@lindesay.co.nz>
* All rights reserved. Distributed under the terms of the MIT license.
*/
@@ -22,6 +23,12 @@
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "WorkStatusView"
#define VIEW_INDEX_BARBER_POLE (int32) 0
#define VIEW_INDEX_PROGRESS_BAR (int32) 1
static const BSize kStatusBarSize = BSize(100,20);
WorkStatusView::WorkStatusView(const char* name)
:
@@ -36,8 +43,10 @@ WorkStatusView::WorkStatusView(const char* name)
fProgressLayout->AddView(fBarberPole);
fProgressLayout->AddView(fProgressBar);
fBarberPole->SetExplicitSize(kStatusBarSize);
fProgressBar->SetMaxValue(1.0f);
fProgressBar->SetBarHeight(20);
fProgressBar->SetBarHeight(kStatusBarSize.Height());
fProgressBar->SetExplicitSize(kStatusBarSize);
fStatusText->SetFontSize(be_plain_font->Size() * 0.9f);
@@ -70,8 +79,8 @@ void
WorkStatusView::SetBusy()
{
fBarberPole->Start();
if (fProgressLayout->VisibleIndex() != 0)
fProgressLayout->SetVisibleItem((int32)0);
if (fProgressLayout->VisibleIndex() != VIEW_INDEX_BARBER_POLE)
fProgressLayout->SetVisibleItem(VIEW_INDEX_BARBER_POLE);
}
@@ -79,7 +88,7 @@ void
WorkStatusView::SetIdle()
{
fBarberPole->Stop();
fProgressLayout->SetVisibleItem((int32)0);
fProgressLayout->SetVisibleItem(VIEW_INDEX_BARBER_POLE);
SetText(NULL);
}
@@ -88,8 +97,8 @@ void
WorkStatusView::SetProgress(float value)
{
fProgressBar->SetTo(value);
if (fProgressLayout->VisibleIndex() != 1)
fProgressLayout->SetVisibleItem(1);
if (fProgressLayout->VisibleIndex() != VIEW_INDEX_PROGRESS_BAR)
fProgressLayout->SetVisibleItem(VIEW_INDEX_PROGRESS_BAR);
}
+23
View File
@@ -9,10 +9,16 @@
#include <unicode/dtptngen.h>
#include <unicode/smpdtfmt.h>
#include <Catalog.h>
#include <Collator.h>
#include <ICUWrapper.h>
#include <Locale.h>
#include <LocaleRoster.h>
#include <StringFormat.h>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "LocaleUtils"
BCollator* LocaleUtils::sSharedCollator = NULL;
@@ -74,3 +80,20 @@ LocaleUtils::TimestampToDateTimeString(uint64 millis)
icuResult.toUTF8(converter);
return result;
}
/*! This is used in situations where the user is required to confirm that they
are as old or older than some minimal age. This is associated with agreeing
to the user usage conditions.
*/
/*static*/ BString
LocaleUtils::CreateTranslatedIAmMinimumAgeSlug(int minimumAge)
{
BString slug;
static BStringFormat format(B_TRANSLATE("{0, plural,"
"one{I am at least one year old}"
"other{I am # years of age or older}}"));
format.Format(slug, minimumAge);
return slug;
}
+2
View File
@@ -18,6 +18,8 @@ public:
static BCollator* GetSharedCollator();
static BString TimestampToDateTimeString(uint64 millis);
static BString CreateTranslatedIAmMinimumAgeSlug(int minimumAge);
private:
static void GetCollator(BCollator* collator);