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