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:
PackageEntryItem(const BPackageEntry * entry,const BString & path)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
EntryPath() const63 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:
PackageContentOutliner(BOutlineListView * listView,const PackageInfo * packageInfo,BLocker & packageLock,PackageInfoRef & packageInfoRef)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
HandleEntry(BPackageEntry * entry)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
HandleEntryAttribute(BPackageEntry * entry,BPackageEntryAttribute * attribute)161 virtual status_t HandleEntryAttribute(BPackageEntry* entry,
162 BPackageEntryAttribute* attribute)
163 {
164 return B_OK;
165 }
166
HandleEntryDone(BPackageEntry * entry)167 virtual status_t HandleEntryDone(BPackageEntry* entry)
168 {
169 return B_OK;
170 }
171
HandlePackageAttribute(const BPackageInfoAttributeValue & value)172 virtual status_t HandlePackageAttribute(
173 const BPackageInfoAttributeValue& value)
174 {
175 return B_OK;
176 }
177
HandleErrorOccurred()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
PackageContentsView(const char * name)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
~PackageContentsView()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
AttachedToWindow()232 PackageContentsView::AttachedToWindow()
233 {
234 BView::AttachedToWindow();
235 }
236
237
238 void
AllAttached()239 PackageContentsView::AllAttached()
240 {
241 BView::AllAttached();
242 }
243
244
245 void
SetPackage(const PackageInfoRef & package)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
Clear()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
_InitContentPopulator()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
_ContentPopulatorThread(void * arg)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
_PopulatePackageContents(const PackageInfoRef & package)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