Downgrade surface format message from warning to debug
[supertux.git] / src / addon / downloader.cpp
index 662bf4b..7c51d84 100644 (file)
 #include "addon/downloader.hpp"
 
 #include <algorithm>
+#include <array>
+#include <assert.h>
 #include <memory>
 #include <physfs.h>
+#include <sstream>
 #include <stdexcept>
 
 #include "util/log.hpp"
@@ -46,31 +49,51 @@ size_t my_curl_physfs_write(void* ptr, size_t size, size_t nmemb, void* userdata
 
 } // namespace
 
-class cURLTransfer : public Transfer
+void
+TransferStatus::abort()
+{
+  m_downloader.abort(id);
+}
+
+void
+TransferStatus::update()
+{
+  m_downloader.update();
+}
+
+class Transfer
 {
 private:
   Downloader& m_downloader;
+  TransferId m_id;
 
   std::string m_url;
   CURL* m_handle;
   std::array<char, CURL_ERROR_SIZE> m_error_buffer;
 
-  curl_off_t m_dltotal;
-  curl_off_t m_dlnow;
-  curl_off_t m_ultotal;
-  curl_off_t m_ulnow;
+  TransferStatusPtr m_status;
+  std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> m_fout;
 
 public:
-  cURLTransfer(Downloader& downloader, const std::string& url) :
+  Transfer(Downloader& downloader, TransferId id,
+           const std::string& url,
+           const std::string& outfile) :
     m_downloader(downloader),
+    m_id(id),
     m_url(url),
-    m_handle(curl_easy_init()),
-    m_error_buffer(),
-    m_dltotal(0),
-    m_dlnow(0),
-    m_ultotal(0),
-    m_ulnow(0)
+    m_handle(),
+    m_error_buffer({{'\0'}}),
+    m_status(new TransferStatus(m_downloader, id)),
+    m_fout(PHYSFS_openWrite(outfile.c_str()), PHYSFS_close)
   {
+    if (!m_fout)
+    {
+      std::ostringstream out;
+      out << "PHYSFS_openRead() failed: " << PHYSFS_getLastError();
+      throw std::runtime_error(out.str());
+    }
+
+    m_handle = curl_easy_init();
     if (!m_handle)
     {
       throw std::runtime_error("curl_easy_init() failed");
@@ -81,12 +104,7 @@ public:
       curl_easy_setopt(m_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
 
       curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);
-      curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION,
-                       [](void* ptr, size_t size, size_t nmemb, void* userdata) -> size_t
-                       {
-                         return static_cast<cURLTransfer*>(userdata)
-                           ->on_data(ptr, size, nmemb);
-                       });
+      curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, &Transfer::on_data_wrap);
 
       curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error_buffer.data());
       curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1);
@@ -94,24 +112,31 @@ public:
       curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1);
 
       curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0);
-      curl_easy_setopt(m_handle, CURLOPT_XFERINFODATA, this);
-      curl_easy_setopt(m_handle, CURLOPT_XFERINFOFUNCTION,
-                       [](void* userdata,
-                          curl_off_t dltotal, curl_off_t dlnow,
-                          curl_off_t ultotal, curl_off_t ulnow)
-                       {
-                         return static_cast<cURLTransfer*>(userdata)
-                           ->on_progress(dltotal, dlnow,
-                                         ultotal, ulnow);
-                       });
+      curl_easy_setopt(m_handle, CURLOPT_PROGRESSDATA, this);
+      curl_easy_setopt(m_handle, CURLOPT_PROGRESSFUNCTION, &Transfer::on_progress_wrap);
     }
   }
 
-  ~cURLTransfer()
+  ~Transfer()
   {
     curl_easy_cleanup(m_handle);
   }
 
+  TransferStatusPtr get_status() const
+  {
+    return m_status;
+  }
+
+  const char* get_error_buffer() const
+  {
+    return m_error_buffer.data();
+  }
+
+  TransferId get_id() const
+  {
+    return m_id;
+  }
+
   CURL* get_curl_handle() const
   {
     return m_handle;
@@ -124,32 +149,44 @@ public:
 
   size_t on_data(void* ptr, size_t size, size_t nmemb)
   {
-    return 0;
+    PHYSFS_write(m_fout.get(), ptr, size, nmemb);
+    return size * nmemb;
   }
 
-  void on_progress(curl_off_t dltotal, curl_off_t dlnow,
-                   curl_off_t ultotal, curl_off_t ulnow)
+  int on_progress(double dltotal, double dlnow,
+                   double ultotal, double ulnow)
   {
-    m_dltotal = dltotal;
-    m_dlnow = dlnow;
+    m_status->dltotal = static_cast<int>(dltotal);
+    m_status->dlnow = static_cast<int>(dlnow);
+
+    m_status->ultotal = static_cast<int>(ultotal);
+    m_status->ulnow = static_cast<int>(ulnow);
+
+    return 0;
+  }
 
-    m_ultotal = ultotal;
-    m_ulnow = ulnow;
+private:
+  static size_t on_data_wrap(char* ptr, size_t size, size_t nmemb, void* userdata)
+  {
+    return static_cast<Transfer*>(userdata)->on_data(ptr, size, nmemb);
   }
 
-  void abort()
+  static int on_progress_wrap(void* userdata,
+                              double dltotal, double dlnow,
+                              double ultotal, double ulnow)
   {
-    m_downloader.abort(*this);
+    return static_cast<Transfer*>(userdata)->on_progress(dltotal, dlnow, ultotal, ulnow);
   }
 
 private:
-  cURLTransfer(const cURLTransfer&) = delete;
-  cURLTransfer& operator=(const cURLTransfer&) = delete;
+  Transfer(const Transfer&) = delete;
+  Transfer& operator=(const Transfer&) = delete;
 };
 
 Downloader::Downloader() :
   m_multi_handle(),
