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