[haiku-commits] haiku: hrev51996 - src/apps/haikudepot/ui src/apps/haikudepot/server data/artwork/icons src/apps/haikudepot/model src/apps/haikudepot

  • From: waddlesplash <waddlesplash@xxxxxxxxx>
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Sat, 9 Jun 2018 13:07:08 -0400 (EDT)

hrev51996 adds 1 changeset to branch 'master'
old head: 466255e3248ba781d11dc3de024fc938a111e76d
new head: a9edb9bffa2100903fc7d6e2a42cedf1131f0172
overview: 
https://git.haiku-os.org/haiku/log/?qt=range&q=a9edb9bffa21+%5E466255e3248b

----------------------------------------------------------------------------

a9edb9bffa21: HaikuDepot: Multiple improvements for user-ratings
  
  * Display of the user-ratings listing improved
  * When a user-rating is created / edited, the pkg is updated
  * Creation date of the user-rating is unpacked shown
  * Ability to create a user-rating with a comment, but no numerical rating
  * Stars display show grey if no numerical rating present
  * Improvements to error reporting when problem arise
  * Parsing of the 'revision' field of the version working
  * Removed debug logging for the text engine
  * Other minor tweaks
  
  Change-Id: I99f881ab1426641ef4177eec2d3bcacc7cb74e95

                                    [ Andrew Lindesay <apl@xxxxxxxxxxxxxx> ]

----------------------------------------------------------------------------

Revision:    hrev51996
Commit:      a9edb9bffa2100903fc7d6e2a42cedf1131f0172
URL:         https://git.haiku-os.org/haiku/commit/?id=a9edb9bffa21
Author:      Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:        Tue Jun  5 18:26:08 2018 UTC
Committer:   waddlesplash <waddlesplash@xxxxxxxxx>
Commit-Date: Sat Jun  9 17:07:03 2018 UTC

----------------------------------------------------------------------------

24 files changed, 921 insertions(+), 392 deletions(-)
data/artwork/icons/HaikuDepot_StarBlue           | Bin 0 -> 1552 bytes
data/artwork/icons/HaikuDepot_StarGray           | Bin 0 -> 1552 bytes
src/apps/haikudepot/HaikuDepot.rdef              |  27 +-
src/apps/haikudepot/HaikuDepotConstants.h        |  26 +-
src/apps/haikudepot/model/Model.cpp              |  60 +++-
src/apps/haikudepot/model/PackageInfo.cpp        |  18 +-
src/apps/haikudepot/model/PackageInfo.h          |   9 +-
.../haikudepot/server/PkgDataUpdateProcess.cpp   |  13 +-
src/apps/haikudepot/server/ServerHelper.cpp      | 115 ++++++
src/apps/haikudepot/server/ServerHelper.h        |  12 +-
src/apps/haikudepot/server/WebAppInterface.cpp   | 273 ++++++++++----
src/apps/haikudepot/server/WebAppInterface.h     |  19 +
.../haikudepot/textview/TextDocumentLayout.cpp   |   6 +-
src/apps/haikudepot/textview/TextEditor.cpp      |   6 -
src/apps/haikudepot/ui/App.cpp                   |  12 +
src/apps/haikudepot/ui/FeaturedPackagesView.cpp  |   4 +-
src/apps/haikudepot/ui/MainWindow.cpp            | 103 +++++-
src/apps/haikudepot/ui/MainWindow.h              |   4 +
src/apps/haikudepot/ui/PackageInfoView.cpp       | 182 ++++------
src/apps/haikudepot/ui/RatePackageWindow.cpp     | 355 ++++++++++++-------
src/apps/haikudepot/ui/RatePackageWindow.h       |   8 +
src/apps/haikudepot/ui/ScreenshotWindow.cpp      |   5 +-
src/apps/haikudepot/ui_generic/RatingView.cpp    |  49 ++-
src/apps/haikudepot/ui_generic/RatingView.h      |   7 +-

----------------------------------------------------------------------------

diff --git a/data/artwork/icons/HaikuDepot_StarBlue 
b/data/artwork/icons/HaikuDepot_StarBlue
new file mode 100644
index 0000000000..bcb5e41bcf
Binary files /dev/null and b/data/artwork/icons/HaikuDepot_StarBlue differ
diff --git a/data/artwork/icons/HaikuDepot_StarGray 
b/data/artwork/icons/HaikuDepot_StarGray
new file mode 100644
index 0000000000..8ee6cb57cf
Binary files /dev/null and b/data/artwork/icons/HaikuDepot_StarGray differ
diff --git a/src/apps/haikudepot/HaikuDepot.rdef 
b/src/apps/haikudepot/HaikuDepot.rdef
index e69fcb0688..35897fec87 100644
--- a/src/apps/haikudepot/HaikuDepot.rdef
+++ b/src/apps/haikudepot/HaikuDepot.rdef
@@ -65,32 +65,23 @@ resource vector_icon {
        $"010C00"
 };
 
-resource(501, "star") #'VICN' array {
-       $"6E636966010300AAFF010A0AB43438BC6638BEF3B4C9C18338C9B238C311C165"
-       $"C59EC928BEF3C457B84FC928BAD5C165010A00010000"
+resource(510, "starblue") #'VICN' array {
+       $"6E636966010300AAFF010A0A40223A38213835432C5A4049545A4B435F384638"
+       $"010A00010000"
 };
 
-resource(502, "thumbs up") #'VICN' array {
-       $"6E63696601020016020000003DC000BD80000000004B30004960000056FF4A01"
-       $"060DF6FFFF0358404C4E4C524CC3AF4C3E5046503A50364EBC65C5F5344C3041"
-       $"3046303F383C343C3C3C3F3A3F3A3F383C323CBB103C303E2C3C2C402C403040"
-       $"2E40344638C16DBB23483C50404C405440010A00010000"
+resource(520, "stargray") #'VICN' array {
+       $"6E6369660105AB010A0A40223A38213835432C5A4049545A4B435F384638010A"
+       $"00010000"
 };
 
-resource(503, "thumbs down") #'VICN' array {
-       $"6E63696601020016020000003DC000BD80000000004B30004960000056FF4A01"
-       $"060DF6FFFF035440344A344E34C217343A30423036303232BACDB98A30342C3F"
-       $"2C3A2C413444304438443B463B463B48384E38C46F38503A5438543C543C503C"
-       $"523C4C4248BFD5C45C44444C4048405040010A00010000"
-};
-
-resource(504, "installed") #'VICN' array {
+resource(530, "installed") #'VICN' array {
        $"6E636966030369D90504011704011F0202043E24C4AC24B93B24243E24B93B24"
        $"C4AC3E58B93B58C4AC58583E58C4AC58B93B0A06333A3C42493051363E4F2D41"
        $"010A0002000100"
 };
 
-resource(505, "arrow left") #'VICN' array {
+resource(540, "arrow left") #'VICN' array {
        $"6E6369660304006603005900020006020000003C6000C000000000004C000048"
        $"A0000080FF80FF00B300010A0722353622362C482C483E363E3648030A000100"
        $"1240A32D00000000000040A32444CEA044D04B01178322040A0101001240A32D"
@@ -98,7 +89,7 @@ resource(505, "arrow left") #'VICN' array {
        $"000040A32442FA1242FD72"
 };
 
-resource(506, "arrow right") #'VICN' array {
+resource(550, "arrow right") #'VICN' array {
        $"6E6369660304006603005900020006020000003C6000C000000000004C000048"
        $"A0000080FF80FF00B300010A0748353448343E223E222C342C3422030A000100"
        $"1240A32D00000000000040A32444D19644D04B01178322040A0101001240A32D"
diff --git a/src/apps/haikudepot/HaikuDepotConstants.h 
b/src/apps/haikudepot/HaikuDepotConstants.h
index 7ca466b908..a3346ef56d 100644
--- a/src/apps/haikudepot/HaikuDepotConstants.h
+++ b/src/apps/haikudepot/HaikuDepotConstants.h
@@ -2,7 +2,8 @@
  * Copyright 2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
-
+#ifndef HAIKU_DEPOT_CONSTANTS_H
+#define HAIKU_DEPOT_CONSTANTS_H
 
 enum {
        MSG_MAIN_WINDOW_CLOSED          = 'mwcl',
@@ -12,11 +13,32 @@ enum {
        MSG_ADD_VISIBLE_PACKAGES        = 'avpk',
        MSG_UPDATE_SELECTED_PACKAGE     = 'uspk',
        MSG_CLIENT_TOO_OLD                      = 'oldc',
+       MSG_NETWORK_TRANSPORT_ERROR     = 'nett',
+       MSG_SERVER_ERROR                        = 'svre',
+       MSG_SERVER_DATA_CHANGED         = 'svdc',
+       MSG_DID_ADD_USER_RATING         = 'adur',
+       MSG_DID_UPDATE_USER_RATING      = 'upur'
 };
 
 
+#define RATING_MISSING -1.0f
+#define RATING_MIN     0.0f
+
+
 #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)
\ No newline at end of file
+#define HD_ERR_NO_DATA                                 (HD_ERROR_BASE + 4)
+
+
+// These constants reference resources in 'HaikuDepot.ref'
+enum {
+       RSRC_STAR_BLUE          = 510,
+       RSRC_STAR_GREY          = 520,
+       RSRC_INSTALLED          = 530,
+       RSRC_ARROW_LEFT         = 540,
+       RSRC_ARROW_RIGHT        = 550,
+};
+
+#endif // HAIKU_DEPOT_CONSTANTS_H
\ No newline at end of file
diff --git a/src/apps/haikudepot/model/Model.cpp 
b/src/apps/haikudepot/model/Model.cpp
index eb8b4ecb5a..d5803c07cc 100644
--- a/src/apps/haikudepot/model/Model.cpp
+++ b/src/apps/haikudepot/model/Model.cpp
@@ -651,6 +651,11 @@ Model::SetShowDevelopPackages(bool show)
 // #pragma mark - information retrieval
 
 
+/*! Initially only superficial data is loaded from the server into the data
+    model of the packages.  When the package is viewed, additional data needs
+    to be populated including ratings.  This method takes care of that.
+*/
+
 void
 Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
 {
@@ -697,7 +702,7 @@ Model::PopulatePackage(const PackageInfoRef& package, 
uint32 flags)
                                BAutolock locker(&fLock);
                                package->ClearUserRatings();
 
-                               int index = 0;
+                               int32 index = 0;
                                while (true) {
                                        BString name;
                                        name << index++;
@@ -706,11 +711,19 @@ Model::PopulatePackage(const PackageInfoRef& package, 
uint32 flags)
                                        if (items.FindMessage(name, &item) != 
B_OK)
                                                break;
 
+                                       BString code;
+                                       if (item.FindString("code", &code) != 
B_OK) {
+                                               printf("corrupt user rating at 
index %" B_PRIi32 "\n",
+                                                       index);
+                                               continue;
+                                       }
+
                                        BString user;
                                        BMessage userInfo;
                                        if (item.FindMessage("user", &userInfo) 
!= B_OK
                                                || 
userInfo.FindString("nickname", &user) != B_OK) {
-                                               // Ignore, we need the user name
+                                               printf("ignored user rating 
[%s] without a user "
+                                                       "nickname\n", 
code.String());
                                                continue;
                                        }
 
@@ -723,7 +736,8 @@ Model::PopulatePackage(const PackageInfoRef& package, 
uint32 flags)
                                        if (item.FindDouble("rating", &rating) 
!= B_OK)
                                                rating = -1;
                                        if (comment.Length() == 0 && rating == 
-1) {
-                                               // No useful information given.
+                                               printf("rating [%s] has no 
comment or rating so will be"
+                                                       "ignored\n", 
code.String());
                                                continue;
                                        }
 
@@ -731,11 +745,13 @@ Model::PopulatePackage(const PackageInfoRef& package, 
uint32 flags)
                                        BString major = "?";
                                        BString minor = "?";
                                        BString micro = "";
+                                       double revision = -1;
                                        BMessage version;
                                        if (item.FindMessage("pkgVersion", 
&version) == B_OK) {
                                                version.FindString("major", 
&major);
                                                version.FindString("minor", 
&minor);
                                                version.FindString("micro", 
&micro);
+                                               version.FindDouble("revision", 
&revision);
                                        }
                                        BString versionString = major;
                                        versionString << ".";
@@ -744,15 +760,43 @@ Model::PopulatePackage(const PackageInfoRef& package, 
uint32 flags)
                                                versionString << ".";
                                                versionString << micro;
                                        }
+                                       if (revision > 0) {
+                                               versionString << "-";
+                                               versionString << (int) revision;
+                                       }
+
+                                       BDateTime createTimestamp;
+                                       double createTimestampMillisF;
+                                       if (item.FindDouble("createTimestamp",
+                                               &createTimestampMillisF) == 
B_OK) {
+                                               double createTimestampSecsF =
+                                                       createTimestampMillisF 
/ 1000.0;
+                                               time_t createTimestampSecs =
+                                                       (time_t) 
createTimestampSecsF;
+                                               
createTimestamp.SetTime_t(createTimestampSecs);
+                                       }
+
                                        // Add the rating to the PackageInfo
-                                       package->AddUserRating(
-                                               UserRating(UserInfo(user), 
rating,
-                                                       comment, languageCode, 
versionString, 0, 0)
-                                       );
+                                       UserRating userRating = 
UserRating(UserInfo(user), rating,
+                                               comment, languageCode, 
versionString, 0, 0,
+                                               createTimestamp);
+                                       package->AddUserRating(userRating);
+
+                                       if (Logger::IsDebugEnabled()) {
+                                               printf("rating [%s] retrieved 
from server\n",
+                                                       code.String());
+                                       }
+                               }
+
+                               if (Logger::IsDebugEnabled()) {
+                                       printf("did retrieve %" B_PRIi32 " user 
ratings for [%s]\n",
+                                               index - 1, 
packageName.String());
                                }
                        } else {
                                _MaybeLogJsonRpcError(info, "retrieve user 
ratings");
                        }
+               } else {
+                       printf("unable to retrieve user ratings\n");
                }
        }
 
