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 _InitDefaultVectorIcon(); 134 } 135 136 137 PackageIconTarRepository::~PackageIconTarRepository() 138 { 139 delete fIconDataBuffer; 140 delete fDefaultIconVectorData; 141 } 142 143 144 void 145 PackageIconTarRepository::Clear() { 146 BAutolock locker(&fLock); 147 fIconCache.Clear(); 148 fDefaultIconCache.Clear(); 149 } 150 151 152 /*! This method will reconfigure itself using the data in the tar file supplied. 153 Any existing data will be flushed and the new tar will be scanned for 154 offsets to usable files. 155 */ 156 157 status_t 158 PackageIconTarRepository::Init(BPath& tarPath) 159 { 160 BAutolock locker(&fLock); 161 162 _Close(); 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 fDefaultIconCache.Clear(); 218 } 219 220 221 /*! This method should be treated private and only called from a situation 222 in which the class's lock is acquired. It is used to populate data from 223 the parsing of the tar headers. It is called from the listener above. 224 */ 225 void 226 PackageIconTarRepository::AddIconTarPtr(const BString& packageName, BitmapSize storedSize, 227 off_t offset) 228 { 229 IconTarPtrRef tarPtrRef = _GetOrCreateIconTarPtr(packageName); 230 tarPtrRef->SetOffset(storedSize, offset); 231 } 232 233 234 bool 235 PackageIconTarRepository::HasAnyIcon(const BString& pkgName) 236 { 237 BAutolock locker(&fLock); 238 HashString key(pkgName); 239 return fIconTarPtrs.ContainsKey(key); 240 } 241 242 243 status_t 244 PackageIconTarRepository::GetIcon(const BString& pkgName, uint32 size, 245 BitmapHolderRef& bitmapHolderRef) 246 { 247 if (0 == size || size > MAX_IMAGE_SIZE) { 248 HDERROR("request to get icon for pkg [%s] with bad size %" B_PRIu32, pkgName.String(), 249 size); 250 return B_BAD_VALUE; 251 } 252 253 bitmapHolderRef.Unset(); 254 255 BAutolock locker(&fLock); 256 status_t result = B_OK; 257 off_t iconDataTarOffset = -1; 258 const IconTarPtrRef tarPtrRef = _GetIconTarPtr(pkgName); 259 BitmapSize storedSize; 260 261 if (tarPtrRef.IsSet()) { 262 storedSize = _BestStoredSize(tarPtrRef, size); 263 iconDataTarOffset = tarPtrRef->Offset(storedSize); 264 } 265 266 if (iconDataTarOffset >= 0) { 267 HashString key = _ToIconCacheKey(pkgName, storedSize, size); 268 269 if (fIconCache.ContainsKey(key)) { 270 bitmapHolderRef.SetTo(fIconCache.Get(key).Get()); 271 } else { 272 result = _CreateIconFromTarOffset(iconDataTarOffset, storedSize, size, bitmapHolderRef); 273 if (result == B_OK) { 274 HDTRACE("loaded package icon [%s] of size %" B_PRId32, pkgName.String(), size); 275 fIconCache.Put(key, bitmapHolderRef); 276 bitmapHolderRef.SetTo(fIconCache.Get(key).Get()); 277 } else { 278 HDERROR("failure to read image for package [%s] at offset %" 279 B_PRIdOFF, pkgName.String(), iconDataTarOffset); 280 } 281 } 282 } 283 284 if (!bitmapHolderRef.IsSet()) 285 result = _GetDefaultIcon(size, bitmapHolderRef); 286 287 return result; 288 } 289 290 291 IconTarPtrRef 292 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const 293 { 294 return fIconTarPtrs.Get(HashString(pkgName)); 295 } 296 297 298 const char* 299 PackageIconTarRepository::_ToIconCacheKeyPart(BitmapSize storedSize) 300 { 301 switch (storedSize) { 302 case BITMAP_SIZE_16: 303 return "16"; 304 // note that size 22 is not supported. 305 case BITMAP_SIZE_32: 306 return "32"; 307 case BITMAP_SIZE_64: 308 return "64"; 309 case BITMAP_SIZE_ANY: 310 return "any"; 311 default: 312 HDFATAL("unsupported bitmap size"); 313 break; 314 } 315 } 316 317 318 const HashString 319 PackageIconTarRepository::_ToIconCacheKey(const BString& pkgName, BitmapSize storedSize, 320 uint32 size) 321 { 322 return HashString( 323 BString(pkgName) << "__s" << _ToIconCacheKeyPart(storedSize) << "__x" << size); 324 } 325 326 327 /*! This method must only be invoked with the class locked. 328 */ 329 status_t 330 PackageIconTarRepository::_CreateIconFromTarOffset(off_t offset, BitmapSize storedSize, uint32 size, 331 BitmapHolderRef& bitmapHolderRef) 332 { 333 fTarIo->Seek(offset, SEEK_SET); 334 fIconDataBuffer->Seek(0, SEEK_SET); 335 336 TarArchiveHeader header; 337 status_t result = TarArchiveService::GetEntry(*fTarIo, header); 338 339 if (result == B_OK && (header.Length() <= 0 || header.Length() > ICON_BUFFER_SIZE_MAX)) { 340 HDERROR("the icon tar entry at %" B_PRIdOFF " has a bad length %" B_PRIdSSIZE, offset, 341 header.Length()); 342 result = B_BAD_DATA; 343 } 344 345 off_t iconDataBufferSize = 0; 346 347 if (result == B_OK) 348 result = fIconDataBuffer->GetSize(&iconDataBufferSize); 349 350 if (result == B_OK && static_cast<size_t>(iconDataBufferSize) < header.Length()) 351 result = fIconDataBuffer->SetSize(header.Length()); 352 353 if (result == B_OK) { 354 BDataIO* tarImageIO = new ConstraintedDataIO(fTarIo, header.Length()); 355 result = DataIOUtils::CopyAll(fIconDataBuffer, tarImageIO); 356 delete tarImageIO; 357 } else { 358 HDERROR("unable to extract data from tar for icon image"); 359 } 360 361 fIconDataBuffer->Seek(0, SEEK_SET); 362 363 BBitmap* bitmap = NULL; 364 365 if (result == B_OK) { 366 if (BITMAP_SIZE_ANY == storedSize) { 367 bitmap = new BBitmap(BRect(0, 0, size - 1, size - 1), 0, B_RGBA32); 368 result = bitmap->InitCheck(); 369 result = BIconUtils::GetVectorIcon( 370 reinterpret_cast<const uint8*>(fIconDataBuffer->Buffer()), header.Length(), bitmap); 371 } else { 372 bitmap = BTranslationUtils::GetBitmap(fIconDataBuffer); 373 374 if (bitmap == NULL) { 375 HDERROR("unable to decode data from tar for icon image"); 376 result = B_ERROR; 377 } 378 } 379 } 380 381 if (result != B_OK) 382 delete bitmap; 383 else 384 bitmapHolderRef.SetTo(new(std::nothrow) BitmapHolder(bitmap), true); 385 386 return result; 387 } 388 389 390 /*! If there is a vector representation (HVIF) then this will be the best 391 option. If there are only bitmap images to choose from then consider what 392 the target size is and choose the best image to match. 393 */ 394 /*static*/ BitmapSize 395 PackageIconTarRepository::_BestStoredSize(const IconTarPtrRef iconTarPtrRef, int32 desiredSize) 396 { 397 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_ANY)) 398 return BITMAP_SIZE_ANY; 399 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_64) && desiredSize >= 64) 400 return BITMAP_SIZE_64; 401 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32) && desiredSize >= 32) 402 return BITMAP_SIZE_32; 403 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32)) 404 return BITMAP_SIZE_16; 405 HDFATAL("unable to get the best stored icon for size"); 406 } 407 408 409 IconTarPtrRef 410 PackageIconTarRepository::_GetOrCreateIconTarPtr(const BString& pkgName) 411 { 412 BAutolock locker(&fLock); 413 HashString key(pkgName); 414 if (!fIconTarPtrs.ContainsKey(key)) { 415 IconTarPtrRef value(new IconTarPtr(pkgName)); 416 fIconTarPtrs.Put(key, value); 417 return value; 418 } 419 return fIconTarPtrs.Get(key); 420 } 421 422 423 status_t 424 PackageIconTarRepository::_GetDefaultIcon(uint32 size, BitmapHolderRef& bitmapHolderRef) 425 { 426 if (fDefaultIconVectorData == NULL) 427 return B_NOT_INITIALIZED; 428 429 bitmapHolderRef.Unset(); 430 431 status_t status = B_OK; 432 HashString key(BString() << size); 433 434 if (!fDefaultIconCache.ContainsKey(key)) { 435 BBitmap* bitmap = NULL; 436 437 if (status == B_OK) { 438 bitmap = new BBitmap(BRect(0, 0, size - 1, size - 1), 0, B_RGBA32); 439 status = bitmap->InitCheck(); 440 } 441 442 if (status == B_OK) { 443 status = BIconUtils::GetVectorIcon(fDefaultIconVectorData, fDefaultIconVectorDataSize, 444 bitmap); 445 } 446 447 if (status == B_OK) { 448 HDINFO("did create default package icon size %" B_PRId32, size); 449 BitmapHolderRef bitmapHolder(new(std::nothrow) BitmapHolder(bitmap), true); 450 fDefaultIconCache.Put(key, bitmapHolder); 451 } else { 452 delete bitmap; 453 bitmap = NULL; 454 } 455 } 456 457 if (status == B_OK) 458 bitmapHolderRef.SetTo(fDefaultIconCache.Get(key).Get()); 459 else 460 HDERROR("failed to create default package icon size %" B_PRId32, size); 461 462 return status; 463 } 464 465 466 void 467 PackageIconTarRepository::_InitDefaultVectorIcon() 468 { 469 if (fDefaultIconVectorData != NULL) { 470 delete fDefaultIconVectorData; 471 fDefaultIconVectorData = NULL; 472 } 473 474 fDefaultIconVectorDataSize = 0; 475 476 BMimeType mimeType("application/x-vnd.haiku-package"); 477 status_t status = mimeType.InitCheck(); 478 479 uint8* data; 480 size_t dataSize; 481 482 if (status == B_OK) 483 status = mimeType.GetIcon(&data, &dataSize); 484 485 if (status == B_OK) { 486 fDefaultIconVectorData = new(std::nothrow) uint8[dataSize]; 487 488 if (fDefaultIconVectorData == NULL) 489 HDFATAL("unable to allocate memory for the default icon"); 490 491 memcpy(fDefaultIconVectorData, data, dataSize); 492 fDefaultIconVectorDataSize = dataSize; 493 } else { 494 fDefaultIconVectorData = NULL; 495 fDefaultIconVectorDataSize = 0; 496 } 497 } 498