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