@@ -808,7 +852,7 @@ Model::_PopulatePackageChangelog(const PackageInfoRef& 
package)
                }
        } else {
                fprintf(stdout, "unable to obtain the changelog for the package"
-                       "[%s]\n", packageName.String());
+                       " [%s]\n", packageName.String());
        }
 }
 
diff --git a/src/apps/haikudepot/model/PackageInfo.cpp 
b/src/apps/haikudepot/model/PackageInfo.cpp
index c8d18ccb28..15e803885b 100644
--- a/src/apps/haikudepot/model/PackageInfo.cpp
+++ b/src/apps/haikudepot/model/PackageInfo.cpp
@@ -86,14 +86,16 @@ UserRating::UserRating()
        fLanguage(),
        fPackageVersion(),
        fUpVotes(0),
-       fDownVotes(0)
+       fDownVotes(0),
+       fCreateTimestamp()
 {
 }
 
 
 UserRating::UserRating(const UserInfo& userInfo, float rating,
                const BString& comment, const BString& language,
-               const BString& packageVersion, int32 upVotes, int32 downVotes)
+               const BString& packageVersion, int32 upVotes, int32 downVotes,
+               const BDateTime& createTimestamp)
        :
        fUserInfo(userInfo),
        fRating(rating),
@@ -101,8 +103,10 @@ UserRating::UserRating(const UserInfo& userInfo, float 
rating,
        fLanguage(language),
        fPackageVersion(packageVersion),
        fUpVotes(upVotes),
-       fDownVotes(downVotes)
+       fDownVotes(downVotes),
+       fCreateTimestamp()
 {
+       fCreateTimestamp.SetTime_t(createTimestamp.Time_t());
 }
 
 
@@ -114,8 +118,10 @@ UserRating::UserRating(const UserRating& other)
        fLanguage(other.fLanguage),
        fPackageVersion(other.fPackageVersion),
        fUpVotes(other.fUpVotes),
-       fDownVotes(other.fDownVotes)
+       fDownVotes(other.fDownVotes),
+       fCreateTimestamp()
 {
+       fCreateTimestamp.SetTime_t(other.CreateTimestamp().Time_t());
 }
 
 
@@ -129,6 +135,7 @@ UserRating::operator=(const UserRating& other)
        fPackageVersion = other.fPackageVersion;
        fUpVotes = other.fUpVotes;
        fDownVotes = other.fDownVotes;
+       fCreateTimestamp.SetTime_t(other.fCreateTimestamp.Time_t());
        return *this;
 }
 
@@ -142,7 +149,8 @@ UserRating::operator==(const UserRating& other) const
                && fLanguage == other.fLanguage
                && fPackageVersion == other.fPackageVersion
                && fUpVotes == other.fUpVotes
-               && fDownVotes == other.fDownVotes;
+               && fDownVotes == other.fDownVotes
+               && fCreateTimestamp == other.fCreateTimestamp;
 }
 
 
