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