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
IconTarPtrEntryListener(PackageIconTarRepository * packageIconTarRepository)51 IconTarPtrEntryListener::IconTarPtrEntryListener(
52 PackageIconTarRepository* packageIconTarRepository)
53 :
54 fPackageIconTarRepository(packageIconTarRepository)
55 {
56 }
57
58
~IconTarPtrEntryListener()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
Handle(const TarArchiveHeader & header,size_t offset,BDataIO * data)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
_LeafNameToBitmapSize(BString & leafName,BitmapSize * storedSize)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
PackageIconTarRepository()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
~PackageIconTarRepository()137 PackageIconTarRepository::~PackageIconTarRepository()
138 {
139 delete fIconDataBuffer;
140 delete fDefaultIconVectorData;
141 }
142
143
144 void
Clear()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
Init(BPath & tarPath)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
_Close()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
AddIconTarPtr(const BString & packageName,BitmapSize storedSize,off_t offset)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
HasAnyIcon(const BString & pkgName)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
GetIcon(const BString & pkgName,uint32 size,BitmapHolderRef & bitmapHolderRef)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
_GetIconTarPtr(const BString & pkgName) const292 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const
293 {
294 return fIconTarPtrs.Get(HashString(pkgName));
295 }
296
297
298 const char*
_ToIconCacheKeyPart(BitmapSize storedSize)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
_ToIconCacheKey(const BString & pkgName,BitmapSize storedSize,uint32 size)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
_CreateIconFromTarOffset(off_t offset,BitmapSize storedSize,uint32 size,BitmapHolderRef & bitmapHolderRef)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
_BestStoredSize(const IconTarPtrRef iconTarPtrRef,int32 desiredSize)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
_GetOrCreateIconTarPtr(const BString & pkgName)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
_GetDefaultIcon(uint32 size,BitmapHolderRef & bitmapHolderRef)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
_InitDefaultVectorIcon()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