1 /* 2 * Copyright 2020-2021, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "PackageIconTarRepository.h" 8 9 #include <Autolock.h> 10 #include <AutoDeleter.h> 11 #include <File.h> 12 #include <StopWatch.h> 13 14 #include "Logger.h" 15 #include "TarArchiveService.h" 16 17 18 #define LIMIT_ICON_CACHE 50 19 20 21 BitmapRef 22 PackageIconTarRepository::sDefaultIcon(new(std::nothrow) SharedBitmap( 23 "application/x-vnd.haiku-package"), true); 24 25 26 /*! An instance of this class can be provided to the TarArchiveService to 27 be called each time a tar entry is found. This way it is able to capture 28 the offsets of the icons in the tar file against the package names. 29 */ 30 31 class IconTarPtrEntryListener : public TarEntryListener 32 { 33 public: 34 IconTarPtrEntryListener( 35 PackageIconTarRepository* 36 fPackageIconTarRepository); 37 virtual ~IconTarPtrEntryListener(); 38 39 virtual status_t Handle( 40 const TarArchiveHeader& header, 41 size_t offset, 42 BDataIO *data); 43 private: 44 status_t _LeafNameToBitmapSize(BString& leafName, 45 BitmapSize* bitmapSize); 46 47 private: 48 PackageIconTarRepository* 49 fPackageIconTarRepository; 50 }; 51 52 53 IconTarPtrEntryListener::IconTarPtrEntryListener( 54 PackageIconTarRepository* packageIconTarRepository) 55 : 56 fPackageIconTarRepository(packageIconTarRepository) 57 { 58 } 59 60 61 IconTarPtrEntryListener::~IconTarPtrEntryListener() 62 { 63 } 64 65 66 /*! The format of the filenames in the archive are of the format; 67 \code 68 hicn/ardino/icon.hvif 69 \endcode 70 The leafname (the last part of the path) determines the type of the file as 71 it could be either in HVIF format or a PNG of various sizes. 72 */ 73 74 status_t 75 IconTarPtrEntryListener::Handle(const TarArchiveHeader& header, 76 size_t offset, BDataIO *data) 77 { 78 if (header.FileType() != TAR_FILE_TYPE_NORMAL) 79 return B_OK; 80 81 BString fileName = header.FileName(); 82 if (!fileName.StartsWith("hicn/")) 83 return B_OK; 84 85 int32 secondSlashIdx = fileName.FindFirst("/", 5); 86 if (secondSlashIdx == B_ERROR || secondSlashIdx == 5) 87 return B_OK; 88 89 BString packageName; 90 BString leafName; 91 fileName.CopyInto(packageName, 5, secondSlashIdx - 5); 92 fileName.CopyInto(leafName, secondSlashIdx + 1, 93 fileName.Length() - (secondSlashIdx + 1)); 94 BitmapSize bitmapSize; 95 96 if (_LeafNameToBitmapSize(leafName, &bitmapSize) == B_OK) { 97 fPackageIconTarRepository->AddIconTarPtr(packageName, bitmapSize, 98 offset); 99 } 100 101 return B_OK; 102 } 103 104 105 status_t 106 IconTarPtrEntryListener::_LeafNameToBitmapSize(BString& leafName, 107 BitmapSize* bitmapSize) 108 { 109 if (leafName == "icon.hvif") { 110 *bitmapSize = BITMAP_SIZE_ANY; 111 return B_OK; 112 } 113 if (leafName == "64.png") { 114 *bitmapSize = BITMAP_SIZE_64; 115 return B_OK; 116 } 117 if (leafName == "32.png") { 118 *bitmapSize = BITMAP_SIZE_32; 119 return B_OK; 120 } 121 if (leafName == "16.png") { 122 *bitmapSize = BITMAP_SIZE_16; 123 return B_OK; 124 } 125 return B_BAD_VALUE; 126 } 127 128 129 PackageIconTarRepository::PackageIconTarRepository() 130 : 131 fTarIo(NULL), 132 fIconCache(LIMIT_ICON_CACHE) 133 { 134 } 135 136 137 PackageIconTarRepository::~PackageIconTarRepository() 138 { 139 } 140 141 142 void 143 PackageIconTarRepository::Clear() { 144 BAutolock locker(&fLock); 145 fIconCache.Clear(); 146 } 147 148 149 /*! This method will reconfigure itself using the data in the tar file supplied. 150 Any existing data will be flushed and the new tar will be scanned for 151 offsets to usable files. 152 */ 153 154 status_t 155 PackageIconTarRepository::Init(BPath& tarPath) 156 { 157 BAutolock locker(&fLock); 158 _Close(); 159 status_t result = B_OK; 160 161 if (tarPath.Path() == NULL) { 162 HDINFO("empty path to tar-ball"); 163 result = B_BAD_VALUE; 164 } 165 166 BFile *tarIo = NULL; 167 168 if (result == B_OK) { 169 HDINFO("will init icon model from tar [%s]", tarPath.Path()); 170 tarIo = new BFile(tarPath.Path(), O_RDONLY); 171 172 if (!tarIo->IsReadable()) { 173 HDERROR("unable to read the tar [%s]", tarPath.Path()); 174 result = B_IO_ERROR; 175 } 176 } 177 178 // will fill the model up with records from the tar-ball. 179 180 if (result == B_OK) { 181 BStopWatch watch("PackageIconTarRepository::Init", true); 182 HDINFO("will read [%s] and collect the tar pointers", tarPath.Path()); 183 184 IconTarPtrEntryListener* listener = new IconTarPtrEntryListener(this); 185 ObjectDeleter<IconTarPtrEntryListener> listenerDeleter(listener); 186 TarArchiveService::ForEachEntry(*tarIo, listener); 187 188 double secs = watch.ElapsedTime() / 1000000.0; 189 HDINFO("did collect %" B_PRIi32 " tar pointers (%6.3g secs)", 190 fIconTarPtrs.Size(), secs); 191 } 192 193 if (result == B_OK) 194 fTarIo = tarIo; 195 else 196 delete tarIo; 197 198 return result; 199 } 200 201 202 void 203 PackageIconTarRepository::_Close() 204 { 205 fIconCache.Clear(); 206 delete fTarIo; 207 fTarIo = NULL; 208 fIconTarPtrs.Clear(); 209 } 210 211 212 /*! This method should be treated private and only called from a situation 213 in which the class's lock is acquired. It is used to populate data from 214 the parsing of the tar headers. It is called from the listener above. 215 */ 216 217 void 218 PackageIconTarRepository::AddIconTarPtr(const BString& packageName, 219 BitmapSize bitmapSize, off_t offset) 220 { 221 IconTarPtrRef tarPtrRef = _GetOrCreateIconTarPtr(packageName); 222 tarPtrRef->SetOffset(bitmapSize, offset); 223 } 224 225 226 bool 227 PackageIconTarRepository::HasAnyIcon(const BString& pkgName) 228 { 229 BAutolock locker(&fLock); 230 HashString key(pkgName); 231 return fIconTarPtrs.ContainsKey(key); 232 } 233 234 235 status_t 236 PackageIconTarRepository::GetIcon(const BString& pkgName, BitmapSize size, 237 BitmapRef& bitmap) 238 { 239 BAutolock locker(&fLock); 240 status_t result = B_OK; 241 BitmapSize actualSize; 242 off_t iconDataTarOffset = -1; 243 const IconTarPtrRef tarPtrRef = _GetIconTarPtr(pkgName); 244 245 if (tarPtrRef.IsSet()) { 246 iconDataTarOffset = _OffsetToBestRepresentation(tarPtrRef, size, 247 &actualSize); 248 } 249 250 if (iconDataTarOffset < 0) 251 bitmap.SetTo(sDefaultIcon); 252 else { 253 HashString key = _ToIconCacheKey(pkgName, actualSize); 254 255 if (!fIconCache.ContainsKey(key)) { 256 result = _CreateIconFromTarOffset(iconDataTarOffset, bitmap); 257 if (result == B_OK) 258 fIconCache.Put(key, bitmap); 259 else { 260 HDERROR("failure to read image for package [%s] at offset %" 261 B_PRIdOFF, pkgName.String(), iconDataTarOffset); 262 fIconCache.Put(key, sDefaultIcon); 263 } 264 } 265 bitmap.SetTo(fIconCache.Get(key).Get()); 266 } 267 return result; 268 } 269 270 271 IconTarPtrRef 272 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const 273 { 274 return fIconTarPtrs.Get(HashString(pkgName)); 275 } 276 277 278 const char* 279 PackageIconTarRepository::_ToIconCacheKeySuffix(BitmapSize size) 280 { 281 switch (size) 282 { 283 case BITMAP_SIZE_16: 284 return "16"; 285 // note that size 22 is not supported. 286 case BITMAP_SIZE_32: 287 return "32"; 288 case BITMAP_SIZE_64: 289 return "64"; 290 case BITMAP_SIZE_ANY: 291 return "any"; 292 default: 293 HDFATAL("unsupported bitmap size"); 294 break; 295 } 296 } 297 298 299 const HashString 300 PackageIconTarRepository::_ToIconCacheKey(const BString& pkgName, 301 BitmapSize size) 302 { 303 return HashString(BString(pkgName) << "__x" << _ToIconCacheKeySuffix(size)); 304 } 305 306 307 status_t 308 PackageIconTarRepository::_CreateIconFromTarOffset(off_t offset, 309 BitmapRef& bitmap) 310 { 311 fTarIo->Seek(offset, SEEK_SET); 312 TarArchiveHeader header; 313 status_t result = TarArchiveService::GetEntry(*fTarIo, header); 314 315 if (result == B_OK && header.Length() <= 0) 316 result = B_BAD_DATA; 317 318 if (result == B_OK) 319 bitmap.SetTo(new(std::nothrow)SharedBitmap(*fTarIo, header.Length())); 320 321 return result; 322 } 323 324 325 /*! If there is a vector representation (HVIF) then this will be the best 326 option. If there are only bitmap images to choose from then consider what 327 the target size is and choose the best image to match. 328 */ 329 330 /*static*/ off_t 331 PackageIconTarRepository::_OffsetToBestRepresentation( 332 const IconTarPtrRef iconTarPtrRef, BitmapSize desiredSize, 333 BitmapSize* actualSize) 334 { 335 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_ANY)) { 336 *actualSize = BITMAP_SIZE_ANY; 337 return iconTarPtrRef->Offset(BITMAP_SIZE_ANY); 338 } 339 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_64) 340 && desiredSize >= BITMAP_SIZE_64) { 341 *actualSize = BITMAP_SIZE_64; 342 return iconTarPtrRef->Offset(BITMAP_SIZE_64); 343 } 344 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32) 345 && desiredSize >= BITMAP_SIZE_32) { 346 *actualSize = BITMAP_SIZE_32; 347 return iconTarPtrRef->Offset(BITMAP_SIZE_32); 348 } 349 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_22) 350 && desiredSize >= BITMAP_SIZE_22) { 351 *actualSize = BITMAP_SIZE_22; 352 return iconTarPtrRef->Offset(BITMAP_SIZE_22); 353 } 354 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_16)) { 355 *actualSize = BITMAP_SIZE_16; 356 return iconTarPtrRef->Offset(BITMAP_SIZE_16); 357 } 358 return -1; 359 } 360 361 362 IconTarPtrRef 363 PackageIconTarRepository::_GetOrCreateIconTarPtr(const BString& pkgName) 364 { 365 BAutolock locker(&fLock); 366 HashString key(pkgName); 367 if (!fIconTarPtrs.ContainsKey(key)) { 368 IconTarPtrRef value(new IconTarPtr(pkgName)); 369 fIconTarPtrs.Put(key, value); 370 return value; 371 } 372 return fIconTarPtrs.Get(key); 373 } 374 375 376 /*static*/ void 377 PackageIconTarRepository::CleanupDefaultIcon() 378 { 379 sDefaultIcon.Unset(); 380 } 381