diff --git a/src/apps/haikudepot/model/PackageInfo.h 
b/src/apps/haikudepot/model/PackageInfo.h
index 0cf570c5f4..1d83dc823d 100644
--- a/src/apps/haikudepot/model/PackageInfo.h
+++ b/src/apps/haikudepot/model/PackageInfo.h
@@ -1,6 +1,6 @@
 /*
  * Copyright 2013-2014, Stephan Aßmus <superstippi@xxxxxx>.
- * Copyright 2016-2017, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * Copyright 2016-2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 #ifndef PACKAGE_INFO_H
@@ -12,6 +12,7 @@
 #include <Referenceable.h>
 #include <package/PackageInfo.h>
 
+#include "DateTime.h"
 #include "List.h"
 #include "PackageInfoListener.h"
 #include "SharedBitmap.h"
@@ -51,7 +52,8 @@ public:
                                                                        const 
BString& comment,
                                                                        const 
BString& language,
                                                                        const 
BString& packageVersion,
-                                                                       int32 
upVotes, int32 downVotes);
+                                                                       int32 
upVotes, int32 downVotes,
+                                                                       const 
BDateTime& createTimestamp);
                                                                
UserRating(const UserRating& other);
 
                        UserRating&                     operator=(const 
UserRating& other);
@@ -73,6 +75,8 @@ public:
                                                                        { 
return fUpVotes; }
                        int32                           DownVotes() const
                                                                        { 
return fDownVotes; }
+                       const BDateTime&        CreateTimestamp() const
+                                                                       { 
return fCreateTimestamp; }
 private:
                        UserInfo                        fUserInfo;
                        float                           fRating;
@@ -81,6 +85,7 @@ private:
                        BString                         fPackageVersion;
                        int32                           fUpVotes;
                        int32                           fDownVotes;
+                       BDateTime                       fCreateTimestamp;
 };
 
 
diff --git a/src/apps/haikudepot/server/PkgDataUpdateProcess.cpp 
b/src/apps/haikudepot/server/PkgDataUpdateProcess.cpp
index be4b1ec8e9..45361c7dc1 100644
--- a/src/apps/haikudepot/server/PkgDataUpdateProcess.cpp
+++ b/src/apps/haikudepot/server/PkgDataUpdateProcess.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * Copyright 2017-2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 
@@ -22,6 +22,7 @@
 #include "DumpExportPkgJsonListener.h"
 #include "DumpExportPkgScreenshot.h"
 #include "DumpExportPkgVersion.h"
+#include "HaikuDepotConstants.h"
 
 
 /*! This package listener (not at the JSON level) is feeding in the
@@ -145,11 +146,13 @@ PackageFillingPkgListener::ConsumePackage(const 
PackageInfoRef& package,
                }
        }
 
-       if (!pkg->DerivedRatingIsNull()) {
-               RatingSummary summary;
+       RatingSummary summary;
+       summary.averageRating = RATING_MISSING;
+
+       if (!pkg->DerivedRatingIsNull())
                summary.averageRating = pkg->DerivedRating();
-               package->SetRatingSummary(summary);
-       }
+
+       package->SetRatingSummary(summary);
 
        if (!pkg->ProminenceOrderingIsNull())
                package->SetProminence(pkg->ProminenceOrdering());
diff --git a/src/apps/haikudepot/server/ServerHelper.cpp 
b/src/apps/haikudepot/server/ServerHelper.cpp
index d9033292b7..69e00a6102 100644
--- a/src/apps/haikudepot/server/ServerHelper.cpp
+++ b/src/apps/haikudepot/server/ServerHelper.cpp
@@ -16,6 +16,7 @@
 
 #include "HaikuDepotConstants.h"
 #include "ServerSettings.h"
+#include "WebAppInterface.h"
 
 
 #undef B_TRANSLATION_CONTEXT
@@ -24,6 +25,120 @@
 #define KEY_HEADER_MINIMUM_VERSION "X-Desktop-Application-Minimum-Version"
 
 
+/*! This method will cause an alert to be shown to the user regarding a
+    JSON-RPC error that has been sent from the application server.  It will
+    send a message to the application looper which will then relay the message
+    to the looper and then onto the user to see.
+*/
+
+void
+ServerHelper::NotifyServerJsonRpcError(BMessage& error)
+{
+       BMessage message(MSG_SERVER_ERROR);
+       message.AddMessage("error", &error);
+       be_app->PostMessage(&message);
+}
+
+
+void
+ServerHelper::AlertServerJsonRpcError(BMessage* message)
+{
+       BMessage error;
+       int32 errorCode = 0;
+
+       if (message->FindMessage("error", &error) == B_OK)
+               errorCode = WebAppInterface::ErrorCodeFromResponse(error);
+
+       BString alertText;
+
+       switch (errorCode) {
+               case ERROR_CODE_VALIDATION:
+                               // TODO; expand on that message.
+                       alertText = B_TRANSLATE("A validation error has 
occurred");
+                       break;
+               case ERROR_CODE_OBJECTNOTFOUND:
+                       alertText = B_TRANSLATE("A requested object or an 
object involved"
+                               " in the request was not found on the server.");
+                       break;
+               case ERROR_CODE_CAPTCHABADRESPONSE:
+                       alertText = B_TRANSLATE("The response to the captcha 
was incorrect.");
+                       break;
+               case ERROR_CODE_AUTHORIZATIONFAILURE:
+               case ERROR_CODE_AUTHORIZATIONRULECONFLICT:
+                       alertText = B_TRANSLATE("Authorization or security 
issue");
+                       break;
+               default:
+                       alertText.SetToFormat(
+                               B_TRANSLATE("An unexpected error has been sent 
from the"
+                                       " HaikuDepot server [%" B_PRIi32 "]"), 
errorCode);
+                       break;
+       }
+
+       BAlert* alert = new BAlert(
+               B_TRANSLATE("Server Error"),
+               alertText,
+               B_TRANSLATE("OK"));
+
+       alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
+       alert->Go();
+}
+
+
+void
+ServerHelper::NotifyTransportError(status_t error)
+{
+       switch (error) {
+               case HD_CLIENT_TOO_OLD:
+                               // this is handled earlier on because it 
requires some
+                               // information from the HTTP request to create 
a sensible
+                               // error message.
+                       break;
+
+               default:
+               {
+                       BMessage message(MSG_NETWORK_TRANSPORT_ERROR);
+                       message.AddInt64("errno", (int64) error);
+                       be_app->PostMessage(&message);
+                       break;
+               }
+       }
+}
+
+
+void
+ServerHelper::AlertTransportError(BMessage* message)
+{
+       status_t errno = B_OK;
+       int64 errnoInt64;
+       message->FindInt64("errno", &errnoInt64);
+       errno = (status_t) errnoInt64;
+
+       BString errorDescription("?");
+       BString alertText;
+
+       switch (errno) {
+               case HD_NETWORK_INACCESSIBLE:
+                       errorDescription = B_TRANSLATE("Network Error");
+                       break;
+               default:
+                       errorDescription.SetTo(strerror(errno));
+                       break;
+       }
+
+       alertText.SetToFormat(B_TRANSLATE("A network transport error has arisen"
+               " communicating with the HaikuDepot server system: %s"),
+               errorDescription.String());
+
+       BAlert* alert = new BAlert(
+               B_TRANSLATE("Network Transport Error"),
+               alertText,
+               B_TRANSLATE("OK"));
+
+       alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
+       alert->Go();
+}
+
+
 void
 ServerHelper::NotifyClientTooOld(const BHttpHeaders& responseHeaders)
 {
diff --git a/src/apps/haikudepot/server/ServerHelper.h 
b/src/apps/haikudepot/server/ServerHelper.h
index b526cf8845..01ad48b217 100644
--- a/src/apps/haikudepot/server/ServerHelper.h
+++ b/src/apps/haikudepot/server/ServerHelper.h
@@ -7,7 +7,9 @@
 #define SERVER_HELPER_H
 
 #include <HttpHeaders.h>
-#include <Message.h>
+
+
+class BMessage;
 
 
 class ServerHelper {
@@ -19,6 +21,14 @@ public:
                                                                                
        const BHttpHeaders& responseHeaders
                                                                                
        );
                static void                                             
AlertClientTooOld(BMessage* message);
+
+               static void                                             
NotifyTransportError(status_t error);
+               static void                                             
AlertTransportError(BMessage* message);
+
+               static void                                             
NotifyServerJsonRpcError(
+                                                                               
        BMessage& error);
+               static void                                             
AlertServerJsonRpcError(
+                                                                               
        BMessage* message);
 };
 
 #endif // SERVER_HELPER_H
diff --git a/src/apps/haikudepot/server/WebAppInterface.cpp 
b/src/apps/haikudepot/server/WebAppInterface.cpp
index 3d77e79517..e0b5956828 100644
--- a/src/apps/haikudepot/server/WebAppInterface.cpp
+++ b/src/apps/haikudepot/server/WebAppInterface.cpp
@@ -10,11 +10,13 @@
 
 #include <AppFileInfo.h>
 #include <Application.h>
+#include <AutoDeleter.h>
 #include <Autolock.h>
 #include <File.h>
 #include <HttpHeaders.h>
 #include <HttpRequest.h>
 #include <Json.h>
+#include <JsonTextWriter.h>
 #include <Message.h>
 #include <Roster.h>
 #include <Url.h>
@@ -363,56 +365,138 @@ WebAppInterface::RetrieveUserRating(const BString& 
packageName,
        const BString &repositoryCode, const BString& username,
        BMessage& message)
 {
-       BString jsonString = JsonBuilder()
-               .AddValue("jsonrpc", "2.0")
-               .AddValue("id", ++fRequestIndex)
-               .AddValue("method", "getUserRatingByUserAndPkgVersion")
-               .AddArray("params")
-                       .AddObject()
-                               .AddValue("userNickname", username)
-                               .AddValue("pkgName", packageName)
-                               .AddValue("pkgVersionArchitectureCode", 
architecture)
-                               .AddValue("pkgVersionMajor", version.Major(), 
true)
-                               .AddValue("pkgVersionMinor", version.Minor(), 
true)
-                               .AddValue("pkgVersionMicro", version.Micro(), 
true)
-                               .AddValue("pkgVersionPreRelease", 
version.PreRelease(), true)
-                               .AddValue("pkgVersionRevision", 
(int)version.Revision())
-                               .AddValue("repositoryCode", repositoryCode)
-                       .EndObject()
-               .EndArray()
-       .End();
+               // BHttpRequest later takes ownership of this.
+       BMallocIO* requestEnvelopeData = new BMallocIO();
+       BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
 
-       return _SendJsonRequest("userrating", jsonString, 0, message);
+       requestEnvelopeWriter.WriteObjectStart();
+       _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
+               "getUserRatingByUserAndPkgVersion");
+       requestEnvelopeWriter.WriteObjectName("params");
+       requestEnvelopeWriter.WriteArrayStart();
+
+       requestEnvelopeWriter.WriteObjectStart();
+
+       requestEnvelopeWriter.WriteObjectName("userNickname");
+       requestEnvelopeWriter.WriteString(username.String());
+       requestEnvelopeWriter.WriteObjectName("pkgName");
+       requestEnvelopeWriter.WriteString(packageName.String());
+       requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
+       requestEnvelopeWriter.WriteString(architecture.String());
+       requestEnvelopeWriter.WriteObjectName("repositoryCode");
+       requestEnvelopeWriter.WriteString(repositoryCode.String());
+
+       if (version.Major().Length() > 0) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
+               requestEnvelopeWriter.WriteString(version.Major().String());
+       }
+
+       if (version.Minor().Length() > 0) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
+               requestEnvelopeWriter.WriteString(version.Minor().String());
+       }
+
+       if (version.Micro().Length() > 0) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
+               requestEnvelopeWriter.WriteString(version.Micro().String());
+       }
+
+       if (version.PreRelease().Length() > 0) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
+               
requestEnvelopeWriter.WriteString(version.PreRelease().String());
+       }
+
+       if (version.Revision() != 0) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
+               requestEnvelopeWriter.WriteInteger(version.Revision());
+       }
+
+       requestEnvelopeWriter.WriteObjectEnd();
+       requestEnvelopeWriter.WriteArrayEnd();
+       requestEnvelopeWriter.WriteObjectEnd();
+
+       return _SendJsonRequest("userrating", requestEnvelopeData,
+               requestEnvelopeData->Position(), NEEDS_AUTHORIZATION, message);
 }
 
 
 status_t
 WebAppInterface::CreateUserRating(const BString& packageName,
+       const BPackageVersion& version,
        const BString& architecture, const BString& repositoryCode,
        const BString& languageCode, const BString& comment,
        const BString& stability, int rating, BMessage& message)
 {
-       BString jsonString = JsonBuilder()
-               .AddValue("jsonrpc", "2.0")
-               .AddValue("id", ++fRequestIndex)
-               .AddValue("method", "createUserRating")
-               .AddArray("params")
-                       .AddObject()
-                               .AddValue("pkgName", packageName)
-                               .AddValue("pkgVersionArchitectureCode", 
architecture)
-                               .AddValue("pkgVersionType", "LATEST")
-                               .AddValue("userNickname", fUsername)
-                               .AddValue("rating", rating)
-                               .AddValue("userRatingStabilityCode", stability, 
true)
-                               .AddValue("comment", comment)
-                               .AddValue("repositoryCode", repositoryCode)
-                               .AddValue("naturalLanguageCode", languageCode)
-                       .EndObject()
-               .EndArray()
-       .End();
+               // BHttpRequest later takes ownership of this.
+       BMallocIO* requestEnvelopeData = new BMallocIO();
+       BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
+
+       requestEnvelopeWriter.WriteObjectStart();
+       _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
+               "createUserRating");
+       requestEnvelopeWriter.WriteObjectName("params");
+       requestEnvelopeWriter.WriteArrayStart();
+
+       requestEnvelopeWriter.WriteObjectStart();
+       requestEnvelopeWriter.WriteObjectName("pkgName");
+       requestEnvelopeWriter.WriteString(packageName.String());
+       requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
+       requestEnvelopeWriter.WriteString(architecture.String());
+       requestEnvelopeWriter.WriteObjectName("repositoryCode");
+       requestEnvelopeWriter.WriteString(repositoryCode.String());
+       requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
+       requestEnvelopeWriter.WriteString(languageCode.String());
+       requestEnvelopeWriter.WriteObjectName("pkgVersionType");
+       requestEnvelopeWriter.WriteString("SPECIFIC");
+       requestEnvelopeWriter.WriteObjectName("userNickname");
+       requestEnvelopeWriter.WriteString(fUsername.String());
+
+       if (!version.Major().IsEmpty()) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
+               requestEnvelopeWriter.WriteString(version.Major());
+       }
+
+       if (!version.Minor().IsEmpty()) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
+               requestEnvelopeWriter.WriteString(version.Minor());
+       }
+
+       if (!version.Micro().IsEmpty()) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
+               requestEnvelopeWriter.WriteString(version.Micro());
+       }
+
+       if (!version.PreRelease().IsEmpty()) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
+               requestEnvelopeWriter.WriteString(version.PreRelease());
+       }
+
+       if (version.Revision() != 0) {
+               requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
+               requestEnvelopeWriter.WriteInteger(version.Revision());
+       }
+
+       if (rating > 0.0f) {
+               requestEnvelopeWriter.WriteObjectName("rating");
+       requestEnvelopeWriter.WriteInteger(rating);
+       }
+
+       if (stability.Length() > 0) {
+               
requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
+               requestEnvelopeWriter.WriteString(stability);
+       }
+
+       if (comment.Length() > 0) {
+               requestEnvelopeWriter.WriteObjectName("comment");
+               requestEnvelopeWriter.WriteString(comment.String());
+       }
 
-       return _SendJsonRequest("userrating", jsonString, NEEDS_AUTHORIZATION,
-               message);
+       requestEnvelopeWriter.WriteObjectEnd();
+       requestEnvelopeWriter.WriteArrayEnd();
+       requestEnvelopeWriter.WriteObjectEnd();
+
+       return _SendJsonRequest("userrating", requestEnvelopeData,
+               requestEnvelopeData->Position(), NEEDS_AUTHORIZATION, message);
 }
 
 
