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