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