/* * Copyright 2017-2021, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ #include "ServerPkgDataUpdateProcess.h" #include #include #include #include #include #include #include #include #include #include "Logger.h" #include "ServerSettings.h" #include "StorageUtils.h" #include "DumpExportPkg.h" #include "DumpExportPkgCategory.h" #include "DumpExportPkgJsonListener.h" #include "DumpExportPkgScreenshot.h" #include "DumpExportPkgVersion.h" #include "HaikuDepotConstants.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "ServerPkgDataUpdateProcess" /*! This package listener (not at the JSON level) is feeding in the packages as they are parsed and processing them. */ class PackageFillingPkgListener : public DumpExportPkgListener { public: PackageFillingPkgListener(Model *model, BString& depotName, Stoppable* stoppable); virtual ~PackageFillingPkgListener(); virtual bool ConsumePackage(const PackageInfoRef& package, DumpExportPkg* pkg); virtual bool Handle(DumpExportPkg* item); virtual void Complete(); uint32 Count(); private: int32 IndexOfPackageByName(const BString& name) const; private: BString fDepotName; Model* fModel; std::vector fCategories; Stoppable* fStoppable; uint32 fCount; bool fDebugEnabled; }; PackageFillingPkgListener::PackageFillingPkgListener(Model* model, BString& depotName, Stoppable* stoppable) : fDepotName(depotName), fModel(model), fStoppable(stoppable), fCount(0), fDebugEnabled(Logger::IsDebugEnabled()) { } PackageFillingPkgListener::~PackageFillingPkgListener() { } bool PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package, DumpExportPkg* pkg) { int32 i; // Collects all of the changes here into one set of notifications to // the package's listeners. This way the quantity of BMessages // communicated back to listeners is considerably reduced. See stop // invocation later in this method. package->StartCollatingChanges(); if (0 != pkg->CountPkgVersions()) { // this makes the assumption that the only version will be the // latest one. DumpExportPkgVersion* pkgVersion = pkg->PkgVersionsItemAt(0); if (!pkgVersion->TitleIsNull()) package->SetTitle(*(pkgVersion->Title())); if (!pkgVersion->SummaryIsNull()) package->SetShortDescription(*(pkgVersion->Summary())); if (!pkgVersion->DescriptionIsNull()) package->SetFullDescription(*(pkgVersion->Description())); if (!pkgVersion->PayloadLengthIsNull()) package->SetSize(static_cast(pkgVersion->PayloadLength())); if (!pkgVersion->CreateTimestampIsNull()) package->SetVersionCreateTimestamp(pkgVersion->CreateTimestamp()); } int32 countPkgCategories = pkg->CountPkgCategories(); for (i = 0; i < countPkgCategories; i++) { BString* categoryCode = pkg->PkgCategoriesItemAt(i)->Code(); CategoryRef category = fModel->CategoryByCode(*categoryCode); if (!category.IsSet()) { HDERROR("unable to find the category for [%s]", categoryCode->String()); } else package->AddCategory(category); } RatingSummary summary; summary.averageRating = RATING_MISSING; if (!pkg->DerivedRatingIsNull()) summary.averageRating = pkg->DerivedRating(); package->SetRatingSummary(summary); package->SetHasChangelog(pkg->HasChangelog()); if (!pkg->ProminenceOrderingIsNull()) package->SetProminence(pkg->ProminenceOrdering()); int32 countPkgScreenshots = pkg->CountPkgScreenshots(); for (i = 0; i < countPkgScreenshots; i++) { DumpExportPkgScreenshot* screenshot = pkg->PkgScreenshotsItemAt(i); package->AddScreenshotInfo(ScreenshotInfoRef(new ScreenshotInfo( *(screenshot->Code()), static_cast(screenshot->Width()), static_cast(screenshot->Height()), static_cast(screenshot->Length()) ), true)); } HDDEBUG("did populate data for [%s] (%s)", pkg->Name()->String(), fDepotName.String()); fCount++; package->EndCollatingChanges(); return !fStoppable->WasStopped(); } uint32 PackageFillingPkgListener::Count() { return fCount; } bool PackageFillingPkgListener::Handle(DumpExportPkg* pkg) { AutoLocker locker(fModel->Lock()); DepotInfoRef depot = fModel->DepotForName(fDepotName); if (depot.Get() != NULL) { const BString packageName = *(pkg->Name()); PackageInfoRef package = depot->PackageByName(packageName); if (package.Get() != NULL) ConsumePackage(package, pkg); else { HDINFO("[PackageFillingPkgListener] unable to find the pkg [%s]", packageName.String()); } } else { HDINFO("[PackageFillingPkgListener] unable to find the depot [%s]", fDepotName.String()); } return !fStoppable->WasStopped(); } void PackageFillingPkgListener::Complete() { } ServerPkgDataUpdateProcess::ServerPkgDataUpdateProcess( BString naturalLanguageCode, BString depotName, Model *model, uint32 serverProcessOptions) : AbstractSingleFileServerProcess(serverProcessOptions), fNaturalLanguageCode(naturalLanguageCode), fModel(model), fDepotName(depotName) { fName.SetToFormat("ServerPkgDataUpdateProcess<%s>", depotName.String()); fDescription.SetTo( B_TRANSLATE("Synchronizing package data for repository " "'%REPO_NAME%'")); fDescription.ReplaceAll("%REPO_NAME%", depotName.String()); } ServerPkgDataUpdateProcess::~ServerPkgDataUpdateProcess() { } const char* ServerPkgDataUpdateProcess::Name() const { return fName.String(); } const char* ServerPkgDataUpdateProcess::Description() const { return fDescription.String(); } BString ServerPkgDataUpdateProcess::UrlPathComponent() { BString urlPath; urlPath.SetToFormat("/__pkg/all-%s-%s.json.gz", _DeriveWebAppRepositorySourceCode().String(), fNaturalLanguageCode.String()); return urlPath; } status_t ServerPkgDataUpdateProcess::GetLocalPath(BPath& path) const { BString webAppRepositorySourceCode = _DeriveWebAppRepositorySourceCode(); if (!webAppRepositorySourceCode.IsEmpty()) { AutoLocker locker(fModel->Lock()); return fModel->DumpExportPkgDataPath(path, webAppRepositorySourceCode); } return B_ERROR; } status_t ServerPkgDataUpdateProcess::ProcessLocalData() { BStopWatch watch("ServerPkgDataUpdateProcess::ProcessLocalData", true); PackageFillingPkgListener* itemListener = new PackageFillingPkgListener(fModel, fDepotName, this); ObjectDeleter itemListenerDeleter(itemListener); BulkContainerDumpExportPkgJsonListener* listener = new BulkContainerDumpExportPkgJsonListener(itemListener); ObjectDeleter listenerDeleter(listener); BPath localPath; status_t result = GetLocalPath(localPath); if (result != B_OK) return result; result = ParseJsonFromFileWithListener(listener, localPath); if (B_OK != result) return result; if (Logger::IsInfoEnabled()) { double secs = watch.ElapsedTime() / 1000000.0; HDINFO("[%s] did process %" B_PRIi32 " packages' data " "in (%6.3g secs)", Name(), itemListener->Count(), secs); } return listener->ErrorStatus(); } status_t ServerPkgDataUpdateProcess::GetStandardMetaDataPath(BPath& path) const { return GetLocalPath(path); } void ServerPkgDataUpdateProcess::GetStandardMetaDataJsonPath( BString& jsonPath) const { jsonPath.SetTo("$.info"); } BString ServerPkgDataUpdateProcess::_DeriveWebAppRepositorySourceCode() const { const DepotInfo* depot = fModel->DepotForName(fDepotName); if (depot == NULL) { return BString(); } return depot->WebAppRepositorySourceCode(); } status_t ServerPkgDataUpdateProcess::RunInternal() { if (_DeriveWebAppRepositorySourceCode().IsEmpty()) { HDINFO("[%s] am not updating data for depot [%s] as there is no" " web app repository source code available", Name(), fDepotName.String()); return B_OK; } return AbstractSingleFileServerProcess::RunInternal(); }