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