@@ -421,31 +505,56 @@ WebAppInterface::UpdateUserRating(const BString& ratingID,
        const BString& languageCode, const BString& comment,
        const BString& stability, int rating, bool active, BMessage& message)
 {
-       BString jsonString = JsonBuilder()
-               .AddValue("jsonrpc", "2.0")
-               .AddValue("id", ++fRequestIndex)
-               .AddValue("method", "updateUserRating")
-               .AddArray("params")
-                       .AddObject()
-                               .AddValue("code", ratingID)
-                               .AddValue("rating", rating)
-                               .AddValue("userRatingStabilityCode", stability, 
true)
-                               .AddValue("comment", comment)
-                               .AddValue("naturalLanguageCode", languageCode)
-                               .AddValue("active", active)
-                               .AddArray("filter")
-                                       .AddItem("ACTIVE")
-                                       .AddItem("NATURALLANGUAGE")
-                                       .AddItem("USERRATINGSTABILITY")
-                                       .AddItem("COMMENT")
-                                       .AddItem("RATING")
-                               .EndArray()
-                       .EndObject()
-               .EndArray()
-       .End();
+               // BHttpRequest later takes ownership of this.
+       BMallocIO* requestEnvelopeData = new BMallocIO();
+       BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
+
+       requestEnvelopeWriter.WriteObjectStart();
+       _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
+               "updateUserRating");
+
+       requestEnvelopeWriter.WriteObjectName("params");
+       requestEnvelopeWriter.WriteArrayStart();
+
+       requestEnvelopeWriter.WriteObjectStart();
+
+       requestEnvelopeWriter.WriteObjectName("code");
+       requestEnvelopeWriter.WriteString(ratingID.String());
+       requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
+       requestEnvelopeWriter.WriteString(languageCode.String());
+       requestEnvelopeWriter.WriteObjectName("active");
+       requestEnvelopeWriter.WriteBoolean(active);
 
-       return _SendJsonRequest("userrating", jsonString, NEEDS_AUTHORIZATION,
-               message);
+       requestEnvelopeWriter.WriteObjectName("filter");
+       requestEnvelopeWriter.WriteArrayStart();
+       requestEnvelopeWriter.WriteString("ACTIVE");
+       requestEnvelopeWriter.WriteString("NATURALLANGUAGE");
+       requestEnvelopeWriter.WriteString("USERRATINGSTABILITY");
+       requestEnvelopeWriter.WriteString("COMMENT");
+       requestEnvelopeWriter.WriteString("RATING");
+       requestEnvelopeWriter.WriteArrayEnd();
+
+       if (rating >= 0) {
+               requestEnvelopeWriter.WriteObjectName("rating");
+               requestEnvelopeWriter.WriteInteger(rating);
+       }
+
+       if (stability.Length() > 0) {
+               
requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
+               requestEnvelopeWriter.WriteString(stability);
+       }
+
+       if (comment.Length() > 0) {
+               requestEnvelopeWriter.WriteObjectName("comment");
+               requestEnvelopeWriter.WriteString(comment);
+       }
+
+       requestEnvelopeWriter.WriteObjectEnd();
+       requestEnvelopeWriter.WriteArrayEnd();
+       requestEnvelopeWriter.WriteObjectEnd();
+
+       return _SendJsonRequest("userrating", requestEnvelopeData,
+               requestEnvelopeData->Position(), NEEDS_AUTHORIZATION, message);
 }
 
 
@@ -555,9 +664,45 @@ WebAppInterface::AuthenticateUser(const BString& nickName,
 }
 
 
+/*! JSON-RPC invocations return a response.  The response may be either
+    a result or it may be an error depending on the response structure.
+    If it is an error then there may be additional detail that is the
+    error code and message.  This method will extract the error code
+    from the response.  This method will return 0 if the payload does
+    not look like an error.
+*/
+
+int32
+WebAppInterface::ErrorCodeFromResponse(BMessage& response)
+{
+       BMessage error;
+       double code;
+
+       if (response.FindMessage("error", &error) == B_OK
+               && error.FindDouble("code", &code) == B_OK) {
+               return (int32) code;
+       }
+
+       return 0;
+}
+
+
 // #pragma mark - private
 
 
+void
+WebAppInterface::_WriteStandardJsonRpcEnvelopeValues(BJsonWriter& writer,
+       const char* methodName)
+{
+       writer.WriteObjectName("jsonrpc");
+       writer.WriteString("2.0");
+       writer.WriteObjectName("id");
+       writer.WriteInteger(++fRequestIndex);
+       writer.WriteObjectName("method");
+       writer.WriteString(methodName);
+}
+
+
 status_t
 WebAppInterface::_SendJsonRequest(const char* domain, BDataIO* requestData,
        size_t requestDataSize, uint32 flags, BMessage& reply) const
@@ -658,10 +803,10 @@ WebAppInterface::_SendJsonRequest(const char* domain, 
BString jsonString,
        if (Logger::IsTraceEnabled())
                printf("_SendJsonRequest(%s)\n", jsonString.String());
 
+       // gets 'adopted' by the subsequent http request.
        BMemoryIO* data = new BMemoryIO(
                jsonString.String(), jsonString.Length() - 1);
 
        return _SendJsonRequest(domain, data, jsonString.Length() - 1, flags,
                reply);
 }
-
diff --git a/src/apps/haikudepot/server/WebAppInterface.h 
b/src/apps/haikudepot/server/WebAppInterface.h
index 09690a78db..b6ce6f7219 100644
--- a/src/apps/haikudepot/server/WebAppInterface.h
+++ b/src/apps/haikudepot/server/WebAppInterface.h
@@ -8,6 +8,7 @@
 
 
 #include <Application.h>
+#include <JsonWriter.h>
 #include <String.h>
 #include <package/PackageVersion.h>
 
@@ -21,6 +22,18 @@ using BPackageKit::BPackageVersion;
 typedef List<BString, false>   StringList;
 
 
