HaikuDepot: notify work status from main window
[haiku.git] / src / apps / haikudepot / server / AbstractServerProcess.cpp
blobc78614f035fdf78fb9b38af302727565d290dce8
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 <unistd.h>
8 #include <errno.h>
9 #include <string.h>
11 #include <AutoDeleter.h>
12 #include <Autolock.h>
13 #include <FileIO.h>
14 #include <HttpTime.h>
15 #include <Locker.h>
16 #include <UrlProtocolRoster.h>
18 #include <support/ZlibCompressionAlgorithm.h>
20 #include "Logger.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
33 // 30 seconds
34 #define TIMEOUT_MICROSECONDS 3e+7
37 AbstractServerProcess::AbstractServerProcess(
38 AbstractServerProcessListener* listener, uint32 options)
40 fLock(),
41 fListener(listener),
42 fWasStopped(false),
43 fProcessState(SERVER_PROCESS_INITIAL),
44 fErrorStatus(B_OK),
45 fOptions(options),
46 fRequest(NULL)
51 AbstractServerProcess::~AbstractServerProcess()
56 bool
57 AbstractServerProcess::HasOption(uint32 flag)
59 return (fOptions & flag) == flag;
63 bool
64 AbstractServerProcess::ShouldAttemptNetworkDownload(bool hasDataAlready)
66 return
67 !HasOption(SERVER_PROCESS_NO_NETWORKING)
68 && !(HasOption(SERVER_PROCESS_PREFER_CACHE) && hasDataAlready);
72 status_t
73 AbstractServerProcess::Run()
76 BAutolock locker(&fLock);
78 if (ProcessState() != SERVER_PROCESS_INITIAL) {
79 printf("cannot start server process as it is not idle");
80 return B_NOT_ALLOWED;
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();
97 return ErrorStatus();
101 bool
102 AbstractServerProcess::WasStopped()
104 BAutolock locker(&fLock);
105 return fWasStopped;
109 status_t
110 AbstractServerProcess::ErrorStatus()
112 BAutolock locker(&fLock);
113 return fErrorStatus;
117 status_t
118 AbstractServerProcess::Stop()
120 BAutolock locker(&fLock);
121 fWasStopped = true;
122 return StopInternal();
126 status_t
127 AbstractServerProcess::StopInternal()
129 if (fRequest != NULL) {
130 return fRequest->Stop();
133 return B_NOT_ALLOWED;
137 bool
138 AbstractServerProcess::IsRunning()
140 return ProcessState() == SERVER_PROCESS_RUNNING;
144 void
145 AbstractServerProcess::SetErrorStatus(status_t value)
147 BAutolock locker(&fLock);
149 if (fErrorStatus == B_OK) {
150 fErrorStatus = value;
155 void
156 AbstractServerProcess::SetProcessState(process_state value)
158 BAutolock locker(&fLock);
159 fProcessState = value;
163 process_state
164 AbstractServerProcess::ProcessState()
166 BAutolock locker(&fLock);
167 return fProcessState;
171 status_t
172 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue) const
174 BPath metaDataPath;
175 BString jsonPath;
177 GetStandardMetaDataPath(metaDataPath);
178 GetStandardMetaDataJsonPath(jsonPath);
180 return IfModifiedSinceHeaderValue(headerValue, metaDataPath, jsonPath);
184 status_t
185 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue,
186 const BPath& metaDataPath, const BString& jsonPath) const
188 headerValue.SetTo("");
189 struct stat s;
191 if (-1 == stat(metaDataPath.Path(), &s)) {
192 if (ENOENT != errno)
193 return B_ERROR;
195 return B_FILE_NOT_FOUND;
198 if (s.st_size == 0)
199 return B_BAD_VALUE;
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));
213 } else {
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());
219 return result;
223 status_t
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);
231 if (result != B_OK)
232 return result;
234 result = listener.ErrorStatus();
236 if (result != B_OK)
237 return result;
239 if (!metaData.IsPopulated()) {
240 fprintf(stderr, "the meta data was read from [%s], but no values "
241 "were extracted\n", path.Path());
242 return B_BAD_DATA;
245 return B_OK;
249 bool
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
259 the listener.
262 status_t
263 AbstractServerProcess::ParseJsonFromFileWithListener(
264 BJsonEventListener *listener,
265 const BPath& path) const
267 const char* pathStr = path.Path();
268 FILE* file = fopen(pathStr, "rb");
270 if (file == NULL) {
271 fprintf(stderr, "unable to find the meta data file at [%s]\n",
272 path.Path());
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
280 // it is parsed.
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);
291 if (B_OK != result)
292 return result;
294 ObjectDeleter<BDataIO> gzDecompressedInputDeleter(gzDecompressedInput);
295 BPrivate::BJson::Parse(gzDecompressedInput, listener);
296 } else {
297 BPrivate::BJson::Parse(&rawInput, listener);
300 return B_OK;
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.
309 status_t
310 AbstractServerProcess::DownloadToLocalFileAtomically(
311 const BPath& targetFilePath,
312 const BUrl& url)
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
323 // gone wrong.
324 off_t size;
325 bool hasFile;
327 result = StorageUtils::ExistsObject(temporaryFilePath, &hasFile, NULL,
328 &size);
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());
334 result = B_IO_ERROR;
339 return result;
343 status_t
344 AbstractServerProcess::DownloadToLocalFile(const BPath& targetFilePath,
345 const BUrl& url, uint32 redirects, uint32 failures)
347 if (WasStopped())
348 return B_CANCELED;
350 if (redirects > MAX_REDIRECTS) {
351 fprintf(stdout, "exceeded %d redirects --> failure\n", MAX_REDIRECTS);
352 return B_IO_ERROR;
355 if (failures > MAX_FAILURES) {
356 fprintf(stdout, "exceeded %d failures\n", MAX_FAILURES);
357 return B_IO_ERROR;
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);
378 thread_id thread;
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&>(
392 fRequest->Result());
393 int32 statusCode = result.StatusCode();
394 const BHttpHeaders responseHeaders = result.Headers();
395 const char *locationC = responseHeaders["Location"];
396 BString location;
398 if (locationC != NULL)
399 location.SetTo(locationC);
401 delete fRequest;
402 fRequest = NULL;
404 if (BHttpRequest::IsSuccessStatusCode(statusCode)) {
405 fprintf(stdout, "[%s] did complete streaming data [%"
406 B_PRIdSSIZE " bytes]\n", Name(), listener.ContentLength());
407 return B_OK;
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,
418 redirects + 1, 0);
421 fprintf(stdout, "[%s] unable to find 'Location' header for redirect\n",
422 Name());
423 return B_IO_ERROR;
424 } else {
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,
429 failures + 1);
432 fprintf(stdout, "[%s] unexpected response from server [%" B_PRId32 "]\n",
433 Name(), statusCode);
434 return B_IO_ERROR;
439 status_t
440 AbstractServerProcess::DeleteLocalFile(const BPath& currentFilePath)
442 if (0 == remove(currentFilePath.Path()))
443 return B_OK;
445 return B_IO_ERROR;
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
452 that location.
455 status_t
456 AbstractServerProcess::MoveDamagedFileAside(const BPath& currentFilePath)
458 BPath damagedFilePath;
459 BString damagedLeaf;
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());
468 return B_IO_ERROR;
471 printf("[%s] did move damaged file [%s] aside to [%s]\n",
472 Name(), currentFilePath.Path(), damagedFilePath.Path());
474 return B_OK;
478 bool
479 AbstractServerProcess::IsSuccess(status_t e) {
480 return e == B_OK || e == APP_ERR_NOT_MODIFIED;