xref: /haiku/src/apps/haikudepot/model/PackageIconTarRepository.cpp (revision 3634f142352af2428aed187781fc9d75075e9140)
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 
6 
7 #include "PackageIconTarRepository.h"
8 
9 #include <Autolock.h>
10 #include <AutoDeleter.h>
11 #include <File.h>
12 #include <StopWatch.h>
13 
14 #include "Logger.h"
15 #include "TarArchiveService.h"
16 
17 
18 #define LIMIT_ICON_CACHE 50
19 
20 
21 BitmapRef
22 PackageIconTarRepository::sDefaultIcon(new(std::nothrow) SharedBitmap(
23 	"application/x-vnd.haiku-package"), true);
24 
25 
26 /*!	An instance of this class can be provided to the TarArchiveService to
27 	be called each time a tar entry is found.  This way it is able to capture
28 	the offsets of the icons in the tar file against the package names.
29 */
30 
31 class IconTarPtrEntryListener : public TarEntryListener
32 {
33 public:
34 								IconTarPtrEntryListener(
35 									PackageIconTarRepository*
36 									fPackageIconTarRepository);
37 	virtual						~IconTarPtrEntryListener();
38 
39 	virtual status_t			Handle(
40 									const TarArchiveHeader& header,
41 									size_t offset,
42 									BDataIO *data);
43 private:
44 			status_t			_LeafNameToBitmapSize(BString& leafName,
45 									BitmapSize* bitmapSize);
46 
47 private:
48 			PackageIconTarRepository*
49 								fPackageIconTarRepository;
50 };
51 
52 
53 IconTarPtrEntryListener::IconTarPtrEntryListener(
54 	PackageIconTarRepository* packageIconTarRepository)
55 	:
56 	fPackageIconTarRepository(packageIconTarRepository)
57 {
58 }
59 
60 
61 IconTarPtrEntryListener::~IconTarPtrEntryListener()
62 {
63 }
64 
65 
66 /*!	The format of the filenames in the archive are of the format;
67 	\code
68 	hicn/ardino/icon.hvif
69 	\endcode
70 	The leafname (the last part of the path) determines the type of the file as
71 	it could be either in HVIF format or a PNG of various sizes.
72 */
73 
74 status_t
75 IconTarPtrEntryListener::Handle(const TarArchiveHeader& header,
76 	size_t offset, BDataIO *data)
77 {
78 	if (header.FileType() != TAR_FILE_TYPE_NORMAL)
79 		return B_OK;
80 
81 	BString fileName = header.FileName();
82 	if (!fileName.StartsWith("hicn/"))
83 		return B_OK;
84 
85 	int32 secondSlashIdx = fileName.FindFirst("/", 5);
86 	if (secondSlashIdx == B_ERROR || secondSlashIdx == 5)
87 		return B_OK;
88 
89 	BString packageName;
90 	BString leafName;
91 	fileName.CopyInto(packageName, 5, secondSlashIdx - 5);
92 	fileName.CopyInto(leafName, secondSlashIdx + 1,
93 		fileName.Length() - (secondSlashIdx + 1));
94 	BitmapSize bitmapSize;
95 
96 	if (_LeafNameToBitmapSize(leafName, &bitmapSize) == B_OK) {
97 		fPackageIconTarRepository->AddIconTarPtr(packageName, bitmapSize,
98 			offset);
99 	}
100 
101 	return B_OK;
102 }
103 
104 
105 status_t
106 IconTarPtrEntryListener::_LeafNameToBitmapSize(BString& leafName,
107 	BitmapSize* bitmapSize)
108 {
109 	if (leafName == "icon.hvif") {
110 		*bitmapSize = BITMAP_SIZE_ANY;
111 		return B_OK;
112 	}
113 	if (leafName == "64.png") {
114 		*bitmapSize = BITMAP_SIZE_64;
115 		return B_OK;
116 	}
117 	if (leafName == "32.png") {
118 		*bitmapSize = BITMAP_SIZE_32;
119 		return B_OK;
120 	}
121 	if (leafName == "16.png") {
122 		*bitmapSize = BITMAP_SIZE_16;
123 		return B_OK;
124 	}
125 	return B_BAD_VALUE;
126 }
127 
128 
129 PackageIconTarRepository::PackageIconTarRepository()
130 	:
131 	fTarIo(NULL),
132 	fIconCache(LIMIT_ICON_CACHE)
133 {
134 }
135 
136 
137 PackageIconTarRepository::~PackageIconTarRepository()
138 {
139 }
140 
141 
142 void
143 PackageIconTarRepository::Clear() {
144 	BAutolock locker(&fLock);
145 	fIconCache.Clear();
146 }
147 
148 
149 /*!	This method will reconfigure itself using the data in the tar file supplied.
150 	Any existing data will be flushed and the new tar will be scanned for
151 	offsets to usable files.
152 */
153 
154 status_t
155 PackageIconTarRepository::Init(BPath& tarPath)
156 {
157 	BAutolock locker(&fLock);
158 	_Close();
159 	status_t result = B_OK;
160 
161 	if (tarPath.Path() == NULL) {
162 		HDINFO("empty path to tar-ball");
163 		result = B_BAD_VALUE;
164 	}
165 
166 	BFile *tarIo = NULL;
167 
168 	if (result == B_OK) {
169 		HDINFO("will init icon model from tar [%s]", tarPath.Path());
170 		tarIo = new BFile(tarPath.Path(), O_RDONLY);
171 
172 		if (!tarIo->IsReadable()) {
173 			HDERROR("unable to read the tar [%s]", tarPath.Path());
174 			result = B_IO_ERROR;
175 		}
176 	}
177 
178 	// will fill the model up with records from the tar-ball.
179 
180 	if (result == B_OK) {
181 		BStopWatch watch("PackageIconTarRepository::Init", true);
182 		HDINFO("will read [%s] and collect the tar pointers", tarPath.Path());
183 
184 		IconTarPtrEntryListener* listener = new IconTarPtrEntryListener(this);
185 		ObjectDeleter<IconTarPtrEntryListener> listenerDeleter(listener);
186 		TarArchiveService::ForEachEntry(*tarIo, listener);
187 
188 		double secs = watch.ElapsedTime() / 1000000.0;
189 		HDINFO("did collect %" B_PRIi32 " tar pointers (%6.3g secs)",
190 			fIconTarPtrs.Size(), secs);
191 	}
192 
193 	if (result == B_OK)
194 		fTarIo = tarIo;
195 	else
196 		delete tarIo;
197 
198 	return result;
199 }
200 
201 
202 void
203 PackageIconTarRepository::_Close()
204 {
205 	fIconCache.Clear();
206 	delete fTarIo;
207 	fTarIo = NULL;
208 	fIconTarPtrs.Clear();
209 }
210 
211 
212 /*!	This method should be treated private and only called from a situation
213 	in which the class's lock is acquired.  It is used to populate data from
214 	the parsing of the tar headers.  It is called from the listener above.
215 */
216 
217 void
218 PackageIconTarRepository::AddIconTarPtr(const BString& packageName,
219 	BitmapSize bitmapSize, off_t offset)
220 {
221 	IconTarPtrRef tarPtrRef = _GetOrCreateIconTarPtr(packageName);
222 	tarPtrRef->SetOffset(bitmapSize, offset);
223 }
224 
225 
226 bool
227 PackageIconTarRepository::HasAnyIcon(const BString& pkgName)
228 {
229 	BAutolock locker(&fLock);
230 	HashString key(pkgName);
231 	return fIconTarPtrs.ContainsKey(key);
232 }
233 
234 
235 status_t
236 PackageIconTarRepository::GetIcon(const BString& pkgName, BitmapSize size,
237 	BitmapRef& bitmap)
238 {
239 	BAutolock locker(&fLock);
240 	status_t result = B_OK;
241 	BitmapSize actualSize;
242 	off_t iconDataTarOffset = -1;
243 	const IconTarPtrRef tarPtrRef = _GetIconTarPtr(pkgName);
244 
245 	if (tarPtrRef.IsSet()) {
246 		iconDataTarOffset = _OffsetToBestRepresentation(tarPtrRef, size,
247 			&actualSize);
248 	}
249 
250 	if (iconDataTarOffset < 0)
251 		bitmap.SetTo(sDefaultIcon);
252 	else {
253 		HashString key = _ToIconCacheKey(pkgName, actualSize);
254 
255 		if (!fIconCache.ContainsKey(key)) {
256 			result = _CreateIconFromTarOffset(iconDataTarOffset, bitmap);
257 			if (result == B_OK)
258 				fIconCache.Put(key, bitmap);
259 			else {
260 				HDERROR("failure to read image for package [%s] at offset %"
261 					B_PRIdOFF, pkgName.String(), iconDataTarOffset);
262 				fIconCache.Put(key, sDefaultIcon);
263 			}
264 		}
265 		bitmap.SetTo(fIconCache.Get(key).Get());
266 	}
267 	return result;
268 }
269 
270 
271 IconTarPtrRef
272 PackageIconTarRepository::_GetIconTarPtr(const BString& pkgName) const
273 {
274 	return fIconTarPtrs.Get(HashString(pkgName));
275 }
276 
277 
278 const char*
279 PackageIconTarRepository::_ToIconCacheKeySuffix(BitmapSize size)
280 {
281 	switch (size)
282 	{
283 		case BITMAP_SIZE_16:
284 			return "16";
285 		// note that size 22 is not supported.
286 		case BITMAP_SIZE_32:
287 			return "32";
288 		case BITMAP_SIZE_64:
289 			return "64";
290 		case BITMAP_SIZE_ANY:
291 			return "any";
292 		default:
293 			HDFATAL("unsupported bitmap size");
294 			break;
295 	}
296 }
297 
298 
299 const HashString
300 PackageIconTarRepository::_ToIconCacheKey(const BString& pkgName,
301 	BitmapSize size)
302 {
303 	return HashString(BString(pkgName) << "__x" << _ToIconCacheKeySuffix(size));
304 }
305 
306 
307 status_t
308 PackageIconTarRepository::_CreateIconFromTarOffset(off_t offset,
309 	BitmapRef& bitmap)
310 {
311 	fTarIo->Seek(offset, SEEK_SET);
312 	TarArchiveHeader header;
313 	status_t result = TarArchiveService::GetEntry(*fTarIo, header);
314 
315 	if (result == B_OK && header.Length() <= 0)
316 		result = B_BAD_DATA;
317 
318 	if (result == B_OK)
319 		bitmap.SetTo(new(std::nothrow)SharedBitmap(*fTarIo, header.Length()));
320 
321 	return result;
322 }
323 
324 
325 /*!	If there is a vector representation (HVIF) then this will be the best
326 	option.  If there are only bitmap images to choose from then consider what
327 	the target size is and choose the best image to match.
328 */
329 
330 /*static*/ off_t
331 PackageIconTarRepository::_OffsetToBestRepresentation(
332 	const IconTarPtrRef iconTarPtrRef, BitmapSize desiredSize,
333 	BitmapSize* actualSize)
334 {
335 	if (iconTarPtrRef->HasOffset(BITMAP_SIZE_ANY)) {
336 		*actualSize = BITMAP_SIZE_ANY;
337 		return iconTarPtrRef->Offset(BITMAP_SIZE_ANY);
338 	}
339 	if (iconTarPtrRef->HasOffset(BITMAP_SIZE_64)
340 			&& desiredSize >= BITMAP_SIZE_64) {
341 		*actualSize = BITMAP_SIZE_64;
342 		return iconTarPtrRef->Offset(BITMAP_SIZE_64);
343 	}
344 	if (iconTarPtrRef->HasOffset(BITMAP_SIZE_32)
345 			&& desiredSize >= BITMAP_SIZE_32) {
346 		*actualSize = BITMAP_SIZE_32;
347 		return iconTarPtrRef->Offset(BITMAP_SIZE_32);
348 	}
349 	if (iconTarPtrRef->HasOffset(BITMAP_SIZE_22)
350 			&& desiredSize >= BITMAP_SIZE_22) {
351 		*actualSize = BITMAP_SIZE_22;
352 		return iconTarPtrRef->Offset(BITMAP_SIZE_22);
353 	}
354 	if (iconTarPtrRef->HasOffset(BITMAP_SIZE_16)) {
355 		*actualSize = BITMAP_SIZE_16;
356 		return iconTarPtrRef->Offset(BITMAP_SIZE_16);
357 	}
358 	return -1;
359 }
360 
361 
362 IconTarPtrRef
363 PackageIconTarRepository::_GetOrCreateIconTarPtr(const BString& pkgName)
364 {
365 	BAutolock locker(&fLock);
366 	HashString key(pkgName);
367 	if (!fIconTarPtrs.ContainsKey(key)) {
368 		IconTarPtrRef value(new IconTarPtr(pkgName));
369 		fIconTarPtrs.Put(key, value);
370 		return value;
371 	}
372 	return fIconTarPtrs.Get(key);
373 }
374 
375 
376 /*static*/ void
377 PackageIconTarRepository::CleanupDefaultIcon()
378 {
379 	sDefaultIcon.Unset();
380 }
381