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
26static int32
27install_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
39PackageInstall::PackageInstall(PackageView* parent)
40	:
41	fParent(parent),
42	fThreadId(-1),
43	fCurrentScript(NULL)
44{
45}
46
47
48PackageInstall::~PackageInstall()
49{
50}
51
52
53status_t
54PackageInstall::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
72void
73PackageInstall::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
98void
99PackageInstall::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
112static inline BString
113get_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
126uint32
127PackageInstall::_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