+static void ExecuteAsThread(SDL_ThreadFunction function, char *name, int data,
+ char *error)
+{
+ static int data_static;
+
+ data_static = data;
+
+ SDL_Thread *thread = SDL_CreateThread(function, name, &data_static);
+
+ if (thread != NULL)
+ SDL_DetachThread(thread);
+ else
+ Error("Cannot create thread to %s!", error);
+
+ // nasty kludge to lower probability of intermingled thread error messages
+ Delay(1);
+}
+
+static void DownloadServerScoreToCacheExt(struct HttpRequest *request,
+ struct HttpResponse *response,
+ int nr)
+{
+ request->hostname = setup.api_server_hostname;
+ request->port = API_SERVER_PORT;
+ request->method = API_SERVER_METHOD;
+ request->uri = API_SERVER_URI_GET;
+
+ snprintf(request->body, MAX_HTTP_BODY_SIZE,
+ "{\n"
+ " \"levelset_identifier\": \"%s\",\n"
+ " \"level_nr\": \"%d\",\n"
+ " \"rate_time_over_score\": \"%d\"\n"
+ "}\n",
+ levelset.identifier, nr, level.rate_time_over_score);
+
+ if (!DoHttpRequest(request, response))
+ {
+ Error("HTTP request failed: %s", GetHttpError());
+
+ return;
+ }
+
+ if (!HTTP_SUCCESS(response->status_code))
+ {
+ Error("server failed to handle request: %d %s",
+ response->status_code,
+ response->status_text);
+
+ return;
+ }
+
+ if (response->body_size == 0)
+ {
+ // no scores available for this level
+
+ return;
+ }
+
+ ConvertHttpResponseBodyToClientEncoding(response);
+
+ char *filename = getScoreCacheFilename(nr);
+ FILE *file;
+ int i;
+
+ // used instead of "leveldir_current->subdir" (for network games)
+ InitScoreCacheDirectory(levelset.identifier);
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot save score cache file '%s'", filename);
+
+ return;
+ }
+
+ for (i = 0; i < response->body_size; i++)
+ fputc(response->body[i], file);
+
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+
+ server_scores.updated = TRUE;
+}
+
+static void DownloadServerScoreToCache(int nr)
+{
+ struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
+ struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+
+ DownloadServerScoreToCacheExt(request, response, nr);
+
+ checked_free(request);
+ checked_free(response);
+}
+
+static int DownloadServerScoreToCacheThread(void *data)
+{
+ DownloadServerScoreToCache(*(int *)data);
+
+ return 0;
+}
+
+static void DownloadServerScoreToCacheAsThread(int nr)
+{
+ ExecuteAsThread(DownloadServerScoreToCacheThread,
+ "DownloadServerScoreToCache", nr,
+ "download scores from server");
+}
+
+static void LoadServerScoreFromCache(int nr)
+{
+ struct ScoreEntry score_entry;
+ struct
+ {
+ void *value;
+ boolean is_string;
+ int string_size;
+ }
+ score_mapping[] =
+ {
+ { &score_entry.score, FALSE, 0 },
+ { &score_entry.time, FALSE, 0 },
+ { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN },
+ { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN },
+
+ { NULL, FALSE, 0 }
+ };
+ char *filename = getScoreCacheFilename(nr);
+ SetupFileHash *score_hash = loadSetupFileHash(filename);
+ int i, j;
+
+ server_scores.num_entries = 0;
+
+ if (score_hash == NULL)
+ return;
+
+ for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+ {
+ score_entry = server_scores.entry[i];
+
+ for (j = 0; score_mapping[j].value != NULL; j++)
+ {
+ char token[10];
+
+ sprintf(token, "%02d.%d", i, j);
+
+ char *value = getHashEntry(score_hash, token);
+
+ if (value == NULL)
+ continue;
+
+ if (score_mapping[j].is_string)
+ {
+ char *score_value = (char *)score_mapping[j].value;
+ int value_size = score_mapping[j].string_size;
+
+ strncpy(score_value, value, value_size);
+ score_value[value_size] = '\0';
+ }
+ else
+ {
+ int *score_value = (int *)score_mapping[j].value;
+
+ *score_value = atoi(value);
+ }
+
+ server_scores.num_entries = i + 1;
+ }
+
+ server_scores.entry[i] = score_entry;
+ }
+
+ freeSetupFileHash(score_hash);
+}
+
+void LoadServerScore(int nr, boolean download_score)
+{
+ if (!setup.api_server)
+ return;
+
+ // always start with reliable default values
+ setServerScoreInfoToDefaults();
+
+ // 1st step: load server scores from cache file (which may not exist)
+ // (this should prevent reading it while the thread is writing to it)
+ LoadServerScoreFromCache(nr);
+
+ if (download_score && runtime.api_server)
+ {
+ // 2nd step: download server scores from score server to cache file
+ // (as thread, as it might time out if the server is not reachable)
+ DownloadServerScoreToCacheAsThread(nr);
+ }
+}
+