xref: /haiku/src/apps/haikudepot/model/PackageScreenshotRepository.cpp (revision b8a45b3a2df2379b4301bf3bd5949b9a105be4ba)
1 /*
2  * Copyright 2024, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 #include "PackageScreenshotRepository.h"
6 
7 #include <unistd.h>
8 
9 #include <AutoDeleter.h>
10 #include <TranslationUtils.h>
11 
12 #include "FileIO.h"
13 #include "Logger.h"
14 #include "StorageUtils.h"
15 #include "WebAppInterface.h"
16 
17 
18 static const uint32 kMaxRetainedCachedScreenshots = 25;
19 
20 
21 PackageScreenshotRepository::PackageScreenshotRepository(
22 	PackageScreenshotRepositoryListenerRef listener,
23 	WebAppInterface* webAppInterface)
24 	:
25 	fListener(listener),
26 	fWebAppInterface(webAppInterface)
27 {
28 	_Init();
29 }
30 
31 
32 PackageScreenshotRepository::~PackageScreenshotRepository()
33 {
34 	fListener.Unset();
35 	_CleanCache();
36 }
37 
38 
39 /*!	This method will load the specified screenshot from remote, but will by-pass
40 	the cache. It will load the data into a file before loading it in order to
41 	avoid the data needing to be resident in memory twice; thus saving memory
42 	use.
43 */
44 
45 status_t
46 PackageScreenshotRepository::LoadScreenshot(const ScreenshotCoordinate& coord,
47 	BitmapHolderRef& bitmapHolderRef)
48 {
49 	if (!coord.IsValid())
50 		return B_BAD_VALUE;
51 
52 	BPath temporaryFilePath(tmpnam(NULL), NULL, true);
53 	status_t result = _DownloadToLocalFile(coord, temporaryFilePath);
54 	const char* temporaryFilePathStr = temporaryFilePath.Path();
55 
56 	if (result == B_OK) {
57 		FILE* file = fopen(temporaryFilePathStr, "rb");
58 
59 		if (file == NULL) {
60 			HDERROR("unable to open the screenshot file for read at [%s]", temporaryFilePathStr);
61 			result = B_IO_ERROR;
62 		}
63 
64 		if (result == B_OK) {
65 			BFileIO fileIo(file, true); // takes ownership
66 			BBitmap* bitmap = BTranslationUtils::GetBitmap(&fileIo);
67 
68 			if (bitmap == NULL)
69 				return B_IO_ERROR;
70 
71 			bitmapHolderRef.SetTo(new(std::nothrow) BitmapHolder(bitmap), true);
72 		}
73 	}
74 
75 	// even if the temporary file cannot be deleted, still return that it was OK.
76 
77 	if (remove(temporaryFilePathStr) != 0)
78 		HDERROR("unable to delete the temporary file [%s]", temporaryFilePathStr);
79 
80 	return result;
81 }
82 
83 
84 status_t
85 PackageScreenshotRepository::CacheAndLoadScreenshot(const ScreenshotCoordinate& coord,
86 	BitmapHolderRef& bitmapHolderRef)
87 {
88 	if (!coord.IsValid())
89 		return B_BAD_VALUE;
90 
91 	CacheScreenshot(coord);
92 
93 	BPositionIO* data = NULL;
94 	status_t result = _CreateCachedData(coord, &data);
95 
96 	if (result == B_OK) {
97 		ObjectDeleter<BPositionIO> dataDeleter(data);
98 		BBitmap* bitmap = BTranslationUtils::GetBitmap(data);
99 
100 		if (bitmap == NULL)
101 			return B_IO_ERROR;
102 
103 		bitmapHolderRef.SetTo(new(std::nothrow) BitmapHolder(bitmap), true);
104 	}
105 
106 	return result;
107 }
108 
109 
110 status_t
111 PackageScreenshotRepository::HasCachedScreenshot(const ScreenshotCoordinate& coord, bool* value)
112 {
113 	if (value == NULL)
114 		debugger("expected the value to be supplied");
115 
116 	*value = false;
117 
118 	if (!coord.IsValid())
119 		return B_BAD_VALUE;
120 
121 	BPath path = _DeriveCachePath(coord);
122 	const char* pathStr = path.Path();
123 	BEntry entry(pathStr);
124 
125 	struct stat s = {};
126 	status_t result = entry.GetStat(&s);
127 
128 	switch (result) {
129 		case B_ENTRY_NOT_FOUND:
130 			*value = false;
131 			return B_OK;
132 		case B_OK:
133 			*value = (s.st_size > 0);
134 			return B_OK;
135 		default:
136 			return result;
137 	}
138 }
139 
140 
141 status_t
142 PackageScreenshotRepository::CacheScreenshot(const ScreenshotCoordinate& coord)
143 {
144 	if (!coord.IsValid())
145 		return B_BAD_VALUE;
146 
147 	bool present = false;
148 	status_t result = HasCachedScreenshot(coord, &present);
149 
150 	if (result == B_OK && present)
151 		return result;
152 
153 	if (result == B_OK)
154 		result = _DownloadToLocalFile(coord, _DeriveCachePath(coord));
155 
156 	return result;
157 }
158 
159 
160 status_t
161 PackageScreenshotRepository::_Init()
162 {
163 	fBaseDirectory.Unset();
164 
165 	status_t result = StorageUtils::LocalWorkingDirectoryPath(
166 		"screenshot_cache", fBaseDirectory);
167 
168 	if (B_OK != result)
169 		HDERROR("unable to setup the cache directory");
170 
171 	_CleanCache();
172 
173 	return result;
174 }
175 
176 
177 /*! Gets all of the cached files and looks to see the last access on them. The
178 	most recent files are retained and the rest are deleted from the disk.
179 */
180 
181 status_t
182 PackageScreenshotRepository::_CleanCache()
183 {
184 	HDINFO("will clean the screenshot cache");
185 	// get all of the files and then order them by last accessed and then
186 	// delete the older ones.
187 	return StorageUtils::RemoveDirectoryContentsRetainingLatestFiles(fBaseDirectory,
188 		kMaxRetainedCachedScreenshots);
189 }
190 
191 
192 status_t
193 PackageScreenshotRepository::_DownloadToLocalFile(const ScreenshotCoordinate& coord,
194 	const BPath& path)
195 {
196 	const char* pathStr = path.Path();
197 	FILE* file = fopen(pathStr, "wb");
198 
199 	if (file == NULL) {
200 		HDERROR("unable to open the screenshot file for writing at [%s]", pathStr);
201 		return B_IO_ERROR;
202 	}
203 
204 	BFileIO outputDataStream(file, true); // takes ownership
205 	status_t result = fWebAppInterface->RetrieveScreenshot(
206 		coord.Code(), coord.Width(), coord.Height(), &outputDataStream);
207 
208 	if (result == B_OK)
209 		result = outputDataStream.Flush();
210 
211 	if (result == B_OK)
212 		fListener->ScreenshotCached(coord);
213 
214 	return result;
215 }
216 
217 
218 BPath
219 PackageScreenshotRepository::_DeriveCachePath(const ScreenshotCoordinate& coord) const
220 {
221 	BPath path(fBaseDirectory);
222 	path.Append(coord.CacheFilename());
223 	return path;
224 }
225 
226 
227 status_t
228 PackageScreenshotRepository::_CreateCachedData(const ScreenshotCoordinate& coord, BPositionIO** data)
229 {
230 	status_t result = B_OK;
231 	BPath path = _DeriveCachePath(coord);
232 	const char* pathStr = path.Path();
233 	FILE* file = fopen(pathStr, "rb");
234 
235 	if (file == NULL) {
236 		HDERROR("unable to open the screenshot file for read at [%s]", pathStr);
237 		result = B_IO_ERROR;
238 	}
239 
240 	if (result == B_OK)
241 		*data = new BFileIO(file, true); // takes ownership
242 
243 	return result;
244 }
245 
246