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
37class InfoJsonExtractEntryListener : public TarEntryListener
38{
39public:
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();
49private:
50			BMallocIO			fInfoJsonData;
51};
52
53
54InfoJsonExtractEntryListener::InfoJsonExtractEntryListener()
55{
56}
57
58InfoJsonExtractEntryListener::~InfoJsonExtractEntryListener()
59{
60}
61
62
63BPositionIO&
64InfoJsonExtractEntryListener::GetInfoJsonData()
65{
66	return fInfoJsonData;
67}
68
69
70status_t
71InfoJsonExtractEntryListener::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
93ServerIconExportUpdateProcess::ServerIconExportUpdateProcess(
94	Model* model,
95	uint32 serverProcessOptions)
96	:
97	AbstractSingleFileServerProcess(serverProcessOptions),
98	fModel(model)
99{
100}
101
102
103ServerIconExportUpdateProcess::~ServerIconExportUpdateProcess()
104{
105}
106
107
108const char*
109ServerIconExportUpdateProcess::Name() const
110{
111	return "ServerIconExportUpdateProcess";
112}
113
114
115const char*
116ServerIconExportUpdateProcess::Description() const
117{
118	return B_TRANSLATE("Synchronizing icons");
119}
120
121
122status_t
123ServerIconExportUpdateProcess::ProcessLocalData()
124{
125	status_t result = fModel->InitPackageIconRepository();
126	_NotifyPackagesWithIconsInDepots();
127	return result;
128}
129
130
131status_t
132ServerIconExportUpdateProcess::GetLocalPath(BPath& path) const
133{
134	return fModel->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
146status_t
147ServerIconExportUpdateProcess::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
212status_t
213ServerIconExportUpdateProcess::GetStandardMetaDataPath(BPath& path) const
214{
215	return B_ERROR;
216		// unsupported
217}
218
219
220void
221ServerIconExportUpdateProcess::GetStandardMetaDataJsonPath(
222	BString& jsonPath) const
223{
224	jsonPath.SetTo("$");
225		// the "$" here indicates that the data is at the top level.
226}
227
228
229BString
230ServerIconExportUpdateProcess::UrlPathComponent()
231{
232	return "/__pkgicon/all.tar.gz";
233}
234
235
236void
237ServerIconExportUpdateProcess::_NotifyPackagesWithIconsInDepots() const
238{
239	for (int32 d = 0; d < fModel->CountDepots(); d++) {
240		_NotifyPackagesWithIconsInDepot(fModel->DepotAtIndex(d));
241	}
242}
243
244
245void
246ServerIconExportUpdateProcess::_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