xref: /haiku/src/apps/haikudepot/ui/RatePackageWindow.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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 "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, Model& model)
173 	:
174 	BWindow(frame, B_TRANSLATE("Rate package"), B_FLOATING_WINDOW_LOOK,
175 		B_FLOATING_SUBSET_WINDOW_FEEL,
176 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
177 	fModel(model),
178 	fRatingText(),
179 	fTextEditor(new TextEditor(), true),
180 	fRating(RATING_NONE),
181 	fRatingDeterminate(false),
182 	fCommentLanguageId(LANGUAGE_DEFAULT_ID),
183 	fWorkerThread(-1)
184 {
185 	AddToSubset(parent);
186 
187 	BStringView* ratingLabel = new BStringView("rating label",
188 		B_TRANSLATE("Your rating:"));
189 
190 	fSetRatingView = new SetRatingView();
191 	fSetRatingView->SetRatingDeterminate(false);
192 	fRatingDeterminateCheckBox = new BCheckBox("has rating", NULL,
193 		new BMessage(MSG_RATING_DETERMINATE_CHANGED));
194 	fRatingDeterminateCheckBox->SetValue(B_CONTROL_OFF);
195 
196 	fTextView = new TextDocumentView();
197 	ScrollView* textScrollView = new ScrollView(
198 		"rating scroll view", fTextView);
199 
200 	// Get a TextDocument with default paragraph and character style
201 	MarkupParser parser;
202 	fRatingText = parser.CreateDocumentFromMarkup("");
203 
204 	fTextView->SetInsets(10.0f);
205 	fTextView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
206 	fTextView->SetTextDocument(fRatingText);
207 	fTextView->SetTextEditor(fTextEditor);
208 
209 	// Construct stability rating popup
210 	BPopUpMenu* stabilityMenu = new BPopUpMenu(B_TRANSLATE("Stability"));
211 	fStabilityField = new BMenuField("stability",
212 		B_TRANSLATE("Stability:"), stabilityMenu);
213 	_InitStabilitiesMenu(stabilityMenu);
214 
215 	// Construct languages popup
216 	BPopUpMenu* languagesMenu = new BPopUpMenu(B_TRANSLATE("Language"));
217 	fCommentLanguageField = new BMenuField("language",
218 		B_TRANSLATE("Comment language:"), languagesMenu);
219 	_InitLanguagesMenu(languagesMenu);
220 
221 	fRatingActiveCheckBox = new BCheckBox("rating active",
222 		B_TRANSLATE("This rating is visible to other users"),
223 		new BMessage(MSG_RATING_ACTIVE_CHANGED));
224 	// Hide the check mark by default, it will be made visible when
225 	// the user already made a rating and it is loaded
226 	fRatingActiveCheckBox->Hide();
227 
228 	// Construct buttons
229 	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
230 		new BMessage(B_QUIT_REQUESTED));
231 
232 	fSendButton = new BButton("send", B_TRANSLATE("Send"),
233 		new BMessage(MSG_SEND));
234 
235 	// Build layout
236 	BLayoutBuilder::Group<>(this, B_VERTICAL)
237 		.AddGrid()
238 			.Add(ratingLabel, 0, 0)
239 			.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 0)
240 				.Add(fRatingDeterminateCheckBox)
241 				.Add(fSetRatingView)
242 			.End()
243 			.AddMenuField(fStabilityField, 0, 1)
244 			.AddMenuField(fCommentLanguageField, 0, 2)
245 		.End()
246 		.Add(textScrollView)
247 		.AddGroup(B_HORIZONTAL)
248 			.Add(fRatingActiveCheckBox)
249 			.AddGlue()
250 			.Add(fCancelButton)
251 			.Add(fSendButton)
252 		.End()
253 		.SetInsets(B_USE_WINDOW_INSETS)
254 	;
255 
256 	// NOTE: Do not make Send the default button. The user might want
257 	// to type line-breaks instead of sending when hitting RETURN.
258 
259 	CenterIn(parent->Frame());
260 }
261 
262 
263 RatePackageWindow::~RatePackageWindow()
264 {
265 }
266 
267 
268 void
269 RatePackageWindow::_InitLanguagesMenu(BPopUpMenu* menu)
270 {
271 	AutoLocker<BLocker> locker(fModel.Lock());
272 	fCommentLanguageId = fModel.Language()->PreferredLanguage()->ID();
273 
274 	LanguageMenuUtils::AddLanguagesToMenu(fModel.Language(), menu);
275 	menu->SetTargetForItems(this);
276 	LanguageMenuUtils::MarkLanguageInMenu(fCommentLanguageId, menu);
277 }
278 
279 
280 void
281 RatePackageWindow::_InitStabilitiesMenu(BPopUpMenu* menu)
282 {
283 	AutoLocker<BLocker> locker(fModel.Lock());
284 	int32 countStabilities = fModel.CountRatingStabilities();
285 
286 	menu->SetTargetForItems(this);
287 
288 	if (0 == countStabilities) {
289 		menu->SetEnabled(false);
290 		return;
291 	}
292 
293 	for (int32 i = 0; i < countStabilities; i++) {
294 		const RatingStabilityRef stability = fModel.RatingStabilityAtIndex(i);
295 		BMessage* message = new BMessage(MSG_STABILITY_SELECTED);
296 		message->AddString("code", stability->Code());
297 		BMenuItem* item = new BMenuItem(stability->Name(), message);
298 		menu->AddItem(item);
299 
300 		if (i == 0) {
301 			fStabilityCode = stability->Code();
302 			item->SetMarked(true);
303 		}
304 	}
305 }
306 
307 
308 void
309 RatePackageWindow::MessageReceived(BMessage* message)
310 {
311 	switch (message->what) {
312 		case MSG_PACKAGE_RATED:
313 			message->FindFloat("rating", &fRating);
314 			fRatingDeterminate = true;
315 			fSetRatingView->SetRatingDeterminate(true);
316 			fRatingDeterminateCheckBox->SetValue(B_CONTROL_ON);
317 			break;
318 
319 		case MSG_STABILITY_SELECTED:
320 			message->FindString("code", &fStabilityCode);
321 			break;
322 
323 		case MSG_LANGUAGE_SELECTED:
324 			message->FindString("id", &fCommentLanguageId);
325 			break;
326 
327 		case MSG_RATING_DETERMINATE_CHANGED:
328 			fRatingDeterminate = fRatingDeterminateCheckBox->Value()
329 				== B_CONTROL_ON;
330 			fSetRatingView->SetRatingDeterminate(fRatingDeterminate);
331 			break;
332 
333 		case MSG_RATING_ACTIVE_CHANGED:
334 		{
335 			int32 value;
336 			if (message->FindInt32("be:value", &value) == B_OK)
337 				fRatingActive = value == B_CONTROL_ON;
338 			break;
339 		}
340 
341 		case MSG_DID_ADD_USER_RATING:
342 		{
343 			BAlert* alert = new(std::nothrow) BAlert(
344 				B_TRANSLATE("User rating"),
345 				B_TRANSLATE("Your rating was uploaded successfully. "
346 					"You can update or remove it at the HaikuDepot Server "
347 					"website."),
348 				B_TRANSLATE("Close"), NULL, NULL,
349 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
350 			alert->Go();
351 			_RefreshPackageData();
352 			break;
353 		}
354 
355 		case MSG_DID_UPDATE_USER_RATING:
356 		{
357 			BAlert* alert = new(std::nothrow) BAlert(
358 				B_TRANSLATE("User rating"),
359 				B_TRANSLATE("Your rating was updated."),
360 				B_TRANSLATE("Close"), NULL, NULL,
361 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
362 			alert->Go();
363 			_RefreshPackageData();
364 			break;
365 		}
366 
367 		case MSG_SEND:
368 			_SendRating();
369 			break;
370 
371 		default:
372 			BWindow::MessageReceived(message);
373 			break;
374 	}
375 }
376 
377 /*! Refresh the data shown about the current page.  This may be useful, for
378     example when somebody adds a rating and that changes the rating of the
379     package or they add a rating and want to see that immediately.  The logic
380     should round-trip to the server so that actual data is shown.
381 */
382 
383 void
384 RatePackageWindow::_RefreshPackageData()
385 {
386 	BMessage message(MSG_SERVER_DATA_CHANGED);
387 	message.AddString("name", fPackage->Name());
388 	be_app->PostMessage(&message);
389 }
390 
391 
392 void
393 RatePackageWindow::SetPackage(const PackageInfoRef& package)
394 {
395 	BAutolock locker(this);
396 	if (!locker.IsLocked() || fWorkerThread >= 0)
397 		return;
398 
399 	fPackage = package;
400 
401 	BString windowTitle(B_TRANSLATE("Rate %Package%"));
402 	windowTitle.ReplaceAll("%Package%", package->Title());
403 	SetTitle(windowTitle);
404 
405 	// See if the user already made a rating for this package,
406 	// pre-fill the UI with that rating. (When sending the rating, the
407 	// old one will be replaced.)
408 	thread_id thread = spawn_thread(&_QueryRatingThreadEntry,
409 		"Query rating", B_NORMAL_PRIORITY, this);
410 	if (thread >= 0)
411 		_SetWorkerThread(thread);
412 }
413 
414 
415 void
416 RatePackageWindow::_SendRating()
417 {
418 	thread_id thread = spawn_thread(&_SendRatingThreadEntry,
419 		"Send rating", B_NORMAL_PRIORITY, this);
420 	if (thread >= 0)
421 		_SetWorkerThread(thread);
422 }
423 
424 
425 void
426 RatePackageWindow::_SetWorkerThread(thread_id thread)
427 {
428 	if (!Lock())
429 		return;
430 
431 	bool enabled = thread < 0;
432 
433 	fStabilityField->SetEnabled(enabled);
434 	fCommentLanguageField->SetEnabled(enabled);
435 	fSendButton->SetEnabled(enabled);
436 
437 	if (thread >= 0) {
438 		fWorkerThread = thread;
439 		resume_thread(fWorkerThread);
440 	} else {
441 		fWorkerThread = -1;
442 	}
443 
444 	Unlock();
445 }
446 
447 
448 /*static*/ int32
449 RatePackageWindow::_QueryRatingThreadEntry(void* data)
450 {
451 	RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
452 	window->_QueryRatingThread();
453 	return 0;
454 }
455 
456 
457 /*! A server request has been made to the server and the server has responded
458     with some data.  The data is known not to be an error and now the data can
459     be extracted into the user interface elements.
460 */
461 
462 void
463 RatePackageWindow::_RelayServerDataToUI(BMessage& response)
464 {
465 	if (Lock()) {
466 		response.FindString("code", &fRatingID);
467 		response.FindBool("active", &fRatingActive);
468 		BString comment;
469 		if (response.FindString("comment", &comment) == B_OK) {
470 			MarkupParser parser;
471 			fRatingText = parser.CreateDocumentFromMarkup(comment);
472 			fTextView->SetTextDocument(fRatingText);
473 		}
474 		if (response.FindString("userRatingStabilityCode",
475 				&fStabilityCode) == B_OK) {
476 			BMenu* menu = fStabilityField->Menu();
477 			AppUtils::MarkItemWithKeyValueInMenu(menu, "code", fStabilityCode);
478 		}
479 		if (response.FindString("naturalLanguageCode",
480 			&fCommentLanguageId) == B_OK && !comment.IsEmpty()) {
481 			LanguageMenuUtils::MarkLanguageInMenu(
482 				fCommentLanguageId, fCommentLanguageField->Menu());
483 		}
484 		double rating;
485 		if (response.FindDouble("rating", &rating) == B_OK) {
486 			fRating = (float)rating;
487 			fRatingDeterminate = fRating >= 0.0f;
488 			fSetRatingView->SetPermanentRating(fRating);
489 		} else {
490 			fRatingDeterminate = false;
491 		}
492 
493 		fSetRatingView->SetRatingDeterminate(fRatingDeterminate);
494 		fRatingDeterminateCheckBox->SetValue(
495 			fRatingDeterminate ? B_CONTROL_ON : B_CONTROL_OFF);
496 		fRatingActiveCheckBox->SetValue(fRatingActive);
497 		fRatingActiveCheckBox->Show();
498 
499 		fSendButton->SetLabel(B_TRANSLATE("Update"));
500 
501 		Unlock();
502 	} else
503 		HDERROR("unable to acquire lock to update the ui");
504 }
505 
506 
507 void
508 RatePackageWindow::_QueryRatingThread()
509 {
510 	if (!Lock()) {
511 		HDERROR("rating query: Failed to lock window");
512 		return;
513 	}
514 
515 	PackageInfoRef package(fPackage);
516 
517 	Unlock();
518 
519 	BAutolock locker(fModel.Lock());
520 	BString nickname = fModel.Nickname();
521 	locker.Unlock();
522 
523 	if (!package.IsSet()) {
524 		HDERROR("rating query: No package");
525 		_SetWorkerThread(-1);
526 		return;
527 	}
528 
529 	WebAppInterface* interface = fModel.GetWebAppInterface();
530 
531 	BMessage info;
532 	const DepotInfo* depot = fModel.DepotForName(package->DepotName());
533 	BString webAppRepositoryCode;
534 	BString webAppRepositorySourceCode;
535 
536 	if (depot != NULL) {
537 		webAppRepositoryCode = depot->WebAppRepositoryCode();
538 		webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
539 	}
540 
541 	if (webAppRepositoryCode.IsEmpty()
542 			|| webAppRepositorySourceCode.IsEmpty()) {
543 		HDERROR("unable to obtain the repository code or repository source "
544 			"code for depot; %s", package->DepotName().String());
545 		BMessenger(this).SendMessage(B_QUIT_REQUESTED);
546 	} else {
547 		status_t status = interface->RetrieveUserRatingForPackageAndVersionByUser(
548 				package->Name(), package->Version(), package->Architecture(),
549 				webAppRepositoryCode, webAppRepositorySourceCode,
550 				nickname, info);
551 
552 		if (status == B_OK) {
553 				// could be an error or could be a valid response envelope
554 				// containing data.
555 			switch (WebAppInterface::ErrorCodeFromResponse(info)) {
556 				case ERROR_CODE_NONE:
557 				{
558 					//info.PrintToStream();
559 					BMessage result;
560 					if (info.FindMessage("result", &result) == B_OK) {
561 						_RelayServerDataToUI(result);
562 					} else {
563 						HDERROR("bad response envelope missing 'result' entry");
564 						ServerHelper::NotifyTransportError(B_BAD_VALUE);
565 						BMessenger(this).SendMessage(B_QUIT_REQUESTED);
566 					}
567 					break;
568 				}
569 				case ERROR_CODE_OBJECTNOTFOUND:
570 						// an expected response
571 					HDINFO("there was no previous rating for this"
572 						" user on this version of this package so a new rating"
573 						" will be added.");
574 					break;
575 				default:
576 					ServerHelper::NotifyServerJsonRpcError(info);
577 					BMessenger(this).SendMessage(B_QUIT_REQUESTED);
578 					break;
579 			}
580 		} else {
581 			HDERROR("an error has arisen communicating with the"
582 				" server to obtain data for an existing rating [%s]",
583 				strerror(status));
584 			ServerHelper::NotifyTransportError(status);
585 			BMessenger(this).SendMessage(B_QUIT_REQUESTED);
586 		}
587 	}
588 
589 	_SetWorkerThread(-1);
590 }
591 
592 
593 int32
594 RatePackageWindow::_SendRatingThreadEntry(void* data)
595 {
596 	RatePackageWindow* window = reinterpret_cast<RatePackageWindow*>(data);
597 	window->_SendRatingThread();
598 	return 0;
599 }
600 
601 
602 void
603 RatePackageWindow::_SendRatingThread()
604 {
605 	if (!Lock()) {
606 		HDERROR("upload rating: Failed to lock window");
607 		return;
608 	}
609 
610 	BMessenger messenger = BMessenger(this);
611 	BString package = fPackage->Name();
612 	BString architecture = fPackage->Architecture();
613 	BString webAppRepositoryCode;
614 	BString webAppRepositorySourceCode;
615 	int rating = (int)fRating;
616 	BString stability = fStabilityCode;
617 	BString comment = fRatingText->Text();
618 	BString languageId = fCommentLanguageId;
619 		// note that the language is a "code" in the server and "id" in ICU
620 	BString ratingID = fRatingID;
621 	bool active = fRatingActive;
622 
623 	if (!fRatingDeterminate)
624 		rating = RATING_NONE;
625 
626 	const DepotInfo* depot = fModel.DepotForName(fPackage->DepotName());
627 
628 	if (depot != NULL) {
629 		webAppRepositoryCode = depot->WebAppRepositoryCode();
630 		webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
631 	}
632 
633 	WebAppInterface* interface = fModel.GetWebAppInterface();
634 
635 	Unlock();
636 
637 	if (webAppRepositoryCode.IsEmpty()) {
638 		HDERROR("unable to find the web app repository code for the "
639 			"local depot %s",
640 			fPackage->DepotName().String());
641 		return;
642 	}
643 
644 	if (webAppRepositorySourceCode.IsEmpty()) {
645 		HDERROR("unable to find the web app repository source code for the "
646 			"local depot %s",
647 			fPackage->DepotName().String());
648 		return;
649 	}
650 
651 	if (stability == "unspecified")
652 		stability = "";
653 
654 	status_t status;
655 	BMessage info;
656 	if (ratingID.Length() > 0) {
657 		HDINFO("will update the existing user rating [%s]", ratingID.String());
658 		status = interface->UpdateUserRating(ratingID,
659 			languageId, comment, stability, rating, active, info);
660 	} else {
661 		HDINFO("will create a new user rating for pkg [%s]", package.String());
662 		status = interface->CreateUserRating(package, fPackage->Version(),
663 			architecture, webAppRepositoryCode, webAppRepositorySourceCode,
664 			languageId, comment, stability, rating, info);
665 	}
666 
667 	if (status == B_OK) {
668 			// could be an error or could be a valid response envelope
669 			// containing data.
670 		switch (WebAppInterface::ErrorCodeFromResponse(info)) {
671 			case ERROR_CODE_NONE:
672 			{
673 				if (ratingID.Length() > 0)
674 					messenger.SendMessage(MSG_DID_UPDATE_USER_RATING);
675 				else
676 					messenger.SendMessage(MSG_DID_ADD_USER_RATING);
677 				break;
678 			}
679 			default:
680 				ServerHelper::NotifyServerJsonRpcError(info);
681 				break;
682 		}
683 	} else {
684 		HDERROR("an error has arisen communicating with the"
685 			" server to obtain data for an existing rating [%s]",
686 			strerror(status));
687 		ServerHelper::NotifyTransportError(status);
688 	}
689 
690 	messenger.SendMessage(B_QUIT_REQUESTED);
691 	_SetWorkerThread(-1);
692 }
693