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
15static const uint32 kMaxRetainedCachedScreenshots = 25;
16
17
18PackageScreenshotRepository::PackageScreenshotRepository(
19	PackageScreenshotRepositoryListenerRef listener,
20	WebAppInterface* webAppInterface)
21	:
22	fListener(listener),
23	fWebAppInterface(webAppInterface)
24{
25	_Init();
26}
27
28
29PackageScreenshotRepository::~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
42status_t
43PackageScreenshotRepository::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
78status_t
79PackageScreenshotRepository::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
102status_t
103PackageScreenshotRepository::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
133status_t
134PackageScreenshotRepository::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
152status_t
153PackageScreenshotRepository::_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
173status_t
174PackageScreenshotRepository::_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
184status_t
185PackageScreenshotRepository::_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
210BPath
211PackageScreenshotRepository::_DeriveCachePath(const ScreenshotCoordinate& coord) const
212{
213	BPath path(fBaseDirectory);
214	path.Append(coord.CacheFilename());
215	return path;
216}
217
218
219status_t
220PackageScreenshotRepository::_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