Explorar el Código

Throttle downloads based on hostname

Clément Wolf hace 1 mes
padre
commit
f2d19ddf53

+ 0 - 215
src/App/Osu/BanchoDownloader.cpp

@@ -1,215 +0,0 @@
-#include "BanchoDownloader.h"
-
-#include <curl/curl.h>
-#include <pthread.h>
-
-#include <sstream>
-
-#include "Bancho.h"
-#include "BanchoNetworking.h"
-#include "BanchoProtocol.h"
-#include "Engine.h"
-#include "miniz.h"
-
-struct DownloadThread {
-    uint32_t id = 0;
-    DownloadStatus status = DOWNLOADING;
-    float progress = 0.f;
-    pthread_t thread = {0};
-};
-
-pthread_mutex_t downloading_mapsets_mutex = PTHREAD_MUTEX_INITIALIZER;
-std::vector<DownloadThread*> downloading_mapsets;
-
-void update_download_progress(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
-                              curl_off_t ulnow) {
-    (void)ultotal;
-    (void)ulnow;
-
-    pthread_mutex_lock(&downloading_mapsets_mutex);
-    auto dt = (DownloadThread*)clientp;
-    if(dltotal == 0) {
-        dt->progress = 0.f;
-    } else if(dlnow > 0) {
-        dt->progress = (float)dlnow / (float)dltotal;
-    }
-    pthread_mutex_unlock(&downloading_mapsets_mutex);
-}
-
-void* run_mapset_download_thread(void* arg) {
-    DownloadThread* dt = (DownloadThread*)arg;
-    downloading_mapsets.push_back(dt);
-    pthread_mutex_unlock(&downloading_mapsets_mutex);
-
-    CURL* curl = curl_easy_init();
-    if(!curl) {
-        debugLog("Failed to initialize cURL\n");
-        pthread_mutex_lock(&downloading_mapsets_mutex);
-        dt->progress = 1.f;
-        dt->status = FAILURE;
-        pthread_mutex_unlock(&downloading_mapsets_mutex);
-        return NULL;
-    }
-
-    const int NB_MIRRORS = 6;
-    const char* mirrors[NB_MIRRORS] = {
-        "https://api.osu.direct/d/%d", "https://chimu.moe/d/%d",     "https://api.nerinyan.moe/d/%d",
-        "https://catboy.best/s/%d",    "https://osu.gatari.pw/d/%d", "https://osu.sayobot.cn/osu.php?s=%d",
-    };
-
-    Packet response;
-    response.memory = (uint8_t*)malloc(2048);
-    bool download_success = false;
-    for(int i = 0; i < 5; i++) {
-        mz_zip_archive zip = {0};
-        mz_zip_archive_file_stat file_stat;
-        mz_uint num_files = 0;
-        std::stringstream ss;
-        ss << MCENGINE_DATA_DIR "maps/";
-        ss << dt->id;
-        auto extract_to = ss.str();
-
-        auto query_url = UString::format(mirrors[i], dt->id);
-        debugLog("Downloading %s\n", query_url.toUtf8());
-        curl_easy_setopt(curl, CURLOPT_URL, query_url.toUtf8());
-        curl_easy_setopt(curl, CURLOPT_USERAGENT, bancho.user_agent.toUtf8());
-        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);
-        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&response);
-        curl_easy_setopt(curl, CURLOPT_XFERINFODATA, dt);
-        curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, update_download_progress);
-        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
-#ifdef _WIN32
-        // ABSOLUTELY RETARDED, FUCK WINDOWS
-        curl_easy_setopt(curl, CURLOPT_CAINFO, "curl-ca-bundle.crt");
-#endif
-        CURLcode res = curl_easy_perform(curl);
-        if(res != CURLE_OK) {
-            debugLog("Failed to download %s: %s\n", query_url.toUtf8(), curl_easy_strerror(res));
-            goto reset;
-        }
-
-        debugLog("Extracting beatmapset %d (%d bytes)\n", dt->id, response.size);
-        if(!mz_zip_reader_init_mem(&zip, response.memory, response.size, 0)) {
-            debugLog("Failed to open .osz file\n");
-            goto reset;
-        }
-        num_files = mz_zip_reader_get_num_files(&zip);
-        if(num_files <= 0) {
-            debugLog(".osz file is empty!\n");
-            goto reset;
-        }
-        if(!env->directoryExists(extract_to)) {
-            env->createDirectory(extract_to);
-        }
-        for(mz_uint i = 0; i < num_files; i++) {
-            if(!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
-            if(mz_zip_reader_is_file_a_directory(&zip, i)) continue;
-
-            char* saveptr = NULL;
-            char* folder = strtok_r(file_stat.m_filename, "/", &saveptr);
-            std::string file_path = extract_to;
-            while(folder != NULL) {
-                if(!strcmp(folder, "..")) {
-                    // Bro...
-                    goto skip_file;
-                }
-
-                file_path.append("/");
-                file_path.append(folder);
-                folder = strtok_r(NULL, "/", &saveptr);
-                if(folder != NULL) {
-                    if(!env->directoryExists(file_path)) {
-                        env->createDirectory(file_path);
-                    }
-                }
-            }
-
-            mz_zip_reader_extract_to_file(&zip, i, file_path.c_str(), 0);
-
-        skip_file:;
-            // When a file can't be extracted we just ignore it (as long as the archive is valid).
-            // We'll check for errors when loading the beatmap.
-        }
-
-        // Success
-        download_success = true;
-        mz_zip_reader_end(&zip);
-        break;
-
-    reset:
-        free(response.memory);
-        response = Packet();
-        response.memory = (uint8_t*)malloc(2048);
-        curl_easy_reset(curl);
-        pthread_mutex_lock(&downloading_mapsets_mutex);
-        dt->progress = 0.f;
-        pthread_mutex_unlock(&downloading_mapsets_mutex);
-        continue;
-    }
-
-    // We're finally done downloading & extracting
-    free(response.memory);
-    curl_easy_cleanup(curl);
-
-    pthread_mutex_lock(&downloading_mapsets_mutex);
-    if(download_success) {
-        auto it = std::find(downloading_mapsets.begin(), downloading_mapsets.end(), dt);
-        downloading_mapsets.erase(it);
-        delete dt;
-    } else {
-        dt->progress = 1.f;
-        dt->status = FAILURE;
-    }
-    pthread_mutex_unlock(&downloading_mapsets_mutex);
-
-    return NULL;
-}
-
-BeatmapDownloadStatus download_mapset(uint32_t set_id) {
-    // Check if we're already downloading
-    BeatmapDownloadStatus status = {0};
-    bool is_downloading = false;
-    pthread_mutex_lock(&downloading_mapsets_mutex);
-    for(auto dt : downloading_mapsets) {
-        if(dt->id == set_id) {
-            status.progress = dt->progress;
-            status.status = dt->status;
-            is_downloading = true;
-            break;
-        }
-    }
-    pthread_mutex_unlock(&downloading_mapsets_mutex);
-    if(is_downloading) {
-        return status;
-    }
-
-    // Check if we already have downloaded it
-    std::stringstream ss;
-    ss << MCENGINE_DATA_DIR "maps/" << set_id;
-    auto map_dir = ss.str();
-    if(env->directoryExists(map_dir)) {
-        status.progress = 1.f;
-        status.status = SUCCESS;
-        return status;
-    }
-
-    // Start download
-    auto dt = new DownloadThread();
-    dt->id = set_id, dt->progress = 0.f;
-    dt->status = DOWNLOADING;
-    dt->thread = {0};
-    pthread_mutex_lock(&downloading_mapsets_mutex);
-    int ret = pthread_create(&dt->thread, NULL, run_mapset_download_thread, dt);
-    if(ret) {
-        pthread_mutex_unlock(&downloading_mapsets_mutex);
-        debugLog("Failed to start download thread: pthread_create() returned %i\n", ret);
-        delete dt;
-        status.progress = 1.f;
-        status.status = FAILURE;
-        return status;
-    } else {
-        status.progress = 0.f;
-        status.status = DOWNLOADING;
-        return status;
-    }
-}

