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 <LayoutBuilder.h>
15#include <LayoutUtils.h>
16#include <OutlineListView.h>
17#include <Path.h>
18#include <ScrollBar.h>
19#include <ScrollView.h>
20#include <StringFormat.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
29using namespace BPackageKit;
30
31using BPackageKit::BHPKG::BNoErrorOutput;
32using BPackageKit::BHPKG::BPackageContentHandler;
33using BPackageKit::BHPKG::BPackageEntry;
34using BPackageKit::BHPKG::BPackageEntryAttribute;
35using BPackageKit::BHPKG::BPackageInfoAttributeValue;
36using 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.
44class CustomScrollView : public BScrollView {
45public:
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
79class PackageEntryItem : public BStringItem {
80public:
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
96private:
97	BString fPath;
98};
99
100
101// #pragma mark - PackageContentOutliner
102
103
104class PackageContentOutliner : public BPackageContentHandler {
105public:
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
221private:
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
239PackageContentsView::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
260PackageContentsView::~PackageContentsView()
261{
262	Clear();
263
264	delete_sem(fContentPopulatorSem);
265	if (fContentPopulator >= 0)
266		wait_for_thread(fContentPopulator, NULL);
267}
268
269
270void
271PackageContentsView::AttachedToWindow()
272{
273	BView::AttachedToWindow();
274}
275
276
277void
278PackageContentsView::AllAttached()
279{
280	BView::AllAttached();
281}
282
283
284void
285PackageContentsView::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
310void
311PackageContentsView::Clear()
312{
313	{
314		BAutolock lock(&fPackageLock);
315		fPackage.Unset();
316	}
317
318	fContentListView->MakeEmpty();
319}
320
321
322// #pragma mark - private
323
324
325void
326PackageContentsView::_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
340PackageContentsView::_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
367bool
368PackageContentsView::_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
424int32
425PackageContentsView::_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