xref: /haiku/src/apps/haikudepot/server/ServerIconExportUpdateProcess.cpp (revision aa3083e086e5a929c061c72983e09d916c548a38)
1 /*
2  * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "ServerIconExportUpdateProcess.h"
7 
8 #include <errno.h>
9 #include <stdio.h>
10 #include <sys/stat.h>
11 #include <time.h>
12 
13 #include <HttpRequest.h>
14 #include <Json.h>
15 #include <Url.h>
16 #include <support/ZlibCompressionAlgorithm.h>
17 
18 #include "ServerSettings.h"
19 #include "StorageUtils.h"
20 #include "TarArchiveService.h"
21 #include "ToFileUrlProtocolListener.h"
22 
23 
24 #define MAX_REDIRECTS 3
25 #define MAX_FAILURES 2
26 
27 #define HTTP_STATUS_OK 200
28 #define HTTP_STATUS_FOUND 302
29 #define HTTP_STATUS_NOT_MODIFIED 304
30 
31 #define APP_ERR_NOT_MODIFIED (B_APP_ERROR_BASE + 452)
32 
33 // 30 seconds
34 #define TIMEOUT_MICROSECONDS 3e+7
35 
36 
37 /*! This constructor will locate the cached data in a standardized location */
38 
39 ServerIconExportUpdateProcess::ServerIconExportUpdateProcess(
40 	const BPath& localStorageDirectoryPath)
41 {
42 	fLocalStorageDirectoryPath = localStorageDirectoryPath;
43 }
44 
45 
46 status_t
47 ServerIconExportUpdateProcess::Run()
48 {
49 	BPath tarGzFilePath(tmpnam(NULL));
50 	status_t result = B_OK;
51 
52 	fprintf(stdout, "will start fetching icons\n");
53 
54 	result = _Download(tarGzFilePath);
55 
56 	if (result != APP_ERR_NOT_MODIFIED) {
57 		if (result != B_OK)
58 			return result;
59 
60 		fprintf(stdout, "delete any existing stored data\n");
61 		StorageUtils::RemoveDirectoryContents(fLocalStorageDirectoryPath);
62 
63 		BFile *tarGzFile = new BFile(tarGzFilePath.Path(), O_RDONLY);
64 		BDataIO* tarIn;
65 
66 		BZlibDecompressionParameters* zlibDecompressionParameters
67 			= new BZlibDecompressionParameters();
68 
69 		result = BZlibCompressionAlgorithm()
70 			.CreateDecompressingInputStream(tarGzFile,
71 			    zlibDecompressionParameters, tarIn);
72 
73 		if (result == B_OK) {
74 			result = TarArchiveService::Unpack(*tarIn,
75 			    fLocalStorageDirectoryPath);
76 
77 			if (result == B_OK) {
78 				if (0 != remove(tarGzFilePath.Path())) {
79 					fprintf(stdout, "unable to delete the temporary tgz path; "
80 					    "%s", tarGzFilePath.Path());
81 				}
82 			}
83 		}
84 
85 		delete tarGzFile;
86 	}
87 
88 	fprintf(stdout, "did complete fetching icons\n");
89 
90 	return result;
91 }
92 
93 
94 status_t
95 ServerIconExportUpdateProcess::_IfModifiedSinceHeaderValue(BString& headerValue,
96 	BPath& iconMetaDataPath) const
97 {
98 	headerValue.SetTo("");
99 	struct stat s;
100 
101 	if (-1 == stat(iconMetaDataPath.Path(), &s)) {
102 		if (ENOENT != errno)
103 			 return B_ERROR;
104 
105 		return B_FILE_NOT_FOUND;
106 	}
107 
108 	IconMetaData iconMetaData;
109 	status_t result = _PopulateIconMetaData(iconMetaData, iconMetaDataPath);
110 
111 	if (result == B_OK) {
112 		_TimestampToRfc2822String(iconMetaData.GetDataModifiedTimestamp(),
113 			headerValue);
114 	}
115 
116 	return result;
117 }
118 
119 
120 status_t
121 ServerIconExportUpdateProcess::_IfModifiedSinceHeaderValue(BString& headerValue)
122     const
123 {
124 	BPath iconMetaDataPath(fLocalStorageDirectoryPath);
125 	iconMetaDataPath.Append("hicn/info.json");
126 	return _IfModifiedSinceHeaderValue(headerValue, iconMetaDataPath);
127 }
128 
129 
130 status_t
131 ServerIconExportUpdateProcess::_Download(BPath& tarGzFilePath)
132 {
133 	BString urlString = ServerSettings::CreateFullUrl("/__pkgicon/all.tar.gz");
134 	return _Download(tarGzFilePath, BUrl(urlString), 0, 0);
135 }
136 
137 
138 status_t
139 ServerIconExportUpdateProcess::_Download(BPath& tarGzFilePath, const BUrl& url,
140 	uint32 redirects, uint32 failures)
141 {
142 	if (redirects > MAX_REDIRECTS) {
143 		fprintf(stdout, "exceeded %d redirects --> failure\n", MAX_REDIRECTS);
144 		return B_IO_ERROR;
145 	}
146 
147 	if (failures > MAX_FAILURES) {
148 		fprintf(stdout, "exceeded %d failures\n", MAX_FAILURES);
149 		return B_IO_ERROR;
150 	}
151 
152 	fprintf(stdout, "will stream '%s' to [%s]\n", url.UrlString().String(),
153 		tarGzFilePath.Path());
154 
155 	bool isSecure = url.Protocol() == BString("https");
156 	ToFileUrlProtocolListener listener(tarGzFilePath, "icon-export",
157 		ServerSettings::UrlConnectionTraceLoggingEnabled());
158 	BUrlContext context;
159 
160 	BHttpHeaders headers;
161 	ServerSettings::AugmentHeaders(headers);
162 
163 	BString ifModifiedSinceHeader;
164 	status_t ifModifiedSinceHeaderStatus = _IfModifiedSinceHeaderValue(
165 		ifModifiedSinceHeader);
166 
167 	if (ifModifiedSinceHeaderStatus == B_OK &&
168 		ifModifiedSinceHeader.Length() > 0) {
169 		headers.AddHeader("If-Modified-Since", ifModifiedSinceHeader);
170 	}
171 
172 	BHttpRequest request(url, isSecure, "HTTP", &listener, &context);
173 	request.SetMethod(B_HTTP_GET);
174 	request.SetHeaders(headers);
175 	request.SetTimeout(TIMEOUT_MICROSECONDS);
176 
177 	thread_id thread = request.Run();
178 	wait_for_thread(thread, NULL);
179 
180 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
181 		request.Result());
182 
183 	int32 statusCode = result.StatusCode();
184 
185 	switch (statusCode) {
186 		case HTTP_STATUS_OK:
187 			fprintf(stdout, "did complete streaming data\n");
188 			return B_OK;
189 
190 		case HTTP_STATUS_NOT_MODIFIED:
191 			fprintf(stdout, "remote data has not changed since [%s]\n",
192 				ifModifiedSinceHeader.String());
193 			return APP_ERR_NOT_MODIFIED;
194 
195 		case HTTP_STATUS_FOUND: // redirect
196 		{
197 			const BHttpHeaders responseHeaders = result.Headers();
198 			const char *locationValue = responseHeaders["Location"];
199 
200 			if (NULL != locationValue && 0 != strlen(locationValue)) {
201 				BUrl location(locationValue);
202 				fprintf(stdout, "will redirect to; %s\n",
203 					location.UrlString().String());
204 				return _Download(tarGzFilePath, location, redirects + 1, 0);
205 			}
206 
207 			fprintf(stdout, "unable to find 'Location' header for redirect\n");
208 			return B_IO_ERROR;
209 		}
210 
211 		default:
212 			if (0 == statusCode || 5 == (statusCode / 100)) {
213 				fprintf(stdout, "error response from server; %zu --> "
214 					"retry...\n", statusCode);
215 				return _Download(tarGzFilePath, url, redirects, failures + 1);
216 			}
217 
218 			fprintf(stdout, "unexpected response from server; %zu\n",
219 				statusCode);
220 			return B_IO_ERROR;
221 	}
222 }
223 
224 
225 status_t
226 ServerIconExportUpdateProcess::_PopulateIconMetaData(IconMetaData& iconMetaData,
227 	BMessage& message) const
228 {
229 	status_t result = B_OK;
230 	double value; // numeric resolution issue?
231 
232 	if (result == B_OK)
233 		result = message.FindDouble("createTimestamp", &value);
234 
235 	if (result == B_OK)
236 		iconMetaData.SetCreateTimestamp((uint64) value);
237 
238 	if (result == B_OK)
239 		result = message.FindDouble("dataModifiedTimestamp", &value);
240 
241 	if (result == B_OK)
242 		iconMetaData.SetDataModifiedTimestamp((uint64) value);
243 
244 	return result;
245 }
246 
247 
248 status_t
249 ServerIconExportUpdateProcess::_PopulateIconMetaData(IconMetaData& iconMetaData,
250 	BString& jsonString) const
251 {
252 	BJson parser;
253 	BMessage infoMetaDataMessage;
254 	status_t result = parser.Parse(infoMetaDataMessage, jsonString);
255 
256 	if (result == B_OK)
257 		return _PopulateIconMetaData(iconMetaData, infoMetaDataMessage);
258 
259 	return result;
260 }
261 
262 
263 status_t
264 ServerIconExportUpdateProcess::_PopulateIconMetaData(IconMetaData& iconMetaData,
265 	BPath& path) const
266 {
267 
268 	BString infoMetaDataStr;
269 	status_t result = StorageUtils::AppendToString(path, infoMetaDataStr);
270 
271 	if (result == B_OK)
272 		return _PopulateIconMetaData(iconMetaData, infoMetaDataStr);
273 
274 	return result;
275 }
276 
277 
278 /* The output format for this is suitable for use in the "If-Modified-Since"
279  * header.  An example of this output would be;
280  * 'Fri, 24 Oct 2014 19:32:27 +0000'
281  */
282 
283 void
284 ServerIconExportUpdateProcess::_TimestampToRfc2822String(
285 	uint64_t timestampMillis,
286 	BString& rfc2822String) const
287 {
288 	char output[256];
289 	output[0] = 0;
290 	time_t t = (timestampMillis / 1000);
291 	strftime(output, 256, "%a, %d %b %Y %T %z", gmtime(&t));
292 	rfc2822String.SetTo(output);
293 }
294 
295