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