1/*
2 * Copyright (c) 2007-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 "UninstallView.h"
11
12#include <stdio.h>
13#include <string.h>
14
15#include <Alert.h>
16#include <Box.h>
17#include <Button.h>
18#include <Catalog.h>
19#include <ControlLook.h>
20#include <Directory.h>
21#include <Entry.h>
22#include <File.h>
23#include <FilePanel.h>
24#include <FindDirectory.h>
25#include <LayoutBuilder.h>
26#include <ListView.h>
27#include <Locale.h>
28#include <NodeMonitor.h>
29#include <ScrollView.h>
30#include <SeparatorView.h>
31#include <String.h>
32#include <StringView.h>
33#include <SpaceLayoutItem.h>
34#include <TextView.h>
35
36#include "main.h"
37
38
39#undef B_TRANSLATION_CONTEXT
40#define B_TRANSLATION_CONTEXT "UninstallView"
41
42
43enum {
44	P_MSG_INSTALL = 'umin',
45	P_MSG_REMOVE = 'umrm',
46	P_MSG_SELECT
47};
48
49
50// TODO list:
51//	- B_ENTRY_MOVED
52//	- Right now the installed package info naming convention is the same
53//		as at SoftwareValet. Maybe there would be a better one?
54//	- Add a status window (reuse the one from PackageInstall)
55
56
57class UninstallView::InfoItem : public BStringItem {
58public:
59	InfoItem(const BString& name, const BString& version,
60			const char* filename, const node_ref& ref)
61		:
62		BStringItem(name.String()),
63		fName(name),
64		fVersion(version),
65		fNodeRef(ref)
66	{
67		if (fName.Length() == 0)
68			SetText(filename);
69	}
70
71	const char* GetName() { return fName.String(); }
72	const char* GetVersion() { return fVersion.String(); };
73	node_ref GetNodeRef() { return fNodeRef; };
74
75private:
76	BString		fName;
77	BString 	fVersion;
78	node_ref	fNodeRef;
79};
80
81
82
83
84UninstallView::UninstallView()
85	:
86	BGroupView(B_VERTICAL),
87	fOpenPanel(new BFilePanel(B_OPEN_PANEL))
88{
89	fNoPackageSelectedString = B_TRANSLATE("No package selected.");
90	_InitView();
91}
92
93
94UninstallView::~UninstallView()
95{
96	// Stop all node watching
97	stop_watching(this);
98}
99
100
101void
102UninstallView::AttachedToWindow()
103{
104	fAppList->SetTarget(this);
105	fInstallButton->SetTarget(this);
106	fRemoveButton->SetTarget(this);
107
108	_ReloadAppList();
109
110	// We loaded the list, but now let's set up a node watcher for the packages
111	// directory, so that we can update the list of installed packages in real
112	// time
113	_CachePathToPackages();
114	node_ref ref;
115	fWatcherRunning = false;
116	BDirectory dir(fToPackages.Path());
117	if (dir.InitCheck() != B_OK) {
118		// The packages/ directory obviously does not exist.
119		// Since this is the case, we need to watch for it to appear first
120
121		BPath path;
122		fToPackages.GetParent(&path);
123		if (dir.SetTo(path.Path()) != B_OK)
124			return;
125	} else
126		fWatcherRunning = true;
127
128	dir.GetNodeRef(&ref);
129
130	if (watch_node(&ref, B_WATCH_DIRECTORY, this) != B_OK) {
131		fWatcherRunning = false;
132		return;
133	}
134}
135
136
137void
138UninstallView::MessageReceived(BMessage* msg)
139{
140	switch (msg->what) {
141		case B_NODE_MONITOR:
142		{
143			int32 opcode;
144			if (msg->FindInt32("opcode", &opcode) != B_OK)
145				break;
146
147			fprintf(stderr, "Got an opcoded node monitor message\n");
148			if (opcode == B_ENTRY_CREATED) {
149				fprintf(stderr, "Created?...\n");
150				BString filename, name, version;
151				node_ref ref;
152				if (msg->FindString("name", &filename) != B_OK
153					|| msg->FindInt32("device", &ref.device) != B_OK
154					|| msg->FindInt64("node", &ref.node) != B_OK)
155					break;
156
157				// TODO: This obviously is a hack
158				// The node watcher informs the view a bit to early, and
159				// because of this the data of the node is not ready at this
160				// moment. For this reason, we must give the filesystem some
161				// time before continuing.
162				usleep(10000);
163
164				if (fWatcherRunning) {
165					_AddFile(filename.String(), ref);
166				} else {
167					// This most likely means we were waiting for
168					// the packages/ dir to appear
169					if (filename == "packages") {
170						if (watch_node(&ref, B_WATCH_DIRECTORY, this) == B_OK)
171							fWatcherRunning = true;
172					}
173				}
174			} else if (opcode == B_ENTRY_REMOVED) {
175				node_ref ref;
176				if (msg->FindInt32("device", &ref.device) != B_OK
177					|| msg->FindInt64("node", &ref.node) != B_OK)
178					break;
179
180				int32 i, count = fAppList->CountItems();
181				InfoItem* iter;
182				for (i = 0; i < count; i++) {
183					iter = static_cast<InfoItem *>(fAppList->ItemAt(i));
184					if (iter->GetNodeRef() == ref) {
185						if (i == fAppList->CurrentSelection())
186							fDescription->SetText(fNoPackageSelectedString);
187						fAppList->RemoveItem(i);
188						delete iter;
189					}
190				}
191			} else if (opcode == B_ENTRY_MOVED) {
192				ino_t from, to;
193				if (msg->FindInt64("from directory", &from) != B_OK
194					|| msg->FindInt64("to directory", &to) != B_OK)
195					break;
196
197				BDirectory packagesDir(fToPackages.Path());
198				node_ref ref;
199				packagesDir.GetNodeRef(&ref);
200
201				if (ref.node == to) {
202					// Package added
203					// TODO
204				} else if (ref.node == from) {
205					// Package removed
206					// TODO
207				}
208			}
209			break;
210		}
211		case P_MSG_SELECT:
212		{
213			fRemoveButton->SetEnabled(false);
214			fDescription->SetText(fNoPackageSelectedString);
215
216			int32 index = fAppList->CurrentSelection();
217			if (index < 0)
218				break;
219
220			fprintf(stderr, "Another debug message...\n");
221
222			InfoItem* item = dynamic_cast<InfoItem*>(fAppList->ItemAt(index));
223			if (!item)
224				break;
225
226			fprintf(stderr, "Uh: %s and %s\n", item->GetName(),
227				item->GetVersion());
228
229			if (fCurrentSelection.SetTo(item->GetName(),
230					item->GetVersion()) != B_OK)
231				break;
232
233			fRemoveButton->SetEnabled(true);
234			fDescription->SetText(fCurrentSelection.Description());
235			break;
236		}
237		case P_MSG_INSTALL:
238		{
239			fOpenPanel->Show();
240			break;
241		}
242		case P_MSG_REMOVE:
243		{
244			if (fCurrentSelection.InitCheck() != B_OK)
245				break;
246
247			int32 index = fAppList->CurrentSelection();
248			if (index < 0)
249				break;
250
251			BAlert* notify;
252			if (fCurrentSelection.Uninstall() == B_OK) {
253				BListItem* item = fAppList->RemoveItem(index);
254				delete item;
255
256				fDescription->SetText(fNoPackageSelectedString);
257
258				notify = new BAlert("removal_success",
259					B_TRANSLATE("The package you selected has been "
260					"successfully removed from your system."),
261					B_TRANSLATE("OK"));
262			} else {
263				notify = new BAlert("removal_failed",
264					B_TRANSLATE(
265					"The selected package was not removed from your system. "
266					"The given installed package information file might have "
267					"been corrupted."), B_TRANSLATE("OK"), NULL,
268					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
269			}
270			notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
271			notify->Go();
272			break;
273		}
274		default:
275			BView::MessageReceived(msg);
276			break;
277	}
278}
279
280
281void
282UninstallView::RefsReceived(BMessage* message)
283{
284	static_cast<PackageInstaller*>(be_app)->RefsReceived(message);
285}
286
287
288void
289UninstallView::_InitView()
290{
291	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
292
293	fAppList = new BListView("pkg_list", B_SINGLE_SELECTION_LIST);
294	fAppList->SetSelectionMessage(new BMessage(P_MSG_SELECT));
295	BScrollView* scrollView = new BScrollView("list_scroll", fAppList,
296		0, false, true, B_NO_BORDER);
297
298	BStringView* descriptionLabel = new BStringView("desc_label",
299		B_TRANSLATE("Package description"));
300	descriptionLabel->SetFont(be_bold_font);
301
302	fDescription = new BTextView("description", B_WILL_DRAW);
303	fDescription->MakeSelectable(false);
304	fDescription->MakeEditable(false);
305	fDescription->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
306	fDescription->SetText(fNoPackageSelectedString);
307
308	fInstallButton = new BButton("install", B_TRANSLATE("Install" B_UTF8_ELLIPSIS),
309		new BMessage(P_MSG_INSTALL));
310	fRemoveButton = new BButton("removal", B_TRANSLATE("Remove"),
311		new BMessage(P_MSG_REMOVE));
312	fRemoveButton->SetEnabled(false);
313
314	const float spacing = be_control_look->DefaultItemSpacing();
315
316	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
317		.Add(scrollView, 10)
318		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
319		.AddGroup(B_VERTICAL)
320			.SetInsets(spacing)
321			.AddGroup(B_HORIZONTAL, 0)
322				.Add(descriptionLabel)
323				.AddGlue()
324			.End()
325			.AddGroup(B_HORIZONTAL, 0)
326				.Add(BSpaceLayoutItem::CreateHorizontalStrut(10))
327				.Add(fDescription)
328			.End()
329		.End()
330		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
331		.AddGroup(B_HORIZONTAL)
332			.SetInsets(spacing)
333			.AddGlue()
334			.Add(fInstallButton)
335			.Add(fRemoveButton)
336		.End()
337	.End();
338}
339
340
341status_t
342UninstallView::_ReloadAppList()
343{
344	_ClearAppList();
345
346	if (fToPackages.InitCheck() != B_OK)
347		_CachePathToPackages();
348
349	BDirectory dir(fToPackages.Path());
350	status_t ret = dir.InitCheck();
351	if (ret != B_OK)
352		return ret;
353
354	BEntry iter;
355	while (dir.GetNextEntry(&iter) == B_OK) {
356		char filename[B_FILE_NAME_LENGTH];
357		if (iter.GetName(filename) != B_OK)
358			continue;
359
360		node_ref ref;
361		if (iter.GetNodeRef(&ref) != B_OK)
362			continue;
363
364		BString filenameString(filename);
365		if (!filenameString.IEndsWith(".pdb")) {
366			printf("Ignoring non-package '%s'\n", filename);
367			continue;
368		}
369
370		printf("Found package '%s'\n", filename);
371		_AddFile(filename, ref);
372	}
373
374	if (ret != B_ENTRY_NOT_FOUND)
375		return ret;
376
377	return B_OK;
378}
379
380
381void
382UninstallView::_ClearAppList()
383{
384	while (BListItem* item = fAppList->RemoveItem((int32)0))
385		delete item;
386}
387
388
389void
390UninstallView::_AddFile(const char* filename, const node_ref& ref)
391{
392	BString name;
393	status_t ret = info_get_package_name(filename, name);
394	if (ret != B_OK || name.Length() == 0)
395		fprintf(stderr, "Error extracting package name: %s\n", strerror(ret));
396	BString version;
397	ret = info_get_package_version(filename, version);
398	if (ret != B_OK || version.Length() == 0) {
399		fprintf(stderr, "Error extracting package version: %s\n",
400			strerror(ret));
401	}
402	fAppList->AddItem(new InfoItem(name, version, filename, ref));
403}
404
405
406void
407UninstallView::_CachePathToPackages()
408{
409	if (find_directory(B_USER_CONFIG_DIRECTORY, &fToPackages) != B_OK)
410		return;
411	if (fToPackages.Append(kPackagesDir) != B_OK)
412		return;
413}
414
415