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