xref: /haiku/src/apps/packageinstaller/PackageInstall.cpp (revision 97dfeb96704e5dbc5bec32ad7b21379d0125e031)
1 /*
2  * Copyright (c) 2010, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Author:
6  *		Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7  */
8 
9 
10 #include "PackageInstall.h"
11 
12 #include "InstalledPackageInfo.h"
13 #include "PackageItem.h"
14 #include "PackageView.h"
15 
16 #include <Alert.h>
17 #include <Catalog.h>
18 #include <Locale.h>
19 #include <stdio.h>
20 
21 
22 #undef B_TRANSLATION_CONTEXT
23 #define B_TRANSLATION_CONTEXT "PackageInstall"
24 
25 
26 static int32
27 install_function(void* data)
28 {
29 	// TODO: Inform if already one thread is running
30 	if (data == NULL)
31 		return -1;
32 
33 	PackageInstall* install = static_cast<PackageInstall*>(data);
34 	install->Install();
35 	return 0;
36 }
37 
38 
39 PackageInstall::PackageInstall(PackageView* parent)
40 	:
41 	fParent(parent),
42 	fThreadId(-1),
43 	fCurrentScript(NULL)
44 {
45 }
46 
47 
48 PackageInstall::~PackageInstall()
49 {
50 }
51 
52 
53 status_t
54 PackageInstall::Start()
55 {
56 	status_t ret = B_OK;
57 
58 	fIdLocker.Lock();
59 	if (fThreadId > -1) {
60 		ret = B_BUSY;
61 	} else {
62 		fThreadId = spawn_thread(install_function, "install_package",
63 			B_NORMAL_PRIORITY, this);
64 		resume_thread(fThreadId);
65 	}
66 	fIdLocker.Unlock();
67 
68 	return ret;
69 }
70 
71 
72 void
73 PackageInstall::Stop()
74 {
75 	// TODO: Argh! No killing of threads!! That leaks resources which they
76 	// allocated. Rather inform them they need to quit, which they do at the
77 	// next convenient time, then use wait_for_thread() here.
78 	fIdLocker.Lock();
79 	if (fThreadId > -1) {
80 		kill_thread(fThreadId);
81 		fThreadId = -1;
82 	}
83 	fIdLocker.Unlock();
84 
85 	fCurrentScriptLocker.Lock();
86 	if (fCurrentScript != NULL) {
87 		thread_id id = fCurrentScript->GetThreadId();
88 		if (id > -1) {
89 			fCurrentScript->SetThreadId(-1);
90 			kill_thread(id);
91 		}
92 		fCurrentScript = NULL;
93 	}
94 	fCurrentScriptLocker.Unlock();
95 }
96 
97 
98 void
99 PackageInstall::Install()
100 {
101 	// A message sending wrapper around _Install()
102 	uint32 code = _Install();
103 
104 	BMessenger messenger(fParent);
105 	if (messenger.IsValid()) {
106 		BMessage message(code);
107 		messenger.SendMessage(&message);
108 	}
109 }
110 
111 
112 static inline BString
113 get_item_progress_string(uint32 index, uint32 total)
114 {
115 	BString label(B_TRANSLATE("%index% of %total%"));
116 	BString indexString;
117 	indexString << (index + 1);
118 	BString totalString;
119 	totalString << total;
120 	label.ReplaceAll("%index%", indexString);
121 	label.ReplaceAll("%total%", totalString);
122 	return label;
123 }
124 
125 
126 uint32
127 PackageInstall::_Install()
128 {
129 	PackageInfo* info = fParent->GetPackageInfo();
130 	pkg_profile* type = static_cast<pkg_profile*>(info->GetProfile(
131 		fParent->CurrentType()));
132 	uint32 n = type->items.CountItems();
133 	uint32 m = info->GetScriptCount();
134 
135 	PackageStatus* progress = fParent->StatusWindow();
136 	progress->Reset(n + m + 5);
137 
138 	progress->StageStep(1, B_TRANSLATE("Preparing package"));
139 
140 	InstalledPackageInfo packageInfo(info->GetName(), info->GetVersion());
141 
142 	status_t err = packageInfo.InitCheck();
143 	if (err == B_OK) {
144 		// The package is already installed, inform the user
145 		BAlert* reinstall = new BAlert("reinstall",
146 			B_TRANSLATE("The given package seems to be already installed on "
147 				"your system. Would you like to uninstall the existing one "
148 				"and continue the installation?"),
149 			B_TRANSLATE("Continue"),
150 			B_TRANSLATE("Abort"));
151 		reinstall->SetShortcut(1, B_ESCAPE);
152 
153 		if (reinstall->Go() == 0) {
154 			// Uninstall the package
155 			err = packageInfo.Uninstall();
156 			if (err != B_OK) {
157 				fprintf(stderr, "Error uninstalling previously installed "
158 					"package: %s\n", strerror(err));
159 				// Ignore error
160 			}
161 
162 			err = packageInfo.SetTo(info->GetName(), info->GetVersion(), true);
163 			if (err != B_OK) {
164 				fprintf(stderr, "Error marking installation of package: "
165 					"%s\n", strerror(err));
166 				return P_MSG_I_ERROR;
167 			}
168 		} else {
169 			// Abort the installation
170 			return P_MSG_I_ABORT;
171 		}
172 	} else if (err == B_ENTRY_NOT_FOUND) {
173 		err = packageInfo.SetTo(info->GetName(), info->GetVersion(), true);
174 		if (err != B_OK) {
175 				fprintf(stderr, "Error marking installation of package: "
176 					"%s\n", strerror(err));
177 			return P_MSG_I_ERROR;
178 		}
179 	} else if (progress->Stopped()) {
180 		return P_MSG_I_ABORT;
181 	} else {
182 		fprintf(stderr, "returning on error\n");
183 		return P_MSG_I_ERROR;
184 	}
185 
186 	progress->StageStep(1, B_TRANSLATE("Installing files and folders"));
187 
188 	// Install files and directories
189 
190 	packageInfo.SetName(info->GetName());
191 	// TODO: Here's a small problem, since right now it's not quite sure
192 	//		which description is really used as such. The one displayed on
193 	//		the installer is mostly package installation description, but
194 	//		most people use it for describing the application in more detail
195 	//		then in the short description.
196 	//		For now, we'll use the short description if possible.
197 	BString description = info->GetShortDescription();
198 	if (description.Length() <= 0)
199 		description = info->GetDescription();
200 	packageInfo.SetDescription(description.String());
201 	packageInfo.SetSpaceNeeded(type->space_needed);
202 
203 	fItemExistsPolicy = P_EXISTS_NONE;
204 
205 	const char* installPath = fParent->CurrentPath()->Path();
206 	for (uint32 i = 0; i < n; i++) {
207 		ItemState state(fItemExistsPolicy);
208 		PackageItem* item = static_cast<PackageItem*>(type->items.ItemAt(i));
209 
210 		err = item->DoInstall(installPath, &state);
211 		if (err == B_FILE_EXISTS) {
212 			// Writing to path failed because path already exists - ask the user
213 			// what to do and retry the writing process
214 			int32 choice = fParent->ItemExists(*item, state.destination,
215 				fItemExistsPolicy);
216 			if (choice != P_EXISTS_ABORT) {
217 				state.policy = choice;
218 				err = item->DoInstall(installPath, &state);
219 			}
220 		}
221 
222 		if (err != B_OK) {
223 			fprintf(stderr, "Error '%s' while writing path\n", strerror(err));
224 			return P_MSG_I_ERROR;
225 		}
226 
227 		if (progress->Stopped())
228 			return P_MSG_I_ABORT;
229 
230 		// Update progress
231 		progress->StageStep(1, NULL, get_item_progress_string(i, n).String());
232 
233 		// Mark installed item in packageInfo
234 		packageInfo.AddItem(state.destination.Path());
235 	}
236 
237 	progress->StageStep(1, B_TRANSLATE("Running post-installation scripts"),
238 		 "");
239 
240 	// Run all scripts
241 	// TODO: Change current working directory to installation location!
242 	for (uint32 i = 0; i < m; i++) {
243 		PackageScript* script = info->GetScript(i);
244 
245 		fCurrentScriptLocker.Lock();
246 		fCurrentScript = script;
247 
248 		status_t status = script->DoInstall(installPath);
249 		if (status != B_OK) {
250 			fprintf(stderr, "Error while running script: %s\n",
251 				strerror(status));
252 			fCurrentScriptLocker.Unlock();
253 			return P_MSG_I_ERROR;
254 		}
255 		fCurrentScriptLocker.Unlock();
256 
257 		wait_for_thread(script->GetThreadId(), &status);
258 
259 		fCurrentScriptLocker.Lock();
260 		script->SetThreadId(-1);
261 		fCurrentScript = NULL;
262 		fCurrentScriptLocker.Unlock();
263 
264 		if (progress->Stopped())
265 			return P_MSG_I_ABORT;
266 
267 		progress->StageStep(1, NULL, get_item_progress_string(i, m).String());
268 	}
269 
270 	progress->StageStep(1, B_TRANSLATE("Finishing installation"), "");
271 
272 	err = packageInfo.Save();
273 	if (err != B_OK)
274 		return P_MSG_I_ERROR;
275 
276 	progress->StageStep(1, B_TRANSLATE("Done"));
277 
278 	// Inform our parent that we finished
279 	return P_MSG_I_FINISHED;
280 }
281 
282