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