xref: /haiku/src/apps/haikudepot/ui/RatePackageWindow.cpp (revision b8a45b3a2df2379b4301bf3bd5949b9a105be4ba)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2016-2024, 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 "SharedIcons.h"
32 #include "TextDocumentView.h"
33 #include "WebAppInterface.h"
34 
35 
36 #undef B_TRANSLATION_CONTEXT
37 #define B_TRANSLATION_CONTEXT "RatePackageWindow"
38 
39 
40 enum {
41 	MSG_SEND						= 'send',
42 	MSG_PACKAGE_RATED				= 'rpkg',
43 	MSG_STABILITY_SELECTED			= 'stbl',
44 	MSG_RATING_ACTIVE_CHANGED		= 'rtac',
45 	MSG_RATING_DETERMINATE_CHANGED	= 'rdch'
46 };
47 
48 //! Layouts the scrollbar so it looks nice with no border and the document
49 // window look.
50 class ScrollView : public BScrollView {
51 public:
52 	ScrollView(const char* name, BView* target)
53 		:
54 		BScrollView(name, target, 0, false, true, B_FANCY_BORDER)
55 	{
56 	}
57 
58 	virtual void DoLayout()
59 	{
60 		BRect innerFrame = Bounds();
61 		innerFrame.InsetBy(2, 2);
62 
63 		BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
64 		BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
65 
66 		if (vScrollBar != NULL)
67 			innerFrame.right -= vScrollBar->Bounds().Width() - 1;
68 		if (hScrollBar != NULL)
69 			innerFrame.bottom -= hScrollBar->Bounds().Height() - 1;
70 
71 		BView* target = Target();
72 		if (target != NULL) {
73 			Target()->MoveTo(innerFrame.left, innerFrame.top);
74 			Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
75 		}
76 
77 		if (vScrollBar != NULL) {
78 			BRect rect = innerFrame;
79 			rect.left = rect.right + 1;
80 			rect.right = rect.left + vScrollBar->Bounds().Width();
81 			rect.top -= 1;
82 			rect.bottom += 1;
83 
84 			vScrollBar->MoveTo(rect.left, rect.top);
85 			vScrollBar->ResizeTo(rect.Width(), rect.Height());
86 		}
87 
88 		if (hScrollBar != NULL) {
89 			BRect rect = innerFrame;
90 			rect.top = rect.bottom + 1;
91 			rect.bottom = rect.top + hScrollBar->Bounds().Height();
92 			rect.left -= 1;
93 			rect.right += 1;
94 
95 			hScrollBar->MoveTo(rect.left, rect.top);
96 			hScrollBar->ResizeTo(rect.Width(), rect.Height());
97 		}
98 	}
99 };
100 
101 
102 class SetRatingView : public RatingView {
103 public:
104 	SetRatingView()
105 		:
106 		RatingView("rate package view"),
107 		fPermanentRating(0.0f),
108 		fRatingDeterminate(true)
109 	{
110 		SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
111 		SetRating(fPermanentRating);
112 	}
113 
114 	virtual void MouseMoved(BPoint where, uint32 transit,
115 		const BMessage* dragMessage)
116 	{
117 		if (dragMessage != NULL)
118 			return;
119 
120 		if ((transit != B_INSIDE_VIEW && transit != B_ENTERED_VIEW)
121 			|| where.x > MinSize().width) {
122 			SetRating(fPermanentRating);
123 			return;
124 		}
125 
126 		float hoverRating = _RatingForMousePos(where);
127 		SetRating(hoverRating);
128 	}
129 
130 	virtual void MouseDown(BPoint where)
131 	{
132 		SetPermanentRating(_RatingForMousePos(where));
133 		BMessage message(MSG_PACKAGE_RATED);
134 		message.AddFloat("rating", fPermanentRating);
135 		Window()->PostMessage(&message, Window());
136 	}
137 
138 	void SetPermanentRating(float rating)
139 	{
140 		fPermanentRating = rating;
141 		SetRating(rating);
142 	}
143 
144 /*! By setting this to false, this indicates that there is no rating for the
145     set; ie NULL.  The indeterminate rating is indicated by a pale grey
146     colored star.
147 */
148 
149 	void SetRatingDeterminate(bool value) {
150 		fRatingDeterminate = value;
151 		Invalidate();
152 	}
153 
154 protected:
155 	virtual const BBitmap* StarBitmap()
156 	{
157 		if (fRatingDeterminate)
158 			return SharedIcons::IconStarBlue16Scaled()->Bitmap();
159 		return SharedIcons::IconStarGrey16Scaled()->Bitmap();
160 	}
161 
162 private:
163 	float _RatingForMousePos(BPoint where)
164 	{
165 		return std::min(5.0f, ceilf(5.0f * where.x / MinSize().width));
166 	}
167 
168 	float		fPermanentRating;
169 	bool		fRatingDeterminate;
170 };
171 
172 
173 RatePackageWindow::RatePackageWindow(BWindow* parent, BRect frame, Model& model)
174 	:
175 	BWindow(frame, B_TRANSLATE("Rate package"), B_FLOATING_WINDOW_LOOK,
176 		B_FLOATING_SUBSET_WINDOW_FEEL,
177 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
178 	fModel(model),
179 	fRatingText(),
180 	fTextEditor(new TextEditor(), true),
181 	fRating(RATING_NONE),
182 	fRatingDeterminate(false),
183 	fCommentLanguageId(LANGUAGE_DEFAULT_ID),
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 	fCommentLanguageId = fModel.Language()->PreferredLanguage()->ID();
274 
275 	LanguageMenuUtils::AddLanguagesToMenu(fModel.Language(), menu);
276 	menu->SetTargetForItems(this);
277 	LanguageMenuUtils::MarkLanguageInMenu(fCommentLanguageId, 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::MessageReceived(BMessage* message)
311 {
312 	switch (message->what) {
313 		case MSG_PACKAGE_RATED:
314 			message->FindFloat("rating", &fRating);
315 			fRatingDeterminate = true;
316 			fSetRatingView->SetRatingDeterminate(true);
317 			fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
318 			break;
319 
320 		case MSG_STABILITY_SELECTED:
321 			message->FindString("code", &fStabilityCode);
322 			break;
323 
324 		case MSG_LANGUAGE_SELECTED:
325 			message->FindString("id", &fCommentLanguageId);
326 			break;
327 
328 		case MSG_RATING_DETERMINATE_CHANGED:
329 			fRatingDeterminate = fRatingDeterminateCheckBox->Value()
330 				== B_CONTROL_ON;
331 			fSetRatingView->SetRatingDeterminate(fRatingDeterminate);
332 			break;
333 
334 		case MSG_RATING_ACTIVE_CHANGED:
335 		{
336 			int32 value;
337 			if (message->FindInt32("be:value", &value) == B_OK)
338 				fRatingActive = value == B_CONTROL_ON;
339 			break;
340 		}
341 
342 		case MSG_DID_ADD_USER_RATING:
343 		{
344 			BAlert* alert = new(std::nothrow) BAlert(
345 				B_TRANSLATE("User rating"),
346 				B_TRANSLATE("Your rating was uploaded successfully. "
347 					"You can update or remove it at the HaikuDepot Server "
348 					"website."),
349 				B_TRANSLATE("Close"), NULL, NULL,
350 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
351 			alert->Go();
352 			_RefreshPackageData();
353 			break;
354 		}
355 
356 		case MSG_DID_UPDATE_USER_RATING:
357 		{
358 			BAlert* alert = new(std::nothrow) BAlert(
359 				B_TRANSLATE("User rating"),
360 				B_TRANSLATE("Your rating was updated."),
361 				B_TRANSLATE("Close"), NULL, NULL,
362 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
363 			alert->Go();
364 			_RefreshPackageData();
365 			break;
366 		}
367 
368 		case MSG_SEND:
369 			_SendRating();
370 			break;
371 
372 		default:
373 			BWindow::MessageReceived(message);
374 			break;
375 	}
376 }
377 
378 /*! Refresh the data shown about the current page.  This may be useful, for
379     example when somebody adds a rating and that changes the rating of the
380     package or they add a rating and want to see that immediately.  The logic
381     should round-trip to the server so that actual data is shown.
382 */
383 
384 void
385 RatePackageWindow::_RefreshPackageData()
386 {
387 	BMessage message(MSG_SERVER_DATA_CHANGED);
388 	message.AddString("name", fPackage->Name());
389 	be_app->PostMessage(&message);
390 }
391 
392 
393 void
394 RatePackageWindow::SetPackage(const PackageInfoRef& package)
395 {
396 	BAutolock locker(this);
397 	if (!locker.IsLocked() || fWorkerThread >= 0)
398 		return;
399 
400 	fPackage = package;
401 
402 	BString windowTitle(B_TRANSLATE("Rate %Package%"));
403 	windowTitle.ReplaceAll("%Package%", package->Title());
404 	SetTitle(windowTitle);
405 
406 	// See if the user already made a rating for this package,
407 	// pre-fill the UI with that rating. (When sending the rating, the
408 	// old one will be replaced.)
409 	thread_id thread = spawn_thread(&_QueryRatingThreadEntry,
410 		"Query rating", B_NORMAL_PRIORITY, this);
411 	if (thread >= 0)
412 		_SetWorkerThread(thread);
413 }
414 
415 
416 void
417 RatePackageWindow::_SendRating()
418 {
419 	thread_id thread = spawn_thread(&_SendRatingThreadEntry,
420 		"Send rating", B_NORMAL_PRIORITY, this);
421 	if (thread >= 0)
422 		_SetWorkerThread(thread);
423 }
424 
425 
426 void
427 RatePackageWindow::_SetWorkerThread(thread_id thread)
428 {
429 	if (!Lock())
430 		return;
431 
432 	bool enabled = thread < 0;
433 
434 	fStabilityField->SetEnabled(enabled);
435 	fCommentLanguageField->SetEnabled(enabled);
436 	fSendButton->SetEnabled(enabled);
437 
438 	if (thread >= 0) {
439 		fWorkerThread = thread;
440 		resume_thread(fWorkerThread);
441 	} else {
442 		fWorkerThread = -1;
443 	}
444 
445 	Unlock();
446 }
447 
448 
449 /*static*/ int32
450 RatePackageWindow::_QueryRatingThreadEntry(void* data)
451 {
452 	RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
453 	window->_QueryRatingThread();
454 	return 0;
455 }
456 
457 
458 /*! A server request has been made to the server and the server has responded
459     with some data.  The data is known not to be an error and now the data can
460     be extracted into the user interface elements.
461 */
462 
463 void
464 RatePackageWindow::_RelayServerDataToUI(BMessage& response)
465 {
466 	if (Lock()) {
467 		response.FindString("code", &fRatingID);
468 		response.FindBool("active", &fRatingActive);
469 		BString comment;
470 		if (response.FindString("comment", &comment) == B_OK) {
471 			MarkupParser parser;
472 			fRatingText = parser.CreateDocumentFromMarkup(comment);
473 			fTextView->SetTextDocument(fRatingText);
474 		}
475 		if (response.FindString("userRatingStabilityCode",
476 				&fStabilityCode) == B_OK) {
477 			BMenu* menu = fStabilityField->Menu();
478 			AppUtils::MarkItemWithKeyValueInMenu(menu, "code", fStabilityCode);
479 		}
480 		if (response.FindString("naturalLanguageCode",
481 			&fCommentLanguageId) == B_OK && !comment.IsEmpty()) {
482 			LanguageMenuUtils::MarkLanguageInMenu(
483 				fCommentLanguageId, fCommentLanguageField->Menu());
484 		}
485 		double rating;
486 		if (response.FindDouble("rating", &rating) == B_OK) {
487 			fRating = (float)rating;
488 			fRatingDeterminate = fRating >= 0.0f;
489 			fSetRatingView->SetPermanentRating(fRating);
490 		} else {
491 			fRatingDeterminate = false;
492 		}
493 
494 		fSetRatingView->SetRatingDeterminate(fRatingDeterminate);
495 		fRatingDeterminateCheckBox->SetValue(
496 			fRatingDeterminate ? B_CONTROL_ON : B_CONTROL_OFF);
497 		fRatingActiveCheckBox->SetValue(fRatingActive);
498 		fRatingActiveCheckBox->Show();
499 
500 		fSendButton->SetLabel(B_TRANSLATE("Update"));
501 
502 		Unlock();
503 	} else
504 		HDERROR("unable to acquire lock to update the ui");
505 }
506 
507 
508 void
509 RatePackageWindow::_QueryRatingThread()
510 {
511 	if (!Lock()) {
512 		HDERROR("rating query: Failed to lock window");
513 		return;
514 	}
515 
516 	PackageInfoRef package(fPackage);
517 
518 	Unlock();
519 
520 	BAutolock locker(fModel.Lock());
521 	BString nickname = fModel.Nickname();
522 	locker.Unlock();
523 
524 	if (!package.IsSet()) {
525 		HDERROR("rating query: No package");
526 		_SetWorkerThread(-1);
527 		return;
528 	}
529 
530 	WebAppInterface* interface = fModel.GetWebAppInterface();
531 
532 	BMessage info;
533 	const DepotInfo* depot = fModel.DepotForName(package->DepotName());
534 	BString webAppRepositoryCode;
535 	BString webAppRepositorySourceCode;
536 
537 	if (depot != NULL) {
538 		webAppRepositoryCode = depot->WebAppRepositoryCode();
539 		webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
540 	}
541 
542 	if (webAppRepositoryCode.IsEmpty()
543 			|| webAppRepositorySourceCode.IsEmpty()) {
544 		HDERROR("unable to obtain the repository code or repository source "
545 			"code for depot; %s", package->DepotName().String());
546 		BMessenger(this).SendMessage(B_QUIT_REQUESTED);
547 	} else {
548 		status_t status = interface->RetrieveUserRatingForPackageAndVersionByUser(
549 				package->Name(), package->Version(), package->Architecture(),
550 				webAppRepositoryCode, webAppRepositorySourceCode,
551 				nickname, info);
552 
553 		if (status == B_OK) {
554 				// could be an error or could be a valid response envelope
555 				// containing data.
556 			switch (WebAppInterface::ErrorCodeFromResponse(info)) {
557 				case ERROR_CODE_NONE:
558 				{
559 					//info.PrintToStream();
560 					BMessage result;
561 					if (info.FindMessage("result", &result) == B_OK) {
562 						_RelayServerDataToUI(result);
563 					} else {
564 						HDERROR("bad response envelope missing 'result' entry");
565 						ServerHelper::NotifyTransportError(B_BAD_VALUE);
566 						BMessenger(this).SendMessage(B_QUIT_REQUESTED);
567 					}
568 					break;
569 				}
570 				case ERROR_CODE_OBJECTNOTFOUND:
571 						// an expected response
572 					HDINFO("there was no previous rating for this"
573 						" user on this version of this package so a new rating"
574 						" will be added.");
575 					break;
576 				default:
577 					ServerHelper::NotifyServerJsonRpcError(info);
578 					BMessenger(this).SendMessage(B_QUIT_REQUESTED);
579 					break;
580 			}
581 		} else {
582 			HDERROR("an error has arisen communicating with the"
583 				" server to obtain data for an existing rating [%s]",
584 				strerror(status));
585 			ServerHelper::NotifyTransportError(status);
586 			BMessenger(this).SendMessage(B_QUIT_REQUESTED);
587 		}
588 	}
589 
590 	_SetWorkerThread(-1);
591 }
592 
593 
594 int32
595 RatePackageWindow::_SendRatingThreadEntry(void* data)
596 {
597 	RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
598 	window->_SendRatingThread();
599 	return 0;
600 }
601 
602 
603 void
604 RatePackageWindow::_SendRatingThread()
605 {
606 	if (!Lock()) {
607 		HDERROR("upload rating: Failed to lock window");
608 		return;
609 	}
610 
611 	BMessenger messenger = BMessenger(this);
612 	BString package = fPackage->Name();
613 	BString architecture = fPackage->Architecture();
614 	BString webAppRepositoryCode;
615 	BString webAppRepositorySourceCode;
616 	int rating = (int)fRating;
617 	BString stability = fStabilityCode;
618 	BString comment = fRatingText->Text();
619 	BString languageId = fCommentLanguageId;
620 		// note that the language is a "code" in the server and "id" in ICU
621 	BString ratingID = fRatingID;
622 	bool active = fRatingActive;
623 
624 	if (!fRatingDeterminate)
625 		rating = RATING_NONE;
626 
627 	const DepotInfo* depot = fModel.DepotForName(fPackage->DepotName());
628 
629 	if (depot != NULL) {
630 		webAppRepositoryCode = depot->WebAppRepositoryCode();
631 		webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
632 	}
633 
634 	WebAppInterface* interface = fModel.GetWebAppInterface();
635 
636 	Unlock();
637 
638 	if (webAppRepositoryCode.IsEmpty()) {
639 		HDERROR("unable to find the web app repository code for the "
640 			"local depot %s",
641 			fPackage->DepotName().String());
642 		return;
643 	}
644 
645 	if (webAppRepositorySourceCode.IsEmpty()) {
646 		HDERROR("unable to find the web app repository source code for the "
647 			"local depot %s",
648 			fPackage->DepotName().String());
649 		return;
650 	}
651 
652 	if (stability == "unspecified")
653 		stability = "";
654 
655 	status_t status;
656 	BMessage info;
657 	if (ratingID.Length() > 0) {
658 		HDINFO("will update the existing user rating [%s]", ratingID.String());
659 		status = interface->UpdateUserRating(ratingID,
660 			languageId, comment, stability, rating, active, info);
661 	} else {
662 		HDINFO("will create a new user rating for pkg [%s]", package.String());
663 		status = interface->CreateUserRating(package, fPackage->Version(),
664 			architecture, webAppRepositoryCode, webAppRepositorySourceCode,
665 			languageId, comment, stability, rating, info);
666 	}
667 
668 	if (status == B_OK) {
669 			// could be an error or could be a valid response envelope
670 			// containing data.
671 		switch (WebAppInterface::ErrorCodeFromResponse(info)) {
672 			case ERROR_CODE_NONE:
673 			{
674 				if (ratingID.Length() > 0)
675 					messenger.SendMessage(MSG_DID_UPDATE_USER_RATING);
676 				else
677 					messenger.SendMessage(MSG_DID_ADD_USER_RATING);
678 				break;
679 			}
680 			default:
681 				ServerHelper::NotifyServerJsonRpcError(info);
682 				break;
683 		}
684 	} else {
685 		HDERROR("an error has arisen communicating with the"
686 			" server to obtain data for an existing rating [%s]",
687 			strerror(status));
688 		ServerHelper::NotifyTransportError(status);
689 	}
690 
691 	messenger.SendMessage(B_QUIT_REQUESTED);
692 	_SetWorkerThread(-1);
693 }
694