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 6 7 #include "ServerPkgDataUpdateProcess.h" 8 9 #include <stdio.h> 10 #include <sys/stat.h> 11 #include <time.h> 12 13 #include <AutoDeleter.h> 14 #include <AutoLocker.h> 15 #include <Catalog.h> 16 #include <FileIO.h> 17 #include <support/StopWatch.h> 18 #include <Url.h> 19 20 #include "Logger.h" 21 #include "ServerSettings.h" 22 #include "StorageUtils.h" 23 #include "DumpExportPkg.h" 24 #include "DumpExportPkgCategory.h" 25 #include "DumpExportPkgJsonListener.h" 26 #include "DumpExportPkgScreenshot.h" 27 #include "DumpExportPkgVersion.h" 28 #include "HaikuDepotConstants.h" 29 30 31 #undef B_TRANSLATION_CONTEXT 32 #define B_TRANSLATION_CONTEXT "ServerPkgDataUpdateProcess" 33 34 35 /*! This package listener (not at the JSON level) is feeding in the 36 packages as they are parsed and processing them. 37 */ 38 39 class PackageFillingPkgListener : public DumpExportPkgListener { 40 public: 41 PackageFillingPkgListener(Model *model, 42 BString& depotName, Stoppable* stoppable); 43 virtual ~PackageFillingPkgListener(); 44 45 virtual bool ConsumePackage(const PackageInfoRef& package, 46 DumpExportPkg* pkg); 47 virtual bool Handle(DumpExportPkg* item); 48 virtual void Complete(); 49 50 uint32 Count(); 51 52 private: 53 int32 IndexOfPackageByName(const BString& name) const; 54 55 private: 56 BString fDepotName; 57 Model* fModel; 58 std::vector<CategoryRef> 59 fCategories; 60 Stoppable* fStoppable; 61 uint32 fCount; 62 bool fDebugEnabled; 63 }; 64 65 66 PackageFillingPkgListener::PackageFillingPkgListener(Model* model, 67 BString& depotName, Stoppable* stoppable) 68 : 69 fDepotName(depotName), 70 fModel(model), 71 fStoppable(stoppable), 72 fCount(0), 73 fDebugEnabled(Logger::IsDebugEnabled()) 74 { 75 } 76 77 78 PackageFillingPkgListener::~PackageFillingPkgListener() 79 { 80 } 81 82 83 bool 84 PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package, 85 DumpExportPkg* pkg) 86 { 87 int32 i; 88 89 // Collects all of the changes here into one set of notifications to 90 // the package's listeners. This way the quantity of BMessages 91 // communicated back to listeners is considerably reduced. See stop 92 // invocation later in this method. 93 94 package->StartCollatingChanges(); 95 96 if (0 != pkg->CountPkgVersions()) { 97 98 // this makes the assumption that the only version will be the 99 // latest one. 100 101 DumpExportPkgVersion* pkgVersion = pkg->PkgVersionsItemAt(0); 102 103 if (!pkgVersion->TitleIsNull()) 104 package->SetTitle(*(pkgVersion->Title())); 105 106 if (!pkgVersion->SummaryIsNull()) 107 package->SetShortDescription(*(pkgVersion->Summary())); 108 109 if (!pkgVersion->DescriptionIsNull()) 110 package->SetFullDescription(*(pkgVersion->Description())); 111 112 if (!pkgVersion->PayloadLengthIsNull()) 113 package->SetSize(static_cast<off_t>(pkgVersion->PayloadLength())); 114 115 if (!pkgVersion->CreateTimestampIsNull()) 116 package->SetVersionCreateTimestamp(pkgVersion->CreateTimestamp()); 117 } 118 119 int32 countPkgCategories = pkg->CountPkgCategories(); 120 121 for (i = 0; i < countPkgCategories; i++) { 122 BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code(); 123 CategoryRef category = fModel->CategoryByCode(*categoryCode); 124 125 if (!category.IsSet()) { 126 HDERROR("unable to find the category for [%s]", 127 categoryCode->String()); 128 } else 129 package->AddCategory(category); 130 } 131 132 RatingSummary summary; 133 summary.averageRating = RATING_MISSING; 134 135 if (!pkg->DerivedRatingIsNull()) 136 summary.averageRating = pkg->DerivedRating(); 137 138 package->SetRatingSummary(summary); 139 140 package->SetHasChangelog(pkg->HasChangelog()); 141 142 if (!pkg->ProminenceOrderingIsNull()) 143 package->SetProminence(pkg->ProminenceOrdering()); 144 145 int32 countPkgScreenshots = pkg->CountPkgScreenshots(); 146 147 for (i = 0; i < countPkgScreenshots; i++) { 148 DumpExportPkgScreenshot* screenshot = pkg->PkgScreenshotsItemAt(i); 149 package->AddScreenshotInfo(ScreenshotInfoRef(new ScreenshotInfo( 150 *(screenshot->Code()), 151 static_cast<int32>(screenshot->Width()), 152 static_cast<int32>(screenshot->Height()), 153 static_cast<int32>(screenshot->Length()) 154 ), true)); 155 } 156 157 HDDEBUG("did populate data for [%s] (%s)", pkg->Name()->String(), 158 fDepotName.String()); 159 160 fCount++; 161 162 package->EndCollatingChanges(); 163 164 return !fStoppable->WasStopped(); 165 } 166 167 168 uint32 169 PackageFillingPkgListener::Count() 170 { 171 return fCount; 172 } 173 174 175 bool 176 PackageFillingPkgListener::Handle(DumpExportPkg* pkg) 177 { 178 AutoLocker<BLocker> locker(fModel->Lock()); 179 DepotInfoRef depot = fModel->DepotForName(fDepotName); 180 181 if (depot.Get() != NULL) { 182 const BString packageName = *(pkg->Name()); 183 PackageInfoRef package = depot->PackageByName(packageName); 184 if (package.Get() != NULL) 185 ConsumePackage(package, pkg); 186 else { 187 HDINFO("[PackageFillingPkgListener] unable to find the pkg [%s]", 188 packageName.String()); 189 } 190 } else { 191 HDINFO("[PackageFillingPkgListener] unable to find the depot [%s]", 192 fDepotName.String()); 193 } 194 195 return !fStoppable->WasStopped(); 196 } 197 198 199 void 200 PackageFillingPkgListener::Complete() 201 { 202 } 203 204 205 ServerPkgDataUpdateProcess::ServerPkgDataUpdateProcess( 206 BString naturalLanguageCode, 207 BString depotName, 208 Model *model, 209 uint32 serverProcessOptions) 210 : 211 AbstractSingleFileServerProcess(serverProcessOptions), 212 fNaturalLanguageCode(naturalLanguageCode), 213 fModel(model), 214 fDepotName(depotName) 215 { 216 fName.SetToFormat("ServerPkgDataUpdateProcess<%s>", depotName.String()); 217 fDescription.SetTo( 218 B_TRANSLATE("Synchronizing package data for repository " 219 "'%REPO_NAME%'")); 220 fDescription.ReplaceAll("%REPO_NAME%", depotName.String()); 221 } 222 223 224 ServerPkgDataUpdateProcess::~ServerPkgDataUpdateProcess() 225 { 226 } 227 228 229 const char* 230 ServerPkgDataUpdateProcess::Name() const 231 { 232 return fName.String(); 233 } 234 235 236 const char* 237 ServerPkgDataUpdateProcess::Description() const 238 { 239 return fDescription.String(); 240 } 241 242 243 BString 244 ServerPkgDataUpdateProcess::UrlPathComponent() 245 { 246 BString urlPath; 247 urlPath.SetToFormat("/__pkg/all-%s-%s.json.gz", 248 _DeriveWebAppRepositorySourceCode().String(), 249 fNaturalLanguageCode.String()); 250 return urlPath; 251 } 252 253 254 status_t 255 ServerPkgDataUpdateProcess::GetLocalPath(BPath& path) const 256 { 257 BString webAppRepositorySourceCode = _DeriveWebAppRepositorySourceCode(); 258 259 if (!webAppRepositorySourceCode.IsEmpty()) { 260 AutoLocker<BLocker> locker(fModel->Lock()); 261 return fModel->DumpExportPkgDataPath(path, webAppRepositorySourceCode); 262 } 263 264 return B_ERROR; 265 } 266 267 268 status_t 269 ServerPkgDataUpdateProcess::ProcessLocalData() 270 { 271 BStopWatch watch("ServerPkgDataUpdateProcess::ProcessLocalData", true); 272 273 PackageFillingPkgListener* itemListener = 274 new PackageFillingPkgListener(fModel, fDepotName, this); 275 ObjectDeleter<PackageFillingPkgListener> 276 itemListenerDeleter(itemListener); 277 278 BulkContainerDumpExportPkgJsonListener* listener = 279 new BulkContainerDumpExportPkgJsonListener(itemListener); 280 ObjectDeleter<BulkContainerDumpExportPkgJsonListener> 281 listenerDeleter(listener); 282 283 BPath localPath; 284 status_t result = GetLocalPath(localPath); 285 286 if (result != B_OK) 287 return result; 288 289 result = ParseJsonFromFileWithListener(listener, localPath); 290 291 if (B_OK != result) 292 return result; 293 294 if (Logger::IsInfoEnabled()) { 295 double secs = watch.ElapsedTime() / 1000000.0; 296 HDINFO("[%s] did process %" B_PRIi32 " packages' data " 297 "in (%6.3g secs)", Name(), itemListener->Count(), secs); 298 } 299 300 return listener->ErrorStatus(); 301 } 302 303 304 status_t 305 ServerPkgDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const 306 { 307 return GetLocalPath(path); 308 } 309 310 311 void 312 ServerPkgDataUpdateProcess::GetStandardMetaDataJsonPath( 313 BString& jsonPath) const 314 { 315 jsonPath.SetTo("$.info"); 316 } 317 318 319 BString 320 ServerPkgDataUpdateProcess::_DeriveWebAppRepositorySourceCode() const 321 { 322 const DepotInfo* depot = fModel->DepotForName(fDepotName); 323 324 if (depot == NULL) { 325 return BString(); 326 } 327 328 return depot->WebAppRepositorySourceCode(); 329 } 330 331 332 status_t 333 ServerPkgDataUpdateProcess::RunInternal() 334 { 335 if (_DeriveWebAppRepositorySourceCode().IsEmpty()) { 336 HDINFO("[%s] am not updating data for depot [%s] as there is no" 337 " web app repository source code available", 338 Name(), fDepotName.String()); 339 return B_OK; 340 } 341 342 return AbstractSingleFileServerProcess::RunInternal(); 343 } 344