mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 15:19:03 +08:00
main app downloader WIP
This commit is contained in:
parent
3fdd3b4514
commit
b733284039
@ -215,6 +215,10 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/HintNotification.hpp
|
GUI/HintNotification.hpp
|
||||||
GUI/FileArchiveDialog.cpp
|
GUI/FileArchiveDialog.cpp
|
||||||
GUI/FileArchiveDialog.hpp
|
GUI/FileArchiveDialog.hpp
|
||||||
|
GUI/Downloader.cpp
|
||||||
|
GUI/Downloader.hpp
|
||||||
|
GUI/DownloaderFileGet.cpp
|
||||||
|
GUI/DownloaderFileGet.hpp
|
||||||
Utils/AppUpdater.cpp
|
Utils/AppUpdater.cpp
|
||||||
Utils/AppUpdater.hpp
|
Utils/AppUpdater.hpp
|
||||||
Utils/Http.cpp
|
Utils/Http.cpp
|
||||||
|
47
src/slic3r/GUI/Downloader.cpp
Normal file
47
src/slic3r/GUI/Downloader.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "Download.hpp"
|
||||||
|
|
||||||
|
namespace Downloader{
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string filename_from_url(const std::string& url)
|
||||||
|
{
|
||||||
|
// TODO: can it be done with curl?
|
||||||
|
size_t slash = url.find_last_of("/");
|
||||||
|
if (slash == std::string::npos && slash != url.size() - 1)
|
||||||
|
return std::string();
|
||||||
|
return url.substr(slash + 1, url.size() - slash + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||||
|
: m_id(ID)
|
||||||
|
, m_filename(filename_from_url(url))
|
||||||
|
{
|
||||||
|
assert(boost::filesystem::is_directory(dest_folder));
|
||||||
|
m_final_path = dest_folder / m_filename;
|
||||||
|
m_file_get = std::make_shared<FileGet>(ID, std::move(url), m_filename, evt_handler, dest_folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Download::start()
|
||||||
|
{
|
||||||
|
m_state = DownloadState::DownloadOngoing;
|
||||||
|
m_file_get->get();
|
||||||
|
}
|
||||||
|
void Download::cancel()
|
||||||
|
{
|
||||||
|
m_state = DownloadState::DownloadStopped;
|
||||||
|
m_file_get->cancel();
|
||||||
|
}
|
||||||
|
void Download::pause()
|
||||||
|
{
|
||||||
|
assert(m_state == DownloadState::DownloadOngoing);
|
||||||
|
m_state = DownloadState::DownloadPaused;
|
||||||
|
m_file_get->pause();
|
||||||
|
}
|
||||||
|
void Download::resume()
|
||||||
|
{
|
||||||
|
assert(m_state == DownloadState::DownloadPaused);
|
||||||
|
m_state = DownloadState::DownloadOngoing;
|
||||||
|
m_file_get->resume();
|
||||||
|
}
|
||||||
|
}
|
43
src/slic3r/GUI/Downloader.hpp
Normal file
43
src/slic3r/GUI/Downloader.hpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef slic3r_Download_hpp_
|
||||||
|
#define slic3r_Download_hpp_
|
||||||
|
|
||||||
|
#include "FileGet.hpp"
|
||||||
|
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace Downloader {
|
||||||
|
|
||||||
|
enum DownloadState
|
||||||
|
{
|
||||||
|
DownloadPending = 0,
|
||||||
|
DownloadOngoing,
|
||||||
|
DownloadStopped,
|
||||||
|
DownloadDone,
|
||||||
|
DownloadError,
|
||||||
|
DownloadPaused,
|
||||||
|
DownloadStateUnknown
|
||||||
|
};
|
||||||
|
|
||||||
|
class Download {
|
||||||
|
public:
|
||||||
|
Download(int ID, std::string url, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
|
||||||
|
void start();
|
||||||
|
void cancel();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
|
||||||
|
int get_id() const { return m_id; }
|
||||||
|
boost::filesystem::path get_final_path() const { return m_final_path; }
|
||||||
|
std::string get_filename() const { return m_filename; }
|
||||||
|
DownloadState get_state() const { return m_state; }
|
||||||
|
void set_state(DownloadState state) { m_state = state; }
|
||||||
|
private:
|
||||||
|
const int m_id;
|
||||||
|
std::string m_filename;
|
||||||
|
boost::filesystem::path m_final_path;
|
||||||
|
std::shared_ptr<FileGet> m_file_get;
|
||||||
|
DownloadState m_state { DownloadState::DownloadPending };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
280
src/slic3r/GUI/DownloaderFileGet.cpp
Normal file
280
src/slic3r/GUI/DownloaderFileGet.cpp
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
#include "FileGet.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <boost/nowide/fstream.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Downloader {
|
||||||
|
|
||||||
|
const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
|
||||||
|
const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
std::string FileGet::escape_url(const std::string& unescaped)
|
||||||
|
{
|
||||||
|
std::string ret_val;
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (curl) {
|
||||||
|
int decodelen;
|
||||||
|
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
|
||||||
|
if (decoded) {
|
||||||
|
ret_val = std::string(decoded);
|
||||||
|
curl_free(decoded);
|
||||||
|
}
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
namespace {
|
||||||
|
unsigned get_current_pid()
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
return GetCurrentProcessId();
|
||||||
|
#else
|
||||||
|
return ::getpid();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// int = DOWNLOAD ID; string = file path
|
||||||
|
wxDEFINE_EVENT(EVT_FILE_COMPLETE, wxCommandEvent);
|
||||||
|
// int = DOWNLOAD ID; string = error msg
|
||||||
|
wxDEFINE_EVENT(EVT_FILE_ERROR, wxCommandEvent);
|
||||||
|
// int = DOWNLOAD ID; string = progress percent
|
||||||
|
wxDEFINE_EVENT(EVT_FILE_PROGRESS, wxCommandEvent);
|
||||||
|
// int = DOWNLOAD ID; string = name
|
||||||
|
wxDEFINE_EVENT(EVT_FILE_NAME_CHANGE, wxCommandEvent);
|
||||||
|
|
||||||
|
|
||||||
|
struct FileGet::priv
|
||||||
|
{
|
||||||
|
const int m_id;
|
||||||
|
std::string m_url;
|
||||||
|
std::string m_filename;
|
||||||
|
std::thread m_io_thread;
|
||||||
|
wxEvtHandler* m_evt_handler;
|
||||||
|
boost::filesystem::path m_dest_folder;
|
||||||
|
boost::filesystem::path m_tmp_path; // path when ongoing download
|
||||||
|
std::atomic_bool m_cancel { false };
|
||||||
|
std::atomic_bool m_pause { false };
|
||||||
|
size_t m_written { 0 };
|
||||||
|
priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
|
||||||
|
|
||||||
|
void get_perform();
|
||||||
|
};
|
||||||
|
|
||||||
|
FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||||
|
: m_id(ID)
|
||||||
|
, m_url(std::move(url))
|
||||||
|
, m_filename(filename)
|
||||||
|
, m_evt_handler(evt_handler)
|
||||||
|
, m_dest_folder(dest_folder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileGet::priv::get_perform()
|
||||||
|
{
|
||||||
|
assert(m_evt_handler);
|
||||||
|
assert(!m_url.empty());
|
||||||
|
assert(!m_filename.empty());
|
||||||
|
assert(boost::filesystem::is_directory(m_dest_folder));
|
||||||
|
|
||||||
|
// open dest file
|
||||||
|
if (m_written == 0)
|
||||||
|
{
|
||||||
|
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||||
|
std::string extension = boost::filesystem::extension(dest_path);
|
||||||
|
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
|
||||||
|
std::string final_filename = just_filename;
|
||||||
|
|
||||||
|
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")))
|
||||||
|
{
|
||||||
|
++version;
|
||||||
|
final_filename = just_filename + "(" + std::to_string(version) + ")";
|
||||||
|
}
|
||||||
|
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_FILE_NAME_CHANGE);
|
||||||
|
evt->SetString(boost::nowide::widen(m_filename));
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||||
|
|
||||||
|
std::cout << "dest_path: " << dest_path.string() << std::endl;
|
||||||
|
std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
|
||||||
|
FILE* file;
|
||||||
|
// open file for writting
|
||||||
|
if (m_written == 0)
|
||||||
|
file = fopen(m_tmp_path.string().c_str(), "wb");
|
||||||
|
else
|
||||||
|
file = fopen(m_tmp_path.string().c_str(), "a");
|
||||||
|
|
||||||
|
std:: string range_string = std::to_string(m_written) + "-";
|
||||||
|
|
||||||
|
size_t written_previously = m_written;
|
||||||
|
size_t written_this_session = 0;
|
||||||
|
Downloader::Http::get(m_url)
|
||||||
|
.size_limit(DOWNLOAD_SIZE_LIMIT) //more?
|
||||||
|
.set_range(range_string)
|
||||||
|
.on_progress([&](Downloader::Http::Progress progress, bool& cancel) {
|
||||||
|
if (m_cancel) {
|
||||||
|
fclose(file);
|
||||||
|
// remove canceled file
|
||||||
|
std::remove(m_tmp_path.string().c_str());
|
||||||
|
m_written = 0;
|
||||||
|
cancel = true;
|
||||||
|
return;
|
||||||
|
// TODO: send canceled event?
|
||||||
|
}
|
||||||
|
if (m_pause) {
|
||||||
|
fclose(file);
|
||||||
|
cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_PROGRESS);
|
||||||
|
/*if (progress.dlnow == 0 && m_written == 0) {
|
||||||
|
evt->SetString("0");
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
} else*/
|
||||||
|
if (progress.dlnow != 0) {
|
||||||
|
if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow);
|
||||||
|
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
// fclose(file); do it?
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_ERROR);
|
||||||
|
evt->SetString(e.what());
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
written_this_session = progress.dlnow;
|
||||||
|
m_written = written_previously + written_this_session;
|
||||||
|
}
|
||||||
|
evt->SetString(std::to_string(progress.dlnow * 100 / progress.dltotal));
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
||||||
|
fclose(file);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_ERROR);
|
||||||
|
evt->SetString(error);
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned /* http_status */) {
|
||||||
|
|
||||||
|
size_t body_size = body.size();
|
||||||
|
// TODO:
|
||||||
|
//if (body_size != expected_size) {
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if (m_written < body.size())
|
||||||
|
{
|
||||||
|
// this code should never be entered. As there should be on_progress call after last bit downloaded.
|
||||||
|
std::string part_for_write = body.substr(m_written);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
//TODO: report?
|
||||||
|
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_ERROR);
|
||||||
|
evt->SetString("Failed to write and move.");
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_COMPLETE);
|
||||||
|
evt->SetString(dest_path.string());
|
||||||
|
evt->SetInt(m_id);
|
||||||
|
m_evt_handler->QueueEvent(evt);
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||||
|
: p(new priv(ID, std::move(url), filename, evt_handler, dest_folder))
|
||||||
|
{}
|
||||||
|
|
||||||
|
FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}
|
||||||
|
|
||||||
|
FileGet::~FileGet()
|
||||||
|
{
|
||||||
|
if (p && p->m_io_thread.joinable()) {
|
||||||
|
p->m_cancel = true;
|
||||||
|
p->m_io_thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileGet::get()
|
||||||
|
{
|
||||||
|
assert(p);
|
||||||
|
if (p->m_io_thread.joinable()) {
|
||||||
|
// This will stop transfers being done by the thread, if any.
|
||||||
|
// Cancelling takes some time, but should complete soon enough.
|
||||||
|
p->m_cancel = true;
|
||||||
|
p->m_io_thread.join();
|
||||||
|
}
|
||||||
|
p->m_cancel = false;
|
||||||
|
p->m_pause = false;
|
||||||
|
p->m_io_thread = std::thread([this]() {
|
||||||
|
p->get_perform();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileGet::cancel()
|
||||||
|
{
|
||||||
|
if(p){
|
||||||
|
p->m_cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileGet::pause()
|
||||||
|
{
|
||||||
|
if (p) {
|
||||||
|
p->m_pause = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void FileGet::resume()
|
||||||
|
{
|
||||||
|
assert(p);
|
||||||
|
if (p->m_io_thread.joinable()) {
|
||||||
|
// This will stop transfers being done by the thread, if any.
|
||||||
|
// Cancelling takes some time, but should complete soon enough.
|
||||||
|
p->m_cancel = true;
|
||||||
|
p->m_io_thread.join();
|
||||||
|
}
|
||||||
|
p->m_cancel = false;
|
||||||
|
p->m_pause = false;
|
||||||
|
p->m_io_thread = std::thread([this]() {
|
||||||
|
p->get_perform();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
src/slic3r/GUI/DownloaderFileGet.hpp
Normal file
38
src/slic3r/GUI/DownloaderFileGet.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#ifndef slic3r_FileGet_hpp_
|
||||||
|
#define slic3r_FileGet_hpp_
|
||||||
|
|
||||||
|
#include "FromSlicer/Http.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <wx/event.h>
|
||||||
|
#include <wx/frame.h>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
namespace Downloader {
|
||||||
|
class FileGet : public std::enable_shared_from_this<FileGet> {
|
||||||
|
private:
|
||||||
|
struct priv;
|
||||||
|
public:
|
||||||
|
FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
|
||||||
|
FileGet(FileGet&& other);
|
||||||
|
~FileGet();
|
||||||
|
|
||||||
|
void get();
|
||||||
|
void cancel();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
static std::string escape_url(const std::string& url);
|
||||||
|
private:
|
||||||
|
std::unique_ptr<priv> p;
|
||||||
|
};
|
||||||
|
// int = DOWNLOAD ID; string = file path
|
||||||
|
wxDECLARE_EVENT(EVT_FILE_COMPLETE, wxCommandEvent);
|
||||||
|
// int = DOWNLOAD ID; string = error msg
|
||||||
|
wxDECLARE_EVENT(EVT_FILE_PROGRESS, wxCommandEvent);
|
||||||
|
// int = DOWNLOAD ID; string = progress percent
|
||||||
|
wxDECLARE_EVENT(EVT_FILE_ERROR, wxCommandEvent);
|
||||||
|
// int = DOWNLOAD ID; string = name
|
||||||
|
wxDECLARE_EVENT(EVT_FILE_NAME_CHANGE, wxCommandEvent);
|
||||||
|
}
|
||||||
|
#endif
|
Loading…
x
Reference in New Issue
Block a user