repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / haikudepot / server / AbstractServerProcess.cpp
blobebb20bad12c59fee7e2abadb8a67a30500a71b45
1 /*
2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5 #include "AbstractServerProcess.h"
7 #include <errno.h>
8 #include <string.h>
10 #include <AutoDeleter.h>
11 #include <FileIO.h>
12 #include <HttpRequest.h>
13 #include <HttpTime.h>
14 #include <UrlProtocolRoster.h>
16 #include <support/ZlibCompressionAlgorithm.h>
18 #include "Logger.h"
19 #include "ServerSettings.h"
20 #include "StandardMetaDataJsonEventListener.h"
21 #include "ToFileUrlProtocolListener.h"
24 #define MAX_REDIRECTS 3
25 #define MAX_FAILURES 2
27 #define HTTP_STATUS_FOUND 302
28 #define HTTP_STATUS_NOT_MODIFIED 304
30 // 30 seconds
31 #define TIMEOUT_MICROSECONDS 3e+7
34 status_t
35 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue) const
37 BPath metaDataPath;
38 BString jsonPath;
40 GetStandardMetaDataPath(metaDataPath);
41 GetStandardMetaDataJsonPath(jsonPath);
43 return IfModifiedSinceHeaderValue(headerValue, metaDataPath, jsonPath);
47 status_t
48 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue,
49 const BPath& metaDataPath, const BString& jsonPath) const
51 headerValue.SetTo("");
52 struct stat s;
54 if (-1 == stat(metaDataPath.Path(), &s)) {
55 if (ENOENT != errno)
56 return B_ERROR;
58 return B_FILE_NOT_FOUND;
61 if (s.st_size == 0)
62 return B_BAD_VALUE;
64 StandardMetaData metaData;
65 status_t result = PopulateMetaData(metaData, metaDataPath, jsonPath);
67 if (result == B_OK) {
69 // An example of this output would be; 'Fri, 24 Oct 2014 19:32:27 +0000'
71 BDateTime modifiedDateTime = metaData
72 .GetDataModifiedTimestampAsDateTime();
73 BPrivate::BHttpTime modifiedHttpTime(modifiedDateTime);
74 headerValue.SetTo(modifiedHttpTime
75 .ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE));
76 } else {
77 fprintf(stderr, "unable to parse the meta-data date and time -"
78 " cannot set the 'If-Modified-Since' header\n");
81 return result;
85 status_t
86 AbstractServerProcess::PopulateMetaData(
87 StandardMetaData& metaData, const BPath& path,
88 const BString& jsonPath) const
90 StandardMetaDataJsonEventListener listener(jsonPath, metaData);
91 status_t result = ParseJsonFromFileWithListener(&listener, path);
93 if (result != B_OK)
94 return result;
96 result = listener.ErrorStatus();
98 if (result != B_OK)
99 return result;
101 if (!metaData.IsPopulated()) {
102 fprintf(stderr, "the meta data was read from [%s], but no values "
103 "were extracted\n", path.Path());
104 return B_BAD_DATA;
107 return B_OK;
111 bool
112 AbstractServerProcess::LooksLikeGzip(const char *pathStr) const
114 int l = strlen(pathStr);
115 return l > 4 && 0 == strncmp(&pathStr[l - 3], ".gz", 3);
119 /*! Note that a B_OK return code from this method may not indicate that the
120 listening process went well. One has to see if there was an error in
121 the listener.
124 status_t
125 AbstractServerProcess::ParseJsonFromFileWithListener(
126 BJsonEventListener *listener,
127 const BPath& path) const
129 const char* pathStr = path.Path();
130 FILE* file = fopen(pathStr, "rb");
132 if (file == NULL) {
133 fprintf(stderr, "unable to find the meta data file at [%s]\n",
134 path.Path());
135 return B_FILE_NOT_FOUND;
138 BFileIO rawInput(file, true); // takes ownership
140 // if the file extension ends with '.gz' then the data will be
141 // compressed and the algorithm needs to decompress the data as
142 // it is parsed.
144 if (LooksLikeGzip(pathStr)) {
145 BDataIO* gzDecompressedInput = NULL;
146 BZlibDecompressionParameters* zlibDecompressionParameters
147 = new BZlibDecompressionParameters();
149 status_t result = BZlibCompressionAlgorithm()
150 .CreateDecompressingInputStream(&rawInput,
151 zlibDecompressionParameters, gzDecompressedInput);
153 if (B_OK != result)
154 return result;
156 ObjectDeleter<BDataIO> gzDecompressedInputDeleter(gzDecompressedInput);
157 BPrivate::BJson::Parse(gzDecompressedInput, listener);
158 } else {
159 BPrivate::BJson::Parse(&rawInput, listener);
162 return B_OK;
166 status_t
167 AbstractServerProcess::DownloadToLocalFile(const BPath& targetFilePath,
168 const BUrl& url, uint32 redirects, uint32 failures)
170 if (redirects > MAX_REDIRECTS) {
171 fprintf(stdout, "exceeded %d redirects --> failure\n", MAX_REDIRECTS);
172 return B_IO_ERROR;
175 if (failures > MAX_FAILURES) {
176 fprintf(stdout, "exceeded %d failures\n", MAX_FAILURES);
177 return B_IO_ERROR;
180 fprintf(stdout, "will stream '%s' to [%s]\n", url.UrlString().String(),
181 targetFilePath.Path());
183 ToFileUrlProtocolListener listener(targetFilePath, LoggingName(),
184 Logger::IsTraceEnabled());
186 BHttpHeaders headers;
187 ServerSettings::AugmentHeaders(headers);
189 BString ifModifiedSinceHeader;
190 status_t ifModifiedSinceHeaderStatus = IfModifiedSinceHeaderValue(
191 ifModifiedSinceHeader);
193 if (ifModifiedSinceHeaderStatus == B_OK &&
194 ifModifiedSinceHeader.Length() > 0) {
195 headers.AddHeader("If-Modified-Since", ifModifiedSinceHeader);
198 BHttpRequest *request = dynamic_cast<BHttpRequest *>(
199 BUrlProtocolRoster::MakeRequest(url, &listener));
200 ObjectDeleter<BHttpRequest> requestDeleter(request);
201 request->SetHeaders(headers);
202 request->SetMaxRedirections(0);
203 request->SetTimeout(TIMEOUT_MICROSECONDS);
205 thread_id thread = request->Run();
206 wait_for_thread(thread, NULL);
208 const BHttpResult& result = dynamic_cast<const BHttpResult&>(
209 request->Result());
211 int32 statusCode = result.StatusCode();
213 if (BHttpRequest::IsSuccessStatusCode(statusCode)) {
214 fprintf(stdout, "did complete streaming data\n");
215 return B_OK;
216 } else if (statusCode == HTTP_STATUS_NOT_MODIFIED) {
217 fprintf(stdout, "remote data has not changed since [%s]\n",
218 ifModifiedSinceHeader.String());
219 return APP_ERR_NOT_MODIFIED;
220 } else if (BHttpRequest::IsRedirectionStatusCode(statusCode)) {
221 const BHttpHeaders responseHeaders = result.Headers();
222 const char *locationValue = responseHeaders["Location"];
224 if (locationValue != NULL && strlen(locationValue) != 0) {
225 BUrl location(result.Url(), locationValue);
226 fprintf(stdout, "will redirect to; %s\n",
227 location.UrlString().String());
228 return DownloadToLocalFile(targetFilePath, location,
229 redirects + 1, 0);
232 fprintf(stdout, "unable to find 'Location' header for redirect\n");
233 return B_IO_ERROR;
234 } else {
235 if (statusCode == 0 || (statusCode / 100) == 5) {
236 fprintf(stdout, "error response from server; %" B_PRId32 " --> "
237 "retry...\n", statusCode);
238 return DownloadToLocalFile(targetFilePath, url, redirects,
239 failures + 1);
242 fprintf(stdout, "unexpected response from server; %" B_PRId32 "\n",
243 statusCode);
244 return B_IO_ERROR;
249 /*! When a file is damaged or corrupted in some way, the file should be 'moved
250 aside' so that it is not involved in the next update. This method will
251 create such an alternative 'damaged' file location and move this file to
252 that location.
255 status_t
256 AbstractServerProcess::MoveDamagedFileAside(const BPath& currentFilePath)
258 BPath damagedFilePath;
259 BString damagedLeaf;
261 damagedLeaf.SetToFormat("%s__damaged", currentFilePath.Leaf());
262 currentFilePath.GetParent(&damagedFilePath);
263 damagedFilePath.Append(damagedLeaf.String());
265 if (0 != rename(currentFilePath.Path(), damagedFilePath.Path())) {
266 printf("unable to move damaged file [%s] aside to [%s]\n",
267 currentFilePath.Path(), damagedFilePath.Path());
268 return B_IO_ERROR;
271 printf("did move damaged file [%s] aside to [%s]\n",
272 currentFilePath.Path(), damagedFilePath.Path());
274 return B_OK;