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