-  m_transfers()
+  m_transfers(),
+  m_next_transfer_id(1)
 {
   curl_global_init(CURL_GLOBAL_ALL);
   m_multi_handle = curl_multi_init();
@@ -217,21 +254,35 @@ Downloader::download(const std::string& url, const std::string& filename)
 }
 
 void
-Downloader::abort(const cURLTransfer& transfer)
+Downloader::abort(TransferId id)
 {
   auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
-                         [&transfer](const std::unique_ptr<cURLTransfer>& rhs)
+                         [&id](const std::unique_ptr<Transfer>& rhs)
                          {
-                           return transfer.get_curl_handle() == rhs->get_curl_handle();
+                           return id == rhs->get_id();
                          });
   if (it == m_transfers.end())
   {
-    log_warning << "transfer not found: " << transfer.get_url() << std::endl;
+    log_warning << "transfer not found: " << id << std::endl;
   }
   else
   {
+    TransferStatusPtr status = (*it)->get_status();
+
     curl_multi_remove_handle(m_multi_handle, (*it)->get_curl_handle());
     m_transfers.erase(it);
+
+    for(auto& callback : status->callbacks)
+    {
+      try
+      {
+        callback(false);
+      }
+      catch(const std::exception& err)
+      {
+        log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
+      }
+    }
   }
 }
 
@@ -242,7 +293,9 @@ Downloader::update()
   CURLMcode ret;
   int running_handles;
   while((ret = curl_multi_perform(m_multi_handle, &running_handles)) == CURLM_CALL_MULTI_PERFORM)
-  {}
+  {
+    log_debug << "updating" << std::endl;
+  }
 
   // check if any downloads got finished
   int msgs_in_queue;
@@ -252,8 +305,52 @@ Downloader::update()
     switch(msg->msg)
     {
       case CURLMSG_DONE:
-        curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
-        //FIXME: finish_transfer(msg->easy_handle);
+        {
+          log_info << "Download completed with " << msg->data.result << std::endl;
+          curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
+
+          auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
+                                 [&msg](const std::unique_ptr<Transfer>& rhs) {
+                                   return rhs->get_curl_handle() == msg->easy_handle;
+                                 });
+          assert(it != m_transfers.end());
+          TransferStatusPtr status = (*it)->get_status();
+          status->error_msg = (*it)->get_error_buffer();
+          m_transfers.erase(it);
+
+          if (msg->data.result == CURLE_OK)
+          {
+            bool success = true;
+            for(auto& callback : status->callbacks)
+            {
+              try
+              {
+                callback(success);
+              }
+              catch(const std::exception& err)
+              {
+                success = false;
+                log_warning << "Exception in Downloader: " << err.what() << std::endl;
+                status->error_msg = err.what();
+              }
+            }
+          }
+          else
+          {
+            log_warning << "Error: " << curl_easy_strerror(msg->data.result) << std::endl;
+            for(auto& callback : status->callbacks)
+            {
+              try
+              {
+                callback(false);
+              }
+              catch(const std::exception& err)
+              {
+                log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
+              }
+            }
+          }
+        }
         break;
 
       default:
@@ -263,13 +360,13 @@ Downloader::update()
   }
 }
 
-TransferHandle
-Downloader::request_download(const std::string& url, const std::string& filename)
+TransferStatusPtr
+Downloader::request_download(const std::string& url, const std::string& outfile)
 {
-  std::unique_ptr<cURLTransfer> transfer(new cURLTransfer(*this, url));
+  std::unique_ptr<Transfer> transfer(new Transfer(*this, m_next_transfer_id++, url, outfile));
   curl_multi_add_handle(m_multi_handle, transfer->get_curl_handle());
   m_transfers.push_back(std::move(transfer));
-  return m_transfers.back().get();
+  return m_transfers.back()->get_status();
 }
 
 /* EOF */