* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "TaskLooper.h"
#include <Catalog.h>
#include <MessageQueue.h>
#include <package/AddRepositoryRequest.h>
#include <package/DropRepositoryRequest.h>
#include <package/RefreshRepositoryRequest.h>
#include <package/PackageRoster.h>
#include <package/RepositoryConfig.h>
#include "constants.h"
#define DEBUGTASK 0
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TaskLooper"
static const BString kLogResultIndicator = "***";
static const BString kCompletedText =
B_TRANSLATE_COMMENT("Completed", "Completed task status message");
static const BString kFailedText =
B_TRANSLATE_COMMENT("Failed", "Failed task status message");
static const BString kAbortedText =
B_TRANSLATE_COMMENT("Aborted", "Aborted task status message");
static const BString kDescriptionText =
B_TRANSLATE_COMMENT("Description", "Failed task error description");
static const BString kDetailsText =
B_TRANSLATE_COMMENT("Details", "Job log details header");
using BSupportKit::BJob;
void
JobStateListener::JobStarted(BJob* job)
{
fJobLog.Add(job->Title());
}
void
JobStateListener::JobSucceeded(BJob* job)
{
BString resultText(kLogResultIndicator);
fJobLog.Add(resultText.Append(kCompletedText));
}
void
JobStateListener::JobFailed(BJob* job)
{
BString resultText(kLogResultIndicator);
resultText.Append(kFailedText).Append(": ")
.Append(strerror(job->Result()));
fJobLog.Add(resultText);
if (job->ErrorString().Length() > 0) {
resultText.SetTo(kLogResultIndicator);
resultText.Append(kDescriptionText).Append(": ")
.Append(job->ErrorString());
fJobLog.Add(resultText);
}
}
void
JobStateListener::JobAborted(BJob* job)
{
BString resultText(kLogResultIndicator);
resultText.Append(kAbortedText).Append(": ")
.Append(strerror(job->Result()));
fJobLog.Add(resultText);
if (job->ErrorString().Length() > 0) {
resultText.SetTo(kLogResultIndicator);
resultText.Append(kDescriptionText).Append(": ")
.Append(job->ErrorString());
fJobLog.Add(resultText);
}
}
BString
JobStateListener::GetJobLog()
{
return fJobLog.Join("\n");
}
TaskLooper::TaskLooper(const BMessenger& target)
:
BLooper("TaskLooper"),
fReplyTarget(target)
{
Run();
fMessenger.SetTo(this);
}
bool
TaskLooper::QuitRequested()
{
return MessageQueue()->IsEmpty();
}
void
TaskLooper::MessageReceived(BMessage* message)
{
switch (message->what)
{
case DO_TASK:
{
RepoRow* rowItem;
status_t result = message->FindPointer(key_rowptr, (void**)&rowItem);
if (result == B_OK) {
int16 queueCount = fTaskQueue.CountItems();
for (int16 index = 0; index<queueCount; index++) {
Task* task = fTaskQueue.ItemAt(index);
if (rowItem == task->rowItem)
break;
}
Task* newTask = new Task();
newTask->rowItem = rowItem;
newTask->name = rowItem->Name();
newTask->resultName = newTask->name;
if (rowItem->IsEnabled()) {
newTask->taskType = DISABLE_REPO;
newTask->taskParam = newTask->name;
} else {
newTask->taskType = ENABLE_REPO;
newTask->taskParam = rowItem->Url();
}
newTask->owner = this;
newTask->fTimer = NULL;
fTaskQueue.AddItem(newTask);
BString threadName(newTask->taskType == ENABLE_REPO ?
"enable_task" : "disable_task");
newTask->threadId = spawn_thread(_DoTask, threadName.String(),
B_NORMAL_PRIORITY, (void*)newTask);
status_t threadResult;
if (newTask->threadId < B_OK)
threadResult = B_ERROR;
else {
threadResult = resume_thread(newTask->threadId);
if (threadResult == B_OK) {
newTask->fTimer = new TaskTimer(fMessenger, newTask);
newTask->fTimer->Start(newTask->name);
BMessage reply(*message);
reply.what = TASK_STARTED;
reply.AddInt16(key_count, fTaskQueue.CountItems());
fReplyTarget.SendMessage(&reply);
} else
kill_thread(newTask->threadId);
}
if (threadResult != B_OK) {
_RemoveAndDelete(newTask);
}
}
break;
}
case TASK_COMPLETED:
case TASK_COMPLETED_WITH_ERRORS:
case TASK_CANCELED:
{
Task* task;
status_t result = message->FindPointer(key_taskptr, (void**)&task);
if (result == B_OK && fTaskQueue.HasItem(task)) {
task->fTimer->Stop(task->resultName);
BMessage reply(message->what);
reply.AddInt16(key_count, fTaskQueue.CountItems()-1);
reply.AddPointer(key_rowptr, task->rowItem);
if (message->what == TASK_COMPLETED_WITH_ERRORS)
reply.AddString(key_details, task->resultErrorDetails);
if (task->taskType == ENABLE_REPO
&& task->name.Compare(task->resultName) != 0) {
reply.AddString(key_name, task->resultName);
}
fReplyTarget.SendMessage(&reply);
_RemoveAndDelete(task);
}
break;
}
case TASK_KILL_REQUEST:
{
Task* task;
status_t result = message->FindPointer(key_taskptr, (void**)&task);
if (result == B_OK && fTaskQueue.HasItem(task)) {
kill_thread(task->threadId);
BMessage reply(TASK_CANCELED);
reply.AddInt16(key_count, fTaskQueue.CountItems()-1);
reply.AddPointer(key_rowptr, task->rowItem);
fReplyTarget.SendMessage(&reply);
_RemoveAndDelete(task);
}
break;
}
}
}
void
TaskLooper::_RemoveAndDelete(Task* task)
{
fTaskQueue.RemoveItem(task);
if (task->fTimer) {
task->fTimer->Lock();
task->fTimer->Quit();
task->fTimer = NULL;
}
delete task;
}
status_t
TaskLooper::_DoTask(void* data)
{
Task* task = (Task*)data;
BString errorDetails, repoName("");
status_t returnResult = B_OK;
DecisionProvider decisionProvider;
JobStateListener listener;
switch (task->taskType)
{
case DISABLE_REPO:
{
BString nameParam(task->taskParam);
BPackageKit::BContext context(decisionProvider, listener);
BPackageKit::DropRepositoryRequest dropRequest(context, nameParam);
status_t result = dropRequest.Process();
if (result != B_OK) {
returnResult = result;
if (result != B_CANCELED) {
errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
"error disabling the repository %name%",
"Error message, do not translate %name%"));
BString nameString("\"");
nameString.Append(nameParam).Append("\"");
errorDetails.ReplaceFirst("%name%", nameString);
_AppendErrorDetails(errorDetails, &listener);
}
}
break;
}
case ENABLE_REPO:
{
BString urlParam(task->taskParam);
BPackageKit::BContext context(decisionProvider, listener);
bool asUserRepository = false;
BPackageKit::AddRepositoryRequest addRequest(context, urlParam,
asUserRepository);
status_t result = addRequest.Process();
if (result != B_OK) {
returnResult = result;
if (result != B_CANCELED) {
errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
"error enabling the repository %url%",
"Error message, do not translate %url%"));
errorDetails.ReplaceFirst("%url%", urlParam);
_AppendErrorDetails(errorDetails, &listener);
}
break;
}
repoName = addRequest.RepositoryName();
BPackageKit::BPackageRoster roster;
BPackageKit::BRepositoryConfig repoConfig;
roster.GetRepositoryConfig(repoName, &repoConfig);
BPackageKit::BRefreshRepositoryRequest refreshRequest(context,
repoConfig);
result = refreshRequest.Process();
if (result != B_OK) {
returnResult = result;
if (result != B_CANCELED) {
errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
"error refreshing the repository cache for %name%",
"Error message, do not translate %name%"));
BString nameString("\"");
nameString.Append(repoName).Append("\"");
errorDetails.ReplaceFirst("%name%", nameString);
_AppendErrorDetails(errorDetails, &listener);
}
}
break;
}
}
BMessage reply;
if (returnResult == B_OK) {
reply.what = TASK_COMPLETED;
if (task->taskType == ENABLE_REPO)
task->resultName = repoName;
} else if (returnResult == B_CANCELED)
reply.what = TASK_CANCELED;
else {
reply.what = TASK_COMPLETED_WITH_ERRORS;
task->resultErrorDetails = errorDetails;
if (task->taskType == ENABLE_REPO)
task->resultName = repoName;
}
reply.AddPointer(key_taskptr, task);
task->owner->PostMessage(&reply);
#if DEBUGTASK
if (returnResult == B_OK || returnResult == B_CANCELED) {
BString degubDetails("Debug info:\n");
degubDetails.Append(listener.GetJobLog());
(new BAlert("debug", degubDetails, "OK"))->Go(NULL);
}
#endif
return 0;
}
void
TaskLooper::_AppendErrorDetails(BString& details, JobStateListener* listener)
{
details.Append("\n\n").Append(kDetailsText).Append(":\n");
details.Append(listener->GetJobLog());
}