+/*! These are error codes that are sent back to the client from the server */
+
+#define ERROR_CODE_NONE                                                        0
+#define ERROR_CODE_VALIDATION                                  -32800
+#define ERROR_CODE_OBJECTNOTFOUND                              -32801
+#define ERROR_CODE_CAPTCHABADRESPONSE                  -32802
+#define ERROR_CODE_AUTHORIZATIONFAILURE                        -32803
+#define ERROR_CODE_BADPKGICON                                  -32804
+#define ERROR_CODE_LIMITEXCEEDED                               -32805
+#define ERROR_CODE_AUTHORIZATIONRULECONFLICT   -32806
+
+
 class WebAppInterface {
 public:
                                                                
WebAppInterface();
@@ -57,6 +70,7 @@ public:
 
                        status_t                        CreateUserRating(
                                                                        const 
BString& packageName,
+                                                                       const 
BPackageVersion& version,
                                                                        const 
BString& architecture,
                                                                        const 
BString& repositoryCode,
                                                                        const 
BString& languageCode,
@@ -92,7 +106,12 @@ public:
                                                                        const 
BString& passwordClear,
                                                                        
BMessage& message);
 
+       static int32                            ErrorCodeFromResponse(BMessage& 
response);
+
 private:
+                       void                            
_WriteStandardJsonRpcEnvelopeValues(
+                                                                       
BJsonWriter& writer,
+                                                                       const 
char* methodName);
                        status_t                        _SendJsonRequest(const 
char* domain,
                                                                        BString 
jsonString, uint32 flags,
                                                                        
BMessage& reply) const;
diff --git a/src/apps/haikudepot/textview/TextDocumentLayout.cpp 
b/src/apps/haikudepot/textview/TextDocumentLayout.cpp
index 8afa1142a2..6d00d78f46 100644
--- a/src/apps/haikudepot/textview/TextDocumentLayout.cpp
+++ b/src/apps/haikudepot/textview/TextDocumentLayout.cpp
@@ -25,9 +25,9 @@ public:
 
        virtual void TextChanged(const TextChangedEvent& event)
        {
-               printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n",
-                       event.FirstChangedParagraph(),
-                       event.ChangedParagraphCount());
+//             printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n",
+//                     event.FirstChangedParagraph(),
+//                     event.ChangedParagraphCount());
                // TODO: The event does not contain useful data. Make the event
                // system work so only the affected paragraphs are updated.
                // I think we need "first affected", "last affected" (both 
relative
diff --git a/src/apps/haikudepot/textview/TextEditor.cpp 
b/src/apps/haikudepot/textview/TextEditor.cpp
index bfd46ba850..eaf6b688d5 100644
--- a/src/apps/haikudepot/textview/TextEditor.cpp
+++ b/src/apps/haikudepot/textview/TextEditor.cpp
@@ -257,8 +257,6 @@ TextEditor::Insert(int32 offset, const BString& string)
 
        if (ret == B_OK) {
                _SetCaretOffset(offset + string.CountChars(), true, false, 
true);
-
-               fDocument->PrintToStream();
        }
 
        return ret;
@@ -275,8 +273,6 @@ TextEditor::Remove(int32 offset, int32 length)
 
        if (ret == B_OK) {
                _SetCaretOffset(offset, true, false, true);
-
-               fDocument->PrintToStream();
        }
 
        return ret;
@@ -293,8 +289,6 @@ TextEditor::Replace(int32 offset, int32 length, const 
BString& string)
 
        if (ret == B_OK) {
                _SetCaretOffset(offset + string.CountChars(), true, false, 
true);
-
-               fDocument->PrintToStream();
        }
 
        return ret;
diff --git a/src/apps/haikudepot/ui/App.cpp b/src/apps/haikudepot/ui/App.cpp
index af3543d509..a28aafaf47 100644
--- a/src/apps/haikudepot/ui/App.cpp
+++ b/src/apps/haikudepot/ui/App.cpp
@@ -108,6 +108,18 @@ App::MessageReceived(BMessage* message)
                        ServerHelper::AlertClientTooOld(message);
                        break;
 
+               case MSG_NETWORK_TRANSPORT_ERROR:
+                       ServerHelper::AlertTransportError(message);
+                       break;
+
+               case MSG_SERVER_ERROR:
+                       ServerHelper::AlertServerJsonRpcError(message);
+                       break;
+
+               case MSG_SERVER_DATA_CHANGED:
+                       fMainWindow->PostMessage(message);
+                       break;
+
                default:
                        BApplication::MessageReceived(message);
                        break;
diff --git a/src/apps/haikudepot/ui/FeaturedPackagesView.cpp 
b/src/apps/haikudepot/ui/FeaturedPackagesView.cpp
index fd6cc9d234..d1ca2e0ee4 100644
--- a/src/apps/haikudepot/ui/FeaturedPackagesView.cpp
+++ b/src/apps/haikudepot/ui/FeaturedPackagesView.cpp
@@ -17,6 +17,7 @@
 #include <SpaceLayoutItem.h>
 
 #include "BitmapView.h"
+#include "HaikuDepotConstants.h"
 #include "MainWindow.h"
 #include "MarkupTextView.h"
 #include "MessagePackageListener.h"
@@ -30,7 +31,8 @@
 
 static const rgb_color kLightBlack = (rgb_color){ 60, 60, 60, 255 };
 
-static BitmapRef sInstalledIcon(new(std::nothrow) SharedBitmap(504), true);
+static BitmapRef sInstalledIcon(new(std::nothrow)
+       SharedBitmap(RSRC_INSTALLED), true);
 
 
 // #pragma mark - PackageView
diff --git a/src/apps/haikudepot/ui/MainWindow.cpp 
b/src/apps/haikudepot/ui/MainWindow.cpp
index 0db941aa7d..d15b7f7d66 100644
--- a/src/apps/haikudepot/ui/MainWindow.cpp
+++ b/src/apps/haikudepot/ui/MainWindow.cpp
@@ -390,6 +390,26 @@ MainWindow::MessageReceived(BMessage* message)
                        _AdoptModel();
                        break;
 
+                       // this may be triggered by, for example, a user rating 
being added
+                       // or having been altered.
+               case MSG_SERVER_DATA_CHANGED:
+               {
+                       BString name;
+                       if (message->FindString("name", &name) == B_OK) {
+                               BAutolock locker(fModel.Lock());
+                               if (fPackageInfoView->Package()->Name() == 
name) {
+                                       _PopulatePackageAsync(true);
+                               } else {
+                                       if (Logger::IsDebugEnabled()) {
+                                               printf("pkg [%s] is updated on 
the server, but is "
+                                                       "not selected so will 
not be updated.\n",
+                                                       name.String());
+                                       }
+                               }
+                       }
+               break;
+        }
+
                case MSG_PACKAGE_SELECTED:
                {
                        BString name;
@@ -624,8 +644,8 @@ MainWindow::StoreSettings(BMessage& settings) const
 void
 MainWindow::PackageChanged(const PackageInfoEvent& event)
 {
-       uint32 whatchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
-       if ((event.Changes() & whatchedChanges) != 0) {
+       uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
+       if ((event.Changes() & watchedChanges) != 0) {
                PackageInfoRef ref(event.Package());
                BMessage message(MSG_PACKAGE_CHANGED);
                message.AddPointer("package", ref.Get());
@@ -850,12 +870,7 @@ MainWindow::_AdoptPackage(const PackageInfoRef& package)
                        fPackageListView->SelectPackage(package);
        }
 
-       // Trigger asynchronous package population from the web-app
-       {
-               AutoLocker<BLocker> lock(&fPackageToPopulateLock);
-               fPackageToPopulate = package;
-       }
-       release_sem_etc(fPackageToPopulateSem, 1, 0);
+       _PopulatePackageAsync(false);
 }
 
 
@@ -1255,6 +1270,34 @@ MainWindow::_PackageActionWorker(void* arg)
 }
 
 
+/*! This method will cause the package to have its data refreshed from
+    the server application.  The refresh happens in the background; this method
+    is asynchronous.
+*/
+
+void
+MainWindow::_PopulatePackageAsync(bool forcePopulate)
+{
+               // Trigger asynchronous package population from the web-app
+       {
+               AutoLocker<BLocker> lock(&fPackageToPopulateLock);
+               fPackageToPopulate = fPackageInfoView->Package();
+               fForcePopulatePackage = forcePopulate;
+       }
+       release_sem_etc(fPackageToPopulateSem, 1, 0);
+
+       if (Logger::IsDebugEnabled()) {
+               printf("pkg [%s] will be updated from the server.\n",
+                       fPackageToPopulate->Name().String());
+       }
+}
+
+
+/*! This method will run in the background.  The thread will block until there
+    is a package to be updated.  When the thread unblocks, it will update the
+    package with information from the server.
+*/
+
 status_t
 MainWindow::_PopulatePackageWorker(void* arg)
 {
@@ -1262,15 +1305,27 @@ MainWindow::_PopulatePackageWorker(void* arg)
 
        while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
                PackageInfoRef package;
+               bool force;
                {
                        AutoLocker<BLocker> 
lock(&window->fPackageToPopulateLock);
                        package = window->fPackageToPopulate;
+                       force = window->fForcePopulatePackage;
                }
 
                if (package.Get() != NULL) {
-                       window->fModel.PopulatePackage(package,
-                               Model::POPULATE_USER_RATINGS | 
Model::POPULATE_SCREEN_SHOTS
-                                       | Model::POPULATE_CHANGELOG);
+                       uint32 populateFlags = Model::POPULATE_USER_RATINGS
+                               | Model::POPULATE_SCREEN_SHOTS
+                               | Model::POPULATE_CHANGELOG;
+
+                       if (force)
+                               populateFlags |= Model::POPULATE_FORCE;
+
+                       window->fModel.PopulatePackage(package, populateFlags);
+
+                       if (Logger::IsDebugEnabled()) {
+                               printf("populating package [%s]\n",
+                                       package->Name().String());
+                       }
                }
        }
 
@@ -1435,9 +1490,35 @@ MainWindow::_UpdateAvailableRepositories()
 }
 
 
+bool
+MainWindow::_SelectedPackageHasWebAppRepositoryCode()
+{
+       const PackageInfoRef& package = fPackageInfoView->Package();
+       const DepotInfo* depot = fModel.DepotForName(package->DepotName());
+
+       BString repositoryCode;
+
+       if (depot != NULL)
+               repositoryCode = depot->WebAppRepositoryCode();
+
+       return !repositoryCode.IsEmpty();
+}
+
+
 void
 MainWindow::_RatePackage()
 {
+       if (!_SelectedPackageHasWebAppRepositoryCode()) {
+               BAlert* alert = new(std::nothrow) BAlert(
+                       B_TRANSLATE("rating_not_possible"),
+                       B_TRANSLATE("Because there is no representation for 
this package "
+                               "on the HaikuDepot server system, it is not 
possible to create "
+                               "a new rating or edit an existing rating."),
+                       B_TRANSLATE("OK"));
+               alert->Go();
+       return;
+       }
+
        if (fModel.Username().IsEmpty()) {
                BAlert* alert = new(std::nothrow) BAlert(
                        B_TRANSLATE("Not logged in"),
diff --git a/src/apps/haikudepot/ui/MainWindow.h 
b/src/apps/haikudepot/ui/MainWindow.h
index 64b580f32d..8b89cd4776 100644
--- a/src/apps/haikudepot/ui/MainWindow.h
+++ b/src/apps/haikudepot/ui/MainWindow.h
@@ -57,6 +57,8 @@ private:
        virtual Model*                          GetModel();
 
 private:
+                       bool                            
_SelectedPackageHasWebAppRepositoryCode();
+
                        void                            _BuildMenu(BMenuBar* 
menuBar);
                        void                            
_BuildUserMenu(BMenuBar* menuBar);
 
@@ -73,6 +75,7 @@ private:
                        void                            
_RefreshRepositories(bool force);
                        void                            
_RefreshPackageList(bool force);
 
+                       void                            
_PopulatePackageAsync(bool forcePopulate);
                        void                            
_StartRefreshWorker(bool force = false);
        static  status_t                        _RefreshModelThreadWorker(void* 
arg);
        static  status_t                        _PackageActionWorker(void* arg);
@@ -127,6 +130,7 @@ private:
 
                        thread_id                       fPopulatePackageWorker;
                        PackageInfoRef          fPackageToPopulate;
+                       bool                            fForcePopulatePackage;
                        BLocker                         fPackageToPopulateLock;
                        sem_id                          fPackageToPopulateSem;
 
diff --git a/src/apps/haikudepot/ui/PackageInfoView.cpp 
b/src/apps/haikudepot/ui/PackageInfoView.cpp
index 4b4e86af8e..59c54020ab 100644
--- a/src/apps/haikudepot/ui/PackageInfoView.cpp
+++ b/src/apps/haikudepot/ui/PackageInfoView.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright 2013-2014, Stephan Aßmus <superstippi@xxxxxx>.
+ * Copyright 2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 
@@ -15,6 +16,7 @@
 #include <CardLayout.h>
 #include <Catalog.h>
 #include <ColumnListView.h>
+#include <DateFormat.h>
 #include <Font.h>
 #include <GridView.h>
 #include <LayoutBuilder.h>
@@ -724,7 +726,6 @@ public:
                fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
 
                BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
-//                     .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
                        .AddGroup(leftGroup, 1.0f)
                                .Add(fScreenshotView)
                                .AddGroup(B_HORIZONTAL)
@@ -867,98 +868,71 @@ private:
 
 class RatingItemView : public BGroupView {
 public:
-       RatingItemView(const UserRating& rating, const BitmapRef& voteUpIcon,
-               const BitmapRef& voteDownIcon)
+       RatingItemView(const UserRating& rating)
                :
                BGroupView(B_HORIZONTAL, 0.0f)
        {
                SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
-               fAvatarView = new BitmapView("avatar view");
-               if (rating.User().Avatar().Get() != NULL) {
-                       fAvatarView->SetBitmap(rating.User().Avatar(),
-                               SharedBitmap::SIZE_16);
+               BLayoutBuilder::Group<BLayoutBuilder::Group<void*> > 
verticalGroup =
+                       BLayoutBuilder::Group<>(this)
+                               .AddGroup(B_VERTICAL, 0.0f);
+
+               {
+                       BStringView* userNicknameView = new 
BStringView("user-nickname",
+                               rating.User().NickName());
+                       userNicknameView->SetFont(be_bold_font);
+                       verticalGroup.Add(userNicknameView);
                }
-               fAvatarView->SetExplicitMinSize(BSize(16.0f, 16.0f));
-
-               fNameView = new BStringView("user name", 
rating.User().NickName());
-
-               BFont nameFont(be_bold_font);
-               nameFont.SetSize(std::max(9.0f, floorf(nameFont.Size() * 
0.9f)));
-               fNameView->SetFont(&nameFont);
-               fNameView->SetExplicitMaxSize(
-                       BSize(nameFont.StringWidth("xxxxxxxxxxxxxxxxxxxxxx"), 
B_SIZE_UNSET));
-
-               fRatingView = new RatingView("package rating view");
-               fRatingView->SetRating(rating.Rating());
-
-               BString ratingLabel;
-               if (rating.Rating() >= 0.0f)
-                       ratingLabel.SetToFormat("%.1f", rating.Rating());
-               fRatingLabelView = new BStringView("rating label", ratingLabel);
-
-               BString versionLabel(B_TRANSLATE("for %Version%"));
-               versionLabel.ReplaceAll("%Version%", rating.PackageVersion());
-               fPackageVersionView = new BStringView("package version",
-                       versionLabel);
-               BFont versionFont(be_plain_font);
-               versionFont.SetSize(std::max(9.0f, floorf(versionFont.Size() * 
0.85f)));
-               fPackageVersionView->SetFont(&versionFont);
-
-               // TODO: User rating IDs to identify which rating to vote up or 
down
-//             BMessage* voteUpMessage = new BMessage(MSG_VOTE_UP);
-//             voteUpMessage->AddInt32("rating id", -1);
-//             BMessage* voteDownMessage = new BMessage(MSG_VOTE_DOWN);
-//             voteDownMessage->AddInt32("rating id", -1);
-//
-//             fVoteUpIconView = new BitmapButton("vote up icon", 
voteUpMessage);
-//             fUpVoteCountView = new BStringView("up vote count", "");
-//             fVoteDownIconView = new BitmapButton("vote down icon", 
voteDownMessage);
-//             fDownVoteCountView = new BStringView("up vote count", "");
-//
-//             fVoteUpIconView->SetBitmap(voteUpIcon, SharedBitmap::SIZE_16);
-//             fVoteDownIconView->SetBitmap(voteDownIcon, 
SharedBitmap::SIZE_16);
-//
-//             fUpVoteCountView->SetFont(&versionFont);
-//             fUpVoteCountView->SetHighColor(kLightBlack);
-//             fDownVoteCountView->SetFont(&versionFont);
-//             fDownVoteCountView->SetHighColor(kLightBlack);
-//
-//             BString voteCountLabel;
-//             voteCountLabel.SetToFormat("%" B_PRId32, rating.UpVotes());
-//             fUpVoteCountView->SetText(voteCountLabel);
-//             voteCountLabel.SetToFormat("%" B_PRId32, rating.DownVotes());
-//             fDownVoteCountView->SetText(voteCountLabel);
-
-               fTextView = new TextView("rating text");
-               ParagraphStyle paragraphStyle(fTextView->ParagraphStyle());
-               paragraphStyle.SetJustify(true);
-               fTextView->SetParagraphStyle(paragraphStyle);
-
-               fTextView->SetText(rating.Comment());
 
-               BLayoutBuilder::Group<>(this)
-                       .Add(fAvatarView, 0.2f)
-                       .AddGroup(B_VERTICAL, 0.0f)
-                               .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
-                                       .Add(fNameView)
-                                       .Add(fRatingView)
-                                       .Add(fRatingLabelView)
-                                       .AddGlue(0.1f)
-                                       .Add(fPackageVersionView)
-                                       .AddGlue(5.0f)
-//                                     .AddGroup(B_HORIZONTAL, 0.0f, 0.0f)
-//                                             .Add(fVoteUpIconView)
-//                                             .Add(fUpVoteCountView)
-//                                             
.AddStrut(B_USE_HALF_ITEM_SPACING)
-//                                             .Add(fVoteDownIconView)
-//                                             .Add(fDownVoteCountView)
-//                                     .End()
-                               .End()
-                               .Add(fTextView)
+               BLayoutBuilder::Group<BLayoutBuilder::Group<
+                       BLayoutBuilder::Group<void *> > > ratingGroup =
+                               verticalGroup.AddGroup(B_HORIZONTAL, 
B_USE_DEFAULT_SPACING);
+
+               if (rating.Rating() >= 0) {
+                       RatingView* ratingView = new RatingView("package rating 
view");
+                       ratingView->SetRating(rating.Rating());
+                       ratingGroup.Add(ratingView);
+               }
+
+               {
+                       BDateFormat dateFormat;
+                       BString createTimestampPresentation;
+
+                       dateFormat.Format(createTimestampPresentation,
+                               rating.CreateTimestamp().Date(), 
B_MEDIUM_DATE_FORMAT);
+
+                       BString ratingContextDescription(
+                               B_TRANSLATE("%hd.timestamp% (version 
%hd.version%)"));
+                       ratingContextDescription.ReplaceAll("%hd.timestamp%",
+                               createTimestampPresentation);
+                       ratingContextDescription.ReplaceAll("%hd.version%",
+                               rating.PackageVersion());
+
+                       BStringView* ratingContextView = new 
BStringView("rating-context",
+                               ratingContextDescription);
+                       BFont versionFont(be_plain_font);
+                       ratingContextView->SetFont(&versionFont);
+                       ratingGroup.Add(ratingContextView);
+               }
+
+               ratingGroup.AddGlue();
+               ratingGroup.End();
+
+               if (rating.Comment() > 0) {
+                       TextView* textView = new TextView("rating-text");
+                       ParagraphStyle 
paragraphStyle(textView->ParagraphStyle());
+                       paragraphStyle.SetJustify(true);
+                       textView->SetParagraphStyle(paragraphStyle);
+                       textView->SetText(rating.Comment());
+                       verticalGroup.AddStrut(8.0f);
+                       verticalGroup.Add(textView);
+                       verticalGroup.AddStrut(8.0f);
+               }
+
+               verticalGroup
                        .End()
-                       .SetInsets(B_USE_DEFAULT_SPACING)
-               ;
+                       .SetInsets(B_USE_DEFAULT_SPACING);
 
                SetFlags(Flags() | B_WILL_DRAW);
        }
@@ -976,19 +950,6 @@ public:
                StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
        }
 
-private:
-       BitmapView*             fAvatarView;
-       BStringView*    fNameView;
-       RatingView*             fRatingView;
-       BStringView*    fRatingLabelView;
-       BStringView*    fPackageVersionView;
-
-//     BitmapView*             fVoteUpIconView;
-//     BStringView*    fUpVoteCountView;
-//     BitmapView*             fVoteDownIconView;
-//     BStringView*    fDownVoteCountView;
-
-       TextView*               fTextView;
 };
 
 
@@ -1062,9 +1023,7 @@ class UserRatingsView : public BGroupView {
 public:
        UserRatingsView()
                :
-               BGroupView("package ratings view", B_HORIZONTAL),
-               fThumbsUpIcon(BitmapRef(new SharedBitmap(502), true)),
-               fThumbsDownIcon(BitmapRef(new SharedBitmap(503), true))
+               BGroupView("package ratings view", B_HORIZONTAL)
        {
                SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
@@ -1077,6 +1036,8 @@ public:
 
                BScrollView* scrollView = new RatingsScrollView(
                        "ratings scroll view", ratingsContainerView);
+               scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
+                       B_SIZE_UNLIMITED));
 
                BLayoutBuilder::Group<>(this)
                        .AddGroup(B_VERTICAL)
@@ -1084,6 +1045,7 @@ public:
                                .AddGlue()
                                .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 
0.0f)
                        .End()
+                       .AddStrut(64.0)
                        .Add(scrollView, 1.0f)
                        .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
                ;
@@ -1122,15 +1084,10 @@ public:
                // TODO: Sort by age or usefullness rating
                for (int i = count - 1; i >= 0; i--) {
                        const UserRating& rating = userRatings.ItemAtFast(i);
-                       // Prevent ratings from showing that have a comment 
which
-                       // is in another language
-                       if (!rating.Comment().IsEmpty()
-                               && fPreferredLanguages.CountItems() > 0
-                               && 
!fPreferredLanguages.Contains(rating.Language())) {
-                               continue;
-                       }
-                       RatingItemView* view = new RatingItemView(rating, 
fThumbsUpIcon,
-                               fThumbsDownIcon);
+                               // was previously filtering comments just for 
the current
+                               // user's language, but as there are not so 
many comments at
+                               // the moment, just show all of them for now.
+                       RatingItemView* view = new RatingItemView(rating);
                        fRatingContainerLayout->AddView(0, view);
                }
        }
@@ -1181,8 +1138,6 @@ private:
 private:
        BGroupLayout*                   fRatingContainerLayout;
        RatingSummaryView*              fRatingSummaryView;
-       BitmapRef                               fThumbsUpIcon;
-       BitmapRef                               fThumbsDownIcon;
        StringList                              fPreferredLanguages;
 };
 
