1 /* 2 * Copyright 2017-2021, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 #include "ServerIconExportUpdateProcess.h" 6 7 #include <sys/stat.h> 8 #include <time.h> 9 10 #include <AutoDeleter.h> 11 #include <AutoLocker.h> 12 #include <Catalog.h> 13 #include <FileIO.h> 14 15 #include "DataIOUtils.h" 16 #include "HaikuDepotConstants.h" 17 #include "Logger.h" 18 #include "ServerHelper.h" 19 #include "StandardMetaDataJsonEventListener.h" 20 #include "StorageUtils.h" 21 #include "TarArchiveService.h" 22 23 #define ENTRY_PATH_METADATA "hicn/info.json" 24 25 #undef B_TRANSLATION_CONTEXT 26 #define B_TRANSLATION_CONTEXT "ServerIconExportUpdateProcess" 27 28 29 /*! This listener will scan the files that are available in the tar file and 30 find the meta-data file. This is a JSON piece of data that describes the 31 timestamp of the last modified data in the tar file. This is then used to 32 establish if the server has any newer data and hence if it is worth 33 downloading fresh data. The process uses the standard HTTP 34 If-Modified-Since header. 35 */ 36 37 class InfoJsonExtractEntryListener : public TarEntryListener 38 { 39 public: 40 InfoJsonExtractEntryListener(); 41 virtual ~InfoJsonExtractEntryListener(); 42 43 virtual status_t Handle( 44 const TarArchiveHeader& header, 45 size_t offset, 46 BDataIO *data); 47 48 BPositionIO& GetInfoJsonData(); 49 private: 50 BMallocIO fInfoJsonData; 51 }; 52 53 54 InfoJsonExtractEntryListener::InfoJsonExtractEntryListener() 55 { 56 } 57 58 InfoJsonExtractEntryListener::~InfoJsonExtractEntryListener() 59 { 60 } 61 62 63 BPositionIO& 64 InfoJsonExtractEntryListener::GetInfoJsonData() 65 { 66 return fInfoJsonData; 67 } 68 69 70 status_t 71 InfoJsonExtractEntryListener::Handle( const TarArchiveHeader& header, 72 size_t offset, BDataIO *data) 73 { 74 if (header.Length() > 0 && header.FileName() == ENTRY_PATH_METADATA) { 75 status_t copyResult = DataIOUtils::CopyAll(&fInfoJsonData, data); 76 if (copyResult == B_OK) { 77 HDINFO("[InfoJsonExtractEntryListener] did extract [%s]", 78 ENTRY_PATH_METADATA); 79 fInfoJsonData.Seek(0, SEEK_SET); 80 return B_CANCELED; 81 // this will prevent further scanning of the tar file 82 } 83 return copyResult; 84 } 85 86 return B_OK; 87 } 88 89 90 /*! This constructor will locate the cached data in a standardized location 91 */ 92 93 ServerIconExportUpdateProcess::ServerIconExportUpdateProcess( 94 Model* model, 95 uint32 serverProcessOptions) 96 : 97 AbstractSingleFileServerProcess(serverProcessOptions), 98 fModel(model) 99 { 100 } 101 102 103 ServerIconExportUpdateProcess::~ServerIconExportUpdateProcess() 104 { 105 } 106 107 108 const char* 109 ServerIconExportUpdateProcess::Name() const 110 { 111 return "ServerIconExportUpdateProcess"; 112 } 113 114 115 const char* 116 ServerIconExportUpdateProcess::Description() const 117 { 118 return B_TRANSLATE("Synchronizing icons"); 119 } 120 121 122 status_t 123 ServerIconExportUpdateProcess::ProcessLocalData() 124 { 125 status_t result = fModel->InitPackageIconRepository(); 126 _NotifyPackagesWithIconsInDepots(); 127 return result; 128 } 129 130 131 status_t 132 ServerIconExportUpdateProcess::GetLocalPath(BPath& path) const 133 { 134 return StorageUtils::IconTarPath(path); 135 } 136 137 138 /*! This overridden method implementation seems inefficient because it will 139 apparently scan the entire tarball for the file that it is looking for, but 140 actually it will cancel the scan once it has found it's target object (the 141 JSON data containing the meta-data) and by convention, the server side will 142 place the meta-data as one of the first objects in the tar-ball so it will 143 find it quickly. 144 */ 145 146 status_t 147 ServerIconExportUpdateProcess::IfModifiedSinceHeaderValue(BString& headerValue) const 148 { 149 headerValue.SetTo(""); 150 151 BPath tarPath; 152 status_t result = GetLocalPath(tarPath); 153 154 // early exit if the tar file is not there. 155 156 if (result == B_OK) { 157 off_t size; 158 bool hasFile; 159 160 result = StorageUtils::ExistsObject(tarPath, &hasFile, NULL, &size); 161 162 if (result == B_OK && (!hasFile || size == 0)) 163 return result; 164 } 165 166 if (result == B_OK) { 167 BFile *tarIo = new BFile(tarPath.Path(), O_RDONLY); 168 ObjectDeleter<BFile> tarIoDeleter(tarIo); 169 InfoJsonExtractEntryListener* extractDataListener 170 = new InfoJsonExtractEntryListener(); 171 ObjectDeleter<InfoJsonExtractEntryListener> extractDataListenerDeleter( 172 extractDataListener); 173 result = TarArchiveService::ForEachEntry(*tarIo, extractDataListener); 174 175 if (result == B_CANCELED) { 176 // the cancellation is expected because it will cancel when it finds 177 // the meta-data file to avoid any further cost of traversing the 178 // tar-ball. 179 result = B_OK; 180 181 StandardMetaData metaData; 182 BString metaDataJsonPath; 183 GetStandardMetaDataJsonPath(metaDataJsonPath); 184 StandardMetaDataJsonEventListener parseInfoJsonListener( 185 metaDataJsonPath, metaData); 186 BPrivate::BJson::Parse( 187 &(extractDataListener->GetInfoJsonData()), 188 &parseInfoJsonListener); 189 190 result = parseInfoJsonListener.ErrorStatus(); 191 192 // An example of this output would be; 'Fri, 24 Oct 2014 19:32:27 +0000' 193 194 if (result == B_OK) { 195 SetIfModifiedSinceHeaderValueFromMetaData( 196 headerValue, metaData); 197 } else { 198 HDERROR("[%s] unable to parse the meta data from the tar file", 199 Name()); 200 } 201 } else { 202 HDERROR("[%s] did not find the metadata [%s] in the tar", 203 Name(), ENTRY_PATH_METADATA); 204 result = B_BAD_DATA; 205 } 206 } 207 208 return result; 209 } 210 211 212 status_t 213 ServerIconExportUpdateProcess::GetStandardMetaDataPath(BPath& path) const 214 { 215 return B_ERROR; 216 // unsupported 217 } 218 219 220 void 221 ServerIconExportUpdateProcess::GetStandardMetaDataJsonPath( 222 BString& jsonPath) const 223 { 224 jsonPath.SetTo("$"); 225 // the "$" here indicates that the data is at the top level. 226 } 227 228 229 BString 230 ServerIconExportUpdateProcess::UrlPathComponent() 231 { 232 return "/__pkgicon/all.tar.gz"; 233 } 234 235 236 void 237 ServerIconExportUpdateProcess::_NotifyPackagesWithIconsInDepots() const 238 { 239 for (int32 d = 0; d < fModel->CountDepots(); d++) { 240 _NotifyPackagesWithIconsInDepot(fModel->DepotAtIndex(d)); 241 } 242 } 243 244 245 void 246 ServerIconExportUpdateProcess::_NotifyPackagesWithIconsInDepot( 247 const DepotInfoRef& depot) const 248 { 249 PackageIconRepository& packageIconRepository 250 = fModel->GetPackageIconRepository(); 251 for (int32 p = 0; p < depot->CountPackages(); p++) { 252 AutoLocker<BLocker> locker(fModel->Lock()); 253 const PackageInfoRef& packageInfoRef = depot->PackageAtIndex(p); 254 if (packageIconRepository.HasAnyIcon(packageInfoRef->Name())) 255 packageInfoRef->NotifyChangedIcon(); 256 } 257 } 258