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 34using namespace BPackageKit; 35 36using BPackageKit::BHPKG::BNoErrorOutput; 37using BPackageKit::BHPKG::BPackageContentHandler; 38using BPackageKit::BHPKG::BPackageEntry; 39using BPackageKit::BHPKG::BPackageEntryAttribute; 40using BPackageKit::BHPKG::BPackageInfoAttributeValue; 41using BPackageKit::BHPKG::BPackageReader; 42 43#undef B_TRANSLATION_CONTEXT 44#define B_TRANSLATION_CONTEXT "PackageContentsView" 45 46 47// #pragma mark - PackageEntryItem 48 49 50class PackageEntryItem : public BStringItem { 51public: 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 67private: 68 BString fPath; 69}; 70 71 72// #pragma mark - PackageContentOutliner 73 74 75class PackageContentOutliner : public BPackageContentHandler { 76public: 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 181private: 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 199PackageContentsView::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 220PackageContentsView::~PackageContentsView() 221{ 222 Clear(); 223 224 delete_sem(fContentPopulatorSem); 225 if (fContentPopulator >= 0) 226 wait_for_thread(fContentPopulator, NULL); 227} 228 229 230void 231PackageContentsView::AttachedToWindow() 232{ 233 BView::AttachedToWindow(); 234} 235 236 237void 238PackageContentsView::AllAttached() 239{ 240 BView::AllAttached(); 241} 242 243 244void 245PackageContentsView::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 274void 275PackageContentsView::Clear() 276{ 277 { 278 BAutolock lock(&fPackageLock); 279 fPackage.Unset(); 280 } 281 282 fContentListView->MakeEmpty(); 283} 284 285 286// #pragma mark - private 287 288 289void 290PackageContentsView::_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 304PackageContentsView::_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 331bool 332PackageContentsView::_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