⛏️ index : haiku.git

/*
 * Copyright 2017-2025, Andrew Lindesay <apl@lindesay.co.nz>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */
#include "ServerIconExportUpdateProcess.h"

#include <sys/stat.h>
#include <time.h>

#include <AutoDeleter.h>
#include <BufferIO.h>
#include <Catalog.h>
#include <FileIO.h>

#include "DataIOUtils.h"
#include "HaikuDepotConstants.h"
#include "Logger.h"
#include "PackageIconTarRepository.h"
#include "ServerHelper.h"
#include "StandardMetaDataJsonEventListener.h"
#include "StorageUtils.h"
#include "TarArchiveService.h"

#define ENTRY_PATH_METADATA "hicn/info.json"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ServerIconExportUpdateProcess"


/*!	This listener will scan the files that are available in the tar file and
	find the meta-data file.  This is a JSON piece of data that describes the
	timestamp of the last modified data in the tar file.  This is then used to
	establish if the server has any newer data and hence if it is worth
	downloading fresh data.  The process uses the standard HTTP
	If-Modified-Since header.
*/

class InfoJsonExtractEntryListener : public TarEntryListener
{
public:
								InfoJsonExtractEntryListener();
	virtual						~InfoJsonExtractEntryListener();

	virtual status_t			Handle(
									const TarArchiveHeader& header,
									size_t offset,
									BDataIO *data);

			BPositionIO&		GetInfoJsonData();
private:
			BMallocIO			fInfoJsonData;
};


InfoJsonExtractEntryListener::InfoJsonExtractEntryListener()
{
}


InfoJsonExtractEntryListener::~InfoJsonExtractEntryListener()
{
}


BPositionIO&
InfoJsonExtractEntryListener::GetInfoJsonData()
{
	return fInfoJsonData;
}


status_t
InfoJsonExtractEntryListener::Handle(const TarArchiveHeader& header, size_t offset, BDataIO* data)
{
	if (header.Length() > 0 && header.FileName() == ENTRY_PATH_METADATA) {
		status_t copyResult = DataIOUtils::CopyAll(&fInfoJsonData, data);
		if (copyResult == B_OK) {
			HDINFO("[InfoJsonExtractEntryListener] did extract [%s]", ENTRY_PATH_METADATA);
			fInfoJsonData.Seek(0, SEEK_SET);
			return B_CANCELED;
				// this will prevent further scanning of the tar file
		}
		return copyResult;
	}

	return B_OK;
}


/*!	This constructor will locate the cached data in a standardized location
 */
ServerIconExportUpdateProcess::ServerIconExportUpdateProcess(Model* model,
	uint32 serverProcessOptions)
	:
	AbstractSingleFileServerProcess(serverProcessOptions),
	fModel(model)
{
}


ServerIconExportUpdateProcess::~ServerIconExportUpdateProcess()
{
}


const char*
ServerIconExportUpdateProcess::Name() const
{
	return "ServerIconExportUpdateProcess";
}


const char*
ServerIconExportUpdateProcess::Description() const
{
	return B_TRANSLATE("Synchronizing icons");
}


status_t
ServerIconExportUpdateProcess::ProcessLocalData()
{
	BPath tarPath;
	status_t result = GetLocalPath(tarPath);

	if (result == B_OK) {
		PackageIconTarRepository* tarRepository = new PackageIconTarRepository(tarPath);

		result = tarRepository->Init();

		if (result == B_OK)
			fModel->SetIconRepository(PackageIconRepositoryRef(tarRepository, true));
		else
			delete tarRepository;
	}

	return result;
}


status_t
ServerIconExportUpdateProcess::GetLocalPath(BPath& path) const
{
	return StorageUtils::IconTarPath(path);
}


/*!	This overridden method implementation seems inefficient because it will
	apparently scan the entire tarball for the file that it is looking for, but
	actually it will cancel the scan once it has found it's target object (the
	JSON data containing the meta-data) and by convention, the server side will
	place the meta-data as one of the first objects in the tar-ball so it will
	find it quickly.
*/

status_t
ServerIconExportUpdateProcess::IfModifiedSinceHeaderValue(BString& headerValue)
{
	headerValue.SetTo("");

	BPath tarPath;
	status_t result = GetLocalPath(tarPath);

	// early exit if the tar file is not there.

	if (result == B_OK) {
		off_t size;
		bool hasFile;

		result = StorageUtils::ExistsObject(tarPath, &hasFile, NULL, &size);

		if (result == B_OK && (!hasFile || size == 0))
			return result;
	}

	if (result == B_OK) {
		BFile* tarIo = new BFile(tarPath.Path(), O_RDONLY);
		BBufferIO* bufferTarIo = new BBufferIO(tarIo, 10 * 1024, true);
		ObjectDeleter<BBufferIO> tarIoDeleter(bufferTarIo);

		InfoJsonExtractEntryListener* extractDataListener = new InfoJsonExtractEntryListener();
		ObjectDeleter<InfoJsonExtractEntryListener> extractDataListenerDeleter(extractDataListener);
		result = TarArchiveService::ForEachEntry(*bufferTarIo, extractDataListener);

		if (result == B_CANCELED) {
			// the cancellation is expected because it will cancel when it finds
			// the meta-data file to avoid any further cost of traversing the
			// tar-ball.
			result = B_OK;

			StandardMetaData metaData;
			BString metaDataJsonPath;
			GetStandardMetaDataJsonPath(metaDataJsonPath);
			StandardMetaDataJsonEventListener parseInfoJsonListener(metaDataJsonPath, metaData);
			BPrivate::BJson::Parse(&(extractDataListener->GetInfoJsonData()),
				&parseInfoJsonListener);

			result = parseInfoJsonListener.ErrorStatus();

			// An example of this output would be; 'Fri, 24 Oct 2014 19:32:27 +0000'

			if (result == B_OK)
				SetIfModifiedSinceHeaderValueFromMetaData(headerValue, metaData);
			else
				HDERROR("[%s] unable to parse the meta data from the tar file", Name());
		} else {
			HDERROR("[%s] did not find the metadata [%s] in the tar", Name(), ENTRY_PATH_METADATA);
			result = B_BAD_DATA;
		}
	}

	return result;
}


status_t
ServerIconExportUpdateProcess::GetStandardMetaDataPath(BPath& path) const
{
	return B_ERROR;
		// unsupported
}


void
ServerIconExportUpdateProcess::GetStandardMetaDataJsonPath(BString& jsonPath) const
{
	jsonPath.SetTo("$");
		// the "$" here indicates that the data is at the top level.
}


BString
ServerIconExportUpdateProcess::UrlPathComponent()
{
	return "/__pkgicon/all.tar.gz";
}