/* * Copyright 2015, TigerKid001. * Copyright 2020-2022, Andrew Lindesay * All rights reserved. Distributed under the terms of the MIT License. */ #include "PackageContentsView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GeneralContentScrollView.h" #include "Logger.h" #include "PackageUtils.h" #include #include #include #include #include using namespace BPackageKit; using BPackageKit::BHPKG::BNoErrorOutput; using BPackageKit::BHPKG::BPackageContentHandler; using BPackageKit::BHPKG::BPackageEntry; using BPackageKit::BHPKG::BPackageEntryAttribute; using BPackageKit::BHPKG::BPackageInfoAttributeValue; using BPackageKit::BHPKG::BPackageReader; #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "PackageContentsView" // #pragma mark - PackageEntryItem class PackageEntryItem : public BStringItem { public: PackageEntryItem(const BPackageEntry* entry, const BString& path) : BStringItem(entry->Name()), fPath(path) { if (fPath.Length() > 0) fPath.Append("/"); fPath.Append(entry->Name()); } inline const BString& EntryPath() const { return fPath; } private: BString fPath; }; // #pragma mark - PackageContentOutliner class PackageContentOutliner : public BPackageContentHandler { public: PackageContentOutliner(BOutlineListView* listView, const PackageInfo* packageInfo, BLocker& packageLock, PackageInfoRef& packageInfoRef) : fListView(listView), fLastParentEntry(NULL), fLastParentItem(NULL), fLastEntry(NULL), fLastItem(NULL), fPackageInfoToPopulate(packageInfo), fPackageLock(packageLock), fPackageInfoRef(packageInfoRef) { } virtual status_t HandleEntry(BPackageEntry* entry) { if (fListView->LockLooperWithTimeout(1000000) != B_OK) return B_ERROR; // Check if we are still supposed to popuplate the list if (fPackageInfoRef.Get() != fPackageInfoToPopulate) { fListView->UnlockLooper(); return B_ERROR; } BString path; const BPackageEntry* parent = entry->Parent(); while (parent != NULL) { if (path.Length() > 0) path.Prepend("/"); path.Prepend(parent->Name()); parent = parent->Parent(); } PackageEntryItem* item = new PackageEntryItem(entry, path); if (entry->Parent() == NULL) { fListView->AddItem(item); fLastParentEntry = NULL; fLastParentItem = NULL; } else if (entry->Parent() == fLastEntry) { fListView->AddUnder(item, fLastItem); fLastParentEntry = fLastEntry; fLastParentItem = fLastItem; } else if (entry->Parent() == fLastParentEntry) { fListView->AddUnder(item, fLastParentItem); } else { // Not the last parent entry, need to search for the parent // among the already added list items. bool foundParent = false; for (int32 i = 0; i < fListView->FullListCountItems(); i++) { PackageEntryItem* listItem = dynamic_cast( fListView->FullListItemAt(i)); if (listItem == NULL) continue; if (listItem->EntryPath() == path) { fLastParentEntry = entry->Parent(); fLastParentItem = listItem; fListView->AddUnder(item, listItem); foundParent = true; break; } } if (!foundParent) { // NOTE: Should not happen. Just add this entry at the // root level. fListView->AddItem(item); fLastParentEntry = NULL; fLastParentItem = NULL; } } fLastEntry = entry; fLastItem = item; fListView->UnlockLooper(); return B_OK; } virtual status_t HandleEntryAttribute(BPackageEntry* entry, BPackageEntryAttribute* attribute) { return B_OK; } virtual status_t HandleEntryDone(BPackageEntry* entry) { return B_OK; } virtual status_t HandlePackageAttribute( const BPackageInfoAttributeValue& value) { return B_OK; } virtual void HandleErrorOccurred() { } private: BOutlineListView* fListView; const BPackageEntry* fLastParentEntry; PackageEntryItem* fLastParentItem; const BPackageEntry* fLastEntry; PackageEntryItem* fLastItem; const PackageInfo* fPackageInfoToPopulate; BLocker& fPackageLock; PackageInfoRef& fPackageInfoRef; }; // #pragma mark - PackageContentView PackageContentsView::PackageContentsView(const char* name) : BView("package_contents_view", B_WILL_DRAW), fPackageLock("package contents populator lock"), fLastPackageState(NONE) { fContentListView = new BOutlineListView("content list view", B_SINGLE_SELECTION_LIST); BScrollView* scrollView = new GeneralContentScrollView( "contents scroll view", fContentListView); BLayoutBuilder::Group<>(this) .Add(scrollView, 1.0f) .SetInsets(0.0f, -1.0f, -1.0f, -1.0f) ; _InitContentPopulator(); } PackageContentsView::~PackageContentsView() { Clear(); delete_sem(fContentPopulatorSem); if (fContentPopulator >= 0) wait_for_thread(fContentPopulator, NULL); } void PackageContentsView::AttachedToWindow() { BView::AttachedToWindow(); } void PackageContentsView::AllAttached() { BView::AllAttached(); } void PackageContentsView::SetPackage(const PackageInfoRef& package) { // When getting a ref to the same package, don't return when the // package state has changed, since in that case, we may now be able // to read contents where we previously could not. (For example, the // package has been installed.) if (fPackage == package && (!package.IsSet() || package->State() == fLastPackageState)) { return; } Clear(); { BAutolock lock(&fPackageLock); fPackage = package; fLastPackageState = package.IsSet() ? package->State() : NONE; } // if the package is not installed and is not a local file on disk then // there is no point in attempting to populate data for it. if (package.IsSet() && (package->State() == ACTIVATED || package->IsLocalFile())) { release_sem_etc(fContentPopulatorSem, 1, 0); } } void PackageContentsView::Clear() { { BAutolock lock(&fPackageLock); fPackage.Unset(); } fContentListView->MakeEmpty(); } // #pragma mark - private void PackageContentsView::_InitContentPopulator() { fContentPopulatorSem = create_sem(0, "PopulatePackageContents"); if (fContentPopulatorSem >= 0) { fContentPopulator = spawn_thread(&_ContentPopulatorThread, "Package Contents Populator", B_NORMAL_PRIORITY, this); if (fContentPopulator >= 0) resume_thread(fContentPopulator); } else fContentPopulator = -1; } /*static*/ int32 PackageContentsView::_ContentPopulatorThread(void* arg) { PackageContentsView* view = reinterpret_cast(arg); while (acquire_sem(view->fContentPopulatorSem) == B_OK) { PackageInfoRef package; { BAutolock lock(&view->fPackageLock); package = view->fPackage; } if (package.IsSet()) { if (!view->_PopulatePackageContents(*package.Get())) { if (view->LockLooperWithTimeout(1000000) == B_OK) { view->fContentListView->AddItem( new BStringItem(B_TRANSLATE(""))); view->UnlockLooper(); } } } } return 0; } bool PackageContentsView::_PopulatePackageContents(const PackageInfo& package) { BPath packagePath; if (PackageUtils::DeriveLocalFilePath(&package, packagePath) != B_OK) { HDDEBUG("unable to obtain local file path"); return false; } // Setup a BPackageReader BNoErrorOutput errorOutput; BPackageReader reader(&errorOutput); status_t status = reader.Init(packagePath.Path()); if (status != B_OK) { HDINFO("PackageContentsView::_PopulatePackageContents(): " "failed to init BPackageReader(%s): %s", packagePath.Path(), strerror(status)); return false; } // Scan package contents and populate list PackageContentOutliner contentHandler(fContentListView, &package, fPackageLock, fPackage); status = reader.ParseContent(&contentHandler); if (status != B_OK) { HDINFO("PackageContentsView::_PopulatePackageContents(): " "failed parse package contents: %s", strerror(status)); // NOTE: Do not return false, since it taken to mean this // is a remote package, but is it not, we simply want to stop // populating the contents early. } return true; }