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