@@ -1516,5 +1471,4 @@ PackageInfoView::Clear()
        fPackageListener->SetPackage(PackageInfoRef(NULL));
 
        fPackage.Unset();
-}
-
+}
\ No newline at end of file
diff --git a/src/apps/haikudepot/ui/RatePackageWindow.cpp 
b/src/apps/haikudepot/ui/RatePackageWindow.cpp
index 4906ac62ed..585eaa3e91 100644
--- a/src/apps/haikudepot/ui/RatePackageWindow.cpp
+++ b/src/apps/haikudepot/ui/RatePackageWindow.cpp
@@ -1,6 +1,6 @@
 /*
  * Copyright 2014, Stephan Aßmus <superstippi@xxxxxx>.
- * Copyright 2016, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
+ * Copyright 2016-2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 
@@ -21,8 +21,10 @@
 #include <ScrollView.h>
 #include <StringView.h>
 
+#include "HaikuDepotConstants.h"
 #include "MarkupParser.h"
 #include "RatingView.h"
+#include "ServerHelper.h"
 #include "TextDocumentView.h"
 #include "WebAppInterface.h"
 
@@ -32,11 +34,12 @@
 
 
 enum {
-       MSG_SEND                                        = 'send',
-       MSG_PACKAGE_RATED                       = 'rpkg',
-       MSG_STABILITY_SELECTED          = 'stbl',
-       MSG_LANGUAGE_SELECTED           = 'lngs',
-       MSG_RATING_ACTIVE_CHANGED       = 'rtac'
+       MSG_SEND                                                = 'send',
+       MSG_PACKAGE_RATED                               = 'rpkg',
+       MSG_STABILITY_SELECTED                  = 'stbl',
+       MSG_LANGUAGE_SELECTED                   = 'lngs',
+       MSG_RATING_ACTIVE_CHANGED               = 'rtac',
+       MSG_RATING_DETERMINATE_CHANGED  = 'rdch'
 };
 
 //! Layouts the scrollbar so it looks nice with no border and the document
@@ -98,7 +101,8 @@ public:
        SetRatingView()
                :
                RatingView("rate package view"),
-               fPermanentRating(0.0f)
+               fPermanentRating(0.0f),
+               fRatingDeterminate(true)
        {
                SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
                SetRating(fPermanentRating);
@@ -134,6 +138,24 @@ public:
                SetRating(rating);
        }
 
+/*! By setting this to false, this indicates that there is no rating for the
+    set; ie NULL.  The indeterminate rating is indicated by a pale grey
+    colored star.
+*/
+
+       void SetRatingDeterminate(bool value) {
+               fRatingDeterminate = value;
+               Invalidate();
+       }
+
+protected:
+       virtual const BBitmap* StarBitmap()
+       {
+               if (fRatingDeterminate)
+                       return fStarBlueBitmap.Bitmap(SharedBitmap::SIZE_16);
+               return fStarGrayBitmap.Bitmap(SharedBitmap::SIZE_16);
+       }
+
 private:
        float _RatingForMousePos(BPoint where)
        {
@@ -141,6 +163,7 @@ private:
        }
 
        float           fPermanentRating;
+       bool            fRatingDeterminate;
 };
 
 
@@ -189,6 +212,10 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, 
BRect frame,
                B_TRANSLATE("Your rating:"));
 
        fSetRatingView = new SetRatingView();
+       fSetRatingView->SetRatingDeterminate(false);
+       fRatingDeterminateCheckBox = new BCheckBox("has rating", NULL,
+               new BMessage(MSG_RATING_DETERMINATE_CHANGED));
+       fRatingDeterminateCheckBox->SetValue(B_CONTROL_OFF);
 
        fTextView = new TextDocumentView();
        ScrollView* textScrollView = new ScrollView(
@@ -258,7 +285,10 @@ RatePackageWindow::RatePackageWindow(BWindow* parent, 
BRect frame,
        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .AddGrid()
                        .Add(ratingLabel, 0, 0)
-                       .Add(fSetRatingView, 1, 0)
+                       .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 0)
+                               .Add(fRatingDeterminateCheckBox)
+                               .Add(fSetRatingView)
+                       .End()
                        .AddMenuField(fStabilityField, 0, 1)
                        .AddMenuField(fCommentLanguageField, 0, 2)
                .End()
@@ -284,12 +314,31 @@ RatePackageWindow::~RatePackageWindow()
 }
 
 
