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