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