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