1/*
2 * Copyright 2017-2018, Andrew Lindesay <apl@lindesay.co.nz>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7#include "ServerIconExportUpdateProcess.h"
8
9#include <stdio.h>
10#include <sys/stat.h>
11#include <time.h>
12
13#include <AutoLocker.h>
14#include <Catalog.h>
15#include <FileIO.h>
16#include <support/StopWatch.h>
17#include <support/ZlibCompressionAlgorithm.h>
18
19#include "HaikuDepotConstants.h"
20#include "Logger.h"
21#include "ServerHelper.h"
22#include "ServerSettings.h"
23#include "StorageUtils.h"
24#include "TarArchiveService.h"
25
26
27#undef B_TRANSLATION_CONTEXT
28#define B_TRANSLATION_CONTEXT "ServerIconExportUpdateProcess"
29
30
31/*! This constructor will locate the cached data in a standardized location */
32
33ServerIconExportUpdateProcess::ServerIconExportUpdateProcess(
34	Model* model,
35	uint32 serverProcessOptions)
36	:
37	AbstractServerProcess(serverProcessOptions),
38	fModel(model),
39	fCountIconsSet(0)
40{
41	if (fModel->IconStoragePath(fLocalIconStoragePath) != B_OK) {
42		printf("[%s] unable to obtain the path for storing icons\n", Name());
43		fLocalIconStoragePath.Unset();
44		fLocalIconStore = NULL;
45	} else {
46		fLocalIconStore = new LocalIconStore(fLocalIconStoragePath);
47	}
48}
49
50
51ServerIconExportUpdateProcess::~ServerIconExportUpdateProcess()
52{
53	delete fLocalIconStore;
54}
55
56
57const char*
58ServerIconExportUpdateProcess::Name() const
59{
60	return "ServerIconExportUpdateProcess";
61}
62
63
64const char*
65ServerIconExportUpdateProcess::Description() const
66{
67	return B_TRANSLATE("Synchronizing icons");
68}
69
70
71status_t
72ServerIconExportUpdateProcess::RunInternal()
73{
74	status_t result = B_OK;
75
76	if (NULL == fLocalIconStore || fLocalIconStoragePath.Path() == NULL)
77		result = B_ERROR;
78
79	if (IsSuccess(result) && HasOption(SERVER_PROCESS_DROP_CACHE)) {
80		result = StorageUtils::RemoveDirectoryContents(fLocalIconStoragePath);
81	}
82
83	if (result == B_OK) {
84		bool hasData;
85
86		result = HasLocalData(&hasData);
87
88		if (result == B_OK && ShouldAttemptNetworkDownload(hasData))
89			result = _DownloadAndUnpack();
90
91		if (IsSuccess(result)) {
92			status_t hasDataResult = HasLocalData(&hasData);
93
94			if (hasDataResult == B_OK && !hasData)
95				result = HD_ERR_NO_DATA;
96		}
97	}
98
99	if (IsSuccess(result) && !WasStopped())
100		result = Populate();
101
102	return result;
103}
104
105
106status_t
107ServerIconExportUpdateProcess::Populate()
108{
109	BStopWatch watch("ServerIconExportUpdateProcess::Populate", true);
110	DepotList depots = fModel->Depots();
111	status_t result = B_OK;
112
113	{
114		AutoLocker<BLocker> locker(fModel->Lock());
115		depots = fModel->Depots();
116	}
117
118	if (Logger::IsDebugEnabled()) {
119		printf("[%s] will populate icons for %" B_PRId32 " depots\n", Name(),
120			depots.CountItems());
121	}
122
123	for (int32 i = 0;
124		(i < depots.CountItems()) && !WasStopped() && (result == B_OK);
125		i++) {
126		AutoLocker<BLocker> locker(fModel->Lock());
127		DepotInfo depotInfo = depots.ItemAtFast(i);
128		result = PopulateForDepot(depotInfo);
129	}
130
131	if (Logger::IsInfoEnabled()) {
132		double secs = watch.ElapsedTime() / 1000000.0;
133		printf("[%s] did populate %" B_PRId32 " packages' icons (%6.3g secs)\n",
134			Name(), fCountIconsSet, secs);
135	}
136
137	return result;
138}
139
140
141/*! This method assumes that the model lock has been acquired */
142
143status_t
144ServerIconExportUpdateProcess::PopulateForDepot(const DepotInfo& depot)
145{
146	printf("[%s] will populate icons for depot [%s]\n",
147		Name(), depot.Name().String());
148	status_t result = B_OK;
149	PackageList packages = depot.Packages();
150	for(int32 j = 0;
151		(j < packages.CountItems()) && !WasStopped() && (result == B_OK);
152		j++) {
153		const PackageInfoRef& packageInfoRef = packages.ItemAtFast(j);
154		result = PopulateForPkg(packageInfoRef);
155
156		if (result == B_FILE_NOT_FOUND)
157			result = B_OK;
158	}
159
160	return result;
161}
162
163
164/*! This method assumes that the model lock has been acquired */
165
166status_t
167ServerIconExportUpdateProcess::PopulateForPkg(const PackageInfoRef& package)
168{
169	BPath bestIconPath;
170
171	if ( fLocalIconStore->TryFindIconPath(
172		package->Name(), bestIconPath) == B_OK) {
173
174		BFile bestIconFile(bestIconPath.Path(), O_RDONLY);
175		BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(bestIconFile), true);
176		package->SetIcon(bitmapRef);
177
178		if (Logger::IsDebugEnabled()) {
179			printf("[%s] have set the package icon for [%s] from [%s]\n",
180				Name(), package->Name().String(), bestIconPath.Path());
181		}
182
183		fCountIconsSet++;
184
185		return B_OK;
186	}
187
188	if (Logger::IsDebugEnabled()) {
189		printf("[%s] did not set the package icon for [%s]; no data\n",
190			Name(), package->Name().String());
191	}
192
193	return B_FILE_NOT_FOUND;
194}
195
196
197status_t
198ServerIconExportUpdateProcess::_DownloadAndUnpack()
199{
200	BPath tarGzFilePath(tmpnam(NULL));
201	status_t result = B_OK;
202
203	printf("[%s] will start fetching icons\n", Name());
204
205	result = _Download(tarGzFilePath);
206
207	switch (result) {
208		case HD_ERR_NOT_MODIFIED:
209			printf("[%s] icons not modified - will use existing\n", Name());
210			return result;
211			break;
212		case B_OK:
213			return _Unpack(tarGzFilePath);
214		default:
215			return (_HandleDownloadFailure() != B_OK) ? result : B_OK;
216	}
217}
218
219
220/*! if the download failed, but there are existing icons in place to use
221    then use those icons.  To detect the existing files, look for the
222    icons' meta-info file.
223*/
224
225status_t
226ServerIconExportUpdateProcess::_HandleDownloadFailure()
227{
228	bool hasData;
229	status_t result = HasLocalData(&hasData);
230
231	if (result == B_OK) {
232		if (hasData) {
233			printf("[%s] failed to update data, but have old data anyway "
234				"so will carry on with that\n", Name());
235		} else {
236			printf("[%s] failed to obtain data\n", Name());
237			result = HD_ERR_NO_DATA;
238		}
239	} else {
240		printf("[%s] unable to detect if there is local data\n", Name());
241	}
242
243	return result;
244}
245
246
247/*! The tar-ball data of icons has arrived and so old data needs to be purged
248    to make way for the new data and the new data needs to be unpacked.
249*/
250
251status_t
252ServerIconExportUpdateProcess::_Unpack(BPath& tarGzFilePath)
253{
254	status_t result;
255	printf("[%s] delete any existing stored data\n", Name());
256	StorageUtils::RemoveDirectoryContents(fLocalIconStoragePath);
257
258	BFile *tarGzFile = new BFile(tarGzFilePath.Path(), O_RDONLY);
259	BDataIO* tarIn;
260
261	BZlibDecompressionParameters* zlibDecompressionParameters
262		= new BZlibDecompressionParameters();
263
264	result = BZlibCompressionAlgorithm()
265		.CreateDecompressingInputStream(tarGzFile,
266			zlibDecompressionParameters, tarIn);
267
268	if (result == B_OK) {
269		BStopWatch watch(
270			"ServerIconExportUpdateProcess::DownloadAndUnpack_Unpack",
271			true);
272
273		result = TarArchiveService::Unpack(*tarIn,
274			fLocalIconStoragePath, NULL);
275
276		if (result == B_OK) {
277			double secs = watch.ElapsedTime() / 1000000.0;
278			printf("[%s] did unpack icon tgz in (%6.3g secs)\n", Name(),
279				secs);
280
281			if (0 != remove(tarGzFilePath.Path())) {
282				printf("unable to delete the temporary tgz path; %s\n",
283					tarGzFilePath.Path());
284			}
285		}
286	}
287
288	delete tarGzFile;
289	printf("[%s] did complete unpacking icons\n", Name());
290	return result;
291}
292
293
294status_t
295ServerIconExportUpdateProcess::HasLocalData(bool* result) const
296{
297	BPath path;
298	status_t status = GetStandardMetaDataPath(path);
299
300	if (status != B_OK)
301		return status;
302
303	off_t size;
304
305	status = StorageUtils::ExistsObject(path, result, NULL, &size);
306
307	if (status == B_OK && size == 0)
308		*result = false;
309
310	return status;
311}
312
313
314status_t
315ServerIconExportUpdateProcess::GetStandardMetaDataPath(BPath& path) const
316{
317	status_t result = fModel->IconStoragePath(path);
318
319	if (result != B_OK)
320		return result;
321
322	path.Append("hicn/info.json");
323	return B_OK;
324}
325
326
327void
328ServerIconExportUpdateProcess::GetStandardMetaDataJsonPath(
329	BString& jsonPath) const
330{
331		// the "$" here indicates that the data is at the top level.
332	jsonPath.SetTo("$");
333}
334
335
336status_t
337ServerIconExportUpdateProcess::_Download(BPath& tarGzFilePath)
338{
339	return DownloadToLocalFileAtomically(tarGzFilePath,
340		ServerSettings::CreateFullUrl("/__pkgicon/all.tar.gz"));
341}
342
343
344
345