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