+void
+RatePackageWindow::DispatchMessage(BMessage* message, BHandler *handler)
+{
+       if (message->what == B_KEY_DOWN) {
+               int8 key;
+                       // if the user presses escape, close the window.
+               if ((message->FindInt8("byte", &key) == B_OK)
+                       && key == B_ESCAPE) {
+                       Quit();
+                       return;
+               }
+       }
+
+       BWindow::DispatchMessage(message, handler);
+}
+
+
 void
 RatePackageWindow::MessageReceived(BMessage* message)
 {
        switch (message->what) {
                case MSG_PACKAGE_RATED:
                        message->FindFloat("rating", &fRating);
+                       fSetRatingView->SetRatingDeterminate(true);
+                       fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
                        break;
 
                case MSG_STABILITY_SELECTED:
@@ -300,6 +349,11 @@ RatePackageWindow::MessageReceived(BMessage* message)
                        message->FindString("code", &fCommentLanguage);
                        break;
 
+               case MSG_RATING_DETERMINATE_CHANGED:
+                       fSetRatingView->SetRatingDeterminate(
+                               fRatingDeterminateCheckBox->Value() == 
B_CONTROL_ON);
+                       break;
+
                case MSG_RATING_ACTIVE_CHANGED:
                {
                        int32 value;
@@ -308,6 +362,32 @@ RatePackageWindow::MessageReceived(BMessage* message)
                        break;
                }
 
+               case MSG_DID_ADD_USER_RATING:
+               {
+                       BAlert* alert = new(std::nothrow) BAlert(
+                               B_TRANSLATE("user_rating"),
+                               B_TRANSLATE("Your rating was uploaded 
successfully. "
+                                       "You can update or remove it at any 
time by visiting the "
+                                       "server application on the web."),
+                               B_TRANSLATE("Close"), NULL, NULL,
+                               B_WIDTH_AS_USUAL, B_WARNING_ALERT);
+                       alert->Go();
+                       _RefreshPackageData();
+                       break;
+               }
+
+               case MSG_DID_UPDATE_USER_RATING:
+               {
+                       BAlert* alert = new(std::nothrow) BAlert(
+                               B_TRANSLATE("user_rating"),
+                               B_TRANSLATE("Your rating was updated."),
+                               B_TRANSLATE("Close"), NULL, NULL,
+                               B_WIDTH_AS_USUAL, B_WARNING_ALERT);
+                       alert->Go();
+                       _RefreshPackageData();
+                       break;
+               }
+
                case MSG_SEND:
                        _SendRating();
                        break;
@@ -318,6 +398,20 @@ RatePackageWindow::MessageReceived(BMessage* message)
        }
 }
 
+/*! Refresh the data shown about the current page.  This may be useful, for
+    example when somebody adds a rating and that changes the rating of the
+    package or they add a rating and want to see that immediately.  The logic
+    should round-trip to the server so that actual data is shown.
+*/
+
+void
+RatePackageWindow::_RefreshPackageData()
+{
+       BMessage message(MSG_SERVER_DATA_CHANGED);
+       message.AddString("name", fPackage->Name());
+       be_app->PostMessage(&message);
+}
+
 
 void
 RatePackageWindow::SetPackage(const PackageInfoRef& package)
@@ -360,8 +454,6 @@ RatePackageWindow::_SetWorkerThread(thread_id thread)
 
        bool enabled = thread < 0;
 
-//     fTextEditor->SetEnabled(enabled);
-//     fSetRatingView->SetEnabled(enabled);
        fStabilityField->SetEnabled(enabled);
        fCommentLanguageField->SetEnabled(enabled);
        fSendButton->SetEnabled(enabled);
@@ -386,6 +478,68 @@ RatePackageWindow::_QueryRatingThreadEntry(void* data)
 }
 
 
