1 /* 2 * Copyright 2020-2024, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 #include "PackageIconTarRepository.h" 6 7 #include <AutoDeleter.h> 8 #include <Autolock.h> 9 #include <File.h> 10 #include <IconUtils.h> 11 #include <StopWatch.h> 12 #include <TranslationUtils.h> 13 14 #include "DataIOUtils.h" 15 #include "Logger.h" 16 #include "TarArchiveService.h" 17 18 19 #define LIMIT_ICON_CACHE 50 20 #define ICON_BUFFER_SIZE_INITIAL 4096 21 #define ICON_BUFFER_SIZE_MAX 32768 22 23 24 /*! An instance of this class can be provided to the TarArchiveService to 25 be called each time a tar entry is found. This way it is able to capture 26 the offsets of the icons in the tar file against the package names. 27 */ 28 29 class IconTarPtrEntryListener : public TarEntryListener 30 { 31 public: 32 IconTarPtrEntryListener( 33 PackageIconTarRepository* 34 fPackageIconTarRepository); 35 virtual ~IconTarPtrEntryListener(); 36 37 virtual status_t Handle( 38 const TarArchiveHeader& header, 39 size_t offset, 40 BDataIO *data); 41 private: 42 status_t _LeafNameToBitmapSize(BString& leafName, 43 BitmapSize* bitmapSize); 44 45 private: 46 PackageIconTarRepository* 47 fPackageIconTarRepository; 48 }; 49 50 51 IconTarPtrEntryListener::IconTarPtrEntryListener( 52 PackageIconTarRepository* packageIconTarRepository) 53 : 54 fPackageIconTarRepository(packageIconTarRepository) 55 { 56 } 57 58 59 IconTarPtrEntryListener::~IconTarPtrEntryListener() 60 { 61 } 62 63 64 /*! The format of the filenames in the archive are of the format; 65 \code 66 hicn/ardino/icon.hvif 67 \endcode 68 The leafname (the last part of the path) determines the type of the file as 69 it could be either in HVIF format or a PNG of various sizes. 70 */ 71 72 status_t 73 IconTarPtrEntryListener::Handle(const TarArchiveHeader& header, 74 size_t offset, BDataIO *data) 75 { 76 if (header.FileType() != TAR_FILE_TYPE_NORMAL) 77 return B_OK; 78 79 BString fileName = header.FileName(); 80 if (!fileName.StartsWith("hicn/")) 81 return B_OK; 82 83 int32 secondSlashIdx = fileName.FindFirst("/", 5); 84 if (secondSlashIdx == B_ERROR || secondSlashIdx == 5) 85 return B_OK; 86 87 BString packageName; 88 BString leafName; 89 fileName.CopyInto(packageName, 5, secondSlashIdx - 5); 90 fileName.CopyInto(leafName, secondSlashIdx + 1, 91 fileName.Length() - (secondSlashIdx + 1)); 92 BitmapSize storedSize; 93 94 if (_LeafNameToBitmapSize(leafName, &storedSize) == B_OK) 95 fPackageIconTarRepository->AddIconTarPtr(packageName, storedSize, offset); 96 97 return B_OK; 98 } 99 100 101 status_t 102 IconTarPtrEntryListener::_LeafNameToBitmapSize(BString& leafName, BitmapSize* storedSize) 103 { 104 if (leafName == "icon.hvif") { 105 *storedSize = BITMAP_SIZE_ANY; 106 return B_OK; 107 } 108 if (leafName == "64.png") { 109 *storedSize = BITMAP_SIZE_64; 110 return B_OK; 111 } 112 if (leafName == "32.png") { 113 *storedSize = BITMAP_SIZE_32; 114 return B_OK; 115 } 116 if (leafName == "16.png") { 117 *storedSize = BITMAP_SIZE_16; 118 return B_OK; 119 } 120 return B_BAD_VALUE; 121 } 122 123 124 PackageIconTarRepository::PackageIconTarRepository() 125 : 126 fTarIo(NULL), 127 fIconCache(LIMIT_ICON_CACHE), 128 fDefaultIconVectorData(NULL), 129 fDefaultIconVectorDataSize(0), 130 fDefaultIconCache(LIMIT_ICON_CACHE), 131 fIconDataBuffer(new BMallocIO()) 132 { 133 } 134 135 136 PackageIconTarRepository::~PackageIconTarRepository() 137 { 138 delete fIconDataBuffer; 139 } 140 141 142 void 143 PackageIconTarRepository::Clear() { 144 BAutolock locker(&fLock); 145 fIconCache.Clear(); 146 fDefaultIconCache.Clear(); 147 } 148 149 150 /*! This method will reconfigure itself using the data in the tar file supplied. 151 Any existing data will be flushed and the new tar will be scanned for 152 offsets to usable files. 153 */ 154 155 status_t 156 PackageIconTarRepository::Init(BPath& tarPath) 157 { 158 BAutolock locker(&fLock); 159 160 _Close(); 161 162 _InitDefaultVectorIcon(); 163 164 status_t result = B_OK; 165 166 if (tarPath.Path() == NULL) { 167 HDINFO("empty path to tar-ball"); 168 result = B_BAD_VALUE; 169 } 170 171 BFile *tarIo = NULL; 172 173 if (result == B_OK) { 174 HDINFO("will init icon model from tar [%s]", tarPath.Path()); 175 tarIo = new BFile(tarPath.Path(), O_RDONLY); 176 177 if (!tarIo->IsReadable()) { 178 HDERROR("unable to read the tar [%s]", tarPath.Path()); 179 result = B_IO_ERROR; 180 } 181 } 182 183 // will fill the model up with records from the tar-ball. 184 185 if (result == B_OK) { 186 BStopWatch watch("PackageIconTarRepository::Init", true); 187 HDINFO("will read [%s] and collect the tar pointers", tarPath.Path()); 188 189 IconTarPtrEntryListener* listener = new IconTarPtrEntryListener(this); 190 ObjectDeleter<IconTarPtrEntryListener> listenerDeleter(listener); 191 TarArchiveService::ForEachEntry(*tarIo, listener); 192 193 double secs = watch.ElapsedTime() / 1000000.0; 194 HDINFO("did collect %" B_PRIi32 " tar pointers (%6.3g secs)", 195 fIconTarPtrs.Size(), secs); 196 } 197 198 if (result == B_OK) 199 fTarIo = tarIo; 200 else 201 delete tarIo; 202 203 if (result == B_OK) 204 result = fIconDataBuffer->SetSize(ICON_BUFFER_SIZE_INITIAL); 205 206 return result; 207 } 208 209 210 void 211 PackageIconTarRepository::_Close() 212 { 213 fIconCache.Clear(); 214 delete fTarIo; 215 fTarIo = NULL; 216 fIconTarPtrs.Clear(); 217 218 delete fDefaultIconVectorData; 219 fDefaultIconVectorData = NULL; 220 fDefaultIconVectorDataSize = 0; 221 fDefaultIconCache.Clear(); 222 } 223 224 225 /*! This method should be treated private and only called from a situation 226 in which the class's lock is acquired. It is used to populate data from 227 the parsing of the tar headers. It is called from the listener above. 228 */ 229 void 230 PackageIconTarRepository::AddIconTarPtr(const BString& packageName, BitmapSize storedSize, 231 off_t offset) 232 { 233 IconTarPtrRef tarPtrRef = _GetOrCreateIconTarPtr(packageName); 234 tarPtrRef->SetOffset(storedSize, offset); 235 } 236 237 238 bool 239 PackageIconTarRepository::HasAnyIcon(const BString& pkgName) 240 { 241 BAutolock locker(&fLock); 242 HashString key(pkgName); 243 return fIconTarPtrs.ContainsKey(key); 244 } 245 246 247 status_t 248 PackageIconTarRepository::GetIcon(const BString& pkgName, uint32 size, 249 BitmapHolderRef& bitmapHolderRef) 250 { 251 if (0 == size || size > MAX_IMAGE_SIZE) { 252 HDERROR("request to get icon for pkg [%s] with bad size %" B_PRIu32, pkgName.String(), 253 size); 254 return B_BAD_VALUE; 255 } 256 257 bitmapHolderRef.Unset(); 258 259 BAutolock locker(&fLock); 260 status_t result = B_OK; 261 off_t iconDataTarOffset = -1; 262 const IconTarPtrRef tarPtrRef = _GetIconTarPtr(pkgName); 263 BitmapSize storedSize; 264 265 if (tarPtrRef.IsSet()) { 266 storedSize = _BestStoredSize(tarPtrRef, size); 267 iconDataTarOffset = tarPtrRef->Offset(storedSize); 268 } 269 270 if (iconDataTarOffset >= 0) { 271 HashString key = _ToIconCacheKey(pkgName, storedSize, size); 272 273 if (fIconCache.ContainsKey(key)) { 274 bitmapHolderRef.SetTo(fIconCache.Get(key).Get()); 275 } else { 276 result = _CreateIconFromTarOffset(iconDataTarOffset, storedSize, size, bitmapHolderRef); 277 if (result == B_OK) { 278 HDTRACE("loaded package icon [%s] of size %" B_PRId32, pkgName.String(), size); 279 fIconCache.Put(key, bitmapHolderRef); 280 bitmapHolderRef.SetTo(fIconCache.Get(key).Get()); 281 } else { 282 HDERROR("failure to read image for package [%s] at offset %" 283 B_PRIdOFF, pkgName.String(), iconDataTarOffset); 284 } 285 } 286 } 287 288 if (!bitmapHolderRef.IsSet()) 289 result = _GetDefaultIcon(size, bitmapHolderRef); 290 291 return result; 292 } 293 294 295 IconTarPtrRef 296 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const 297 { 298 return fIconTarPtrs.Get(HashString(pkgName)); 299 } 300 301 302 const char* 303 PackageIconTarRepository::_ToIconCacheKeyPart(BitmapSize storedSize) 304 { 305 switch (storedSize) { 306 case BITMAP_SIZE_16: 307 return "16"; 308 // note that size 22 is not supported. 309 case BITMAP_SIZE_32: 310 return "32"; 311 case BITMAP_SIZE_64: 312 return "64"; 313 case BITMAP_SIZE_ANY: 314 return "any"; 315 default: 316 HDFATAL("unsupported bitmap size"); 317 break; 318 } 319 } 320 321 322 const HashString 323 PackageIconTarRepository::_ToIconCacheKey(const BString& pkgName, BitmapSize storedSize, 324 uint32 size) 325 { 326 return HashString( 327 BString(pkgName) << "__s" << _ToIconCacheKeyPart(storedSize) << "__x" << size); 328 } 329 330 331 /*! This method must only be invoked with the class locked. 332 */ 333 status_t 334 PackageIconTarRepository::_CreateIconFromTarOffset(off_t offset, BitmapSize storedSize, uint32 size, 335 BitmapHolderRef& bitmapHolderRef) 336 { 337 fTarIo->Seek(offset, SEEK_SET); 338 fIconDataBuffer->Seek(0, SEEK_SET); 339 340 TarArchiveHeader header; 341 status_t result = TarArchiveService::GetEntry(*fTarIo, header); 342 343 if (result == B_OK && (header.Length() <= 0 || header.Length() > ICON_BUFFER_SIZE_MAX)) { 344 HDERROR("the icon tar entry at %" B_PRIdOFF " has a bad length %" B_PRIdSSIZE, offset, 345 header.Length()); 346 result = B_BAD_DATA; 347 } 348 349 off_t iconDataBufferSize = 0; 350 351 if (result == B_OK) 352 result = fIconDataBuffer->GetSize(&iconDataBufferSize); 353 354 if (result == B_OK && static_cast<size_t>(iconDataBufferSize) < header.Length()) 355 result = fIconDataBuffer->SetSize(header.Length()); 356 357 if (result == B_OK) { 358 BDataIO* tarImageIO = new ConstraintedDataIO(fTarIo, header.Length()); 359 result = DataIOUtils::CopyAll(fIconDataBuffer, tarImageIO); 360 delete tarImageIO; 361 } else { 362 HDERROR("unable to extract data from tar for icon image"); 363 } 364 365 fIconDataBuffer->Seek(0, SEEK_SET); 366 367 BBitmap* bitmap = NULL; 368 369 if (result == B_OK) { 370 if (BITMAP_SIZE_ANY == storedSize) { 371 bitmap = new BBitmap(BRect(0, 0, size - 1, size - 1), 0, B_RGBA32); 372 result = bitmap->InitCheck(); 373 result = BIconUtils::GetVectorIcon( 374 reinterpret_cast<const uint8*>(fIconDataBuffer->Buffer()), header.Length(), bitmap); 375 } else { 376 bitmap = BTranslationUtils::GetBitmap(fIconDataBuffer); 377 378 if (bitmap == NULL) { 379 HDERROR("unable to decode data from tar for icon image"); 380 result = B_ERROR; 381 } 382 } 383 } 384 385 if (result != B_OK) 386 delete bitmap; 387 else 388 bitmapHolderRef.SetTo(new(std::nothrow) BitmapHolder(bitmap), true); 389 390 return result; 391 } 392 393 394 /*! If there is a vector representation (HVIF) then this will be the best 395 option. If there are only bitmap images to choose from then consider what 396 the target size is and choose the best image to match. 397 */ 398 /*static*/ BitmapSize 399 PackageIconTarRepository::_BestStoredSize(const IconTarPtrRef iconTarPtrRef, int32 desiredSize) 400 { 401 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_ANY)) 402 return BITMAP_SIZE_ANY; 403 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_64) && desiredSize >= 64) 404 return BITMAP_SIZE_64; 405 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32) && desiredSize >= 32) 406 return BITMAP_SIZE_32; 407 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32)) 408 return BITMAP_SIZE_16; 409 HDFATAL("unable to get the best stored icon for size"); 410 } 411 412 413 IconTarPtrRef 414 PackageIconTarRepository::_GetOrCreateIconTarPtr(const BString& pkgName) 415 { 416 BAutolock locker(&fLock); 417 HashString key(pkgName); 418 if (!fIconTarPtrs.ContainsKey(key)) { 419 IconTarPtrRef value(new IconTarPtr(pkgName)); 420 fIconTarPtrs.Put(key, value); 421 return value; 422 } 423 return fIconTarPtrs.Get(key); 424 } 425 426 427 status_t 428 PackageIconTarRepository::_GetDefaultIcon(uint32 size, BitmapHolderRef& bitmapHolderRef) 429 { 430 if (fDefaultIconVectorData == NULL) 431 return B_NOT_INITIALIZED; 432 433 bitmapHolderRef.Unset(); 434 435 status_t status = B_OK; 436 HashString key(BString() << size); 437 438 if (!fDefaultIconCache.ContainsKey(key)) { 439 BBitmap* bitmap = NULL; 440 441 if (status == B_OK) { 442 bitmap = new BBitmap(BRect(0, 0, size - 1, size - 1), 0, B_RGBA32); 443 status = bitmap->InitCheck(); 444 } 445 446 if (status == B_OK) { 447 status = BIconUtils::GetVectorIcon(fDefaultIconVectorData, fDefaultIconVectorDataSize, 448 bitmap); 449 } 450 451 if (status == B_OK) { 452 HDINFO("did create default package icon size %" B_PRId32, size); 453 BitmapHolderRef bitmapHolder(new(std::nothrow) BitmapHolder(bitmap), true); 454 fDefaultIconCache.Put(key, bitmapHolder); 455 } else { 456 delete bitmap; 457 bitmap = NULL; 458 } 459 } 460 461 if (status == B_OK) 462 bitmapHolderRef.SetTo(fDefaultIconCache.Get(key).Get()); 463 else 464 HDERROR("failed to create default package icon size %" B_PRId32, size); 465 466 return status; 467 } 468 469 470 void 471 PackageIconTarRepository::_InitDefaultVectorIcon() 472 { 473 if (fDefaultIconVectorData != NULL) { 474 delete fDefaultIconVectorData; 475 fDefaultIconVectorData = NULL; 476 } 477 478 fDefaultIconVectorDataSize = 0; 479 480 BMimeType mimeType("application/x-vnd.haiku-package"); 481 status_t status = mimeType.InitCheck(); 482 483 uint8* data; 484 size_t dataSize; 485 486 if (status == B_OK) 487 status = mimeType.GetIcon(&data, &dataSize); 488 489 if (status == B_OK) { 490 fDefaultIconVectorData = new(std::nothrow) uint8[dataSize]; 491 492 if (fDefaultIconVectorData == NULL) 493 HDFATAL("unable to allocate memory for the default icon"); 494 495 memcpy(fDefaultIconVectorData, data, dataSize); 496 fDefaultIconVectorDataSize = dataSize; 497 } else { 498 fDefaultIconVectorData = NULL; 499 fDefaultIconVectorDataSize = 0; 500 } 501 } 502