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