+/*! A server request has been made to the server and the server has responded
+    with some data.  The data is known not to be an error and now the data can
+    be extracted into the user interface elements.
+*/
+
+void
+RatePackageWindow::_RelayServerDataToUI(BMessage& response)
+{
+       if (Lock()) {
+               response.FindString("code", &fRatingID);
+               response.FindBool("active", &fRatingActive);
+               BString comment;
+               if (response.FindString("comment", &comment) == B_OK) {
+                       MarkupParser parser;
+                       fRatingText = parser.CreateDocumentFromMarkup(comment);
+                       fTextView->SetTextDocument(fRatingText);
+               }
+               if (response.FindString("userRatingStabilityCode",
+                       &fStability) == B_OK) {
+                       int32 index = 0;
+                       for (int32 i = fStabilityCodes.CountItems() - 1; i >= 
0; i--) {
+                               const StabilityRating& stability
+                                       = fStabilityCodes.ItemAtFast(i);
+                               if (stability.Name() == fStability) {
+                                       index = i;
+                                       break;
+                               }
+                       }
+                       BMenuItem* item = 
fStabilityField->Menu()->ItemAt(index);
+                       if (item != NULL)
+                               item->SetMarked(true);
+               }
+               if (response.FindString("naturalLanguageCode",
+                       &fCommentLanguage) == B_OK) {
+                       BMenuItem* item = fCommentLanguageField->Menu()->ItemAt(
+                               
fModel.SupportedLanguages().IndexOf(fCommentLanguage));
+                       if (item != NULL)
+                               item->SetMarked(true);
+               }
+               double rating;
+               if (response.FindDouble("rating", &rating) == B_OK) {
+                       fRating = (float)rating;
+                       fSetRatingView->SetPermanentRating(fRating);
+                       fSetRatingView->SetRatingDeterminate(true);
+                       fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
+               } else {
+                       fSetRatingView->SetRatingDeterminate(false);
+                       fRatingDeterminateCheckBox->SetValue(B_CONTROL_OFF);
+               }
+
+               fRatingActiveCheckBox->SetValue(fRatingActive);
+               fRatingActiveCheckBox->Show();
+
+               fSendButton->SetLabel(B_TRANSLATE("Update"));
+
+               Unlock();
+       } else {
+               fprintf(stderr, "unable to acquire lock to update the ui\n");
+       }
+}
+
+
 void
 RatePackageWindow::_QueryRatingThread()
 {
@@ -416,67 +570,50 @@ RatePackageWindow::_QueryRatingThread()
        if (depot != NULL)
                repositoryCode = depot->WebAppRepositoryCode();
 
-       if (repositoryCode.Length() == 0) {
+       if (repositoryCode.IsEmpty()) {
                printf("unable to obtain the repository code for depot; %s\n",
                        package->DepotName().String());
+               BMessenger(this).SendMessage(B_QUIT_REQUESTED);
        } else {
                status_t status = interface.RetrieveUserRating(
                        package->Name(), package->Version(), 
package->Architecture(),
                        repositoryCode, username, info);
 
-       //      info.PrintToStream();
-
-               BMessage result;
-               if (status == B_OK && info.FindMessage("result", &result) == 
B_OK
-                       && Lock()) {
-
-                       result.FindString("code", &fRatingID);
-                       result.FindBool("active", &fRatingActive);
-                       BString comment;
-                       if (result.FindString("comment", &comment) == B_OK) {
-                               MarkupParser parser;
-                               fRatingText = 
parser.CreateDocumentFromMarkup(comment);
-                               fTextView->SetTextDocument(fRatingText);
-                       }
-                       if (result.FindString("userRatingStabilityCode",
-                               &fStability) == B_OK) {
-                               int32 index = 0;
-                               for (int32 i = fStabilityCodes.CountItems() - 
1; i >= 0; i--) {
-                                       const StabilityRating& stability
-                                               = fStabilityCodes.ItemAtFast(i);
-                                       if (stability.Name() == fStability) {
-                                               index = i;
-                                               break;
+               if (status == B_OK) {
+                               // could be an error or could be a valid 
response envelope
+                               // containing data.
+                       switch (interface.ErrorCodeFromResponse(info)) {
+                               case ERROR_CODE_NONE:
+                               {
+                                       //info.PrintToStream();
+                                       BMessage result;
+                                       if (info.FindMessage("result", &result) 
== B_OK) {
+                                               _RelayServerDataToUI(result);
+                                       } else {
+                                               fprintf(stderr, "bad response 
envelope missing 'result'"
+                                                       "entry\n");
+                                               
ServerHelper::NotifyTransportError(B_BAD_VALUE);
+                                               
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
                                        }
+                                       break;
                                }
-                               BMenuItem* item = 
fStabilityField->Menu()->ItemAt(index);
-                               if (item != NULL)
-                                       item->SetMarked(true);
-                       }
-                       if (result.FindString("naturalLanguageCode",
-                               &fCommentLanguage) == B_OK) {
-                               BMenuItem* item = 
fCommentLanguageField->Menu()->ItemAt(
-                                       
fModel.SupportedLanguages().IndexOf(fCommentLanguage));
-                               if (item != NULL)
-                                       item->SetMarked(true);
+                               case ERROR_CODE_OBJECTNOTFOUND:
+                                               // an expected response
+                                       fprintf(stderr, "there was no previous 
rating for this"
+                                               " user on this version of this 
package so a new rating"
+                                               " will be added.\n");
+                                       break;
+                               default:
+                                       
ServerHelper::NotifyServerJsonRpcError(info);
+                                       
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
+                                       break;
                        }
-                       double rating;
-                       if (result.FindDouble("rating", &rating) == B_OK) {
-                               fRating = (float)rating;
-                               fSetRatingView->SetPermanentRating(fRating);
-                       }
-
-                       fRatingActiveCheckBox->SetValue(fRatingActive);
-                       fRatingActiveCheckBox->Show();
-
-                       fSendButton->SetLabel(B_TRANSLATE("Update"));
-
-                       Unlock();
                } else {
-                       fprintf(stderr, "rating query: Failed response: %s\n",
+                       fprintf(stderr, "an error has arisen communicating with 
the"
+                               " server to obtain data for an existing rating 
[%s]\n",
                                strerror(status));
-                       if (!info.IsEmpty())
-                               info.PrintToStream();
+                       ServerHelper::NotifyTransportError(status);
+                       BMessenger(this).SendMessage(B_QUIT_REQUESTED);
                }
        }
 
@@ -501,6 +638,7 @@ RatePackageWindow::_SendRatingThread()
                return;
        }
 
+       BMessenger messenger = BMessenger(this);
        BString package = fPackage->Name();
        BString architecture = fPackage->Architecture();
        BString repositoryCode;
@@ -533,86 +671,41 @@ RatePackageWindow::_SendRatingThread()
        status_t status;
        BMessage info;
        if (ratingID.Length() > 0) {
+               printf("will update the existing user rating [%s]\n",
+                       ratingID.String());
                status = interface.UpdateUserRating(ratingID,
-               languageCode, comment, stability, rating, active, info);
+                       languageCode, comment, stability, rating, active, info);
        } else {
-               status = interface.CreateUserRating(package, architecture,
-                       repositoryCode, languageCode, comment, stability, 
rating, info);
+               printf("will create a new user rating for pkg [%s]\n",
+                       package.String());
+               status = interface.CreateUserRating(package, 
fPackage->Version(),
+                       architecture, repositoryCode, languageCode, comment, 
stability,
+                       rating, info);
        }
 
-       BString error = B_TRANSLATE(
-               "There was a puzzling response from the web service.");
-
-       BMessage result;
        if (status == B_OK) {
-               if (info.FindMessage("result", &result) == B_OK) {
-                       error = "";
-               } else if (info.FindMessage("error", &result) == B_OK) {
-                       result.PrintToStream();
-                       BString message;
-                       if (result.FindString("message", &message) == B_OK) {
-                               if (message == "objectnotfound") {
-                                       error = B_TRANSLATE("The package was 
not found by the "
-                                               "web service. This probably 
means that it comes "
-                                               "from a depot which is not 
tracked there. Rating "
-                                               "such packages is unfortunately 
not supported.");
-                               } else {
-                                       error << B_TRANSLATE(" It responded 
with: ");
-                                       error << message;
-                               }
+                       // could be an error or could be a valid response 
envelope
+                       // containing data.
+               switch (interface.ErrorCodeFromResponse(info)) {
+                       case ERROR_CODE_NONE:
+                       {
+                               if (ratingID.Length() > 0)
+                                       
messenger.SendMessage(MSG_DID_UPDATE_USER_RATING);
+                               else
+                                       
messenger.SendMessage(MSG_DID_ADD_USER_RATING);
+                               break;
                        }
+                       default:
+                               ServerHelper::NotifyServerJsonRpcError(info);
+                               break;
                }
        } else {
-               error = B_TRANSLATE(
-                       "It was not possible to contact the web service.");
+               fprintf(stderr, "an error has arisen communicating with the"
+                       " server to obtain data for an existing rating [%s]\n",
+                       strerror(status));
+               ServerHelper::NotifyTransportError(status);
        }
 
-       if (!error.IsEmpty()) {
-               BString failedTitle;
-               if (ratingID.Length() > 0)
-                       failedTitle = B_TRANSLATE("Failed to update rating");
-               else
-                       failedTitle = B_TRANSLATE("Failed to rate package");
-
-               BAlert* alert = new(std::nothrow) BAlert(
-                       failedTitle,
-                       error,
-                       B_TRANSLATE("Close"), NULL, NULL,
-                       B_WIDTH_AS_USUAL, B_WARNING_ALERT);
-
-               if (alert != NULL)
-                       alert->Go();
-
-               fprintf(stderr,
-                       B_TRANSLATE("Failed to create or update rating: %s\n"),
-                       error.String());
-               if (!info.IsEmpty())
-                       info.PrintToStream();
-
-               _SetWorkerThread(-1);
-       } else {
-               _SetWorkerThread(-1);
-
-               fModel.PopulatePackage(fPackage,
-                       Model::POPULATE_FORCE | Model::POPULATE_USER_RATINGS);
-
-               BMessenger(this).SendMessage(B_QUIT_REQUESTED);
-
-               BString message;
-               if (ratingID.Length() > 0) {
-                       message = B_TRANSLATE("Your rating was updated 
successfully.");
-               } else {
-                       message = B_TRANSLATE("Your rating was uploaded 
successfully. "
-                               "You can update or remove it at any time by 
rating the "
-                               "package again.");
-               }
-
-               BAlert* alert = new(std::nothrow) BAlert(
-                       B_TRANSLATE("Success"),
-                       message,
-                       B_TRANSLATE("Close"));
-
-               if (alert != NULL)
-                       alert->Go();
-       }
+       messenger.SendMessage(B_QUIT_REQUESTED);
+       _SetWorkerThread(-1);
 }
diff --git a/src/apps/haikudepot/ui/RatePackageWindow.h 
b/src/apps/haikudepot/ui/RatePackageWindow.h
index 1835df97a4..b5c6889f79 100644
--- a/src/apps/haikudepot/ui/RatePackageWindow.h
+++ b/src/apps/haikudepot/ui/RatePackageWindow.h
@@ -1,5 +1,6 @@
 /*
  * Copyright 2014, Stephan Aßmus <superstippi@xxxxxx>.
+ * Copyright 2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 #ifndef RATE_PACKAGE_WINDOW_H
@@ -26,11 +27,15 @@ public:
                                                                        Model& 
model);
        virtual                                         ~RatePackageWindow();
 
+       virtual void                            DispatchMessage(BMessage* 
message,
+                                                                       
BHandler *handler);
        virtual void                            MessageReceived(BMessage* 
message);
 
                        void                            SetPackage(const 
PackageInfoRef& package);
 
 private:
+                       void                            
_RelayServerDataToUI(BMessage& result);
+
                        void                            _SendRating();
 
                        void                            
_SetWorkerThread(thread_id thread);
@@ -41,6 +46,8 @@ private:
        static  int32                           _SendRatingThreadEntry(void* 
data);
                        void                            _SendRatingThread();
 
+                       void                            _RefreshPackageData();
+
 private:
                        Model&                          fModel;
                        TextDocumentRef         fRatingText;
@@ -54,6 +61,7 @@ private:
                        PackageInfoRef          fPackage;
 
                        SetRatingView*          fSetRatingView;
+                       BCheckBox*                      
fRatingDeterminateCheckBox;
                        BMenuField*                     fStabilityField;
                        BMenuField*                     fCommentLanguageField;
                        TextDocumentView*       fTextView;
diff --git a/src/apps/haikudepot/ui/ScreenshotWindow.cpp 
b/src/apps/haikudepot/ui/ScreenshotWindow.cpp
index 48a341e485..3ef69a0f84 100644
--- a/src/apps/haikudepot/ui/ScreenshotWindow.cpp
+++ b/src/apps/haikudepot/ui/ScreenshotWindow.cpp
@@ -17,6 +17,7 @@
 
 #include "BarberPole.h"
 #include "BitmapView.h"
+#include "HaikuDepotConstants.h"
 #include "WebAppInterface.h"
 
 
@@ -29,9 +30,9 @@ static const rgb_color kBackgroundColor = { 51, 102, 152, 255 
};
        // transparent regions
 
 static BitmapRef sNextButtonIcon(
-       new(std::nothrow) SharedBitmap(505), true);
+       new(std::nothrow) SharedBitmap(RSRC_ARROW_LEFT), true);
 static BitmapRef sPreviousButtonIcon(
-       new(std::nothrow) SharedBitmap(506), true);
+       new(std::nothrow) SharedBitmap(RSRC_ARROW_RIGHT), true);
 
 
 ScreenshotWindow::ScreenshotWindow(BWindow* parent, BRect frame)
diff --git a/src/apps/haikudepot/ui_generic/RatingView.cpp 
b/src/apps/haikudepot/ui_generic/RatingView.cpp
index bd733103c0..ca0c001b23 100644
--- a/src/apps/haikudepot/ui_generic/RatingView.cpp
+++ b/src/apps/haikudepot/ui_generic/RatingView.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright 2013-2014, Stephan Aßmus <superstippi@xxxxxx>.
+ * Copyright 2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 
@@ -10,12 +11,15 @@
 
 #include <LayoutUtils.h>
 
+#include "HaikuDepotConstants.h"
+
 
 RatingView::RatingView(const char* name)
        :
        BView(name, B_WILL_DRAW),
-       fStarBitmap(501),
-       fRating(-1.0f)
+       fStarBlueBitmap(RSRC_STAR_BLUE),
+       fStarGrayBitmap(RSRC_STAR_GREY),
+       fRating(RATING_MISSING)
 {
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetLowUIColor(ViewUIColor());
@@ -33,16 +37,26 @@ RatingView::AttachedToWindow()
        AdoptParentColors();
 }
 
+/*! This method will return a star image that can be used repeatedly in the
+    user interface in order to signify the rating given by a user.  It could
+    be grey if no rating is assigned.
+*/
+
+const BBitmap*
+RatingView::StarBitmap()
+{
+       if (fRating < RATING_MIN)
+               return fStarGrayBitmap.Bitmap(SharedBitmap::SIZE_16);
+       return fStarBlueBitmap.Bitmap(SharedBitmap::SIZE_16);
+}
+
 
 void
 RatingView::Draw(BRect updateRect)
 {
        FillRect(updateRect, B_SOLID_LOW);
+       const BBitmap* star = StarBitmap();
 
-       if (fRating < 0.0f)
-               return;
-
-       const BBitmap* star = fStarBitmap.Bitmap(SharedBitmap::SIZE_16);
        if (star == NULL) {
                fprintf(stderr, "No star icon found in application 
resources.\n");
                return;
@@ -56,21 +70,20 @@ RatingView::Draw(BRect updateRect)
                x += 16 + 2;
        }
 
-       if (fRating >= 5.0f)
-               return;
+       if (fRating >= RATING_MIN && fRating < 5.0f) {
+               SetDrawingMode(B_OP_OVER);
 
-       SetDrawingMode(B_OP_OVER);
+               BRect rect(Bounds());
+               rect.right = x - 2;
+               rect.left = ceilf(rect.left + (fRating / 5.0f) * rect.Width());
 
-       BRect rect(Bounds());
-       rect.right = x - 2;
-       rect.left = ceilf(rect.left + (fRating / 5.0f) * rect.Width());
+               rgb_color color = LowColor();
+               color.alpha = 190;
+               SetHighColor(color);
 
-       rgb_color color = LowColor();
-       color.alpha = 190;
-       SetHighColor(color);
-
-       SetDrawingMode(B_OP_ALPHA);
-       FillRect(rect, B_SOLID_HIGH);
+               SetDrawingMode(B_OP_ALPHA);
+               FillRect(rect, B_SOLID_HIGH);
+       }
 }
 
 
diff --git a/src/apps/haikudepot/ui_generic/RatingView.h 
b/src/apps/haikudepot/ui_generic/RatingView.h
index 3f439ead1e..05abe99cda 100644
--- a/src/apps/haikudepot/ui_generic/RatingView.h
+++ b/src/apps/haikudepot/ui_generic/RatingView.h
@@ -1,5 +1,6 @@
 /*
  * Copyright 2013-2014, Stephan Aßmus <superstippi@xxxxxx>.
+ * Copyright 2018, Andrew Lindesay <apl@xxxxxxxxxxxxxx>.
  * All rights reserved. Distributed under the terms of the MIT License.
  */
 #ifndef RATING_VIEW_H
@@ -26,8 +27,12 @@ public:
                        void                            SetRating(float rating);
                        float                           Rating() const;
 
+protected:
+       virtual const BBitmap*          StarBitmap();
+                       SharedBitmap            fStarBlueBitmap;
+                       SharedBitmap            fStarGrayBitmap;
+
 private:
-                       SharedBitmap            fStarBitmap;
                        float                           fRating;
 };
 


Other related posts:

  • » [haiku-commits] haiku: hrev51996 - src/apps/haikudepot/ui src/apps/haikudepot/server data/artwork/icons src/apps/haikudepot/model src/apps/haikudepot - waddlesplash