xref: /haiku/src/apps/haikudepot/ui/PackageContentsView.cpp (revision d0ac609964842f8cdb6d54b3c539c6c15293e172)
1 /*
2  * Copyright 2015, TigerKid001.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "PackageContentsView.h"
7 
8 #include <algorithm>
9 #include <stdio.h>
10 
11 #include <Autolock.h>
12 #include <Catalog.h>
13 #include <FindDirectory.h>
14 #include <MessageFormat.h>
15 #include <LayoutBuilder.h>
16 #include <LayoutUtils.h>
17 #include <OutlineListView.h>
18 #include <Path.h>
19 #include <ScrollBar.h>
20 #include <ScrollView.h>
21 #include <StringItem.h>
22 
23 #include <package/PackageDefs.h>
24 #include <package/hpkg/NoErrorOutput.h>
25 #include <package/hpkg/PackageContentHandler.h>
26 #include <package/hpkg/PackageEntry.h>
27 #include <package/hpkg/PackageReader.h>
28 
29 using namespace BPackageKit;
30 
31 using BPackageKit::BHPKG::BNoErrorOutput;
32 using BPackageKit::BHPKG::BPackageContentHandler;
33 using BPackageKit::BHPKG::BPackageEntry;
34 using BPackageKit::BHPKG::BPackageEntryAttribute;
35 using BPackageKit::BHPKG::BPackageInfoAttributeValue;
36 using BPackageKit::BHPKG::BPackageReader;
37 
38 #undef B_TRANSLATION_CONTEXT
39 #define B_TRANSLATION_CONTEXT "PackageContentsView"
40 
41 
42 //! Layouts the scrollbar so it looks nice with no border and the document
43 // window look.
44 class CustomScrollView : public BScrollView {
45 public:
46 	CustomScrollView(const char* name, BView* target)
47 		:
48 		BScrollView(name, target, 0, false, true, B_NO_BORDER)
49 	{
50 	}
51 
52 	virtual void DoLayout()
53 	{
54 		BRect innerFrame = Bounds();
55 		innerFrame.right -= B_V_SCROLL_BAR_WIDTH + 1;
56 
57 		BView* target = Target();
58 		if (target != NULL) {
59 			Target()->MoveTo(innerFrame.left, innerFrame.top);
60 			Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
61 		}
62 
63 		BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
64 		if (scrollBar != NULL) {
65 			BRect rect = innerFrame;
66 			rect.left = rect.right + 1;
67 			rect.right = rect.left + B_V_SCROLL_BAR_WIDTH;
68 			rect.bottom -= B_H_SCROLL_BAR_HEIGHT;
69 
70 			scrollBar->MoveTo(rect.left, rect.top);
71 			scrollBar->ResizeTo(rect.Width(), rect.Height());
72 		}
73 	}
74 };
75 
76 // #pragma mark - PackageEntryItem
77 
78 
79 class PackageEntryItem : public BStringItem {
80 public:
81 	PackageEntryItem(const BPackageEntry* entry, const BString& path)
82 		:
83 		BStringItem(entry->Name()),
84 		fPath(path)
85 	{
86 		if (fPath.Length() > 0)
87 			fPath.Append("/");
88 		fPath.Append(entry->Name());
89 	}
90 
91 	inline const BString& EntryPath() const
92 	{
93 		return fPath;
94 	}
95 
96 private:
97 	BString fPath;
98 };
99 
100 
101 // #pragma mark - PackageContentOutliner
102 
103 
104 class PackageContentOutliner : public BPackageContentHandler {
105 public:
106 	PackageContentOutliner(BOutlineListView* listView,
107 			const PackageInfo* packageInfo,
108 			BLocker& packageLock, PackageInfoRef& packageInfoRef)
109 		:
110 		fListView(listView),
111 		fLastParentEntry(NULL),
112 		fLastParentItem(NULL),
113 		fLastEntry(NULL),
114 		fLastItem(NULL),
115 
116 		fPackageInfoToPopulate(packageInfo),
117 		fPackageLock(packageLock),
118 		fPackageInfoRef(packageInfoRef)
119 	{
120 	}
121 
122 	virtual status_t HandleEntry(BPackageEntry* entry)
123 	{
124 //		printf("HandleEntry(%s/%s)\n",
125 //			entry->Parent() != NULL ? entry->Parent()->Name() : "NULL",
126 //			entry->Name());
127 
128 		if (fListView->LockLooperWithTimeout(1000000) != B_OK)
129 			return B_ERROR;
130 
131 		// Check if we are still supposed to popuplate the list
132 		if (fPackageInfoRef.Get() != fPackageInfoToPopulate) {
133 //			printf("stopping package content population\n");
134 			fListView->UnlockLooper();
135 			return B_ERROR;
136 		}
137 
138 		BString path;
139 		const BPackageEntry* parent = entry->Parent();
140 		while (parent != NULL) {
141 			if (path.Length() > 0)
142 				path.Prepend("/");
143 			path.Prepend(parent->Name());
144 			parent = parent->Parent();
145 		}
146 
147 		PackageEntryItem* item = new PackageEntryItem(entry, path);
148 
149 		if (entry->Parent() == NULL) {
150 //			printf("  adding root entry\n");
151 			fListView->AddItem(item);
152 			fLastParentEntry = NULL;
153 			fLastParentItem = NULL;
154 		} else if (entry->Parent() == fLastEntry) {
155 //			printf("  adding to last entry %s\n", fLastEntry->Name());
156 			fListView->AddUnder(item, fLastItem);
157 			fLastParentEntry = fLastEntry;
158 			fLastParentItem = fLastItem;
159 		} else if (entry->Parent() == fLastParentEntry) {
160 //			printf("  adding to last parent %s\n", fLastParentEntry->Name());
161 			fListView->AddUnder(item, fLastParentItem);
162 		} else {
163 			// Not the last parent entry, need to search for the parent
164 			// among the already added list items.
165 			bool foundParent = false;
166 			for (int32 i = 0; i < fListView->FullListCountItems(); i++) {
167 				PackageEntryItem* listItem
168 					= dynamic_cast<PackageEntryItem*>(
169 						fListView->FullListItemAt(i));
170 				if (listItem == NULL)
171 					continue;
172 				if (listItem->EntryPath() == path) {
173 					fLastParentEntry = entry->Parent();
174 					fLastParentItem = listItem;
175 //					printf("  found parent %s\n", listItem->Text());
176 					fListView->AddUnder(item, listItem);
177 					foundParent = true;
178 					break;
179 				}
180 			}
181 			if (!foundParent) {
182 				// NOTE: Should not happen. Just add this entry at the
183 				// root level.
184 //				printf("Did not find parent entry for %s (%s)!\n",
185 //					entry->Name(), entry->Parent()->Name());
186 				fListView->AddItem(item);
187 				fLastParentEntry = NULL;
188 				fLastParentItem = NULL;
189 			}
190 		}
191 
192 		fLastEntry = entry;
193 		fLastItem = item;
194 
195 		fListView->UnlockLooper();
196 
197 		return B_OK;
198 	}
199 
200 	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
201 		BPackageEntryAttribute* attribute)
202 	{
203 		return B_OK;
204 	}
205 
206 	virtual status_t HandleEntryDone(BPackageEntry* entry)
207 	{
208 		return B_OK;
209 	}
210 
211 	virtual status_t HandlePackageAttribute(
212 		const BPackageInfoAttributeValue& value)
213 	{
214 		return B_OK;
215 	}
216 
217 	virtual void HandleErrorOccurred()
218 	{
219 	}
220 
221 private:
222 	BOutlineListView*		fListView;
223 
224 	const BPackageEntry*	fLastParentEntry;
225 	PackageEntryItem*		fLastParentItem;
226 
227 	const BPackageEntry*	fLastEntry;
228 	PackageEntryItem*		fLastItem;
229 
230 	const PackageInfo*		fPackageInfoToPopulate;
231 	BLocker&				fPackageLock;
232 	PackageInfoRef&			fPackageInfoRef;
233 };
234 
235 
236 // #pragma mark - PackageContentView
237 
238 
239 PackageContentsView::PackageContentsView(const char* name)
240 	:
241 	BView("package_contents_view", B_WILL_DRAW),
242 	fPackageLock("package contents populator lock"),
243 	fLastPackageState(NONE)
244 {
245 	fContentListView = new BOutlineListView("content list view",
246 		B_SINGLE_SELECTION_LIST);
247 
248 	BScrollView* scrollView = new CustomScrollView("contents scroll view",
249 		fContentListView);
250 
251 	BLayoutBuilder::Group<>(this)
252 		.Add(scrollView, 1.0f)
253 		.SetInsets(0.0f, -1.0f, -1.0f, -1.0f)
254 	;
255 
256 	_InitContentPopulator();
257 }
258 
259 
260 PackageContentsView::~PackageContentsView()
261 {
262 	Clear();
263 
264 	delete_sem(fContentPopulatorSem);
265 	if (fContentPopulator >= 0)
266 		wait_for_thread(fContentPopulator, NULL);
267 }
268 
269 
270 void
271 PackageContentsView::AttachedToWindow()
272 {
273 	BView::AttachedToWindow();
274 }
275 
276 
277 void
278 PackageContentsView::AllAttached()
279 {
280 	BView::AllAttached();
281 }
282 
283 
284 void
285 PackageContentsView::SetPackage(const PackageInfoRef& package)
286 {
287 	// When getting a ref to the same package, don't return when the
288 	// package state has changed, since in that case, we may now be able
289 	// to read contents where we previously could not. (For example, the
290 	// package has been installed.)
291 	if (fPackage == package
292 		&& (package.Get() == NULL || package->State() == fLastPackageState)) {
293 		return;
294 	}
295 
296 //	printf("PackageContentsView::SetPackage(%s)\n",
297 //		package.Get() != NULL ? package->Name().String() : "NULL");
298 
299 	Clear();
300 
301 	{
302 		BAutolock lock(&fPackageLock);
303 		fPackage = package;
304 		fLastPackageState = package.Get() != NULL ? package->State() : NONE;
305 	}
306 	release_sem_etc(fContentPopulatorSem, 1, 0);
307 }
308 
309 
310 void
311 PackageContentsView::Clear()
312 {
313 	{
314 		BAutolock lock(&fPackageLock);
315 		fPackage.Unset();
316 	}
317 
318 	fContentListView->MakeEmpty();
319 }
320 
321 
322 // #pragma mark - private
323 
324 
325 void
326 PackageContentsView::_InitContentPopulator()
327 {
328 	fContentPopulatorSem = create_sem(0, "PopulatePackageContents");
329 	if (fContentPopulatorSem >= 0) {
330 		fContentPopulator = spawn_thread(&_ContentPopulatorThread,
331 			"Package Contents Populator", B_NORMAL_PRIORITY, this);
332 		if (fContentPopulator >= 0)
333 			resume_thread(fContentPopulator);
334 	} else
335 		fContentPopulator = -1;
336 }
337 
338 
339 /*static*/ int32
340 PackageContentsView::_ContentPopulatorThread(void* arg)
341 {
342 	PackageContentsView* view = reinterpret_cast<PackageContentsView*>(arg);
343 
344 	while (acquire_sem(view->fContentPopulatorSem) == B_OK) {
345 		PackageInfoRef package;
346 		{
347 			BAutolock lock(&view->fPackageLock);
348 			package = view->fPackage;
349 		}
350 
351 		if (package.Get() != NULL) {
352 			if (!view->_PopuplatePackageContens(*package.Get())) {
353 				if (view->LockLooperWithTimeout(1000000) == B_OK) {
354 					view->fContentListView->AddItem(
355 						new BStringItem(B_TRANSLATE("<Package contents not "
356 							"available for remote packages>")));
357 					view->UnlockLooper();
358 				}
359 			}
360 		}
361 	}
362 
363 	return 0;
364 }
365 
366 
367 bool
368 PackageContentsView::_PopuplatePackageContens(const PackageInfo& package)
369 {
370 	BPath packagePath;
371 
372 	// Obtain path to the package file
373 	if (package.IsLocalFile()) {
374 		BString pathString = package.LocalFilePath();
375 		packagePath.SetTo(pathString.String());
376 	} else {
377 		int32 installLocation = _InstallLocation(package);
378 		if (installLocation == B_PACKAGE_INSTALLATION_LOCATION_SYSTEM) {
379 			if (find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &packagePath)
380 				!= B_OK) {
381 				return false;
382 			}
383 		} else if (installLocation == B_PACKAGE_INSTALLATION_LOCATION_HOME) {
384 			if (find_directory(B_USER_PACKAGES_DIRECTORY, &packagePath)
385 				!= B_OK) {
386 				return false;
387 			}
388 		} else {
389 			printf("PackageContentsView::_PopuplatePackageContens(): "
390 				"unknown install location");
391 			return false;
392 		}
393 
394 		packagePath.Append(package.FileName());
395 	}
396 
397 	// Setup a BPackageReader
398 	BNoErrorOutput errorOutput;
399 	BPackageReader reader(&errorOutput);
400 
401 	status_t status = reader.Init(packagePath.Path());
402 	if (status != B_OK) {
403 		printf("PackageContentsView::_PopuplatePackageContens(): "
404 			"failed to init BPackageReader(%s): %s\n",
405 			packagePath.Path(), strerror(status));
406 		return false;
407 	}
408 
409 	// Scan package contents and populate list
410 	PackageContentOutliner contentHandler(fContentListView, &package,
411 		fPackageLock, fPackage);
412 	status = reader.ParseContent(&contentHandler);
413 	if (status != B_OK) {
414 		printf("PackageContentsView::_PopuplatePackageContens(): "
415 			"failed parse package contents: %s\n", strerror(status));
416 		// NOTE: Do not return false, since it taken to mean this
417 		// is a remote package, but is it not, we simply want to stop
418 		// populating the contents early.
419 	}
420 	return true;
421 }
422 
423 
424 int32
425 PackageContentsView::_InstallLocation(const PackageInfo& package) const
426 {
427 	const PackageInstallationLocationSet& locations
428 		= package.InstallationLocations();
429 
430 	// If the package is already installed, return its first installed location
431 	if (locations.size() != 0)
432 		return *locations.begin();
433 
434 	return B_PACKAGE_INSTALLATION_LOCATION_SYSTEM;
435 }
436