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"
11 #include <AutoDeleter.h>
16 #include <UrlProtocolRoster.h>
18 #include <support/ZlibCompressionAlgorithm.h>
21 #include "ServerSettings.h"
22 #include "StandardMetaDataJsonEventListener.h"
23 #include "StorageUtils.h"
24 #include "ToFileUrlProtocolListener.h"
27 #define MAX_REDIRECTS 3
28 #define MAX_FAILURES 2
30 #define HTTP_STATUS_FOUND 302
31 #define HTTP_STATUS_NOT_MODIFIED 304
34 #define TIMEOUT_MICROSECONDS 3e+7
37 AbstractServerProcess::AbstractServerProcess(
38 AbstractServerProcessListener
* listener
, uint32 options
)
43 fProcessState(SERVER_PROCESS_INITIAL
),
51 AbstractServerProcess::~AbstractServerProcess()
57 AbstractServerProcess::HasOption(uint32 flag
)
59 return (fOptions
& flag
) == flag
;
64 AbstractServerProcess::ShouldAttemptNetworkDownload(bool hasDataAlready
)
67 !HasOption(SERVER_PROCESS_NO_NETWORKING
)
68 && !(HasOption(SERVER_PROCESS_PREFER_CACHE
) && hasDataAlready
);
73 AbstractServerProcess::Run()
76 BAutolock
locker(&fLock
);
78 if (ProcessState() != SERVER_PROCESS_INITIAL
) {
79 printf("cannot start server process as it is not idle");
83 fProcessState
= SERVER_PROCESS_RUNNING
;
86 SetErrorStatus(RunInternal());
88 SetProcessState(SERVER_PROCESS_COMPLETE
);
90 // this process may be part of a larger bulk-load process and
91 // if so, the process orchestration needs to know when this
92 // process has completed.
94 if (fListener
!= NULL
)
95 fListener
->ServerProcessExited();
102 AbstractServerProcess::WasStopped()
104 BAutolock
locker(&fLock
);
110 AbstractServerProcess::ErrorStatus()
112 BAutolock
locker(&fLock
);
118 AbstractServerProcess::Stop()
120 BAutolock
locker(&fLock
);
122 return StopInternal();
127 AbstractServerProcess::StopInternal()
129 if (fRequest
!= NULL
) {
130 return fRequest
->Stop();
133 return B_NOT_ALLOWED
;
138 AbstractServerProcess::IsRunning()
140 return ProcessState() == SERVER_PROCESS_RUNNING
;
145 AbstractServerProcess::SetErrorStatus(status_t value
)
147 BAutolock
locker(&fLock
);
149 if (fErrorStatus
== B_OK
) {
150 fErrorStatus
= value
;
156 AbstractServerProcess::SetProcessState(process_state value
)
158 BAutolock
locker(&fLock
);
159 fProcessState
= value
;
164 AbstractServerProcess::ProcessState()
166 BAutolock
locker(&fLock
);
167 return fProcessState
;
172 AbstractServerProcess::IfModifiedSinceHeaderValue(BString
& headerValue
) const
177 GetStandardMetaDataPath(metaDataPath
);
178 GetStandardMetaDataJsonPath(jsonPath
);
180 return IfModifiedSinceHeaderValue(headerValue
, metaDataPath
, jsonPath
);
185 AbstractServerProcess::IfModifiedSinceHeaderValue(BString
& headerValue
,
186 const BPath
& metaDataPath
, const BString
& jsonPath
) const
188 headerValue
.SetTo("");
191 if (-1 == stat(metaDataPath
.Path(), &s
)) {
195 return B_FILE_NOT_FOUND
;
201 StandardMetaData metaData
;
202 status_t result
= PopulateMetaData(metaData
, metaDataPath
, jsonPath
);
204 if (result
== B_OK
) {
206 // An example of this output would be; 'Fri, 24 Oct 2014 19:32:27 +0000'
208 BDateTime modifiedDateTime
= metaData
209 .GetDataModifiedTimestampAsDateTime();
210 BPrivate::BHttpTime
modifiedHttpTime(modifiedDateTime
);
211 headerValue
.SetTo(modifiedHttpTime
212 .ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE
));
214 fprintf(stderr
, "unable to parse the meta-data date and time from [%s]"
215 " - cannot set the 'If-Modified-Since' header\n",
216 metaDataPath
.Path());
224 AbstractServerProcess::PopulateMetaData(
225 StandardMetaData
& metaData
, const BPath
& path
,
226 const BString
& jsonPath
) const
228 StandardMetaDataJsonEventListener
listener(jsonPath
, metaData
);
229 status_t result
= ParseJsonFromFileWithListener(&listener
, path
);
234 result
= listener
.ErrorStatus();
239 if (!metaData
.IsPopulated()) {
240 fprintf(stderr
, "the meta data was read from [%s], but no values "
241 "were extracted\n", path
.Path());
250 AbstractServerProcess::LooksLikeGzip(const char *pathStr
) const
252 int l
= strlen(pathStr
);
253 return l
> 4 && 0 == strncmp(&pathStr
[l
- 3], ".gz", 3);
257 /*! Note that a B_OK return code from this method may not indicate that the
258 listening process went well. One has to see if there was an error in
263 AbstractServerProcess::ParseJsonFromFileWithListener(
264 BJsonEventListener
*listener
,
265 const BPath
& path
) const
267 const char* pathStr
= path
.Path();
268 FILE* file
= fopen(pathStr
, "rb");
271 fprintf(stderr
, "unable to find the meta data file at [%s]\n",
273 return B_FILE_NOT_FOUND
;
276 BFileIO
rawInput(file
, true); // takes ownership
278 // if the file extension ends with '.gz' then the data will be
279 // compressed and the algorithm needs to decompress the data as
282 if (LooksLikeGzip(pathStr
)) {
283 BDataIO
* gzDecompressedInput
= NULL
;
284 BZlibDecompressionParameters
* zlibDecompressionParameters
285 = new BZlibDecompressionParameters();
287 status_t result
= BZlibCompressionAlgorithm()
288 .CreateDecompressingInputStream(&rawInput
,
289 zlibDecompressionParameters
, gzDecompressedInput
);
294 ObjectDeleter
<BDataIO
> gzDecompressedInputDeleter(gzDecompressedInput
);
295 BPrivate::BJson::Parse(gzDecompressedInput
, listener
);
297 BPrivate::BJson::Parse(&rawInput
, listener
);
304 /*! In order to reduce the chance of failure half way through downloading a
305 large file, this method will download the file to a temporary file and
306 then it can rename the file to the final target file.
310 AbstractServerProcess::DownloadToLocalFileAtomically(
311 const BPath
& targetFilePath
,
314 BPath
temporaryFilePath(tmpnam(NULL
), NULL
, true);
315 status_t result
= DownloadToLocalFile(
316 temporaryFilePath
, url
, 0, 0);
318 // not copying if the data has not changed because the data will be
319 // zero length. This is if the result is APP_ERR_NOT_MODIFIED.
320 if (result
== B_OK
) {
322 // if the file is zero length then assume that something has
327 result
= StorageUtils::ExistsObject(temporaryFilePath
, &hasFile
, NULL
,
330 if (result
== B_OK
&& hasFile
&& size
> 0) {
331 if (rename(temporaryFilePath
.Path(), targetFilePath
.Path()) != 0) {
332 printf("[%s] did rename [%s] --> [%s]\n",
333 Name(), temporaryFilePath
.Path(), targetFilePath
.Path());
344 AbstractServerProcess::DownloadToLocalFile(const BPath
& targetFilePath
,
345 const BUrl
& url
, uint32 redirects
, uint32 failures
)
350 if (redirects
> MAX_REDIRECTS
) {
351 fprintf(stdout
, "exceeded %d redirects --> failure\n", MAX_REDIRECTS
);
355 if (failures
> MAX_FAILURES
) {
356 fprintf(stdout
, "exceeded %d failures\n", MAX_FAILURES
);
360 fprintf(stdout
, "[%s] will stream '%s' to [%s]\n",
361 Name(), url
.UrlString().String(), targetFilePath
.Path());
363 ToFileUrlProtocolListener
listener(targetFilePath
, Name(),
364 Logger::IsTraceEnabled());
366 BHttpHeaders headers
;
367 ServerSettings::AugmentHeaders(headers
);
369 BString ifModifiedSinceHeader
;
370 status_t ifModifiedSinceHeaderStatus
= IfModifiedSinceHeaderValue(
371 ifModifiedSinceHeader
);
373 if (ifModifiedSinceHeaderStatus
== B_OK
&&
374 ifModifiedSinceHeader
.Length() > 0) {
375 headers
.AddHeader("If-Modified-Since", ifModifiedSinceHeader
);
381 fRequest
= dynamic_cast<BHttpRequest
*>(
382 BUrlProtocolRoster::MakeRequest(url
, &listener
));
383 fRequest
->SetHeaders(headers
);
384 fRequest
->SetMaxRedirections(0);
385 fRequest
->SetTimeout(TIMEOUT_MICROSECONDS
);
386 thread
= fRequest
->Run();
389 wait_for_thread(thread
, NULL
);
391 const BHttpResult
& result
= dynamic_cast<const BHttpResult
&>(
393 int32 statusCode
= result
.StatusCode();
394 const BHttpHeaders responseHeaders
= result
.Headers();
395 const char *locationC
= responseHeaders
["Location"];
398 if (locationC
!= NULL
)
399 location
.SetTo(locationC
);
404 if (BHttpRequest::IsSuccessStatusCode(statusCode
)) {
405 fprintf(stdout
, "[%s] did complete streaming data [%"
406 B_PRIdSSIZE
" bytes]\n", Name(), listener
.ContentLength());
408 } else if (statusCode
== HTTP_STATUS_NOT_MODIFIED
) {
409 fprintf(stdout
, "[%s] remote data has not changed since [%s]\n",
410 Name(), ifModifiedSinceHeader
.String());
411 return APP_ERR_NOT_MODIFIED
;
412 } else if (BHttpRequest::IsRedirectionStatusCode(statusCode
)) {
413 if (location
.Length() != 0) {
414 BUrl
redirectUrl(result
.Url(), location
);
415 fprintf(stdout
, "[%s] will redirect to; %s\n",
416 Name(), redirectUrl
.UrlString().String());
417 return DownloadToLocalFile(targetFilePath
, redirectUrl
,
421 fprintf(stdout
, "[%s] unable to find 'Location' header for redirect\n",
425 if (statusCode
== 0 || (statusCode
/ 100) == 5) {
426 fprintf(stdout
, "error response from server [%" B_PRId32
"] --> "
427 "retry...\n", statusCode
);
428 return DownloadToLocalFile(targetFilePath
, url
, redirects
,
432 fprintf(stdout
, "[%s] unexpected response from server [%" B_PRId32
"]\n",
440 AbstractServerProcess::DeleteLocalFile(const BPath
& currentFilePath
)
442 if (0 == remove(currentFilePath
.Path()))
449 /*! When a file is damaged or corrupted in some way, the file should be 'moved
450 aside' so that it is not involved in the next update. This method will
451 create such an alternative 'damaged' file location and move this file to
456 AbstractServerProcess::MoveDamagedFileAside(const BPath
& currentFilePath
)
458 BPath damagedFilePath
;
461 damagedLeaf
.SetToFormat("%s__damaged", currentFilePath
.Leaf());
462 currentFilePath
.GetParent(&damagedFilePath
);
463 damagedFilePath
.Append(damagedLeaf
.String());
465 if (0 != rename(currentFilePath
.Path(), damagedFilePath
.Path())) {
466 printf("[%s] unable to move damaged file [%s] aside to [%s]\n",
467 Name(), currentFilePath
.Path(), damagedFilePath
.Path());
471 printf("[%s] did move damaged file [%s] aside to [%s]\n",
472 Name(), currentFilePath
.Path(), damagedFilePath
.Path());
479 AbstractServerProcess::IsSuccess(status_t e
) {
480 return e
== B_OK
|| e
== APP_ERR_NOT_MODIFIED
;