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