xref: /haiku/src/apps/haikudepot/ui/RatePackageWindow.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "RatePackageWindow.h"
8 
9 #include <algorithm>
10 #include <stdio.h>
11 
12 #include <Alert.h>
13 #include <Autolock.h>
14 #include <AutoLocker.h>
15 #include <Catalog.h>
16 #include <Button.h>
17 #include <CheckBox.h>
18 #include <LayoutBuilder.h>
19 #include <MenuField.h>
20 #include <MenuItem.h>
21 #include <ScrollView.h>
22 #include <StringView.h>
23 
24 #include "AppUtils.h"
25 #include "HaikuDepotConstants.h"
26 #include "LanguageMenuUtils.h"
27 #include "Logger.h"
28 #include "MarkupParser.h"
29 #include "RatingView.h"
30 #include "ServerHelper.h"
31 #include "TextDocumentView.h"
32 #include "WebAppInterface.h"
33 
34 
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "RatePackageWindow"
37 
38 
39 enum {
40 	MSG_SEND						= 'send',
41 	MSG_PACKAGE_RATED				= 'rpkg',
42 	MSG_STABILITY_SELECTED			= 'stbl',
43 	MSG_RATING_ACTIVE_CHANGED		= 'rtac',
44 	MSG_RATING_DETERMINATE_CHANGED	= 'rdch'
45 };
46 
47 //! Layouts the scrollbar so it looks nice with no border and the document
48 // window look.
49 class ScrollView : public BScrollView {
50 public:
51 	ScrollView(const char* name, BView* target)
52 		:
53 		BScrollView(name, target, 0, false, true, B_FANCY_BORDER)
54 	{
55 	}
56 
57 	virtual void DoLayout()
58 	{
59 		BRect innerFrame = Bounds();
60 		innerFrame.InsetBy(2, 2);
61 
62 		BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
63 		BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
64 
65 		if (vScrollBar != NULL)
66 			innerFrame.right -= vScrollBar->Bounds().Width() - 1;
67 		if (hScrollBar != NULL)
68 			innerFrame.bottom -= hScrollBar->Bounds().Height() - 1;
69 
70 		BView* target = Target();
71 		if (target != NULL) {
72 			Target()->MoveTo(innerFrame.left, innerFrame.top);
73 			Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
74 		}
75 
76 		if (vScrollBar != NULL) {
77 			BRect rect = innerFrame;
78 			rect.left = rect.right + 1;
79 			rect.right = rect.left + vScrollBar->Bounds().Width();
80 			rect.top -= 1;
81 			rect.bottom += 1;
82 
83 			vScrollBar->MoveTo(rect.left, rect.top);
84 			vScrollBar->ResizeTo(rect.Width(), rect.Height());
85 		}
86 
87 		if (hScrollBar != NULL) {
88 			BRect rect = innerFrame;
89 			rect.top = rect.bottom + 1;
90 			rect.bottom = rect.top + hScrollBar->Bounds().Height();
91 			rect.left -= 1;
92 			rect.right += 1;
93 
94 			hScrollBar->MoveTo(rect.left, rect.top);
95 			hScrollBar->ResizeTo(rect.Width(), rect.Height());
96 		}
97 	}
98 };
99 
100 
101 class SetRatingView : public RatingView {
102 public:
103 	SetRatingView()
104 		:
105 		RatingView("rate package view"),
106 		fPermanentRating(0.0f),
107 		fRatingDeterminate(true)
108 	{
109 		SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
110 		SetRating(fPermanentRating);
111 	}
112 
113 	virtual void MouseMoved(BPoint where, uint32 transit,
114 		const BMessage* dragMessage)
115 	{
116 		if (dragMessage != NULL)
117 			return;
118 
119 		if ((transit != B_INSIDE_VIEW && transit != B_ENTERED_VIEW)
120 			|| where.x > MinSize().width) {
121 			SetRating(fPermanentRating);
122 			return;
123 		}
124 
125 		float hoverRating = _RatingForMousePos(where);
126 		SetRating(hoverRating);
127 	}
128 
129 	virtual void MouseDown(BPoint where)
130 	{
131 		SetPermanentRating(_RatingForMousePos(where));
132 		BMessage message(MSG_PACKAGE_RATED);
133 		message.AddFloat("rating", fPermanentRating);
134 		Window()->PostMessage(&message, Window());
135 	}
136 
137 	void SetPermanentRating(float rating)
138 	{
139 		fPermanentRating = rating;
140 		SetRating(rating);
141 	}
142 
143 /*! By setting this to false, this indicates that there is no rating for the
144     set; ie NULL.  The indeterminate rating is indicated by a pale grey
145     colored star.
146 */
147 
148 	void SetRatingDeterminate(bool value) {
149 		fRatingDeterminate = value;
150 		Invalidate();
151 	}
152 
153 protected:
154 	virtual const BBitmap* StarBitmap()
155 	{
156 		if (fRatingDeterminate)
157 			return fStarBlueBitmap->Bitmap(BITMAP_SIZE_16);
158 		return fStarGrayBitmap->Bitmap(BITMAP_SIZE_16);
159 	}
160 
161 private:
162 	float _RatingForMousePos(BPoint where)
163 	{
164 		return std::min(5.0f, ceilf(5.0f * where.x / MinSize().width));
165 	}
166 
167 	float		fPermanentRating;
168 	bool		fRatingDeterminate;
169 };
170 
171 
172 RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame,
173 	Model& model)
174 	:
175 	BWindow(frame, B_TRANSLATE("Rate package"),
176 		B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
177 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
178 	fModel(model),
179 	fRatingText(),
180 	fTextEditor(new TextEditor(), true),
181 	fRating(RATING_NONE),
182 	fRatingDeterminate(false),
183 	fCommentLanguageCode(LANGUAGE_DEFAULT_CODE),
184 	fWorkerThread(-1)
185 {
186 	AddToSubset(parent);
187 
188 	BStringView* ratingLabel = new BStringView("rating label",
189 		B_TRANSLATE("Your rating:"));
190 
191 	fSetRatingView = new SetRatingView();
192 	fSetRatingView->SetRatingDeterminate(false);
193 	fRatingDeterminateCheckBox = new BCheckBox("has rating", NULL,
194 		new BMessage(MSG_RATING_DETERMINATE_CHANGED));
195 	fRatingDeterminateCheckBox->SetValue(B_CONTROL_OFF);
196 
197 	fTextView = new TextDocumentView();
198 	ScrollView* textScrollView = new ScrollView(
199 		"rating scroll view", fTextView);
200 
201 	// Get a TextDocument with default paragraph and character style
202 	MarkupParser parser;
203 	fRatingText = parser.CreateDocumentFromMarkup("");
204 
205 	fTextView->SetInsets(10.0f);
206 	fTextView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
207 	fTextView->SetTextDocument(fRatingText);
208 	fTextView->SetTextEditor(fTextEditor);
209 
210 	// Construct stability rating popup
211 	BPopUpMenu* stabilityMenu = new BPopUpMenu(B_TRANSLATE("Stability"));
212 	fStabilityField = new BMenuField("stability",
213 		B_TRANSLATE("Stability:"), stabilityMenu);
214 	_InitStabilitiesMenu(stabilityMenu);
215 
216 	// Construct languages popup
217 	BPopUpMenu* languagesMenu = new BPopUpMenu(B_TRANSLATE("Language"));
218 	fCommentLanguageField = new BMenuField("language",
219 		B_TRANSLATE("Comment language:"), languagesMenu);
220 	_InitLanguagesMenu(languagesMenu);
221 
222 	fRatingActiveCheckBox = new BCheckBox("rating active",
223 		B_TRANSLATE("This rating is visible to other users"),
224 		new BMessage(MSG_RATING_ACTIVE_CHANGED));
225 	// Hide the check mark by default, it will be made visible when
226 	// the user already made a rating and it is loaded
227 	fRatingActiveCheckBox->Hide();
228 
229 	// Construct buttons
230 	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
231 		new BMessage(B_QUIT_REQUESTED));
232 
233 	fSendButton = new BButton("send", B_TRANSLATE("Send"),
234 		new BMessage(MSG_SEND));
235 
236 	// Build layout
237 	BLayoutBuilder::Group<>(this, B_VERTICAL)
238 		.AddGrid()
239 			.Add(ratingLabel, 0, 0)
240 			.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 0)
241 				.Add(fRatingDeterminateCheckBox)
242 				.Add(fSetRatingView)
243 			.End()
244 			.AddMenuField(fStabilityField, 0, 1)
245 			.AddMenuField(fCommentLanguageField, 0, 2)
246 		.End()
247 		.Add(textScrollView)
248 		.AddGroup(B_HORIZONTAL)
249 			.Add(fRatingActiveCheckBox)
250 			.AddGlue()
251 			.Add(fCancelButton)
252 			.Add(fSendButton)
253 		.End()
254 		.SetInsets(B_USE_WINDOW_INSETS)
255 	;
256 
257 	// NOTE: Do not make Send the default button. The user might want
258 	// to type line-breaks instead of sending when hitting RETURN.
259 
260 	CenterIn(parent->Frame());
261 }
262 
263 
264 RatePackageWindow::~RatePackageWindow()
265 {
266 }
267 
268 
269 void
270 RatePackageWindow::_InitLanguagesMenu(BPopUpMenu* menu)
271 {
272 	AutoLocker<BLocker> locker(fModel.Lock());
273 	fCommentLanguageCode = fModel.Language()->PreferredLanguage()->Code();
274 
275 	LanguageMenuUtils::AddLanguagesToMenu(fModel.Language(), menu);
276 	menu->SetTargetForItems(this);
277 	LanguageMenuUtils::MarkLanguageInMenu(fCommentLanguageCode, menu);
278 }
279 
280 
281 void
282 RatePackageWindow::_InitStabilitiesMenu(BPopUpMenu* menu)
283 {
284 	AutoLocker<BLocker> locker(fModel.Lock());
285 	int32 countStabilities = fModel.CountRatingStabilities();
286 
287 	menu->SetTargetForItems(this);
288 
289 	if (0 == countStabilities) {
290 		menu->SetEnabled(false);
291 		return;
292 	}
293 
294 	for (int32 i = 0; i < countStabilities; i++) {
295 		const RatingStabilityRef stability = fModel.RatingStabilityAtIndex(i);
296 		BMessage* message = new BMessage(MSG_STABILITY_SELECTED);
297 		message->AddString("code", stability->Code());
298 		BMenuItem* item = new BMenuItem(stability->Name(), message);
299 		menu->AddItem(item);
300 
301 		if (i == 0) {
302 			fStabilityCode = stability->Code();
303 			item->SetMarked(true);
304 		}
305 	}
306 }
307 
308 
309 void
310 RatePackageWindow::DispatchMessage(BMessage* message, BHandler *handler)
311 {
312 	if (message->what == B_KEY_DOWN) {
313 		int8 key;
314 			// if the user presses escape, close the window.
315 		if ((message->FindInt8("byte", &key) == B_OK)
316 			&& key == B_ESCAPE) {
317 			Quit();
318 			return;
319 		}
320 	}
321 
322 	BWindow::DispatchMessage(message, handler);
323 }
324 
325 
326 void
327 RatePackageWindow::MessageReceived(BMessage* message)
328 {
329 	switch (message->what) {
330 		case MSG_PACKAGE_RATED:
331 			message->FindFloat("rating", &fRating);
332 			fRatingDeterminate = true;
333 			fSetRatingView->SetRatingDeterminate(true);
334 			fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
335 			break;
336 
337 		case MSG_STABILITY_SELECTED:
338 			message->FindString("code", &fStabilityCode);
339 			break;
340 
341 		case MSG_LANGUAGE_SELECTED:
342 			message->FindString("code", &fCommentLanguageCode);
343 			break;
344 
345 		case MSG_RATING_DETERMINATE_CHANGED:
346 			fRatingDeterminate = fRatingDeterminateCheckBox->Value()
347 				== B_CONTROL_ON;
348 			fSetRatingView->SetRatingDeterminate(fRatingDeterminate);
349 			break;
350 
351 		case MSG_RATING_ACTIVE_CHANGED:
352 		{
353 			int32 value;
354 			if (message->FindInt32("be:value", &value) == B_OK)
355 				fRatingActive = value == B_CONTROL_ON;
356 			break;
357 		}
358 
359 		case MSG_DID_ADD_USER_RATING:
360 		{
361 			BAlert* alert = new(std::nothrow) BAlert(
362 				B_TRANSLATE("User rating"),
363 				B_TRANSLATE("Your rating was uploaded successfully. "
364 					"You can update or remove it at the HaikuDepot Server "
365 					"website."),
366 				B_TRANSLATE("Close"), NULL, NULL,
367 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
368 			alert->Go();
369 			_RefreshPackageData();
370 			break;
371 		}
372 
373 		case MSG_DID_UPDATE_USER_RATING:
374 		{
375 			BAlert* alert = new(std::nothrow) BAlert(
376 				B_TRANSLATE("User rating"),
377 				B_TRANSLATE("Your rating was updated."),
378 				B_TRANSLATE("Close"), NULL, NULL,
379 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
380 			alert->Go();
381 			_RefreshPackageData();
382 			break;
383 		}
384 
385 		case MSG_SEND:
386 			_SendRating();
387 			break;
388 
389 		default:
390 			BWindow::MessageReceived(message);
391 			break;
392 	}
393 }
394 
395 /*! Refresh the data shown about the current page.  This may be useful, for
396     example when somebody adds a rating and that changes the rating of the
397     package or they add a rating and want to see that immediately.  The logic
398     should round-trip to the server so that actual data is shown.
399 */
400 
401 void
402 RatePackageWindow::_RefreshPackageData()
403 {
404 	BMessage message(MSG_SERVER_DATA_CHANGED);
405 	message.AddString("name", fPackage->Name());
406 	be_app->PostMessage(&message);
407 }
408 
409 
410 void
411 RatePackageWindow::SetPackage(const PackageInfoRef& package)
412 {
413 	BAutolock locker(this);
414 	if (!locker.IsLocked() || fWorkerThread >= 0)
415 		return;
416 
417 	fPackage = package;
418 
419 	BString windowTitle(B_TRANSLATE("Rate %Package%"));
420 	windowTitle.ReplaceAll("%Package%", package->Title());
421 	SetTitle(windowTitle);
422 
423 	// See if the user already made a rating for this package,
424 	// pre-fill the UI with that rating. (When sending the rating, the
425 	// old one will be replaced.)
426 	thread_id thread = spawn_thread(&_QueryRatingThreadEntry,
427 		"Query rating", B_NORMAL_PRIORITY, this);
428 	if (thread >= 0)
429 		_SetWorkerThread(thread);
430 }
431 
432 
433 void
434 RatePackageWindow::_SendRating()
435 {
436 	thread_id thread = spawn_thread(&_SendRatingThreadEntry,
437 		"Send rating", B_NORMAL_PRIORITY, this);
438 	if (thread >= 0)
439 		_SetWorkerThread(thread);
440 }
441 
442 
443 void
444 RatePackageWindow::_SetWorkerThread(thread_id thread)
445 {
446 	if (!Lock())
447 		return;
448 
449 	bool enabled = thread < 0;
450 
451 	fStabilityField->SetEnabled(enabled);
452 	fCommentLanguageField->SetEnabled(enabled);
453 	fSendButton->SetEnabled(enabled);
454 
455 	if (thread >= 0) {
456 		fWorkerThread = thread;
457 		resume_thread(fWorkerThread);
458 	} else {
459 		fWorkerThread = -1;
460 	}
461 
462 	Unlock();
463 }
464 
465 
466 /*static*/ int32
467 RatePackageWindow::_QueryRatingThreadEntry(void* data)
468 {
469 	RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
470 	window->_QueryRatingThread();
471 	return 0;
472 }
473 
474 
475 /*! A server request has been made to the server and the server has responded
476     with some data.  The data is known not to be an error and now the data can
477     be extracted into the user interface elements.
478 */
479 
480 void
481 RatePackageWindow::_RelayServerDataToUI(BMessage& response)
482 {
483 	if (Lock()) {
484 		response.FindString("code", &fRatingID);
485 		response.FindBool("active", &fRatingActive);
486 		BString comment;
487 		if (response.FindString("comment", &comment) == B_OK) {
488 			MarkupParser parser;
489 			fRatingText = parser.CreateDocumentFromMarkup(comment);
490 			fTextView->SetTextDocument(fRatingText);
491 		}
492 		if (response.FindString("userRatingStabilityCode",
493 				&fStabilityCode) == B_OK) {
494 			BMenu* menu = fStabilityField->Menu();
495 			AppUtils::MarkItemWithCodeInMenu(fStabilityCode, menu);
496 		}
497 		if (response.FindString("naturalLanguageCode",
498 			&fCommentLanguageCode) == B_OK && !comment.IsEmpty()) {
499 			LanguageMenuUtils::MarkLanguageInMenu(
500 				fCommentLanguageCode, fCommentLanguageField->Menu());
501 		}
502 		double rating;
503 		if (response.FindDouble("rating", &rating) == B_OK) {
504 			fRating = (float)rating;
505 			fRatingDeterminate = fRating >= 0.0f;
506 			fSetRatingView->SetPermanentRating(fRating);
507 		} else {
508 			fRatingDeterminate = false;
509 		}
510 
511 		fSetRatingView->SetRatingDeterminate(fRatingDeterminate);
512 		fRatingDeterminateCheckBox->SetValue(
513 			fRatingDeterminate ? B_CONTROL_ON : B_CONTROL_OFF);
514 		fRatingActiveCheckBox->SetValue(fRatingActive);
515 		fRatingActiveCheckBox->Show();
516 
517 		fSendButton->SetLabel(B_TRANSLATE("Update"));
518 
519 		Unlock();
520 	} else
521 		HDERROR("unable to acquire lock to update the ui");
522 }
523 
524 
525 void
526 RatePackageWindow::_QueryRatingThread()
527 {
528 	if (!Lock()) {
529 		HDERROR("rating query: Failed to lock window");
530 		return;
531 	}
532 
533 	PackageInfoRef package(fPackage);
534 
535 	Unlock();
536 
537 	BAutolock locker(fModel.Lock());
538 	BString nickname = fModel.Nickname();
539 	locker.Unlock();
540 
541 	if (!package.IsSet()) {
542 		HDERROR("rating query: No package");
543 		_SetWorkerThread(-1);
544 		return;
545 	}
546 
547 	WebAppInterface* interface = fModel.GetWebAppInterface();
548 
549 	BMessage info;
550 	const DepotInfo* depot = fModel.DepotForName(package->DepotName());
551 	BString webAppRepositoryCode;
552 	BString webAppRepositorySourceCode;
553 
554 	if (depot != NULL) {
555 		webAppRepositoryCode = depot->WebAppRepositoryCode();
556 		webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
557 	}
558 
559 	if (webAppRepositoryCode.IsEmpty()
560 			|| webAppRepositorySourceCode.IsEmpty()) {
561 		HDERROR("unable to obtain the repository code or repository source "
562 			"code for depot; %s", package->DepotName().String());
563 		BMessenger(this).SendMessage(B_QUIT_REQUESTED);
564 	} else {
565 		status_t status = interface->RetrieveUserRatingForPackageAndVersionByUser(
566 				package->Name(), package->Version(), package->Architecture(),
567 				webAppRepositoryCode, webAppRepositorySourceCode,
568 				nickname, info);
569 
570 		if (status == B_OK) {
571 				// could be an error or could be a valid response envelope
572 				// containing data.
573 			switch (WebAppInterface::ErrorCodeFromResponse(info)) {
574 				case ERROR_CODE_NONE:
575 				{
576 					//info.PrintToStream();
577 					BMessage result;
578 					if (info.FindMessage("result", &result) == B_OK) {
579 						_RelayServerDataToUI(result);
580 					} else {
581 						HDERROR("bad response envelope missing 'result' entry");
582 						ServerHelper::NotifyTransportError(B_BAD_VALUE);
583 						BMessenger(this).SendMessage(B_QUIT_REQUESTED);
584 					}
585 					break;
586 				}
587 				case ERROR_CODE_OBJECTNOTFOUND:
588 						// an expected response
589 					HDINFO("there was no previous rating for this"
590 						" user on this version of this package so a new rating"
591 						" will be added.");
592 					break;
593 				default:
594 					ServerHelper::NotifyServerJsonRpcError(info);
595 					BMessenger(this).SendMessage(B_QUIT_REQUESTED);
596 					break;
597 			}
598 		} else {
599 			HDERROR("an error has arisen communicating with the"
600 				" server to obtain data for an existing rating [%s]",
601 				strerror(status));
602 			ServerHelper::NotifyTransportError(status);
603 			BMessenger(this).SendMessage(B_QUIT_REQUESTED);
604 		}
605 	}
606 
607 	_SetWorkerThread(-1);
608 }
609 
610 
611 int32
612 RatePackageWindow::_SendRatingThreadEntry(void* data)
613 {
614 	RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
615 	window->_SendRatingThread();
616 	return 0;
617 }
618 
619 
620 void
621 RatePackageWindow::_SendRatingThread()
622 {
623 	if (!Lock()) {
624 		HDERROR("upload rating: Failed to lock window");
625 		return;
626 	}
627 
628 	BMessenger messenger = BMessenger(this);
629 	BString package = fPackage->Name();
630 	BString architecture = fPackage->Architecture();
631 	BString webAppRepositoryCode;
632 	BString webAppRepositorySourceCode;
633 	int rating = (int)fRating;
634 	BString stability = fStabilityCode;
635 	BString comment = fRatingText->Text();
636 	BString languageCode = fCommentLanguageCode;
637 	BString ratingID = fRatingID;
638 	bool active = fRatingActive;
639 
640 	if (!fRatingDeterminate)
641 		rating = RATING_NONE;
642 
643 	const DepotInfo* depot = fModel.DepotForName(fPackage->DepotName());
644 
645 	if (depot != NULL) {
646 		webAppRepositoryCode = depot->WebAppRepositoryCode();
647 		webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
648 	}
649 
650 	WebAppInterface* interface = fModel.GetWebAppInterface();
651 
652 	Unlock();
653 
654 	if (webAppRepositoryCode.IsEmpty()) {
655 		HDERROR("unable to find the web app repository code for the "
656 			"local depot %s",
657 			fPackage->DepotName().String());
658 		return;
659 	}
660 
661 	if (webAppRepositorySourceCode.IsEmpty()) {
662 		HDERROR("unable to find the web app repository source code for the "
663 			"local depot %s",
664 			fPackage->DepotName().String());
665 		return;
666 	}
667 
668 	if (stability == "unspecified")
669 		stability = "";
670 
671 	status_t status;
672 	BMessage info;
673 	if (ratingID.Length() > 0) {
674 		HDINFO("will update the existing user rating [%s]", ratingID.String());
675 		status = interface->UpdateUserRating(ratingID,
676 			languageCode, comment, stability, rating, active, info);
677 	} else {
678 		HDINFO("will create a new user rating for pkg [%s]", package.String());
679 		status = interface->CreateUserRating(package, fPackage->Version(),
680 			architecture, webAppRepositoryCode, webAppRepositorySourceCode,
681 			languageCode, comment, stability, rating, info);
682 	}
683 
684 	if (status == B_OK) {
685 			// could be an error or could be a valid response envelope
686 			// containing data.
687 		switch (WebAppInterface::ErrorCodeFromResponse(info)) {
688 			case ERROR_CODE_NONE:
689 			{
690 				if (ratingID.Length() > 0)
691 					messenger.SendMessage(MSG_DID_UPDATE_USER_RATING);
692 				else
693 					messenger.SendMessage(MSG_DID_ADD_USER_RATING);
694 				break;
695 			}
696 			default:
697 				ServerHelper::NotifyServerJsonRpcError(info);
698 				break;
699 		}
700 	} else {
701 		HDERROR("an error has arisen communicating with the"
702 			" server to obtain data for an existing rating [%s]",
703 			strerror(status));
704 		ServerHelper::NotifyTransportError(status);
705 	}
706 
707 	messenger.SendMessage(B_QUIT_REQUESTED);
708 	_SetWorkerThread(-1);
709 }
710