xref: /haiku/src/apps/haikudepot/model/PackageIconTarRepository.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
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