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