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