+ 0 - 16
src/App/Osu/BanchoDownloader.h

@@ -1,16 +0,0 @@
-#pragma once
-#include "OsuBeatmap.h"
-#include "OsuDatabaseBeatmap.h"
-
-enum DownloadStatus {
-    DOWNLOADING,
-    FAILURE,
-    SUCCESS,
-};
-
-struct BeatmapDownloadStatus {
-    float progress;
-    DownloadStatus status;
-};
-
-BeatmapDownloadStatus download_mapset(uint32_t set_id);

+ 2 - 13
src/App/Osu/BanchoNetworking.cpp

@@ -95,11 +95,9 @@ void disconnect() {
     //      While offline ones would be "By score", "By pp", etc
     bancho.osu->m_songBrowser2->onSortScoresChange(UString("Sort By Score"), 0);
 
-    pthread_mutex_unlock(&outgoing_mutex);
+    abort_downloads();
 
-    pthread_mutex_lock(&avatars_mtx);
-    avatar_downloading_thread_id++;
-    pthread_mutex_unlock(&avatars_mtx);
+    pthread_mutex_unlock(&outgoing_mutex);
 }
 
 void reconnect() {
@@ -136,15 +134,6 @@ void reconnect() {
     login_packet = new_login_packet;
     try_logging_in = true;
     pthread_mutex_unlock(&outgoing_mutex);
-
-    pthread_mutex_lock(&avatars_mtx);
-    avatar_downloading_thread_id++;
-    pthread_t dummy = 0;
-    int ret = pthread_create(&dummy, NULL, avatar_downloading_thread, NULL);
-    if(ret) {
-        debugLog("Failed to start avatar downloading thread: pthread_create() returned %i\n", ret);
-    }
-    pthread_mutex_unlock(&avatars_mtx);
 }
 
 size_t curl_write(void *contents, size_t size, size_t nmemb, void *userp) {

+ 288 - 0
src/App/Osu/Downloader.cpp

@@ -0,0 +1,288 @@
+#include "Downloader.h"
+
+#include <curl/curl.h>
+#include <pthread.h>
+
+#include <mutex>
+#include <sstream>
+
+#include "Bancho.h"
+#include "BanchoNetworking.h"
+#include "BanchoProtocol.h"
+#include "ConVar.h"
+#include "Engine.h"
+#include "miniz.h"
+
+struct DownloadResult {
+    std::string url;
+    std::vector<uint8_t> data;
+    float progress = 0.f;
+};
+
+struct DownloadThread {
+    bool running;
+    pthread_t id;
+    std::string endpoint;
+    std::vector<DownloadResult*> downloads;
+};
+
+std::mutex threads_mtx;
+std::vector<DownloadThread*> threads;
+
+void abort_downloads() {
+    threads_mtx.lock();
+    for(auto thread : threads) {
+        thread->running = false;
+    }
+    threads_mtx.unlock();
+}
+
+void update_download_progress(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
+                              curl_off_t ulnow) {
+    (void)ultotal;
+    (void)ulnow;
+
+    threads_mtx.lock();
+    auto result = (DownloadResult*)clientp;
+    if(dltotal == 0) {
+        result->progress = 0.f;
+    } else if(dlnow > 0) {
+        result->progress = (float)dlnow / (float)dltotal;
+    }
+    threads_mtx.unlock();
+}
+
+void* do_downloads(void* arg) {
+    auto thread = (DownloadThread*)arg;
+
+    Packet response;
+    CURL* curl = curl_easy_init();
+    if(!curl) {
+        debugLog("Failed to initialize cURL!\n");
+        goto end_thread;
+    }
+
+    while(thread->running) {
+        env->sleep(100000);  // wait 100ms between every download
+
+        DownloadResult* result = nullptr;
+        std::string url;
+
+        threads_mtx.lock();
+        for(auto download : thread->downloads) {
+            if(download->progress == 0.f) {
+                result = download;
+                url = download->url;
+                break;
+            }
+        }
+        threads_mtx.unlock();
+        if(!result) continue;
+
+        free(response.memory);
+        response = Packet();
+
+        debugLog("Downloading %s\n", url.c_str());
+        curl_easy_reset(curl);
+        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+        curl_easy_setopt(curl, CURLOPT_USERAGENT, bancho.user_agent.toUtf8());
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&response);
+        curl_easy_setopt(curl, CURLOPT_XFERINFODATA, result);
+        curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, update_download_progress);
+        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
+#ifdef _WIN32
+        // ABSOLUTELY RETARDED, FUCK WINDOWS
+        curl_easy_setopt(curl, CURLOPT_CAINFO, "curl-ca-bundle.crt");
+#endif
+        CURLcode res = curl_easy_perform(curl);
+        if(res == CURLE_OK) {
+            threads_mtx.lock();
+            result->progress = 1.f;
+            result->data = std::vector<uint8_t>(response.memory, response.memory + response.size);
+            threads_mtx.unlock();
+        } else {
+            debugLog("Failed to download %s: %s\n", url.c_str(), curl_easy_strerror(res));
+
+            int response_code;
+            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+
+            threads_mtx.lock();
+            if(response_code == 429) {
+                result->progress = 0.f;
+            } else {
+                result->data = std::vector<uint8_t>(response.memory, response.memory + response.size);
+                result->progress = -1.f;
+            }
+            threads_mtx.unlock();
+
+            if(response_code == 429) {
+                // Try again 5s later
+                env->sleep(5000000);
+            }
+        }
+    }
+
+end_thread:
+    curl_easy_cleanup(curl);
+
+    threads_mtx.lock();
+    std::vector<DownloadThread*> new_threads;
+    for(auto dt : threads) {
+        if(thread != dt) {
+            new_threads.push_back(dt);
+        }
+    }
+    threads = std::move(new_threads);
+    threads_mtx.unlock();
+
+    free(response.memory);
+    for(auto result : thread->downloads) {
+        delete result;
+    }
+    delete thread;
+    return NULL;
+}
+
+void download(const char* url, float* progress, std::vector<uint8_t>& out) {
+    char* hostname = NULL;
+    bool download_found = false;
+    DownloadThread* matching_thread = nullptr;
+
+    CURLU* urlu = curl_url();
+    if(!urlu) {
+        *progress = -1.f;
+        return;
+    }
+
+    if(curl_url_set(urlu, CURLUPART_URL, url, 0) != CURLUE_OK) {
+        *progress = -1.f;
+        goto end;
+    }
+
+    if(curl_url_get(urlu, CURLUPART_HOST, &hostname, 0) != CURLUE_OK) {
+        *progress = -1.f;
+        goto end;
+    }
+
+    threads_mtx.lock();
+    for(auto thread : threads) {
+        if(thread->running && !strcmp(thread->endpoint.c_str(), hostname)) {
+            matching_thread = thread;
+            break;
+        }
+    }
+    if(matching_thread == nullptr) {
+        matching_thread = new DownloadThread();
+        matching_thread->running = true;
+        matching_thread->endpoint = std::string(hostname);
+        int ret = pthread_create(&matching_thread->id, NULL, do_downloads, matching_thread);
+        if(ret) {
+            debugLog("Failed to start download thread: pthread_create() returned %i\n", ret);
+            *progress = -1.f;
+            delete matching_thread;
+            goto end;
+        } else {
+            threads.push_back(matching_thread);
+        }
+    }
+
+    for(int i = 0; i < matching_thread->downloads.size(); i++) {
+        DownloadResult* result = matching_thread->downloads[i];
+
+        if(result->url == url) {
+            *progress = result->progress;
+            if(result->progress == -1.f || result->progress == 1.f) {
+                out = result->data;
+                delete matching_thread->downloads[i];
+                matching_thread->downloads.erase(matching_thread->downloads.begin() + i);
+            }
+
+            download_found = true;
+            break;
+        }
+    }
+
+    if(!download_found) {
+        auto newdl = new DownloadResult{.url = url};
+        matching_thread->downloads.push_back(newdl);
+        *progress = 0.f;
+    }
+    threads_mtx.unlock();
+
+end:
+    curl_url_cleanup(urlu);
+    free(hostname);
+}
+
+void download_beatmapset(uint32_t set_id, float* progress) {
+    // Check if we already have downloaded it
+    std::stringstream ss;
+    ss << MCENGINE_DATA_DIR "maps/" << std::to_string(set_id) << "/";
+    auto map_dir = ss.str();
+    if(env->directoryExists(map_dir)) {
+        *progress = 1.f;
+        return;
+    }
+
+    std::vector<uint8_t> data;
+    auto mirror = convar->getConVarByName("beatmap_mirror")->getString();
+    auto url = UString::format(mirror.toUtf8(), set_id);
+    download(url.toUtf8(), progress, data);
+    if(*progress != 1.f) return;
+
+    // Download succeeded: save map to disk
+    mz_zip_archive zip = {0};
+    mz_zip_archive_file_stat file_stat;
+    mz_uint num_files = 0;
+
+    debugLog("Extracting beatmapset %d (%d bytes)\n", set_id, data.size());
+    if(!mz_zip_reader_init_mem(&zip, data.data(), data.size(), 0)) {
+        debugLog("Failed to open .osz file\n");
+        *progress = -1.f;
+        return;
+    }
+
+    num_files = mz_zip_reader_get_num_files(&zip);
+    if(num_files <= 0) {
+        debugLog(".osz file is empty!\n");
+        mz_zip_reader_end(&zip);
+        *progress = -1.f;
+        return;
+    }
+    if(!env->directoryExists(map_dir)) {
+        env->createDirectory(map_dir);
+    }
+    for(mz_uint i = 0; i < num_files; i++) {
+        if(!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
+        if(mz_zip_reader_is_file_a_directory(&zip, i)) continue;
+
+        char* saveptr = NULL;
+        char* folder = strtok_r(file_stat.m_filename, "/", &saveptr);
+        std::string file_path = map_dir;
+        while(folder != NULL) {
+            if(!strcmp(folder, "..")) {
+                // Bro...
+                goto skip_file;
+            }
+
+            file_path.append("/");
+            file_path.append(folder);
+            folder = strtok_r(NULL, "/", &saveptr);
+            if(folder != NULL) {
+                if(!env->directoryExists(file_path)) {
+                    env->createDirectory(file_path);
+                }
+            }
+        }
+
+        mz_zip_reader_extract_to_file(&zip, i, file_path.c_str(), 0);
+
+    skip_file:;
+        // When a file can't be extracted we just ignore it (as long as the archive is valid).
+        // We'll check for errors when loading the beatmap.
+    }
+
+    // Success
+    mz_zip_reader_end(&zip);
+}

+ 13 - 0
src/App/Osu/Downloader.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "cbase.h"
+
+void abort_downloads();
+
+// Downloads `url` and stores downloaded file data into `out`
+// When file is fully downloaded, `progress` is 1 and `out` is not NULL
+// When download fails, `progress` is -1
+void download(const char *url, float *progress, std::vector<uint8_t> &out);
+
+// Downloads and extracts given beatmapset
+// When download/extraction fails, `progress` is -1
+void download_beatmapset(uint32_t set_id, float *progress);

+ 9 - 0
src/App/Osu/Osu.cpp

@@ -148,6 +148,15 @@ ConVar mp_password("mp_password", "", FCVAR_NONE);
 ConVar mp_autologin("mp_autologin", false, FCVAR_NONE);
 ConVar submit_scores("submit_scores", false, FCVAR_NONE);
 
+// Some alternative mirrors:
+// - https://api.osu.direct/d/%d
+// - https://chimu.moe/d/%d
+// - https://api.nerinyan.moe/d/%d
+// - https://osu.gatari.pw/d/%d
+// - https://osu.sayobot.cn/osu.php?s=%d
+ConVar beatmap_mirror("beatmap_mirror", "https://catboy.best/s/%d", FCVAR_NONE,
+                      "mirror from which beatmapsets will be downloaded");
+
 ConVar *Osu::version = &osu_version;
 ConVar *Osu::debug = &osu_debug;
 ConVar *Osu::ui_scale = &osu_ui_scale;

+ 9 - 6
src/App/Osu/OsuRoom.cpp

@@ -3,18 +3,19 @@
 #include <sstream>
 
 #include "Bancho.h"
-#include "BanchoDownloader.h"
 #include "BanchoNetworking.h"
 #include "BanchoUsers.h"
 #include "CBaseUIButton.h"
 #include "CBaseUIContainer.h"
 #include "CBaseUILabel.h"
 #include "CBaseUITextbox.h"
+#include "Downloader.h"
 #include "Engine.h"
 #include "Keyboard.h"
 #include "Mouse.h"
 #include "Osu.h"
 #include "OsuBackgroundImageHandler.h"
+#include "OsuBeatmap.h"
 #include "OsuChat.h"
 #include "OsuDatabase.h"
 #include "OsuHUD.h"
@@ -231,19 +232,21 @@ void OsuRoom::draw(Graphics *g) {
         return;
     }
 
-    auto status = download_mapset(set_id);
-    if(status.status == FAILURE) {
+    // Download mapset
+    float progress = -1.f;
+    download_beatmapset(set_id, &progress);
+    if(progress == -1.f) {
         auto error_str = UString::format("Failed to download beatmapset %d :(", set_id);
         m_map_title->setText(error_str);
         m_map_title->setSizeToContent(0, 0);
         m_ready_btn->is_loading = true;
         bancho.room.map_id = 0;  // don't try downloading it again
-    } else if(status.status == DOWNLOADING) {
-        auto text = UString::format("Downloading... %.2f%%", status.progress * 100.f);
+    } else if(progress < 1.f) {
+        auto text = UString::format("Downloading... %.2f%%", progress * 100.f);
         m_map_title->setText(text.toUtf8());
         m_map_title->setSizeToContent(0, 0);
         m_ready_btn->is_loading = true;
-    } else if(status.status == SUCCESS) {
+    } else {
         std::stringstream ss;
         ss << MCENGINE_DATA_DIR "maps/" << std::to_string(set_id) << "/";
         auto mapset_path = ss.str();

+ 37 - 128
src/App/Osu/OsuUIAvatar.cpp

@@ -8,29 +8,27 @@
 
 #include "Bancho.h"
 #include "BanchoNetworking.h"
+#include "Downloader.h"
 #include "Engine.h"
 #include "Osu.h"
 #include "OsuSkin.h"
 #include "OsuUIUserContextMenu.h"
 #include "ResourceManager.h"
 
-int avatar_downloading_thread_id = 0;
-pthread_mutex_t avatars_mtx = PTHREAD_MUTEX_INITIALIZER;
-std::vector<uint32_t> avatars_to_load;
-std::vector<uint32_t> avatars_loaded;
+// Returns true when avatar is fully downloaded
+bool download_avatar(uint32_t user_id) {
+    if(user_id == 0) return false;
 
-void *avatar_downloading_thread(void *arg) {
-    (void)arg;
-
-    pthread_mutex_lock(&avatars_mtx);
-    avatar_downloading_thread_id++;
-    int thread_id = avatar_downloading_thread_id;
-    pthread_mutex_unlock(&avatars_mtx);
-
-    UString endpoint = bancho.endpoint;
+    // XXX: clear blacklist when changing endpoint
+    static std::vector<uint32_t> blacklist;
+    for(auto bl : blacklist) {
+        if(user_id == bl) {
+            return false;
+        }
+    }
 
     std::stringstream ss;
-    ss << MCENGINE_DATA_DIR "avatars/" << endpoint.toUtf8();
+    ss << MCENGINE_DATA_DIR "avatars/" << bancho.endpoint.toUtf8();
     auto server_dir = ss.str();
     if(!env->directoryExists(server_dir)) {
         if(!env->directoryExists(MCENGINE_DATA_DIR "avatars")) {
@@ -39,95 +37,25 @@ void *avatar_downloading_thread(void *arg) {
         env->createDirectory(server_dir);
     }
 
-    CURL *curl = curl_easy_init();
-    if(!curl) {
-        debugLog("Failed to initialize cURL, avatar downloading disabled.\n");
-        return NULL;
+    float progress = -1.f;
+    std::vector<uint8_t> data;
+    auto img_url = UString::format("https://a.%s/%d", bancho.endpoint.toUtf8(), user_id);
+    download(img_url.toUtf8(), &progress, data);
+    if(progress == -1.f) blacklist.push_back(user_id);
+    if(data.empty()) return false;
+
+    std::stringstream ss2;
+    ss2 << server_dir << "/" << std::to_string(user_id);
+    auto img_path = ss2.str();
+    FILE *file = fopen(img_path.c_str(), "wb");
+    if(file != NULL) {
+        fwrite(data.data(), data.size(), 1, file);
+        fflush(file);
+        fclose(file);
     }
 
-    std::vector<uint32_t> blacklist;
-    blacklist.push_back(0);  // make sure we don't try to load user id 0
-
-    while(thread_id == avatar_downloading_thread_id) {
-    loop:
-        env->sleep(100000);  // wait 100ms between every download
-
-        pthread_mutex_lock(&avatars_mtx);
-        if(avatars_to_load.empty()) {
-            pthread_mutex_unlock(&avatars_mtx);
-            continue;
-        }
-        uint32_t avatar_id = avatars_to_load.front();
-        for(auto bl : blacklist) {
-            if(avatar_id == bl) {
-                avatars_to_load.erase(avatars_to_load.begin());
-                pthread_mutex_unlock(&avatars_mtx);
-                goto loop;
-            }
-        }
-        pthread_mutex_unlock(&avatars_mtx);
-
-        auto img_url = UString::format("https://a.%s/%d", endpoint.toUtf8(), avatar_id);
-        debugLog("Downloading %s\n", img_url.toUtf8());
-        Packet response;
-        response.memory = (uint8_t *)malloc(2048);
-        curl_easy_setopt(curl, CURLOPT_URL, img_url.toUtf8());
-        curl_easy_setopt(curl, CURLOPT_USERAGENT, bancho.user_agent.toUtf8());
-        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);
-        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
-#ifdef _WIN32
-        // ABSOLUTELY RETARDED, FUCK WINDOWS
-        curl_easy_setopt(curl, CURLOPT_CAINFO, "curl-ca-bundle.crt");
-#endif
-        CURLcode res = curl_easy_perform(curl);
-        if(res != CURLE_OK) {
-            debugLog("Failed to download %s: %s\n", img_url.toUtf8(), curl_easy_strerror(res));
-
-            int response_code;
-            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
-            if(response_code == 429) {
-                // Fetching avatars too quickly, try again 5s later
-                env->sleep(5000000);
-            } else {
-                // Failed to load avatar, don't try to fetch it again
-                blacklist.push_back(avatar_id);
-            }
-
-            // We still save the avatar if it 404s, since the server will return the default avatar
-            if(response_code != 404) {
-                free(response.memory);
-                curl_easy_reset(curl);
-                continue;
-            }
-        }
-
-        std::stringstream ss2;
-        ss2 << server_dir << "/" << std::to_string(avatar_id);
-        auto img_path = ss2.str();
-        FILE *file = fopen(img_path.c_str(), "wb");
-        if(file != NULL) {
-            fwrite(response.memory, response.size, 1, file);
-            fflush(file);
-            fclose(file);
-        }
-        free(response.memory);
-        curl_easy_reset(curl);
-
-        pthread_mutex_lock(&avatars_mtx);
-        if(thread_id == avatar_downloading_thread_id) {
-            avatars_to_load.erase(avatars_to_load.begin());
-            avatars_loaded.push_back(avatar_id);
-        }
-        pthread_mutex_unlock(&avatars_mtx);
-    }
-
-    pthread_mutex_lock(&avatars_mtx);
-    avatars_to_load.clear();
-    avatars_loaded.clear();
-    pthread_mutex_unlock(&avatars_mtx);
-
-    curl_easy_cleanup(curl);
-    return NULL;
+    // NOTE: We return true even if progress is -1. Because we still get avatars from a 404!
+    return true;
 }
 
 OsuUIAvatar::OsuUIAvatar(uint32_t player_id, float xPos, float yPos, float xSize, float ySize)
@@ -157,7 +85,14 @@ OsuUIAvatar::OsuUIAvatar(uint32_t player_id, float xPos, float yPos, float xSize
 void OsuUIAvatar::draw(Graphics *g, float alpha) {
     if(!on_screen) return;  // Comment when you need to debug on_screen logic
 
-    if(avatar != nullptr) {
+    if(avatar == nullptr) {
+        // Don't download during gameplay to avoid lagspikes
+        if(!bancho.osu->isInPlayMode()) {
+            if(download_avatar(m_player_id)) {
+                avatar = engine->getResourceManager()->loadImageAbs(avatar_path, avatar_path);
+            }
+        }
+    } else {
         g->pushTransform();
         g->setColor(0xffffffff);
         g->setAlpha(alpha);
@@ -181,32 +116,6 @@ void OsuUIAvatar::draw(Graphics *g, float alpha) {
     // }
 }
 
-void OsuUIAvatar::mouse_update(bool *propagate_clicks) {
-    CBaseUIButton::mouse_update(propagate_clicks);
-
-    if(avatar == nullptr && m_player_id != 0) {
-        pthread_mutex_lock(&avatars_mtx);
-
-        // Check if it has finished downloading
-        auto it = std::find(avatars_loaded.begin(), avatars_loaded.end(), m_player_id);
-        if(it != avatars_loaded.end()) {
-            avatars_loaded.erase(it);
-            avatar = engine->getResourceManager()->loadImageAbs(avatar_path, avatar_path);
-        }
-
-        // Check if avatar is on screen and *still* not downloaded yet
-        if(avatar == nullptr && on_screen) {
-            // Request download if not done so already
-            auto it = std::find(avatars_to_load.begin(), avatars_to_load.end(), m_player_id);
-            if(it == avatars_to_load.end()) {
-                debugLog("Adding avatar %d to download queue\n", m_player_id);
-                avatars_to_load.push_back(m_player_id);
-            }
-        }
-        pthread_mutex_unlock(&avatars_mtx);
-    }
-}
-
 void OsuUIAvatar::onAvatarClicked(CBaseUIButton *btn) {
     if(bancho.osu->isInPlayMode()) {
         // Don't want context menu to pop up while playing a map

+ 0 - 6
src/App/Osu/OsuUIAvatar.h

@@ -8,7 +8,6 @@ class OsuUIAvatar : public CBaseUIButton {
     OsuUIAvatar(uint32_t player_id, float xPos, float yPos, float xSize, float ySize);
 
     virtual void draw(Graphics *g, float alpha = 1.f);
-    virtual void mouse_update(bool *propagate_clicks);
 
     void onAvatarClicked(CBaseUIButton *btn);
 
@@ -17,8 +16,3 @@ class OsuUIAvatar : public CBaseUIButton {
     Image *avatar = nullptr;
     bool on_screen = false;
 };
-
-// Accessed from BanchoNetworking
-extern int avatar_downloading_thread_id;
-extern pthread_mutex_t avatars_mtx;
-void *avatar_downloading_thread(void *arg);