1 /* 2 * Copyright 2017 Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Brian Hill 7 */ 8 9 10 #include "TaskLooper.h" 11 12 #include <Catalog.h> 13 #include <MessageQueue.h> 14 #include <package/AddRepositoryRequest.h> 15 #include <package/DropRepositoryRequest.h> 16 #include <package/RefreshRepositoryRequest.h> 17 #include <package/PackageRoster.h> 18 #include <package/RepositoryConfig.h> 19 20 #include "constants.h" 21 22 #define DEBUGTASK 0 23 24 #undef B_TRANSLATION_CONTEXT 25 #define B_TRANSLATION_CONTEXT "TaskLooper" 26 27 static const BString kLogResultIndicator = "***"; 28 static const BString kCompletedText = 29 B_TRANSLATE_COMMENT("Completed", "Completed task status message"); 30 static const BString kFailedText = 31 B_TRANSLATE_COMMENT("Failed", "Failed task status message"); 32 static const BString kAbortedText = 33 B_TRANSLATE_COMMENT("Aborted", "Aborted task status message"); 34 static const BString kDescriptionText = 35 B_TRANSLATE_COMMENT("Description", "Failed task error description"); 36 static const BString kDetailsText = 37 B_TRANSLATE_COMMENT("Details", "Job log details header"); 38 39 using BSupportKit::BJob; 40 41 42 void 43 JobStateListener::JobStarted(BJob* job) 44 { 45 fJobLog.Add(job->Title()); 46 } 47 48 49 void 50 JobStateListener::JobSucceeded(BJob* job) 51 { 52 BString resultText(kLogResultIndicator); 53 fJobLog.Add(resultText.Append(kCompletedText)); 54 } 55 56 57 void 58 JobStateListener::JobFailed(BJob* job) 59 { 60 BString resultText(kLogResultIndicator); 61 resultText.Append(kFailedText).Append(": ") 62 .Append(strerror(job->Result())); 63 fJobLog.Add(resultText); 64 if (job->ErrorString().Length() > 0) { 65 resultText.SetTo(kLogResultIndicator); 66 resultText.Append(kDescriptionText).Append(": ") 67 .Append(job->ErrorString()); 68 fJobLog.Add(resultText); 69 } 70 } 71 72 73 void 74 JobStateListener::JobAborted(BJob* job) 75 { 76 BString resultText(kLogResultIndicator); 77 resultText.Append(kAbortedText).Append(": ") 78 .Append(strerror(job->Result())); 79 fJobLog.Add(resultText); 80 if (job->ErrorString().Length() > 0) { 81 resultText.SetTo(kLogResultIndicator); 82 resultText.Append(kDescriptionText).Append(": ") 83 .Append(job->ErrorString()); 84 fJobLog.Add(resultText); 85 } 86 } 87 88 89 BString 90 JobStateListener::GetJobLog() 91 { 92 return fJobLog.Join("\n"); 93 } 94 95 96 TaskLooper::TaskLooper(const BMessenger& target) 97 : 98 BLooper("TaskLooper"), 99 fReplyTarget(target) 100 { 101 Run(); 102 fMessenger.SetTo(this); 103 } 104 105 106 bool 107 TaskLooper::QuitRequested() 108 { 109 return MessageQueue()->IsEmpty(); 110 } 111 112 113 void 114 TaskLooper::MessageReceived(BMessage* message) 115 { 116 switch (message->what) 117 { 118 case DO_TASK: 119 { 120 RepoRow* rowItem; 121 status_t result = message->FindPointer(key_rowptr, (void**)&rowItem); 122 if (result == B_OK) { 123 // Check to make sure there isn't already an existing task for this 124 int16 queueCount = fTaskQueue.CountItems(); 125 for (int16 index = 0; index<queueCount; index++) { 126 Task* task = fTaskQueue.ItemAt(index); 127 if (rowItem == task->rowItem) 128 break; 129 } 130 131 // Initialize task 132 Task* newTask = new Task(); 133 newTask->rowItem = rowItem; 134 newTask->name = rowItem->Name(); 135 newTask->resultName = newTask->name; 136 if (rowItem->IsEnabled()) { 137 newTask->taskType = DISABLE_REPO; 138 newTask->taskParam = newTask->name; 139 } else { 140 newTask->taskType = ENABLE_REPO; 141 newTask->taskParam = rowItem->Url(); 142 } 143 newTask->owner = this; 144 newTask->fTimer = NULL; 145 146 // Add to queue and start 147 fTaskQueue.AddItem(newTask); 148 BString threadName(newTask->taskType == ENABLE_REPO ? 149 "enable_task" : "disable_task"); 150 newTask->threadId = spawn_thread(_DoTask, threadName.String(), 151 B_NORMAL_PRIORITY, (void*)newTask); 152 status_t threadResult; 153 if (newTask->threadId < B_OK) 154 threadResult = B_ERROR; 155 else { 156 threadResult = resume_thread(newTask->threadId); 157 if (threadResult == B_OK) { 158 newTask->fTimer = new TaskTimer(fMessenger, newTask); 159 newTask->fTimer->Start(newTask->name); 160 // Reply to view 161 BMessage reply(*message); 162 reply.what = TASK_STARTED; 163 reply.AddInt16(key_count, fTaskQueue.CountItems()); 164 fReplyTarget.SendMessage(&reply); 165 } else 166 kill_thread(newTask->threadId); 167 } 168 if (threadResult != B_OK) { 169 _RemoveAndDelete(newTask); 170 } 171 } 172 break; 173 } 174 175 case TASK_COMPLETED: 176 case TASK_COMPLETED_WITH_ERRORS: 177 case TASK_CANCELED: 178 { 179 Task* task; 180 status_t result = message->FindPointer(key_taskptr, (void**)&task); 181 if (result == B_OK && fTaskQueue.HasItem(task)) { 182 task->fTimer->Stop(task->resultName); 183 BMessage reply(message->what); 184 reply.AddInt16(key_count, fTaskQueue.CountItems()-1); 185 reply.AddPointer(key_rowptr, task->rowItem); 186 if (message->what == TASK_COMPLETED_WITH_ERRORS) 187 reply.AddString(key_details, task->resultErrorDetails); 188 if (task->taskType == ENABLE_REPO 189 && task->name.Compare(task->resultName) != 0) { 190 reply.AddString(key_name, task->resultName); 191 } 192 fReplyTarget.SendMessage(&reply); 193 _RemoveAndDelete(task); 194 } 195 break; 196 } 197 198 case TASK_KILL_REQUEST: 199 { 200 Task* task; 201 status_t result = message->FindPointer(key_taskptr, (void**)&task); 202 if (result == B_OK && fTaskQueue.HasItem(task)) { 203 kill_thread(task->threadId); 204 BMessage reply(TASK_CANCELED); 205 reply.AddInt16(key_count, fTaskQueue.CountItems()-1); 206 reply.AddPointer(key_rowptr, task->rowItem); 207 fReplyTarget.SendMessage(&reply); 208 _RemoveAndDelete(task); 209 } 210 break; 211 } 212 } 213 } 214 215 216 void 217 TaskLooper::_RemoveAndDelete(Task* task) 218 { 219 fTaskQueue.RemoveItem(task); 220 if (task->fTimer) { 221 task->fTimer->Lock(); 222 task->fTimer->Quit(); 223 task->fTimer = NULL; 224 } 225 delete task; 226 } 227 228 229 status_t 230 TaskLooper::_DoTask(void* data) 231 { 232 Task* task = (Task*)data; 233 BString errorDetails, repoName(""); 234 status_t returnResult = B_OK; 235 DecisionProvider decisionProvider; 236 JobStateListener listener; 237 switch (task->taskType) 238 { 239 case DISABLE_REPO: 240 { 241 BString nameParam(task->taskParam); 242 BPackageKit::BContext context(decisionProvider, listener); 243 BPackageKit::DropRepositoryRequest dropRequest(context, nameParam); 244 status_t result = dropRequest.Process(); 245 if (result != B_OK) { 246 returnResult = result; 247 if (result != B_CANCELED) { 248 errorDetails.Append(B_TRANSLATE_COMMENT("There was an " 249 "error disabling the repository %name%", 250 "Error message, do not translate %name%")); 251 BString nameString("\""); 252 nameString.Append(nameParam).Append("\""); 253 errorDetails.ReplaceFirst("%name%", nameString); 254 _AppendErrorDetails(errorDetails, &listener); 255 } 256 } 257 break; 258 } 259 260 case ENABLE_REPO: 261 { 262 BString urlParam(task->taskParam); 263 BPackageKit::BContext context(decisionProvider, listener); 264 // Add repository 265 bool asUserRepository = false; 266 // TODO does this ever change? 267 BPackageKit::AddRepositoryRequest addRequest(context, urlParam, 268 asUserRepository); 269 status_t result = addRequest.Process(); 270 if (result != B_OK) { 271 returnResult = result; 272 if (result != B_CANCELED) { 273 errorDetails.Append(B_TRANSLATE_COMMENT("There was an " 274 "error enabling the repository %url%", 275 "Error message, do not translate %url%")); 276 errorDetails.ReplaceFirst("%url%", urlParam); 277 _AppendErrorDetails(errorDetails, &listener); 278 } 279 break; 280 } 281 // Continue on to refresh repo cache 282 repoName = addRequest.RepositoryName(); 283 BPackageKit::BPackageRoster roster; 284 BPackageKit::BRepositoryConfig repoConfig; 285 roster.GetRepositoryConfig(repoName, &repoConfig); 286 BPackageKit::BRefreshRepositoryRequest refreshRequest(context, 287 repoConfig); 288 result = refreshRequest.Process(); 289 if (result != B_OK) { 290 returnResult = result; 291 if (result != B_CANCELED) { 292 errorDetails.Append(B_TRANSLATE_COMMENT("There was an " 293 "error refreshing the repository cache for %name%", 294 "Error message, do not translate %name%")); 295 BString nameString("\""); 296 nameString.Append(repoName).Append("\""); 297 errorDetails.ReplaceFirst("%name%", nameString); 298 _AppendErrorDetails(errorDetails, &listener); 299 } 300 } 301 break; 302 } 303 } 304 // Report completion status 305 BMessage reply; 306 if (returnResult == B_OK) { 307 reply.what = TASK_COMPLETED; 308 // Add the repo name if we need to update the list row value 309 if (task->taskType == ENABLE_REPO) 310 task->resultName = repoName; 311 } else if (returnResult == B_CANCELED) 312 reply.what = TASK_CANCELED; 313 else { 314 reply.what = TASK_COMPLETED_WITH_ERRORS; 315 task->resultErrorDetails = errorDetails; 316 if (task->taskType == ENABLE_REPO) 317 task->resultName = repoName; 318 } 319 reply.AddPointer(key_taskptr, task); 320 task->owner->PostMessage(&reply); 321 #if DEBUGTASK 322 if (returnResult == B_OK || returnResult == B_CANCELED) { 323 BString degubDetails("Debug info:\n"); 324 degubDetails.Append(listener.GetJobLog()); 325 (new BAlert("debug", degubDetails, "OK"))->Go(NULL); 326 } 327 #endif // DEBUGTASK 328 return 0; 329 } 330 331 332 void 333 TaskLooper::_AppendErrorDetails(BString& details, JobStateListener* listener) 334 { 335 details.Append("\n\n").Append(kDetailsText).Append(":\n"); 336 details.Append(listener->GetJobLog()); 337 } 338