From Andrew Lindesay <apl@xxxxxxxxxxxxxx>:
Andrew Lindesay has uploaded this change for review. (
https://review.haiku-os.org/c/haiku/+/2467 ;)
Change subject: HaikuDepot: Check User Auth on Start
......................................................................
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
---
M src/apps/haikudepot/HaikuDepotConstants.h
M src/apps/haikudepot/Jamfile
M src/apps/haikudepot/model/Model.cpp
M src/apps/haikudepot/server/ProcessCoordinator.cpp
M src/apps/haikudepot/server/ProcessCoordinator.h
M src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp
M src/apps/haikudepot/server/ProcessCoordinatorFactory.h
A src/apps/haikudepot/server/UserDetailVerifierProcess.cpp
A src/apps/haikudepot/server/UserDetailVerifierProcess.h
M src/apps/haikudepot/server/WebAppInterface.cpp
M src/apps/haikudepot/server/WebAppInterface.h
M src/apps/haikudepot/ui/MainWindow.cpp
M src/apps/haikudepot/ui/MainWindow.h
A src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.cpp
A src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.h
M src/apps/haikudepot/ui/UserUsageConditionsWindow.cpp
M src/apps/haikudepot/ui/WorkStatusView.cpp
17 files changed, 1,191 insertions(+), 108 deletions(-)
git pull ssh://git.haiku-os.org:22/haiku refs/changes/67/2467/1
diff --git a/src/apps/haikudepot/HaikuDepotConstants.h
b/src/apps/haikudepot/HaikuDepotConstants.h
index 190f8cb..21249cd 100644
--- a/src/apps/haikudepot/HaikuDepotConstants.h
+++ b/src/apps/haikudepot/HaikuDepotConstants.h
@@ -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 @@
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 @@
#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"
diff --git a/src/apps/haikudepot/Jamfile b/src/apps/haikudepot/Jamfile
index 27a1432..2d61ec5 100644
--- a/src/apps/haikudepot/Jamfile
+++ b/src/apps/haikudepot/Jamfile
@@ -146,6 +146,7 @@
ScreenshotWindow.cpp
ScrollableGroupView.cpp
SharedBitmap.cpp
+ ToLatestUserUsageConditionsWindow.cpp
UserCredentials.cpp
UserDetail.cpp
UserLoginWindow.cpp
@@ -172,6 +173,7 @@
ServerIconExportUpdateProcess.cpp
StandardMetaDataJsonEventListener.cpp
StandardMetaData.cpp
+ UserDetailVerifierProcess.cpp
WebAppInterface.cpp
# tar
diff --git a/src/apps/haikudepot/model/Model.cpp
b/src/apps/haikudepot/model/Model.cpp
index ff8266a..25d3d97 100644
--- a/src/apps/haikudepot/model/Model.cpp
+++ b/src/apps/haikudepot/model/Model.cpp
@@ -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 @@
}
+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 @@
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();
}
diff --git a/src/apps/haikudepot/server/ProcessCoordinator.cpp
b/src/apps/haikudepot/server/ProcessCoordinator.cpp
index 563c4e8..e1cccdb 100644
--- a/src/apps/haikudepot/server/ProcessCoordinator.cpp
+++ b/src/apps/haikudepot/server/ProcessCoordinator.cpp
@@ -76,9 +76,13 @@
// #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 @@
node->Process()->SetListener(NULL);
delete node;
}
+ delete fMessage;
}
@@ -147,6 +152,10 @@
node->StopProcess();
}
}
+ if (fListener != NULL) {
+ ProcessCoordinatorState state = _CreateStatus();
+ fListener->CoordinatorChanged(state);
+ }
}
@@ -175,6 +184,20 @@
}
+const BString&
+ProcessCoordinator::Name() const
+{
+ return fName;
+}
+
+
+BMessage*
+ProcessCoordinator::Message() const
+{
+ return fMessage;
+}
+
+
BString
ProcessCoordinator::_CreateStatusMessage()
{
diff --git a/src/apps/haikudepot/server/ProcessCoordinator.h
b/src/apps/haikudepot/server/ProcessCoordinator.h
index c0a9d32..c7102f1 100644
--- a/src/apps/haikudepot/server/ProcessCoordinator.h
+++ b/src/apps/haikudepot/server/ProcessCoordinator.h
@@ -74,7 +74,9 @@
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 @@
float Progress();
+ const BString& Name() const;
+ BMessage* Message() const;
+
private:
bool _IsRunning(ProcessNode*
node);
void
_CoordinateAndCallListener();
@@ -103,11 +108,14 @@
void
_StopSuccessorNodesToErroredOrStoppedNodes();
void
_StopSuccessorNodes(ProcessNode* node);
+private:
+ BString fName;
BLocker fLock;
List<ProcessNode*, true>
fNodes;
ProcessCoordinatorListener*
fListener;
+ BMessage* fMessage;
bool fWasStopped;
};
diff --git a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp
b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp
index 69ea8ee..38b2e29 100644
--- a/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp
+++ b/src/apps/haikudepot/server/ProcessCoordinatorFactory.cpp
@@ -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 @@
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,
diff --git a/src/apps/haikudepot/server/ProcessCoordinatorFactory.h
b/src/apps/haikudepot/server/ProcessCoordinatorFactory.h
index e3ba0b5..41ca271 100644
--- a/src/apps/haikudepot/server/ProcessCoordinatorFactory.h
+++ b/src/apps/haikudepot/server/ProcessCoordinatorFactory.h
@@ -13,6 +13,7 @@
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 @@
ProcessCoordinatorListener*
processCoordinatorListener,
Model*
model, bool forceLocalUpdate);
+
+ static ProcessCoordinator* CreateUserDetailVerifierCoordinator(
+
UserDetailVerifierListener*
+
userDetailVerifierListener,
+
ProcessCoordinatorListener*
+
processCoordinatorListener,
+ Model*
model);
private:
static uint32
_CalculateServerProcessOptions();
diff --git a/src/apps/haikudepot/server/UserDetailVerifierProcess.cpp
b/src/apps/haikudepot/server/UserDetailVerifierProcess.cpp
new file mode 100644
index 0000000..fad55c7
--- /dev/null
+++ b/src/apps/haikudepot/server/UserDetailVerifierProcess.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2020, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * 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;
+}
\ No newline at end of file
diff --git a/src/apps/haikudepot/server/UserDetailVerifierProcess.h
b/src/apps/haikudepot/server/UserDetailVerifierProcess.h
new file mode 100644
index 0000000..2e5ad39
--- /dev/null
+++ b/src/apps/haikudepot/server/UserDetailVerifierProcess.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * 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
diff --git a/src/apps/haikudepot/server/WebAppInterface.cpp
b/src/apps/haikudepot/server/WebAppInterface.cpp
index 3a57823..6805980 100644
--- a/src/apps/haikudepot/server/WebAppInterface.cpp
+++ b/src/apps/haikudepot/server/WebAppInterface.cpp
@@ -438,6 +438,37 @@
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)
{
diff --git a/src/apps/haikudepot/server/WebAppInterface.h
b/src/apps/haikudepot/server/WebAppInterface.h
index 449bff9..38f10c3 100644
--- a/src/apps/haikudepot/server/WebAppInterface.h
+++ b/src/apps/haikudepot/server/WebAppInterface.h
@@ -100,6 +100,9 @@
const
BString& code,
UserUsageConditions& conditions);
+ status_t
AgreeUserUsageConditions(const BString& code,
+
BMessage& responsePayload);
+
status_t RetrieveScreenshot(
const
BString& code,
int32
width, int32 height,
diff --git a/src/apps/haikudepot/ui/MainWindow.cpp
b/src/apps/haikudepot/ui/MainWindow.cpp
index 1882c39..8c05ff8 100644
--- a/src/apps/haikudepot/ui/MainWindow.cpp
+++ b/src/apps/haikudepot/ui/MainWindow.cpp
@@ -3,7 +3,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@xxxxxx>.
* Copyright 2013, Rene Gollent, rene@xxxxxxxxxxx.
* Copyright 2013, Ingo Weinhold, ingo_weinhold@xxxxxx.
- * Copyright 2016-2019, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * Copyright 2016-2020, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
* Copyright 2017, Julian Harnath <julian.harnath@xxxxxxxxxxxxxx>.
* 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,10 @@
MSG_SHOW_DEVELOP_PACKAGES = 'sdvl'
};
+#define SPIN_UNTIL_COORDINATOR_FINISHED_MI 250 * 1000
+ // quarter of a second
+
+#define KEY_ERROR_STATUS "errorStatus"
using namespace BPackageKit;
using namespace BPackageKit::BManager::BPrivate;
@@ -133,7 +136,7 @@
fLogOutItem(NULL),
fUsersUserUsageConditionsMenuItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
- fBulkLoadProcessCoordinator(NULL),
+ fCoordinator(NULL),
fSinglePackageMode(false)
{
BMenuBar* menuBar = new BMenuBar("Main Menu");
@@ -144,7 +147,6 @@
set_small_font(userMenuBar);
userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
menuBar->MaxSize().height));
- _UpdateAuthorization();
fFilterView = new FilterView();
fFeaturedPackagesView = new FeaturedPackagesView();
@@ -207,6 +209,7 @@
fListTabs->Select(1);
_RestoreNickname(settings);
+ _UpdateAuthorization();
_RestoreWindowFrame(settings);
atomic_set(&fPackagesToShowListID, 0);
@@ -233,7 +236,7 @@
fLogOutItem(NULL),
fUsersUserUsageConditionsMenuItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
- fBulkLoadProcessCoordinator(NULL),
+ fCoordinator(NULL),
fSinglePackageMode(true)
{
fFilterView = new FilterView();
@@ -290,7 +293,7 @@
be_app->PostMessage(&message);
- _StopBulkLoad();
+ _StopProcessCoordinators();
return true;
}
@@ -301,8 +304,14 @@
{
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 +327,10 @@
_StartBulkLoad(true);
break;
+ case MSG_WORK_STATUS_CLEAR:
+ _HandleWorkStatusClear();
+ break;
+
case MSG_WORK_STATUS_CHANGE:
_HandleWorkStatusChangeMessageReceived(message);
break;
@@ -347,6 +360,7 @@
break;
case MSG_AUTHORIZATION_CHANGED:
+ _StartUserVerify();
_UpdateAuthorization();
break;
@@ -529,8 +543,10 @@
}
}
- if (!fSinglePackageMode && (changes &
PKG_CHANGED_STATE) != 0)
+ if (!fSinglePackageMode && (changes &
PKG_CHANGED_STATE) != 0
+ && fCoordinator == NULL) {
fWorkStatusView->PackageStatusChanged(ref);
+ }
}
break;
}
@@ -619,6 +635,18 @@
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;
@@ -905,67 +933,24 @@
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 +960,25 @@
"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();
}
@@ -1205,6 +1193,21 @@
void
+MainWindow::_StartUserVerify()
+{
+ if (!fModel.Nickname().IsEmpty()) {
+ _AddProcessCoordinator(
+
ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator(
+ this,
+ // UserDetailVerifierListener
+ this,
+ // ProcessCoordinatorListener
+ &fModel) );
+ }
+}
+
+
+void
MainWindow::_UpdateAuthorization()
{
BString nickname(fModel.Nickname());
@@ -1368,4 +1371,180 @@
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 (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) {
+ {
+ AutoLocker<BLocker> lock(&fCoordinatorLock);
+ if (fCoordinator == NULL)
+ return;
+ }
+
+ usleep(SPIN_UNTIL_COORDINATOR_FINISHED_MI);
+ }
+}
+
+
+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 (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()) {
+ 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");
+ }
}
\ No newline at end of file
diff --git a/src/apps/haikudepot/ui/MainWindow.h
b/src/apps/haikudepot/ui/MainWindow.h
index 2c403d3..31fe854 100644
--- a/src/apps/haikudepot/ui/MainWindow.h
+++ b/src/apps/haikudepot/ui/MainWindow.h
@@ -2,7 +2,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@xxxxxx>.
* Copyright 2013, Rene Gollent <rene@xxxxxxxxxxx>.
* Copyright 2017, Julian Harnath <julian.harnath@xxxxxxxxxxxxxx>.
- * Copyright 2017-2019, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * Copyright 2017-2020, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
* 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 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 @@
// 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 @@
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 @@
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 @@
void _OpenLoginWindow(
const
BMessage& onSuccessMessage);
+ void _StartUserVerify();
void _UpdateAuthorization();
void
_UpdateAvailableRepositories();
void _RatePackage();
@@ -104,6 +119,9 @@
void
_ViewUserUsageConditions(
UserUsageConditionsSelectionMode mode);
+ void
_HandleUserUsageConditionsNotLatest(
+ const
UserDetail& userDetail);
+
private:
FilterView* fFilterView;
TabView* fListTabs;
@@ -131,8 +149,11 @@
Model fModel;
ModelListenerRef fModelListener;
PackageList fVisiblePackages;
- ProcessCoordinator* fBulkLoadProcessCoordinator;
- BLocker
fBulkLoadProcessCoordinatorLock;
+
+ std::queue<ProcessCoordinator*>
+
fCoordinatorQueue;
+ ProcessCoordinator* fCoordinator;
+ BLocker fCoordinatorLock;
bool fSinglePackageMode;
diff --git a/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.cpp
b/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.cpp
new file mode 100644
index 0000000..2e5f5f3
--- /dev/null
+++ b/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2020, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * 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 "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'
+};
+
+
+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)
+{
+ 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
+ NULL);
+
+ fConfirmUserUsageConditionsCheckBox = new BCheckBox(
+ "confirm usage conditions",
+ B_TRANSLATE("I agree to the usage conditions"),
+ NULL);
+
+ 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);
+
+ _EnableMutableControls(false);
+}
+
+
+void
+ToLatestUserUsageConditionsWindow::_EnableMutableControls(bool enabled)
+{
+ fUserUsageConditionsLink->SetEnabled(enabled);
+ fAgreeButton->SetEnabled(enabled);
+ fLogoutButton->SetEnabled(enabled);
+ fConfirmUserUsageConditionsCheckBox->SetEnabled(enabled);
+ fConfirmMinimumAgeCheckBox->SetEnabled(enabled);
+}
+
+
+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;
+ 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");
+ }
+ }
+
+ _EnableMutableControls(false);
+ 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();
+ _EnableMutableControls(true);
+}
+
+
+void
+ToLatestUserUsageConditionsWindow::_DisplayData(
+ const UserUsageConditions& userUsageConditions)
+{
+ fUserUsageConditions = userUsageConditions;
+ 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);
+ _EnableMutableControls(true);
+}
+
+
+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;
+
+ // validate that the user has checked both of the checkboxes.
+
+ if (!ageChecked || !conditionsChecked) {
+ BAlert* alert = new(std::nothrow) BAlert(
+ B_TRANSLATE("Input validation"),
+ B_TRANSLATE("Both the minimum age as well as the usage
conditions "
+ "must be acknowledged by selecting the
checkboxes before "
+ "being able to proceed."),
+ B_TRANSLATE("OK"), NULL, NULL,
+ B_WIDTH_AS_USUAL, B_WARNING_ALERT);
+
+ if (alert != NULL)
+ alert->Go();
+ return;
+ }
+
+ _Agree();
+}
\ No newline at end of file
diff --git a/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.h
b/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.h
new file mode 100644
index 0000000..249a27b
--- /dev/null
+++ b/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019-2020, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * 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(bool enabled);
+ 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;
+};
+
+
+#endif // TO_LATEST_USER_USAGE_CONDITIONS_WINDOW_H
diff --git a/src/apps/haikudepot/ui/UserUsageConditionsWindow.cpp
b/src/apps/haikudepot/ui/UserUsageConditionsWindow.cpp
index dc7bd04..a6c062b 100644
--- a/src/apps/haikudepot/ui/UserUsageConditionsWindow.cpp
+++ b/src/apps/haikudepot/ui/UserUsageConditionsWindow.cpp
@@ -104,7 +104,7 @@
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();
diff --git a/src/apps/haikudepot/ui/WorkStatusView.cpp
b/src/apps/haikudepot/ui/WorkStatusView.cpp
index 7e5ade2..24ce127 100644
--- a/src/apps/haikudepot/ui/WorkStatusView.cpp
+++ b/src/apps/haikudepot/ui/WorkStatusView.cpp
@@ -22,6 +22,9 @@
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "WorkStatusView"
+#define SIZE_STATUS_BAR BSize(100,20)
+#define VIEW_INDEX_BARBER_POLE (int32) 0
+#define VIEW_INDEX_PROGRESS_BAR (int32) 1
WorkStatusView::WorkStatusView(const char* name)
:
@@ -36,8 +39,10 @@
fProgressLayout->AddView(fBarberPole);
fProgressLayout->AddView(fProgressBar);
+ fBarberPole->SetExplicitSize(SIZE_STATUS_BAR);
fProgressBar->SetMaxValue(1.0f);
- fProgressBar->SetBarHeight(20);
+ fProgressBar->SetBarHeight(SIZE_STATUS_BAR.Height());
+ fProgressBar->SetExplicitSize(SIZE_STATUS_BAR);
fStatusText->SetFontSize(be_plain_font->Size() * 0.9f);
@@ -70,8 +75,8 @@
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 +84,7 @@
WorkStatusView::SetIdle()
{
fBarberPole->Stop();
- fProgressLayout->SetVisibleItem((int32)0);
+ fProgressLayout->SetVisibleItem(VIEW_INDEX_BARBER_POLE);
SetText(NULL);
}
@@ -88,8 +93,8 @@
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);
}
--
To view, visit https://review.haiku-os.org/c/haiku/+/2467
To unsubscribe, or for help writing mail filters, visit
https://review.haiku-os.org/settings
Gerrit-Project: haiku
Gerrit-Branch: master
Gerrit-Change-Id: I4c9620648819ecd14fb095e4cb2c66fe7b2a0920
Gerrit-Change-Number: 2467
Gerrit-PatchSet: 1
Gerrit-Owner: Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Gerrit-MessageType: newchange