xref: /haiku/src/apps/packageinstaller/UninstallView.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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 
43 enum {
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 
57 class UninstallView::InfoItem : public BStringItem {
58 public:
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 
75 private:
76 	BString		fName;
77 	BString 	fVersion;
78 	node_ref	fNodeRef;
79 };
80 
81 
82 
83 
84 UninstallView::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 
94 UninstallView::~UninstallView()
95 {
96 	// Stop all node watching
97 	stop_watching(this);
98 }
99 
100 
101 void
102 UninstallView::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 
137 void
138 UninstallView::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 
281 void
282 UninstallView::RefsReceived(BMessage* message)
283 {
284 	static_cast<PackageInstaller*>(be_app)->RefsReceived(message);
285 }
286 
287 
288 void
289 UninstallView::_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 
341 status_t
342 UninstallView::_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 
381 void
382 UninstallView::_ClearAppList()
383 {
384 	while (BListItem* item = fAppList->RemoveItem((int32)0))
385 		delete item;
386 }
387 
388 
389 void
390 UninstallView::_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 
406 void
407 UninstallView::_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