[haiku-commits] Change in haiku[master]: HaikuDepot: Check User Auth on Start

  • From: Gerrit <review@xxxxxxxxxxxxxxxxxxx>
  • To: waddlesplash <waddlesplash@xxxxxxxxx>, haiku-commits@xxxxxxxxxxxxx
  • Date: Fri, 10 Apr 2020 11:50:12 +0000

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

Other related posts: