Merge branch 'dk_thingiverse'

This commit is contained in:
Lukas Matena 2025-04-07 17:46:42 +02:00
commit 575824443a
6 changed files with 140 additions and 10 deletions

View File

@ -140,6 +140,18 @@ Downloader::Downloader()
Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this); Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this);
} }
namespace {
bool is_any_subdomain(const std::string& url, const std::vector<std::string>& subdomains)
{
for (const std::string& sub : subdomains)
{
if(FileGet::is_subdomain(url, sub))
return true;
}
return false;
}
}
void Downloader::start_download(const std::string& full_url) void Downloader::start_download(const std::string& full_url)
{ {
assert(m_initialized); assert(m_initialized);
@ -156,14 +168,14 @@ void Downloader::start_download(const std::string& full_url)
size_t id = get_next_id(); size_t id = get_next_id();
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) { if (!boost::starts_with(escaped_url, "https://") || !is_any_subdomain(escaped_url, {"printables.com", "thingiverse.com"})) {
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url); std::string msg = format(_L("Download won't start. Download URL doesn't point to allowed subdomains : %1%"), escaped_url);
BOOST_LOG_TRIVIAL(error) << msg; BOOST_LOG_TRIVIAL(error) << msg;
NotificationManager* ntf_mngr = wxGetApp().notification_manager(); NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg); ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg);
return; return;
} }
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder, true)); m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder, true));
NotificationManager* ntf_mngr = wxGetApp().notification_manager(); NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2)); ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2));
@ -202,7 +214,7 @@ void Downloader::on_progress(wxCommandEvent& event)
float percent = (float)std::stoi(into_u8(event.GetString())) / 100.f; float percent = (float)std::stoi(into_u8(event.GetString())) / 100.f;
//BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent; //BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent;
NotificationManager* ntf_mngr = wxGetApp().notification_manager(); NotificationManager* ntf_mngr = wxGetApp().notification_manager();
BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent; //BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent;
ntf_mngr->set_download_URL_progress(id, percent); ntf_mngr->set_download_URL_progress(id, percent);
} }
void Downloader::on_error(wxCommandEvent& event) void Downloader::on_error(wxCommandEvent& event)
@ -250,7 +262,9 @@ bool Downloader::user_action_callback(DownloaderUserAction action, int id)
void Downloader::on_name_change(wxCommandEvent& event) void Downloader::on_name_change(wxCommandEvent& event)
{ {
size_t id = event.GetInt();
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->set_download_URL_filename(id, into_u8(event.GetString()));
} }
void Downloader::on_paused(wxCommandEvent& event) void Downloader::on_paused(wxCommandEvent& event)

View File

@ -12,6 +12,7 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/nowide/cstdio.hpp> #include <boost/nowide/cstdio.hpp>
#include <iostream> #include <iostream>
#include <regex>
#include "format.hpp" #include "format.hpp"
#include "GUI.hpp" #include "GUI.hpp"
@ -68,6 +69,26 @@ unsigned get_current_pid()
return ::getpid(); return ::getpid();
#endif #endif
} }
std::string extract_filename_from_header(const std::string& headers) {
// Split the headers into lines
std::istringstream header_stream(headers);
std::string line;
while (std::getline(header_stream, line)) {
if (line.find("content-disposition") != std::string::npos) {
// Apply regex to extract filename from the content-disposition line
std::regex filename_regex("filename\\s*=\\s*\"([^\"]+)\"", std::regex::icase);
std::smatch match;
if (std::regex_search(line, match, filename_regex) && match.size() > 1) {
return match.str(1);
}
}
}
return {};
}
} }
// int = DOWNLOAD ID; string = file path // int = DOWNLOAD ID; string = file path
@ -111,6 +132,8 @@ FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEv
, m_dest_folder(dest_folder) , m_dest_folder(dest_folder)
, m_load_after(load_after) , m_load_after(load_after)
{ {
// Prevent ':' in filename
m_filename.erase(std::remove(m_filename.begin(), m_filename.end(), ':'), m_filename.end());
} }
void FileGet::priv::get_perform() void FileGet::priv::get_perform()
@ -129,7 +152,7 @@ void FileGet::priv::get_perform()
std::string extension = dest_path.extension().string(); std::string extension = dest_path.extension().string();
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size()); std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
std::string final_filename = just_filename; std::string final_filename = just_filename;
// Find unsed filename // Find unused filename
try { try {
size_t version = 0; size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download"))) while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
@ -154,7 +177,6 @@ void FileGet::priv::get_perform()
} }
m_filename = final_filename + extension; m_filename = final_filename + extension;
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download"); m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
@ -247,13 +269,48 @@ void FileGet::priv::get_perform()
m_written = written_previously + written_this_session; m_written = written_previously + written_this_session;
} }
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size; int percent_total = m_absolute_size == 0 ? 0 : (written_previously + progress.dlnow) * 100 / m_absolute_size;
evt->SetString(std::to_string(percent_total)); evt->SetString(std::to_string(percent_total));
evt->SetInt(m_id); evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt); m_evt_handler->QueueEvent(evt);
} }
}) })
.on_headers([&](const std::string& headers) {
// we are looking for content-disposition header in response, to use it as correct filename
std::string new_filename = extract_filename_from_header(headers);
if (new_filename.empty()) {
return;
}
// Find unused filename
boost::filesystem::path temp_dest_path = m_dest_folder / new_filename;
std::string extension = temp_dest_path.extension().string();
std::string just_filename = new_filename.substr(0, new_filename.size() - extension.size());
std::string final_filename = just_filename;
try {
size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)))
{
++version;
if (version > 999) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string());
return;
}
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
}
} catch (const boost::filesystem::filesystem_error& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to resolved filename from headers.";
return;
}
m_filename = final_filename + extension;
dest_path = m_dest_folder / m_filename;
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
evt->SetString(from_u8(m_filename));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.on_error([&](std::string body, std::string error, unsigned http_status) { .on_error([&](std::string body, std::string error, unsigned http_status) {
if (file != NULL) if (file != NULL)
fclose(file); fclose(file);
@ -268,19 +325,31 @@ void FileGet::priv::get_perform()
.on_complete([&](std::string body, unsigned /* http_status */) { .on_complete([&](std::string body, unsigned /* http_status */) {
try try
{ {
// If server is not sending Content-Length header, the progress function does not write all data to file.
// We need to write it now.
if (written_this_session < body.size()) {
std::string part_for_write = body.substr(written_this_session);
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
}
fclose(file); fclose(file);
boost::filesystem::rename(m_tmp_path, dest_path); boost::filesystem::rename(m_tmp_path, dest_path);
} }
catch (const std::exception& /*e*/) catch (const std::exception& e)
{ {
//TODO: report? //TODO: report?
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path); //error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR); wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString("Failed to write and move."); evt->SetString(GUI::format("Failed to write and move %1% to %2%", m_tmp_path, dest_path));
evt->SetInt(m_id); evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt); m_evt_handler->QueueEvent(evt);
return; return;
} }
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
int percent_total = 100;
evt->SetString(std::to_string(percent_total));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
DownloadEventData event_data = {m_id, dest_path.wstring(), m_load_after}; DownloadEventData event_data = {m_id, dest_path.wstring(), m_load_after};
wxQueueEvent(m_evt_handler, new Event<DownloadEventData>(EVT_DWNLDR_FILE_COMPLETE, event_data)); wxQueueEvent(m_evt_handler, new Event<DownloadEventData>(EVT_DWNLDR_FILE_COMPLETE, event_data));
}) })

View File

@ -1119,6 +1119,11 @@ void NotificationManager::URLDownloadWithPrintablesLinkNotification::render_text
//------URLDownloadNotification---------------- //------URLDownloadNotification----------------
void NotificationManager::URLDownloadNotification::set_filename(const std::string& filename_line)
{
m_text1 = filename_line;
init();
}
void NotificationManager::URLDownloadNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) void NotificationManager::URLDownloadNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{ {
@ -2624,6 +2629,19 @@ void NotificationManager::set_download_URL_error(size_t id, const std::string& t
} }
} }
} }
void NotificationManager::set_download_URL_filename(size_t id, const std::string& filename)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload) {
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
if (ntf->get_download_id() != id)
continue;
ntf->set_filename(_u8L("Download") + ": " + filename);
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
return;
}
}
}
void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback) void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback)
{ {

View File

@ -258,6 +258,7 @@ public:
void set_download_URL_paused(size_t id); void set_download_URL_paused(size_t id);
void set_download_URL_canceled(size_t id); void set_download_URL_canceled(size_t id);
void set_download_URL_error(size_t id, const std::string& text); void set_download_URL_error(size_t id, const std::string& text);
void set_download_URL_filename(size_t id, const std::string& filename);
// slicing progress // slicing progress
void init_slicing_progress_notification(std::function<bool()> cancel_callback); void init_slicing_progress_notification(std::function<bool()> cancel_callback);
void set_slicing_progress_began(); void set_slicing_progress_began();
@ -561,6 +562,7 @@ private:
void set_paused(bool paused) { m_download_paused = paused; } void set_paused(bool paused) { m_download_paused = paused; }
void set_error_message(const std::string& message) { m_error_message = message; } void set_error_message(const std::string& message) { m_error_message = message; }
bool compare_text(const std::string& text) const override { return false; }; bool compare_text(const std::string& text) const override { return false; };
void set_filename(const std::string& filename_line);
protected: protected:
void render_close_button(const float win_size_x, const float win_size_y, void render_close_button(const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override; const float win_pos_x, const float win_pos_y) override;

View File

@ -137,12 +137,14 @@ struct Http::priv
Http::ProgressFn progressfn; Http::ProgressFn progressfn;
Http::IPResolveFn ipresolvefn; Http::IPResolveFn ipresolvefn;
Http::RetryFn retryfn; Http::RetryFn retryfn;
Http::HeadersFn headersfn;
priv(const std::string &url); priv(const std::string &url);
~priv(); ~priv();
static bool ca_file_supported(::CURL *curl); static bool ca_file_supported(::CURL *curl);
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
static size_t headercb(void *data, size_t size, size_t nmemb, void *userp);
static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow); static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow);
static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
@ -230,6 +232,14 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
return realsize; return realsize;
} }
size_t Http::priv::headercb(void *data, size_t size, size_t nmemb, void *userp)
{
std::string header(reinterpret_cast<char*>(data), size * nmemb);
std::string *header_data = static_cast<std::string*>(userp);
header_data->append(header);
return size * nmemb;
}
int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{ {
auto self = static_cast<priv*>(userp); auto self = static_cast<priv*>(userp);
@ -377,6 +387,10 @@ void Http::priv::http_perform(const HttpRetryOpt& retry_opts)
::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5);
std::string header_data;
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headercb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_data);
if (headerlist != nullptr) { if (headerlist != nullptr) {
::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
} }
@ -444,6 +458,9 @@ void Http::priv::http_perform(const HttpRetryOpt& retry_opts)
if (http_status >= 400) { if (http_status >= 400) {
if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); } if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
} else { } else {
if (headersfn && !header_data.empty()) {
headersfn(header_data);
}
if (completefn) { completefn(std::move(buffer), http_status); } if (completefn) { completefn(std::move(buffer), http_status); }
if (ipresolvefn) { if (ipresolvefn) {
char* ct; char* ct;
@ -657,6 +674,12 @@ Http& Http::on_retry(RetryFn fn)
return *this; return *this;
} }
Http& Http::on_headers(HeadersFn fn)
{
if (p) { p->headersfn = std::move(fn); }
return *this;
}
Http& Http::cookie_file(const std::string& file_path) Http& Http::cookie_file(const std::string& file_path)
{ {
if (p) { if (p) {

View File

@ -67,6 +67,8 @@ public:
//<bool - false if canceled(int - attempt number, unsigned - ms to next attempt, 0 if last)> //<bool - false if canceled(int - attempt number, unsigned - ms to next attempt, 0 if last)>
typedef std::function<bool(int, unsigned)> RetryFn; typedef std::function<bool(int, unsigned)> RetryFn;
typedef std::function<void(const std::string&)> HeadersFn;
Http(Http &&other); Http(Http &&other);
// Note: strings are expected to be UTF-8-encoded // Note: strings are expected to be UTF-8-encoded
@ -147,6 +149,8 @@ public:
Http& on_retry(RetryFn fn); Http& on_retry(RetryFn fn);
Http& on_headers(HeadersFn fn);
Http& cookie_file(const std::string& file_path); Http& cookie_file(const std::string& file_path);
Http& cookie_jar(const std::string& file_path); Http& cookie_jar(const std::string& file_path);
Http& set_referer(const std::string& referer); Http& set_referer(const std::string& referer);