xref: /haiku/src/apps/haikudepot/model/PackageIconTarRepository.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
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 	_InitDefaultVectorIcon();
134 }
135 
136 
137 PackageIconTarRepository::~PackageIconTarRepository()
138 {
139 	delete fIconDataBuffer;
140 	delete fDefaultIconVectorData;
141 }
142 
143 
144 void
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
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
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
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
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
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
292 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const
293 {
294 	return fIconTarPtrs.Get(HashString(pkgName));
295 }
296 
297 
298 const char*
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
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
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
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
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
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
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