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 bitmapSize; 93 94 if (_LeafNameToBitmapSize(leafName, &bitmapSize) == B_OK) { 95 fPackageIconTarRepository->AddIconTarPtr(packageName, bitmapSize, 96 offset); 97 } 98 99 return B_OK; 100 } 101 102 103 status_t 104 IconTarPtrEntryListener::_LeafNameToBitmapSize(BString& leafName, 105 BitmapSize* bitmapSize) 106 { 107 if (leafName == "icon.hvif") { 108 *bitmapSize = BITMAP_SIZE_ANY; 109 return B_OK; 110 } 111 if (leafName == "64.png") { 112 *bitmapSize = BITMAP_SIZE_64; 113 return B_OK; 114 } 115 if (leafName == "32.png") { 116 *bitmapSize = BITMAP_SIZE_32; 117 return B_OK; 118 } 119 if (leafName == "16.png") { 120 *bitmapSize = BITMAP_SIZE_16; 121 return B_OK; 122 } 123 return B_BAD_VALUE; 124 } 125 126 127 PackageIconTarRepository::PackageIconTarRepository() 128 : 129 fTarIo(NULL), 130 fIconCache(LIMIT_ICON_CACHE), 131 fDefaultIconVectorData(NULL), 132 fDefaultIconVectorDataSize(0), 133 fDefaultIconCache(LIMIT_ICON_CACHE), 134 fIconDataBuffer(new BMallocIO()) 135 { 136 } 137 138 139 PackageIconTarRepository::~PackageIconTarRepository() 140 { 141 delete fIconDataBuffer; 142 } 143 144 145 void 146 PackageIconTarRepository::Clear() { 147 BAutolock locker(&fLock); 148 fIconCache.Clear(); 149 fDefaultIconCache.Clear(); 150 } 151 152 153 /*! This method will reconfigure itself using the data in the tar file supplied. 154 Any existing data will be flushed and the new tar will be scanned for 155 offsets to usable files. 156 */ 157 158 status_t 159 PackageIconTarRepository::Init(BPath& tarPath) 160 { 161 BAutolock locker(&fLock); 162 163 _Close(); 164 165 _InitDefaultVectorIcon(); 166 167 status_t result = B_OK; 168 169 if (tarPath.Path() == NULL) { 170 HDINFO("empty path to tar-ball"); 171 result = B_BAD_VALUE; 172 } 173 174 BFile *tarIo = NULL; 175 176 if (result == B_OK) { 177 HDINFO("will init icon model from tar [%s]", tarPath.Path()); 178 tarIo = new BFile(tarPath.Path(), O_RDONLY); 179 180 if (!tarIo->IsReadable()) { 181 HDERROR("unable to read the tar [%s]", tarPath.Path()); 182 result = B_IO_ERROR; 183 } 184 } 185 186 // will fill the model up with records from the tar-ball. 187 188 if (result == B_OK) { 189 BStopWatch watch("PackageIconTarRepository::Init", true); 190 HDINFO("will read [%s] and collect the tar pointers", tarPath.Path()); 191 192 IconTarPtrEntryListener* listener = new IconTarPtrEntryListener(this); 193 ObjectDeleter<IconTarPtrEntryListener> listenerDeleter(listener); 194 TarArchiveService::ForEachEntry(*tarIo, listener); 195 196 double secs = watch.ElapsedTime() / 1000000.0; 197 HDINFO("did collect %" B_PRIi32 " tar pointers (%6.3g secs)", 198 fIconTarPtrs.Size(), secs); 199 } 200 201 if (result == B_OK) 202 fTarIo = tarIo; 203 else 204 delete tarIo; 205 206 if (result == B_OK) 207 result = fIconDataBuffer->SetSize(ICON_BUFFER_SIZE_INITIAL); 208 209 return result; 210 } 211 212 213 void 214 PackageIconTarRepository::_Close() 215 { 216 fIconCache.Clear(); 217 delete fTarIo; 218 fTarIo = NULL; 219 fIconTarPtrs.Clear(); 220 221 delete fDefaultIconVectorData; 222 fDefaultIconVectorData = NULL; 223 fDefaultIconVectorDataSize = 0; 224 fDefaultIconCache.Clear(); 225 } 226 227 228 /*! This method should be treated private and only called from a situation 229 in which the class's lock is acquired. It is used to populate data from 230 the parsing of the tar headers. It is called from the listener above. 231 */ 232 void 233 PackageIconTarRepository::AddIconTarPtr(const BString& packageName, BitmapSize bitmapSize, 234 off_t offset) 235 { 236 IconTarPtrRef tarPtrRef = _GetOrCreateIconTarPtr(packageName); 237 tarPtrRef->SetOffset(bitmapSize, offset); 238 } 239 240 241 bool 242 PackageIconTarRepository::HasAnyIcon(const BString& pkgName) 243 { 244 BAutolock locker(&fLock); 245 HashString key(pkgName); 246 return fIconTarPtrs.ContainsKey(key); 247 } 248 249 250 status_t 251 PackageIconTarRepository::GetIcon(const BString& pkgName, uint32 size, 252 BitmapHolderRef& bitmapHolderRef) 253 { 254 if (0 == size || size > MAX_IMAGE_SIZE) { 255 HDERROR("request to get icon for pkg [%s] with bad size %" B_PRIu32, pkgName.String(), 256 size); 257 return B_BAD_VALUE; 258 } 259 260 bitmapHolderRef.Unset(); 261 262 BAutolock locker(&fLock); 263 status_t result = B_OK; 264 off_t iconDataTarOffset = -1; 265 const IconTarPtrRef tarPtrRef = _GetIconTarPtr(pkgName); 266 BitmapSize bitmapSize; 267 268 if (tarPtrRef.IsSet()) { 269 bitmapSize = _BestStoredSize(tarPtrRef, size); 270 iconDataTarOffset = tarPtrRef->Offset(bitmapSize); 271 } 272 273 if (iconDataTarOffset >= 0) { 274 HashString key = _ToIconCacheKey(pkgName, bitmapSize); 275 276 if (fIconCache.ContainsKey(key)) { 277 bitmapHolderRef.SetTo(fIconCache.Get(key).Get()); 278 } else { 279 result = _CreateIconFromTarOffset(iconDataTarOffset, bitmapHolderRef); 280 if (result == B_OK) { 281 HDTRACE("loaded package icon [%s] of size %" B_PRId32, pkgName.String(), size); 282 fIconCache.Put(key, bitmapHolderRef); 283 bitmapHolderRef.SetTo(fIconCache.Get(key).Get()); 284 } else { 285 HDERROR("failure to read image for package [%s] at offset %" 286 B_PRIdOFF, pkgName.String(), iconDataTarOffset); 287 } 288 } 289 } 290 291 if (!bitmapHolderRef.IsSet()) 292 result = _GetDefaultIcon(size, bitmapHolderRef); 293 294 return result; 295 } 296 297 298 IconTarPtrRef 299 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const 300 { 301 return fIconTarPtrs.Get(HashString(pkgName)); 302 } 303 304 305 const char* 306 PackageIconTarRepository::_ToIconCacheKeySuffix(BitmapSize size) 307 { 308 switch (size) 309 { 310 case BITMAP_SIZE_16: 311 return "16"; 312 // note that size 22 is not supported. 313 case BITMAP_SIZE_32: 314 return "32"; 315 case BITMAP_SIZE_64: 316 return "64"; 317 case BITMAP_SIZE_ANY: 318 return "any"; 319 default: 320 HDFATAL("unsupported bitmap size"); 321 break; 322 } 323 } 324 325 326 const HashString 327 PackageIconTarRepository::_ToIconCacheKey(const BString& pkgName, BitmapSize size) 328 { 329 return HashString(BString(pkgName) << "__x" << _ToIconCacheKeySuffix(size)); 330 } 331 332 333 /*! This method must only be invoked with the class locked. 334 */ 335 status_t 336 PackageIconTarRepository::_CreateIconFromTarOffset(off_t offset, BitmapHolderRef& bitmapHolderRef) 337 { 338 fTarIo->Seek(offset, SEEK_SET); 339 fIconDataBuffer->Seek(0, SEEK_SET); 340 341 TarArchiveHeader header; 342 status_t result = TarArchiveService::GetEntry(*fTarIo, header); 343 344 if (result == B_OK && (header.Length() <= 0 || header.Length() > ICON_BUFFER_SIZE_MAX)) { 345 HDERROR("the icon tar entry at %" B_PRIdOFF " has a bad length %" B_PRIdSSIZE, offset, 346 header.Length()); 347 result = B_BAD_DATA; 348 } 349 350 off_t iconDataBufferSize = 0; 351 352 if (result == B_OK) 353 result = fIconDataBuffer->GetSize(&iconDataBufferSize); 354 355 if (result == B_OK && static_cast<size_t>(iconDataBufferSize) < header.Length()) 356 result = fIconDataBuffer->SetSize(header.Length()); 357 358 if (result == B_OK) { 359 BDataIO* tarImageIO = new ConstraintedDataIO(fTarIo, header.Length()); 360 result = DataIOUtils::CopyAll(fIconDataBuffer, tarImageIO); 361 delete tarImageIO; 362 } else { 363 HDERROR("unable to extract data from tar for icon image"); 364 } 365 366 fIconDataBuffer->Seek(0, SEEK_SET); 367 368 if (result == B_OK) { 369 BBitmap* bitmap = BTranslationUtils::GetBitmap(fIconDataBuffer); 370 371 if (bitmap == NULL) { 372 HDERROR("unable to decode data from tar for icon image"); 373 result = B_ERROR; 374 } else { 375 bitmapHolderRef.SetTo(new(std::nothrow) BitmapHolder(bitmap), true); 376 } 377 } 378 379 return result; 380 } 381 382 383 /*! If there is a vector representation (HVIF) then this will be the best 384 option. If there are only bitmap images to choose from then consider what 385 the target size is and choose the best image to match. 386 */ 387 /*static*/ BitmapSize 388 PackageIconTarRepository::_BestStoredSize(const IconTarPtrRef iconTarPtrRef, int32 desiredSize) 389 { 390 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_ANY)) 391 return BITMAP_SIZE_ANY; 392 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_64) && desiredSize >= 64) 393 return BITMAP_SIZE_64; 394 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32) && desiredSize >= 32) 395 return BITMAP_SIZE_32; 396 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32)) 397 return BITMAP_SIZE_16; 398 HDFATAL("unable to get the best stored icon for size"); 399 } 400 401 402 IconTarPtrRef 403 PackageIconTarRepository::_GetOrCreateIconTarPtr(const BString& pkgName) 404 { 405 BAutolock locker(&fLock); 406 HashString key(pkgName); 407 if (!fIconTarPtrs.ContainsKey(key)) { 408 IconTarPtrRef value(new IconTarPtr(pkgName)); 409 fIconTarPtrs.Put(key, value); 410 return value; 411 } 412 return fIconTarPtrs.Get(key); 413 } 414 415 416 status_t 417 PackageIconTarRepository::_GetDefaultIcon(uint32 size, BitmapHolderRef& bitmapHolderRef) 418 { 419 if (fDefaultIconVectorData == NULL) 420 return B_NOT_INITIALIZED; 421 422 bitmapHolderRef.Unset(); 423 424 status_t status = B_OK; 425 HashString key(BString() << size); 426 427 if (!fDefaultIconCache.ContainsKey(key)) { 428 BBitmap* bitmap = NULL; 429 430 if (status == B_OK) { 431 bitmap = new BBitmap(BRect(0, 0, size - 1, size - 1), 0, B_RGBA32); 432 status = bitmap->InitCheck(); 433 } 434 435 if (status == B_OK) { 436 status = BIconUtils::GetVectorIcon(fDefaultIconVectorData, fDefaultIconVectorDataSize, 437 bitmap); 438 } 439 440 if (status == B_OK) { 441 HDINFO("did create default package icon size %" B_PRId32, size); 442 BitmapHolderRef bitmapHolder(new(std::nothrow) BitmapHolder(bitmap), true); 443 fDefaultIconCache.Put(key, bitmapHolder); 444 } else { 445 delete bitmap; 446 bitmap = NULL; 447 } 448 } 449 450 if (status == B_OK) 451 bitmapHolderRef.SetTo(fDefaultIconCache.Get(key).Get()); 452 else 453 HDERROR("failed to create default package icon size %" B_PRId32, size); 454 455 return status; 456 } 457 458 459 void 460 PackageIconTarRepository::_InitDefaultVectorIcon() 461 { 462 if (fDefaultIconVectorData != NULL) { 463 delete fDefaultIconVectorData; 464 fDefaultIconVectorData = NULL; 465 } 466 467 fDefaultIconVectorDataSize = 0; 468 469 BMimeType mimeType("application/x-vnd.haiku-package"); 470 status_t status = mimeType.InitCheck(); 471 472 uint8* data; 473 size_t dataSize; 474 475 if (status == B_OK) 476 status = mimeType.GetIcon(&data, &dataSize); 477 478 if (status == B_OK) { 479 fDefaultIconVectorData = new(std::nothrow) uint8[dataSize]; 480 481 if (fDefaultIconVectorData == NULL) 482 HDFATAL("unable to allocate memory for the default icon"); 483 484 memcpy(fDefaultIconVectorData, data, dataSize); 485 fDefaultIconVectorDataSize = dataSize; 486 } else { 487 fDefaultIconVectorData = NULL; 488 fDefaultIconVectorDataSize = 0; 489 } 490 } 491