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