xref: /haiku/src/preferences/repositories/TaskLooper.cpp (revision 856ecc7b19a4a9942cfe3010cab9bc3524870732)
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
JobStarted(BJob * job)43 JobStateListener::JobStarted(BJob* job)
44 {
45 	fJobLog.Add(job->Title());
46 }
47 
48 
49 void
JobSucceeded(BJob * job)50 JobStateListener::JobSucceeded(BJob* job)
51 {
52 	BString resultText(kLogResultIndicator);
53 	fJobLog.Add(resultText.Append(kCompletedText));
54 }
55 
56 
57 void
JobFailed(BJob * job)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
JobAborted(BJob * job)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
GetJobLog()90 JobStateListener::GetJobLog()
91 {
92 	return fJobLog.Join("\n");
93 }
94 
95 
TaskLooper(const BMessenger & target)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
QuitRequested()107 TaskLooper::QuitRequested()
108 {
109 	return MessageQueue()->IsEmpty();
110 }
111 
112 
113 void
MessageReceived(BMessage * message)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
_RemoveAndDelete(Task * task)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
_DoTask(void * data)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
_AppendErrorDetails(BString & details,JobStateListener * listener)333 TaskLooper::_AppendErrorDetails(BString& details, JobStateListener* listener)
334 {
335 	details.Append("\n\n").Append(kDetailsText).Append(":\n");
336 	details.Append(listener->GetJobLog());
337 }
338