mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-04-20 04:59:39 +08:00
SPE-2740: Change Downloader to receive files from url that redirects from download id.
Http callback on response headers, to change filename. Notification filename change. Write to file on success callback if file part was not written on progress callback.
This commit is contained in:
parent
64a9a8c02b
commit
0e485bc56e
@ -169,7 +169,7 @@ void Downloader::start_download(const std::string& full_url)
|
||||
size_t id = get_next_id();
|
||||
|
||||
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;
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg);
|
||||
@ -214,7 +214,7 @@ void Downloader::on_progress(wxCommandEvent& event)
|
||||
float percent = (float)std::stoi(into_u8(event.GetString())) / 100.f;
|
||||
//BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent;
|
||||
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);
|
||||
}
|
||||
void Downloader::on_error(wxCommandEvent& event)
|
||||
@ -262,7 +262,9 @@ bool Downloader::user_action_callback(DownloaderUserAction action, int id)
|
||||
|
||||
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)
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
|
||||
#include "format.hpp"
|
||||
#include "GUI.hpp"
|
||||
@ -68,6 +69,26 @@ unsigned get_current_pid()
|
||||
return ::getpid();
|
||||
#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
|
||||
@ -111,6 +132,8 @@ FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEv
|
||||
, m_dest_folder(dest_folder)
|
||||
, 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()
|
||||
@ -129,7 +152,7 @@ void FileGet::priv::get_perform()
|
||||
std::string extension = dest_path.extension().string();
|
||||
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
// Find unsed filename
|
||||
// Find unused filename
|
||||
try {
|
||||
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")))
|
||||
@ -154,7 +177,6 @@ void FileGet::priv::get_perform()
|
||||
}
|
||||
|
||||
m_filename = final_filename + extension;
|
||||
|
||||
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
|
||||
@ -247,12 +269,47 @@ void FileGet::priv::get_perform()
|
||||
m_written = written_previously + written_this_session;
|
||||
}
|
||||
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->SetInt(m_id);
|
||||
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) {
|
||||
if (file != NULL)
|
||||
@ -268,19 +325,31 @@ void FileGet::priv::get_perform()
|
||||
.on_complete([&](std::string body, unsigned /* http_status */) {
|
||||
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);
|
||||
boost::filesystem::rename(m_tmp_path, dest_path);
|
||||
}
|
||||
catch (const std::exception& /*e*/)
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
//TODO: report?
|
||||
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
|
||||
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);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
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};
|
||||
wxQueueEvent(m_evt_handler, new Event<DownloadEventData>(EVT_DWNLDR_FILE_COMPLETE, event_data));
|
||||
})
|
||||
|
@ -1119,6 +1119,11 @@ void NotificationManager::URLDownloadWithPrintablesLinkNotification::render_text
|
||||
|
||||
|
||||
//------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)
|
||||
{
|
||||
@ -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)
|
||||
{
|
||||
|
@ -258,6 +258,7 @@ public:
|
||||
void set_download_URL_paused(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_filename(size_t id, const std::string& filename);
|
||||
// slicing progress
|
||||
void init_slicing_progress_notification(std::function<bool()> cancel_callback);
|
||||
void set_slicing_progress_began();
|
||||
@ -561,6 +562,7 @@ private:
|
||||
void set_paused(bool paused) { m_download_paused = paused; }
|
||||
void set_error_message(const std::string& message) { m_error_message = message; }
|
||||
bool compare_text(const std::string& text) const override { return false; };
|
||||
void set_filename(const std::string& filename_line);
|
||||
protected:
|
||||
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;
|
||||
|
@ -137,12 +137,14 @@ struct Http::priv
|
||||
Http::ProgressFn progressfn;
|
||||
Http::IPResolveFn ipresolvefn;
|
||||
Http::RetryFn retryfn;
|
||||
Http::HeadersFn headersfn;
|
||||
|
||||
priv(const std::string &url);
|
||||
~priv();
|
||||
|
||||
static bool ca_file_supported(::CURL *curl);
|
||||
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_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);
|
||||
@ -230,6 +232,14 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
std::string header_data;
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headercb);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_data);
|
||||
|
||||
if (headerlist != nullptr) {
|
||||
::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 (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
|
||||
} else {
|
||||
if (headersfn && !header_data.empty()) {
|
||||
headersfn(header_data);
|
||||
}
|
||||
if (completefn) { completefn(std::move(buffer), http_status); }
|
||||
if (ipresolvefn) {
|
||||
char* ct;
|
||||
@ -657,6 +674,12 @@ Http& Http::on_retry(RetryFn fn)
|
||||
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)
|
||||
{
|
||||
if (p) {
|
||||
|
@ -67,6 +67,8 @@ public:
|
||||
//<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<void(const std::string&)> HeadersFn;
|
||||
|
||||
Http(Http &&other);
|
||||
|
||||
// Note: strings are expected to be UTF-8-encoded
|
||||
@ -147,6 +149,8 @@ public:
|
||||
|
||||
Http& on_retry(RetryFn fn);
|
||||
|
||||
Http& on_headers(HeadersFn fn);
|
||||
|
||||
Http& cookie_file(const std::string& file_path);
|
||||
Http& cookie_jar(const std::string& file_path);
|
||||
Http& set_referer(const std::string& referer);
|
||||
|
Loading…
x
Reference in New Issue
Block a user