1 /* 2 * Copyright 2015, TigerKid001. 3 * Copyright 2020-2024, 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 "PackageKitUtils.h" 27 #include "PackageUtils.h" 28 29 #include <package/PackageDefs.h> 30 #include <package/hpkg/NoErrorOutput.h> 31 #include <package/hpkg/PackageContentHandler.h> 32 #include <package/hpkg/PackageEntry.h> 33 #include <package/hpkg/PackageReader.h> 34 35 using namespace BPackageKit; 36 37 using BPackageKit::BHPKG::BNoErrorOutput; 38 using BPackageKit::BHPKG::BPackageContentHandler; 39 using BPackageKit::BHPKG::BPackageEntry; 40 using BPackageKit::BHPKG::BPackageEntryAttribute; 41 using BPackageKit::BHPKG::BPackageInfoAttributeValue; 42 using BPackageKit::BHPKG::BPackageReader; 43 44 #undef B_TRANSLATION_CONTEXT 45 #define B_TRANSLATION_CONTEXT "PackageContentsView" 46 47 48 // #pragma mark - PackageEntryItem 49 50 51 class PackageEntryItem : public BStringItem { 52 public: 53 PackageEntryItem(const BPackageEntry* entry, const BString& path) 54 : 55 BStringItem(entry->Name()), 56 fPath(path) 57 { 58 if (fPath.Length() > 0) 59 fPath.Append("/"); 60 fPath.Append(entry->Name()); 61 } 62 63 inline const BString& EntryPath() const 64 { 65 return fPath; 66 } 67 68 private: 69 BString fPath; 70 }; 71 72 73 // #pragma mark - PackageContentOutliner 74 75 76 class PackageContentOutliner : public BPackageContentHandler { 77 public: 78 PackageContentOutliner(BOutlineListView* listView, 79 const PackageInfo* packageInfo, 80 BLocker& packageLock, PackageInfoRef& packageInfoRef) 81 : 82 fListView(listView), 83 fLastParentEntry(NULL), 84 fLastParentItem(NULL), 85 fLastEntry(NULL), 86 fLastItem(NULL), 87 88 fPackageInfoToPopulate(packageInfo), 89 fPackageLock(packageLock), 90 fPackageInfoRef(packageInfoRef) 91 { 92 } 93 94 virtual status_t HandleEntry(BPackageEntry* entry) 95 { 96 if (fListView->LockLooperWithTimeout(1000000) != B_OK) 97 return B_ERROR; 98 99 // Check if we are still supposed to popuplate the list 100 if (fPackageInfoRef.Get() != fPackageInfoToPopulate) { 101 fListView->UnlockLooper(); 102 return B_ERROR; 103 } 104 105 BString path; 106 const BPackageEntry* parent = entry->Parent(); 107 while (parent != NULL) { 108 if (path.Length() > 0) 109 path.Prepend("/"); 110 path.Prepend(parent->Name()); 111 parent = parent->Parent(); 112 } 113 114 PackageEntryItem* item = new PackageEntryItem(entry, path); 115 116 if (entry->Parent() == NULL) { 117 fListView->AddItem(item); 118 fLastParentEntry = NULL; 119 fLastParentItem = NULL; 120 } else if (entry->Parent() == fLastEntry) { 121 fListView->AddUnder(item, fLastItem); 122 fLastParentEntry = fLastEntry; 123 fLastParentItem = fLastItem; 124 } else if (entry->Parent() == fLastParentEntry) { 125 fListView->AddUnder(item, fLastParentItem); 126 } else { 127 // Not the last parent entry, need to search for the parent 128 // among the already added list items. 129 bool foundParent = false; 130 for (int32 i = 0; i < fListView->FullListCountItems(); i++) { 131 PackageEntryItem* listItem 132 = dynamic_cast<PackageEntryItem*>( 133 fListView->FullListItemAt(i)); 134 if (listItem == NULL) 135 continue; 136 if (listItem->EntryPath() == path) { 137 fLastParentEntry = entry->Parent(); 138 fLastParentItem = listItem; 139 fListView->AddUnder(item, listItem); 140 foundParent = true; 141 break; 142 } 143 } 144 if (!foundParent) { 145 // NOTE: Should not happen. Just add this entry at the 146 // root level. 147 fListView->AddItem(item); 148 fLastParentEntry = NULL; 149 fLastParentItem = NULL; 150 } 151 } 152 153 fLastEntry = entry; 154 fLastItem = item; 155 156 fListView->UnlockLooper(); 157 158 return B_OK; 159 } 160 161 virtual status_t HandleEntryAttribute(BPackageEntry* entry, 162 BPackageEntryAttribute* attribute) 163 { 164 return B_OK; 165 } 166 167 virtual status_t HandleEntryDone(BPackageEntry* entry) 168 { 169 return B_OK; 170 } 171 172 virtual status_t HandlePackageAttribute( 173 const BPackageInfoAttributeValue& value) 174 { 175 return B_OK; 176 } 177 178 virtual void HandleErrorOccurred() 179 { 180 } 181 182 private: 183 BOutlineListView* fListView; 184 185 const BPackageEntry* fLastParentEntry; 186 PackageEntryItem* fLastParentItem; 187 188 const BPackageEntry* fLastEntry; 189 PackageEntryItem* fLastItem; 190 191 const PackageInfo* fPackageInfoToPopulate; 192 BLocker& fPackageLock; 193 PackageInfoRef& fPackageInfoRef; 194 }; 195 196 197 // #pragma mark - PackageContentView 198 199 200 PackageContentsView::PackageContentsView(const char* name) 201 : 202 BView("package_contents_view", B_WILL_DRAW), 203 fPackageLock("package contents populator lock"), 204 fLastPackageState(NONE) 205 { 206 fContentListView = new BOutlineListView("content list view", 207 B_SINGLE_SELECTION_LIST); 208 209 BScrollView* scrollView = new GeneralContentScrollView( 210 "contents scroll view", fContentListView); 211 212 BLayoutBuilder::Group<>(this) 213 .Add(scrollView, 1.0f) 214 .SetInsets(0.0f, -1.0f, -1.0f, -1.0f) 215 ; 216 217 _InitContentPopulator(); 218 } 219 220 221 PackageContentsView::~PackageContentsView() 222 { 223 Clear(); 224 225 delete_sem(fContentPopulatorSem); 226 if (fContentPopulator >= 0) 227 wait_for_thread(fContentPopulator, NULL); 228 } 229 230 231 void 232 PackageContentsView::AttachedToWindow() 233 { 234 BView::AttachedToWindow(); 235 } 236 237 238 void 239 PackageContentsView::AllAttached() 240 { 241 BView::AllAttached(); 242 } 243 244 245 void 246 PackageContentsView::SetPackage(const PackageInfoRef& package) 247 { 248 PackageState packageState = PackageUtils::State(package); 249 250 // When getting a ref to the same package, don't return when the 251 // package state has changed, since in that case, we may now be able 252 // to read contents where we previously could not. (For example, the 253 // package has been installed.) 254 if (fPackage == package && (!package.IsSet() || packageState == fLastPackageState)) 255 return; 256 257 Clear(); 258 259 { 260 BAutolock lock(&fPackageLock); 261 fPackage = package; 262 fLastPackageState = packageState; 263 } 264 265 // if the package is not installed and is not a local file on disk then 266 // there is no point in attempting to populate data for it. 267 268 if (PackageUtils::IsActivatedOrLocalFile(package)) 269 release_sem_etc(fContentPopulatorSem, 1, 0); 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)) { 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 PackageInfoRef& package) 332 { 333 BPath packagePath; 334 335 if (PackageKitUtils::DeriveLocalFilePath(package, packagePath) != B_OK) { 336 HDDEBUG("unable to obtain local file path"); 337 return false; 338 } 339 340 // Setup a BPackageReader 341 BNoErrorOutput errorOutput; 342 BPackageReader reader(&errorOutput); 343 344 status_t status = reader.Init(packagePath.Path()); 345 if (status != B_OK) { 346 HDINFO("PackageContentsView::_PopulatePackageContents(): " 347 "failed to init BPackageReader(%s): %s", 348 packagePath.Path(), strerror(status)); 349 return false; 350 } 351 352 // Scan package contents and populate list 353 PackageContentOutliner contentHandler(fContentListView, package.Get(), fPackageLock, fPackage); 354 status = reader.ParseContent(&contentHandler); 355 if (status != B_OK) { 356 HDINFO("PackageContentsView::_PopulatePackageContents(): " 357 "failed parse package contents: %s", strerror(status)); 358 // NOTE: Do not return false, since it taken to mean this 359 // is a remote package, but is it not, we simply want to stop 360 // populating the contents early. 361 } 362 return true; 363 } 364