2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>.
3 * All rights reserved. Distributed under the terms of the MIT License.
5 #include "AbstractServerProcess.h"
10 #include <AutoDeleter.h>
12 #include <HttpRequest.h>
14 #include <UrlProtocolRoster.h>
16 #include <support/ZlibCompressionAlgorithm.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
31 #define TIMEOUT_MICROSECONDS 3e+7
35 AbstractServerProcess::IfModifiedSinceHeaderValue(BString
& headerValue
) const
40 GetStandardMetaDataPath(metaDataPath
);
41 GetStandardMetaDataJsonPath(jsonPath
);
43 return IfModifiedSinceHeaderValue(headerValue
, metaDataPath
, jsonPath
);
48 AbstractServerProcess::IfModifiedSinceHeaderValue(BString
& headerValue
,
49 const BPath
& metaDataPath
, const BString
& jsonPath
) const
51 headerValue
.SetTo("");
54 if (-1 == stat(metaDataPath
.Path(), &s
)) {
58 return B_FILE_NOT_FOUND
;
64 StandardMetaData metaData
;
65 status_t result
= PopulateMetaData(metaData
, metaDataPath
, jsonPath
);
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
));
77 fprintf(stderr
, "unable to parse the meta-data date and time -"
78 " cannot set the 'If-Modified-Since' header\n");
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
);
96 result
= listener
.ErrorStatus();
101 if (!metaData
.IsPopulated()) {
102 fprintf(stderr
, "the meta data was read from [%s], but no values "
103 "were extracted\n", path
.Path());
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
125 AbstractServerProcess::ParseJsonFromFileWithListener(
126 BJsonEventListener
*listener
,
127 const BPath
& path
) const
129 const char* pathStr
= path
.Path();
130 FILE* file
= fopen(pathStr
, "rb");
133 fprintf(stderr
, "unable to find the meta data file at [%s]\n",
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
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
);
156 ObjectDeleter
<BDataIO
> gzDecompressedInputDeleter(gzDecompressedInput
);
157 BPrivate::BJson::Parse(gzDecompressedInput
, listener
);
159 BPrivate::BJson::Parse(&rawInput
, listener
);
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
);
175 if (failures
> MAX_FAILURES
) {
176 fprintf(stdout
, "exceeded %d failures\n", MAX_FAILURES
);
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
&>(
211 int32 statusCode
= result
.StatusCode();
213 if (BHttpRequest::IsSuccessStatusCode(statusCode
)) {
214 fprintf(stdout
, "did complete streaming data\n");
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
,
232 fprintf(stdout
, "unable to find 'Location' header for redirect\n");
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
,
242 fprintf(stdout
, "unexpected response from server; %" B_PRId32
"\n",
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
256 AbstractServerProcess::MoveDamagedFileAside(const BPath
& currentFilePath
)
258 BPath damagedFilePath
;
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());
271 printf("did move damaged file [%s] aside to [%s]\n",
272 currentFilePath
.Path(), damagedFilePath
.Path());