reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

download.cpp (6196B)


      1 /* ReaPack: Package manager for REAPER
      2  * Copyright (C) 2015-2025  Christian Fillion
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU Lesser General Public License as published by
      6  * the Free Software Foundation, either version 3 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU Lesser General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Lesser General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 #include "download.hpp"
     19 
     20 #include "buildinfo.hpp"
     21 #include "filesystem.hpp"
     22 #include "hash.hpp"
     23 #include "reapack.hpp"
     24 #include "win32.hpp"
     25 
     26 #include <cassert>
     27 
     28 #include <reaper_plugin_functions.h>
     29 
     30 static const int DOWNLOAD_TIMEOUT = 15;
     31 // to set the amount of concurrent downloads, change the size of
     32 // the m_pool member in ThreadPool (thread.hpp)
     33 
     34 static CURLSH *g_curlShare = nullptr;
     35 static std::mutex g_curlMutex;
     36 
     37 static void LockCurlMutex(CURL *, curl_lock_data, curl_lock_access, void *)
     38 {
     39   g_curlMutex.lock();
     40 }
     41 
     42 static void UnlockCurlMutex(CURL *, curl_lock_data, curl_lock_access, void *)
     43 {
     44   g_curlMutex.unlock();
     45 }
     46 
     47 void DownloadContext::GlobalInit()
     48 {
     49   curl_global_init(CURL_GLOBAL_DEFAULT);
     50 
     51   g_curlShare = curl_share_init();
     52   assert(g_curlShare);
     53 
     54   curl_share_setopt(g_curlShare, CURLSHOPT_LOCKFUNC, LockCurlMutex);
     55   curl_share_setopt(g_curlShare, CURLSHOPT_UNLOCKFUNC, UnlockCurlMutex);
     56 
     57   curl_share_setopt(g_curlShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
     58   curl_share_setopt(g_curlShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
     59 }
     60 
     61 void DownloadContext::GlobalCleanup()
     62 {
     63   curl_share_cleanup(g_curlShare);
     64   curl_global_cleanup();
     65 }
     66 
     67 DownloadContext::DownloadContext()
     68 {
     69   m_curl = curl_easy_init();
     70 
     71   char userAgent[64];
     72   snprintf(userAgent, sizeof(userAgent), "ReaPack/%s REAPER/%s",
     73     REAPACK_VERSION, GetAppVersion());
     74 
     75   curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent);
     76   curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_LIMIT, 1);
     77   curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_TIME, DOWNLOAD_TIMEOUT);
     78   curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, DOWNLOAD_TIMEOUT);
     79   curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true);
     80   curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, 5);
     81   curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, "");
     82   curl_easy_setopt(m_curl, CURLOPT_FAILONERROR, true);
     83   curl_easy_setopt(m_curl, CURLOPT_SHARE, g_curlShare);
     84   curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, false);
     85 }
     86 
     87 DownloadContext::~DownloadContext()
     88 {
     89   curl_easy_cleanup(m_curl);
     90 }
     91 
     92 size_t Download::WriteData(char *data, size_t rawsize, size_t nmemb, void *ptr)
     93 {
     94   const size_t size = rawsize * nmemb;
     95 
     96   static_cast<WriteContext *>(ptr)->write(data, size);
     97 
     98   return size;
     99 }
    100 
    101 int Download::UpdateProgress(void *ptr, const double, const double,
    102     const double, const double)
    103 {
    104   return static_cast<Download *>(ptr)->aborted();
    105 }
    106 
    107 Download::Download(const std::string &url, const NetworkOpts &opts, const int flags)
    108   : m_url(url), m_opts(opts), m_flags(flags)
    109 {
    110 }
    111 
    112 void Download::setName(const std::string &name)
    113 {
    114   setSummary({ "Downloading", name });
    115 }
    116 
    117 bool Download::run()
    118 {
    119   WriteContext write;
    120 
    121   Hash::Algorithm algo;
    122   if(!m_expectedChecksum.empty()) {
    123     if(Hash::getAlgorithm(m_expectedChecksum, &algo))
    124       write.hash = std::make_unique<Hash>(algo);
    125     else {
    126       const std::string &error = String::format(
    127         "Unsupported checksum: %s", m_expectedChecksum.c_str());
    128       setError({error, m_url});
    129       return false;
    130     }
    131   }
    132 
    133   if(!(write.stream = openStream()))
    134     return false;
    135 
    136   thread_local DownloadContext ctx;
    137 
    138   curl_easy_setopt(ctx, CURLOPT_URL, m_url.c_str());
    139   curl_easy_setopt(ctx, CURLOPT_PROXY, m_opts.proxy.c_str());
    140   curl_easy_setopt(ctx, CURLOPT_SSL_VERIFYPEER, m_opts.verifyPeer);
    141 #ifdef __APPLE__
    142   curl_easy_setopt(ctx, CURLOPT_CAINFO, nullptr);
    143 #endif
    144 
    145   curl_easy_setopt(ctx, CURLOPT_PROGRESSFUNCTION, UpdateProgress);
    146   curl_easy_setopt(ctx, CURLOPT_PROGRESSDATA, this);
    147 
    148   curl_easy_setopt(ctx, CURLOPT_WRITEFUNCTION, WriteData);
    149   curl_easy_setopt(ctx, CURLOPT_WRITEDATA, &write);
    150 
    151   curl_slist *headers = nullptr;
    152   if(has(Download::NoCacheFlag))
    153     headers = curl_slist_append(headers, "Cache-Control: no-cache");
    154   curl_easy_setopt(ctx, CURLOPT_HTTPHEADER, headers);
    155 
    156   std::string errbuf = "No error message";
    157   errbuf.resize(CURL_ERROR_SIZE - 1, '\0');
    158   curl_easy_setopt(ctx, CURLOPT_ERRORBUFFER, errbuf.data());
    159 
    160   const CURLcode res = curl_easy_perform(ctx);
    161   curl_slist_free_all(headers);
    162   closeStream();
    163 
    164   if(res != CURLE_OK) {
    165 #ifdef _WIN32
    166     errbuf = Win32::ansi2utf8(errbuf);
    167 #endif
    168 
    169     const std::string &err = String::format(
    170       "%s (%d): %s", curl_easy_strerror(res), res, errbuf.c_str());
    171     setError({err, m_url});
    172     return false;
    173   }
    174   else if(write.hash && write.hash->digest() != m_expectedChecksum) {
    175     const std::string &err = String::format(
    176       "Checksum mismatch.\nExpected: %s\nActual: %s",
    177       m_expectedChecksum.c_str(), write.hash->digest().c_str()
    178     );
    179     setError({err, m_url});
    180     return false;
    181   }
    182 
    183   return true;
    184 }
    185 
    186 void Download::WriteContext::write(const char *data, const size_t len)
    187 {
    188   stream->write(data, len);
    189 
    190   if(hash)
    191     hash->addData(data, len);
    192 }
    193 
    194 MemoryDownload::MemoryDownload(const std::string &url, const NetworkOpts &opts, int flags)
    195   : Download(url, opts, flags)
    196 {
    197   setName(url);
    198 }
    199 
    200 FileDownload::FileDownload(const Path &target, const std::string &url,
    201     const NetworkOpts &opts, int flags)
    202   : Download(url, opts, flags), m_path(target)
    203 {
    204   setName(target.join());
    205 }
    206 
    207 bool FileDownload::save()
    208 {
    209   if(state() == Success)
    210     return FS::rename(m_path);
    211   else
    212     return FS::remove(m_path.temp());
    213 }
    214 
    215 std::ostream *FileDownload::openStream()
    216 {
    217   if(FS::open(m_stream, m_path.temp()))
    218     return &m_stream;
    219   else {
    220     setError({FS::lastError(), m_path.temp().join()});
    221     return nullptr;
    222   }
    223 }
    224 
    225 void FileDownload::closeStream()
    226 {
    227   m_stream.close();
    228 }