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 #include "PackageIconTarRepository.h" 6 7 #include <Autolock.h> 8 #include <AutoDeleter.h> 9 #include <File.h> 10 #include <StopWatch.h> 11 12 #include "Logger.h" 13 #include "TarArchiveService.h" 14 15 16 #define LIMIT_ICON_CACHE 50 17 18 19 BitmapRef 20 PackageIconTarRepository::sDefaultIcon(new(std::nothrow) SharedBitmap( 21 "application/x-vnd.haiku-package"), true); 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 { 132 } 133 134 135 PackageIconTarRepository::~PackageIconTarRepository() 136 { 137 } 138 139 140 void 141 PackageIconTarRepository::Clear() { 142 BAutolock locker(&fLock); 143 fIconCache.Clear(); 144 } 145 146 147 /*! This method will reconfigure itself using the data in the tar file supplied. 148 Any existing data will be flushed and the new tar will be scanned for 149 offsets to usable files. 150 */ 151 152 status_t 153 PackageIconTarRepository::Init(BPath& tarPath) 154 { 155 BAutolock locker(&fLock); 156 _Close(); 157 status_t result = B_OK; 158 159 if (tarPath.Path() == NULL) { 160 HDINFO("empty path to tar-ball"); 161 result = B_BAD_VALUE; 162 } 163 164 BFile *tarIo = NULL; 165 166 if (result == B_OK) { 167 HDINFO("will init icon model from tar [%s]", tarPath.Path()); 168 tarIo = new BFile(tarPath.Path(), O_RDONLY); 169 170 if (!tarIo->IsReadable()) { 171 HDERROR("unable to read the tar [%s]", tarPath.Path()); 172 result = B_IO_ERROR; 173 } 174 } 175 176 // will fill the model up with records from the tar-ball. 177 178 if (result == B_OK) { 179 BStopWatch watch("PackageIconTarRepository::Init", true); 180 HDINFO("will read [%s] and collect the tar pointers", tarPath.Path()); 181 182 IconTarPtrEntryListener* listener = new IconTarPtrEntryListener(this); 183 ObjectDeleter<IconTarPtrEntryListener> listenerDeleter(listener); 184 TarArchiveService::ForEachEntry(*tarIo, listener); 185 186 double secs = watch.ElapsedTime() / 1000000.0; 187 HDINFO("did collect %" B_PRIi32 " tar pointers (%6.3g secs)", 188 fIconTarPtrs.Size(), secs); 189 } 190 191 if (result == B_OK) 192 fTarIo = tarIo; 193 else 194 delete tarIo; 195 196 return result; 197 } 198 199 200 void 201 PackageIconTarRepository::_Close() 202 { 203 fIconCache.Clear(); 204 delete fTarIo; 205 fTarIo = NULL; 206 fIconTarPtrs.Clear(); 207 } 208 209 210 /*! This method should be treated private and only called from a situation 211 in which the class's lock is acquired. It is used to populate data from 212 the parsing of the tar headers. It is called from the listener above. 213 */ 214 215 void 216 PackageIconTarRepository::AddIconTarPtr(const BString& packageName, 217 BitmapSize bitmapSize, off_t offset) 218 { 219 IconTarPtrRef tarPtrRef = _GetOrCreateIconTarPtr(packageName); 220 tarPtrRef->SetOffset(bitmapSize, offset); 221 } 222 223 224 bool 225 PackageIconTarRepository::HasAnyIcon(const BString& pkgName) 226 { 227 BAutolock locker(&fLock); 228 HashString key(pkgName); 229 return fIconTarPtrs.ContainsKey(key); 230 } 231 232 233 status_t 234 PackageIconTarRepository::GetIcon(const BString& pkgName, BitmapSize size, 235 BitmapRef& bitmap) 236 { 237 BAutolock locker(&fLock); 238 status_t result = B_OK; 239 BitmapSize actualSize; 240 off_t iconDataTarOffset = -1; 241 const IconTarPtrRef tarPtrRef = _GetIconTarPtr(pkgName); 242 243 if (tarPtrRef.IsSet()) { 244 iconDataTarOffset = _OffsetToBestRepresentation(tarPtrRef, size, 245 &actualSize); 246 } 247 248 if (iconDataTarOffset < 0) 249 bitmap.SetTo(sDefaultIcon); 250 else { 251 HashString key = _ToIconCacheKey(pkgName, actualSize); 252 253 if (!fIconCache.ContainsKey(key)) { 254 result = _CreateIconFromTarOffset(iconDataTarOffset, bitmap); 255 if (result == B_OK) 256 fIconCache.Put(key, bitmap); 257 else { 258 HDERROR("failure to read image for package [%s] at offset %" 259 B_PRIdOFF, pkgName.String(), iconDataTarOffset); 260 fIconCache.Put(key, sDefaultIcon); 261 } 262 } 263 bitmap.SetTo(fIconCache.Get(key).Get()); 264 } 265 return result; 266 } 267 268 269 IconTarPtrRef 270 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const 271 { 272 return fIconTarPtrs.Get(HashString(pkgName)); 273 } 274 275 276 const char* 277 PackageIconTarRepository::_ToIconCacheKeySuffix(BitmapSize size) 278 { 279 switch (size) 280 { 281 case BITMAP_SIZE_16: 282 return "16"; 283 // note that size 22 is not supported. 284 case BITMAP_SIZE_32: 285 return "32"; 286 case BITMAP_SIZE_64: 287 return "64"; 288 case BITMAP_SIZE_ANY: 289 return "any"; 290 default: 291 HDFATAL("unsupported bitmap size"); 292 break; 293 } 294 } 295 296 297 const HashString 298 PackageIconTarRepository::_ToIconCacheKey(const BString& pkgName, 299 BitmapSize size) 300 { 301 return HashString(BString(pkgName) << "__x" << _ToIconCacheKeySuffix(size)); 302 } 303 304 305 status_t 306 PackageIconTarRepository::_CreateIconFromTarOffset(off_t offset, 307 BitmapRef& bitmap) 308 { 309 fTarIo->Seek(offset, SEEK_SET); 310 TarArchiveHeader header; 311 status_t result = TarArchiveService::GetEntry(*fTarIo, header); 312 313 if (result == B_OK && header.Length() <= 0) 314 result = B_BAD_DATA; 315 316 if (result == B_OK) 317 bitmap.SetTo(new(std::nothrow)SharedBitmap(*fTarIo, header.Length())); 318 319 return result; 320 } 321 322 323 /*! If there is a vector representation (HVIF) then this will be the best 324 option. If there are only bitmap images to choose from then consider what 325 the target size is and choose the best image to match. 326 */ 327 328 /*static*/ off_t 329 PackageIconTarRepository::_OffsetToBestRepresentation( 330 const IconTarPtrRef iconTarPtrRef, BitmapSize desiredSize, 331 BitmapSize* actualSize) 332 { 333 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_ANY)) { 334 *actualSize = BITMAP_SIZE_ANY; 335 return iconTarPtrRef->Offset(BITMAP_SIZE_ANY); 336 } 337 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_64) 338 && desiredSize >= BITMAP_SIZE_64) { 339 *actualSize = BITMAP_SIZE_64; 340 return iconTarPtrRef->Offset(BITMAP_SIZE_64); 341 } 342 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32) 343 && desiredSize >= BITMAP_SIZE_32) { 344 *actualSize = BITMAP_SIZE_32; 345 return iconTarPtrRef->Offset(BITMAP_SIZE_32); 346 } 347 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_22) 348 && desiredSize >= BITMAP_SIZE_22) { 349 *actualSize = BITMAP_SIZE_22; 350 return iconTarPtrRef->Offset(BITMAP_SIZE_22); 351 } 352 if (iconTarPtrRef->HasOffset(BITMAP_SIZE_16)) { 353 *actualSize = BITMAP_SIZE_16; 354 return iconTarPtrRef->Offset(BITMAP_SIZE_16); 355 } 356 return -1; 357 } 358 359 360 IconTarPtrRef 361 PackageIconTarRepository::_GetOrCreateIconTarPtr(const BString& pkgName) 362 { 363 BAutolock locker(&fLock); 364 HashString key(pkgName); 365 if (!fIconTarPtrs.ContainsKey(key)) { 366 IconTarPtrRef value(new IconTarPtr(pkgName)); 367 fIconTarPtrs.Put(key, value); 368 return value; 369 } 370 return fIconTarPtrs.Get(key); 371 } 372 373 374 /*static*/ void 375 PackageIconTarRepository::CleanupDefaultIcon() 376 { 377 sDefaultIcon.Unset(); 378 } 379