mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-05 17:17:42 +00:00
Various improvements to SimplyPrint integration (#4831)
* Allow using BBL's device tab when 3rd party print host is used * Add option to open SimplyPrint panel in device tab after uploading * Fix default print host for prusa connect * Do not set api key in device view when SimplyPrint is used * Sending 3mf file to SimplyPrint when using BBL printers * Fix file extension when uploading 3mf * Prepare for large file uploading * Implement chunk upload * Fix file uploading exceeding content size * Fix wrong field type * Add `temp=true` to all chunk upload calls * Add macro to enable test api * Merge branch 'main' into dev/simplyprint-improve * Fix another missing `temp=true` * Add delete token * Try fixing build error on *nix systems * Merge branch 'main' into dev/simplyprint-improve * Merge branch 'main' into dev/simplyprint-improve * Merge remote-tracking branch 'remote/main' into dev/simplyprint-improve # Conflicts: # src/slic3r/GUI/BackgroundSlicingProcess.cpp * Move the `bbl_use_print_host_webui` option to print host dialog. Also make it a derived option of `print_host_webui` instead. * Merge branch 'main' into dev/simplyprint-improve # Conflicts: # src/slic3r/GUI/MainFrame.cpp # src/slic3r/GUI/Plater.cpp * Merge branch 'main' into dev/simplyprint-improve # Conflicts: # src/slic3r/GUI/Plater.cpp * Use a more generic option instead of SimplyPrint specific * Merge branch 'main' into dev/simplyprint-improve * Merge branch 'main' into dev/simplyprint-improve
This commit is contained in:
@@ -86,6 +86,13 @@ std::unique_ptr<CurlGlobalInit> CurlGlobalInit::instance;
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
std::mutex g_mutex;
|
||||
|
||||
struct form_file
|
||||
{
|
||||
fs::ifstream ifs;
|
||||
boost::filesystem::ifstream::off_type init_offset;
|
||||
size_t content_length;
|
||||
};
|
||||
|
||||
struct Http::priv
|
||||
{
|
||||
enum {
|
||||
@@ -103,7 +110,7 @@ struct Http::priv
|
||||
std::string buffer;
|
||||
// Used for storing file streams added as multipart form parts
|
||||
// Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
|
||||
std::deque<fs::ifstream> form_files;
|
||||
std::deque<form_file> form_files;
|
||||
std::string postfields;
|
||||
std::string error_buffer; // Used for CURLOPT_ERRORBUFFER
|
||||
std::string headers;
|
||||
@@ -130,7 +137,7 @@ struct Http::priv
|
||||
|
||||
void set_timeout_connect(long timeout);
|
||||
void set_timeout_max(long timeout);
|
||||
void form_add_file(const char *name, const fs::path &path, const char* filename);
|
||||
void form_add_file(const char *name, const fs::path &path, const char* filename, boost::filesystem::ifstream::off_type offset, size_t length);
|
||||
/* mime */
|
||||
void mime_form_add_text(const char* name, const char* value);
|
||||
void mime_form_add_file(const char* name, const char* path);
|
||||
@@ -254,15 +261,27 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double
|
||||
|
||||
size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp)
|
||||
{
|
||||
auto stream = reinterpret_cast<fs::ifstream*>(userp);
|
||||
auto f = reinterpret_cast<form_file*>(userp);
|
||||
|
||||
try {
|
||||
stream->read(buffer, size * nitems);
|
||||
size_t max_read_size = size * nitems;
|
||||
if (f->content_length == 0) {
|
||||
// Unlimited
|
||||
f->ifs.read(buffer, max_read_size);
|
||||
} else {
|
||||
unsigned long long read_size = f->ifs.tellg() - f->init_offset;
|
||||
if (read_size >= f->content_length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
max_read_size = std::min(max_read_size, size_t(f->content_length - read_size));
|
||||
f->ifs.read(buffer, max_read_size);
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
return CURL_READFUNC_ABORT;
|
||||
}
|
||||
|
||||
return stream->gcount();
|
||||
return f->ifs.gcount();
|
||||
}
|
||||
|
||||
size_t Http::priv::headers_cb(char *buffer, size_t size, size_t nitems, void *userp)
|
||||
@@ -286,7 +305,7 @@ void Http::priv::set_timeout_max(long timeout)
|
||||
::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
|
||||
}
|
||||
|
||||
void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
|
||||
void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename, boost::filesystem::ifstream::off_type offset, size_t length)
|
||||
{
|
||||
// We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
|
||||
// and so we use CURLFORM_STREAM with boost ifstream to read the file.
|
||||
@@ -295,18 +314,21 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
|
||||
filename = path.string().c_str();
|
||||
}
|
||||
|
||||
form_files.emplace_back(path, std::ios::in | std::ios::binary);
|
||||
auto &stream = form_files.back();
|
||||
stream.seekg(0, std::ios::end);
|
||||
size_t size = stream.tellg();
|
||||
stream.seekg(0);
|
||||
form_files.emplace_back(form_file{{path, std::ios::in | std::ios::binary}, offset, length});
|
||||
auto &f = form_files.back();
|
||||
size_t size = length;
|
||||
if (length == 0) {
|
||||
f.ifs.seekg(0, std::ios::end);
|
||||
size = f.ifs.tellg() - offset;
|
||||
}
|
||||
f.ifs.seekg(offset);
|
||||
|
||||
if (filename != nullptr) {
|
||||
::curl_formadd(&form, &form_end,
|
||||
CURLFORM_COPYNAME, name,
|
||||
CURLFORM_FILENAME, filename,
|
||||
CURLFORM_CONTENTTYPE, "application/octet-stream",
|
||||
CURLFORM_STREAM, static_cast<void*>(&stream),
|
||||
CURLFORM_STREAM, static_cast<void*>(&f),
|
||||
CURLFORM_CONTENTSLENGTH, static_cast<long>(size),
|
||||
CURLFORM_END
|
||||
);
|
||||
@@ -587,9 +609,9 @@ Http& Http::form_add(const std::string &name, const std::string &contents)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_add_file(const std::string &name, const fs::path &path)
|
||||
Http& Http::form_add_file(const std::string &name, const fs::path &path, boost::filesystem::ifstream::off_type offset, size_t length)
|
||||
{
|
||||
if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); }
|
||||
if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr, offset, length); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -607,15 +629,15 @@ Http& Http::mime_form_add_file(std::string &name, const char* path)
|
||||
}
|
||||
|
||||
|
||||
Http& Http::form_add_file(const std::wstring& name, const fs::path& path)
|
||||
Http& Http::form_add_file(const std::wstring& name, const fs::path& path, boost::filesystem::ifstream::off_type offset, size_t length)
|
||||
{
|
||||
if (p) { p->form_add_file((char*)name.c_str(), path.c_str(), nullptr); }
|
||||
if (p) { p->form_add_file((char*)name.c_str(), path.c_str(), nullptr, offset, length); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename)
|
||||
Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename, boost::filesystem::ifstream::off_type offset, size_t length)
|
||||
{
|
||||
if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); }
|
||||
if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str(), offset, length); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,15 +121,15 @@ public:
|
||||
// Add a HTTP multipart form field
|
||||
Http& form_add(const std::string &name, const std::string &contents);
|
||||
// Add a HTTP multipart form file data contents, `name` is the name of the part
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path);
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, boost::filesystem::ifstream::off_type offset = 0, size_t length = 0);
|
||||
// Add a HTTP mime form field
|
||||
Http& mime_form_add_text(std::string& name, std::string& value);
|
||||
// Add a HTTP mime form file
|
||||
Http& mime_form_add_file(std::string& name, const char* path);
|
||||
// Same as above except also override the file's filename with a wstring type
|
||||
Http& form_add_file(const std::wstring& name, const boost::filesystem::path& path);
|
||||
Http& form_add_file(const std::wstring& name, const boost::filesystem::path& path, boost::filesystem::ifstream::off_type offset = 0, size_t length = 0);
|
||||
// Same as above except also override the file's filename with a custom one
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename, boost::filesystem::ifstream::off_type offset = 0, size_t length = 0);
|
||||
|
||||
#ifdef WIN32
|
||||
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "MKS.hpp"
|
||||
#include "ESP3D.hpp"
|
||||
#include "../GUI/PrintHostDialogs.hpp"
|
||||
#include "../GUI/MainFrame.hpp"
|
||||
#include "Obico.hpp"
|
||||
#include "Flashforge.hpp"
|
||||
#include "SimplyPrint.hpp"
|
||||
@@ -314,6 +315,10 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||
|
||||
if (success) {
|
||||
emit_progress(100);
|
||||
if (the_job.switch_to_device_tab) {
|
||||
const auto mainframe = GUI::wxGetApp().mainframe;
|
||||
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
|
||||
|
||||
struct PrintHostUpload
|
||||
{
|
||||
bool use_3mf;
|
||||
boost::filesystem::path source_path;
|
||||
boost::filesystem::path upload_path;
|
||||
|
||||
@@ -85,6 +86,7 @@ struct PrintHostJob
|
||||
{
|
||||
PrintHostUpload upload_data;
|
||||
std::unique_ptr<PrintHost> printhost;
|
||||
bool switch_to_device_tab{false};
|
||||
bool cancelled = false;
|
||||
|
||||
PrintHostJob() {}
|
||||
@@ -92,6 +94,7 @@ struct PrintHostJob
|
||||
PrintHostJob(PrintHostJob &&other)
|
||||
: upload_data(std::move(other.upload_data))
|
||||
, printhost(std::move(other.printhost))
|
||||
, switch_to_device_tab(other.switch_to_device_tab)
|
||||
, cancelled(other.cancelled)
|
||||
{}
|
||||
|
||||
@@ -103,7 +106,8 @@ struct PrintHostJob
|
||||
PrintHostJob& operator=(PrintHostJob &&other)
|
||||
{
|
||||
upload_data = std::move(other.upload_data);
|
||||
printhost = std::move(other.printhost);
|
||||
printhost = std::move(other.printhost);
|
||||
switch_to_device_tab = other.switch_to_device_tab;
|
||||
cancelled = other.cancelled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -9,17 +9,36 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// to make testing easier
|
||||
//#define SIMPLYPRINT_TEST
|
||||
|
||||
#ifdef SIMPLYPRINT_TEST
|
||||
#define URL_BASE_HOME "https://test.simplyprint.io"
|
||||
#define URL_BASE_API "https://testapi.simplyprint.io"
|
||||
#else
|
||||
#define URL_BASE_HOME "https://simplyprint.io"
|
||||
#define URL_BASE_API "https://api.simplyprint.io"
|
||||
#endif
|
||||
|
||||
static constexpr boost::asio::ip::port_type CALLBACK_PORT = 21328;
|
||||
static const std::string CALLBACK_URL = "http://localhost:21328/callback";
|
||||
static const std::string RESPONSE_TYPE = "code";
|
||||
static const std::string CLIENT_ID = "simplyprintorcaslicer";
|
||||
static const std::string CLIENT_SCOPES = "user.read files.temp_upload";
|
||||
static const std::string OAUTH_CREDENTIAL_PATH = "simplyprint_oauth.json";
|
||||
static const std::string TOKEN_URL = "https://simplyprint.io/api/oauth2/Token";
|
||||
static const std::string TOKEN_URL = URL_BASE_API"/oauth2/Token";
|
||||
#ifdef SIMPLYPRINT_TEST
|
||||
static constexpr uint64_t MAX_SINGLE_UPLOAD_FILE_SIZE = 100000ull; // Max file size that can be uploaded in a single http request
|
||||
#else
|
||||
static constexpr uint64_t MAX_SINGLE_UPLOAD_FILE_SIZE = 100000000ull; // Max file size that can be uploaded in a single http request
|
||||
#endif
|
||||
static const std::string CHUNCK_RECEIVE_URL = URL_BASE_API"/0/files/ChunkReceive";
|
||||
|
||||
static std::string generate_verification_code(int code_length = 32)
|
||||
{
|
||||
@@ -57,6 +76,9 @@ static std::string url_encode(const std::vector<std::pair<std::string, std::stri
|
||||
q.reserve(query.size());
|
||||
|
||||
std::transform(query.begin(), query.end(), std::back_inserter(q), [](const auto& kv) {
|
||||
if (kv.second.empty()) {
|
||||
return Http::url_encode(kv.first);
|
||||
}
|
||||
return Http::url_encode(kv.first) + "=" + Http::url_encode(kv.second);
|
||||
});
|
||||
|
||||
@@ -65,6 +87,19 @@ static std::string url_encode(const std::vector<std::pair<std::string, std::stri
|
||||
|
||||
static void set_auth(Http& http, const std::string& access_token) { http.header("Authorization", "Bearer " + access_token); }
|
||||
|
||||
static bool should_open_in_external_browser()
|
||||
{
|
||||
const auto& app = wxGetApp();
|
||||
|
||||
if (app.preset_bundle->use_bbl_device_tab()) {
|
||||
// When using bbl device tab, we always need to open external browser
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, if user choose to switch to device tab, then don't bother opening external browser
|
||||
return !app.app_config->get_bool("open_device_tab_post_upload");
|
||||
}
|
||||
|
||||
SimplyPrint::SimplyPrint(DynamicPrintConfig* config)
|
||||
{
|
||||
cred_file = (boost::filesystem::path(data_dir()) / OAUTH_CREDENTIAL_PATH).make_preferred().string();
|
||||
@@ -87,7 +122,7 @@ GUI::OAuthParams SimplyPrint::get_oauth_params() const
|
||||
{"code_challenge", code_challenge},
|
||||
{"code_challenge_method", "S256"},
|
||||
};
|
||||
const auto login_url = (boost::format("https://simplyprint.io/panel/oauth2/authorize?%s") % url_encode(query_parameters)).str();
|
||||
const auto login_url = (boost::format(URL_BASE_HOME"/panel/oauth2/authorize?%s") % url_encode(query_parameters)).str();
|
||||
|
||||
return GUI::OAuthParams{
|
||||
login_url,
|
||||
@@ -96,8 +131,8 @@ GUI::OAuthParams SimplyPrint::get_oauth_params() const
|
||||
CALLBACK_URL,
|
||||
CLIENT_SCOPES,
|
||||
RESPONSE_TYPE,
|
||||
"https://simplyprint.io/login-success",
|
||||
"https://simplyprint.io/login-success",
|
||||
URL_BASE_HOME"/login-success",
|
||||
URL_BASE_HOME"/login-success",
|
||||
TOKEN_URL,
|
||||
verification_code,
|
||||
state,
|
||||
@@ -226,7 +261,7 @@ bool SimplyPrint::test(wxString& curl_msg) const
|
||||
|
||||
return do_api_call(
|
||||
[](bool is_retry) {
|
||||
auto http = Http::get("https://api.simplyprint.io/oauth2/TokenInfo");
|
||||
auto http = Http::get(URL_BASE_API"/oauth2/TokenInfo");
|
||||
http.header("Accept", "application/json");
|
||||
return http;
|
||||
},
|
||||
@@ -241,30 +276,31 @@ bool SimplyPrint::test(wxString& curl_msg) const
|
||||
});
|
||||
}
|
||||
|
||||
bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
bool SimplyPrint::do_temp_upload(const boost::filesystem::path& file_path,
|
||||
const std::string& chunk_id,
|
||||
const std::string& filename,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn) const
|
||||
{
|
||||
if (cred.find("access_token") == cred.end()) {
|
||||
error_fn(_L("SimplyPrint account not linked. Go to Connect options to set it up."));
|
||||
if (file_path.empty() == chunk_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid arguments: both file_path and chunk_id are set or not provided";
|
||||
error_fn(_L("Internel error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If file is over 100 MB, fail
|
||||
if (boost::filesystem::file_size(upload_data.source_path) > 104857600ull) {
|
||||
error_fn(_L("File size exceeds the 100MB upload limit. Please upload your file through the panel."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto filename = upload_data.upload_path.filename().string();
|
||||
|
||||
return do_api_call(
|
||||
[&upload_data, &prorgess_fn, &filename](bool is_retry) {
|
||||
auto http = Http::post("https://simplyprint.io/api/files/TempUpload");
|
||||
http.form_add_file("file", upload_data.source_path.string(), filename)
|
||||
.on_progress([&prorgess_fn](Http::Progress progress, bool& cancel) { prorgess_fn(std::move(progress), cancel); });
|
||||
[&file_path, &chunk_id, &prorgess_fn, &filename](bool is_retry) {
|
||||
auto http = Http::post(URL_BASE_HOME"/api/files/TempUpload");
|
||||
if (!file_path.empty()) {
|
||||
http.form_add_file("file", file_path, filename);
|
||||
} else {
|
||||
http.form_add("chunkId", chunk_id);
|
||||
}
|
||||
http.on_progress([&prorgess_fn](Http::Progress progress, bool& cancel) { prorgess_fn(std::move(progress), cancel); });
|
||||
|
||||
return http;
|
||||
},
|
||||
[&error_fn, &filename](std::string body, unsigned status) {
|
||||
[&error_fn, &filename, this](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File uploaded: HTTP %1%: %2%") % status % body;
|
||||
|
||||
// Get file UUID
|
||||
@@ -283,8 +319,15 @@ bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Er
|
||||
const std::string uuid = j["uuid"];
|
||||
|
||||
// Launch external browser for file importing after uploading
|
||||
const auto url = "https://simplyprint.io/panel?" + url_encode({{"import", "tmp:" + uuid}, {"filename", filename}});
|
||||
wxLaunchDefaultBrowser(url);
|
||||
const auto url = URL_BASE_HOME"/panel?" + url_encode({{"import", "tmp:" + uuid}, {"filename", filename}});
|
||||
|
||||
if (should_open_in_external_browser()) {
|
||||
wxLaunchDefaultBrowser(url);
|
||||
} else {
|
||||
const auto mainframe = GUI::wxGetApp().mainframe;
|
||||
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
||||
mainframe->load_printer_url(url);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
@@ -295,4 +338,155 @@ bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Er
|
||||
});
|
||||
}
|
||||
|
||||
bool SimplyPrint::do_chunk_upload(const boost::filesystem::path& file_path, const std::string& filename, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||
{
|
||||
const auto file_size = boost::filesystem::file_size(file_path);
|
||||
#ifdef SIMPLYPRINT_TEST
|
||||
constexpr auto buffer_size = MAX_SINGLE_UPLOAD_FILE_SIZE;
|
||||
#else
|
||||
constexpr auto buffer_size = MAX_SINGLE_UPLOAD_FILE_SIZE - 1000000;
|
||||
#endif
|
||||
|
||||
const auto chunk_amount = (size_t)ceil((double) file_size / buffer_size);
|
||||
|
||||
std::string chunk_id;
|
||||
std::string delete_token;
|
||||
|
||||
// Tell SimplyPrint that the upload has failed and the chunks should be deleted
|
||||
// Note: any error happens here won't be notified to the user
|
||||
const auto clean_up = [this, &chunk_id, &delete_token]() {
|
||||
if (chunk_id.empty()) {
|
||||
// The initial upload failed, do nothing
|
||||
BOOST_LOG_TRIVIAL(warning) << "SimplyPrint: Initial chunk upload failed, skip delete";
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!delete_token.empty());
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: Deleting file chunk %s...") % chunk_id;
|
||||
const std::vector<std::pair<std::string, std::string>> query_parameters{
|
||||
{"id", chunk_id},
|
||||
{"temp", "true"},
|
||||
{"delete", delete_token},
|
||||
};
|
||||
const auto url = (boost::format("%s?%s") % CHUNCK_RECEIVE_URL % url_encode(query_parameters)).str();
|
||||
do_api_call(
|
||||
[&url](bool is_retry) {
|
||||
auto http = Http::get(url);
|
||||
return http;
|
||||
},
|
||||
[&chunk_id](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File chunk %1% deleted: HTTP %2%: %3%") % chunk_id % status % body;
|
||||
return true;
|
||||
},
|
||||
[&chunk_id](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("SimplyPrint: Error deleting file chunk %1%: %2%, HTTP %3%, body: `%4%`") %
|
||||
chunk_id % error % status % body;
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
// Do chunk upload
|
||||
for (size_t i = 0; i < chunk_amount; i++) {
|
||||
std::string url;
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> query_parameters{
|
||||
{"i", std::to_string(i)},
|
||||
{"temp", "true"},
|
||||
};
|
||||
if (i == 0) {
|
||||
query_parameters.emplace_back("filename", filename);
|
||||
query_parameters.emplace_back("chunks", std::to_string(chunk_amount));
|
||||
query_parameters.emplace_back("totalsize", std::to_string(file_size));
|
||||
} else {
|
||||
query_parameters.emplace_back("id", chunk_id);
|
||||
}
|
||||
url = (boost::format("%s?%s") % CHUNCK_RECEIVE_URL % url_encode(query_parameters)).str();
|
||||
}
|
||||
|
||||
// Calculate the offset and length of current chunk
|
||||
const boost::filesystem::ifstream::off_type offset = i * buffer_size;
|
||||
const size_t length = i == (chunk_amount - 1) ? file_size - offset : buffer_size;
|
||||
|
||||
const bool succ = do_api_call(
|
||||
[&url, &file_path, &filename, i, chunk_amount, file_size, offset, length, prorgess_fn](bool is_retry) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: Start uploading file chunk [%1%/%2%]...") % (i + 1) % chunk_amount;
|
||||
auto http = Http::post(url);
|
||||
http.form_add_file("file", file_path, filename, offset, length);
|
||||
|
||||
http.on_progress([&prorgess_fn, file_size, offset](Http::Progress progress, bool& cancel) {
|
||||
progress.ultotal = file_size;
|
||||
progress.ulnow += offset;
|
||||
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
});
|
||||
|
||||
return http;
|
||||
},
|
||||
[&error_fn, i, chunk_amount, this, &chunk_id, &delete_token](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File chunk [%1%/%2%] uploaded: HTTP %3%: %4%") % (i + 1) % chunk_amount % status % body;
|
||||
if (i == 0) {
|
||||
// First chunk, parse chunk id
|
||||
const auto j = nlohmann::json::parse(body, nullptr, false, true);
|
||||
if (j.is_discarded()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on ChunkReceive: " << body;
|
||||
error_fn(_L("Unknown error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j.find("id") == j.end() || j.find("delete_token") == j.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on ChunkReceive: " << body;
|
||||
error_fn(_L("Unknown error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned long id = j["id"];
|
||||
|
||||
chunk_id = std::to_string(id);
|
||||
delete_token = j["delete_token"];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[this, &error_fn, i, chunk_amount](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("SimplyPrint: Error uploading file chunk [%1%/%2%]: %3%, HTTP %4%, body: `%5%`") %
|
||||
(i + 1) % chunk_amount % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!succ) {
|
||||
clean_up();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!chunk_id.empty());
|
||||
|
||||
// Finally, complete the upload using the chunk id
|
||||
const bool succ = do_temp_upload({}, chunk_id, filename, prorgess_fn, error_fn);
|
||||
if (!succ) {
|
||||
clean_up();
|
||||
}
|
||||
|
||||
return succ;
|
||||
}
|
||||
|
||||
|
||||
bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
if (cred.find("access_token") == cred.end()) {
|
||||
error_fn(_L("SimplyPrint account not linked. Go to Connect options to set it up."));
|
||||
return false;
|
||||
}
|
||||
const auto filename = upload_data.upload_path.filename().string();
|
||||
|
||||
if (boost::filesystem::file_size(upload_data.source_path) > MAX_SINGLE_UPLOAD_FILE_SIZE) {
|
||||
// If file is over 100 MB, do chunk upload
|
||||
return do_chunk_upload(upload_data.source_path, filename, prorgess_fn, error_fn);
|
||||
} else {
|
||||
// Normal upload
|
||||
return do_temp_upload(upload_data.source_path, {}, filename, prorgess_fn, error_fn);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -15,10 +15,37 @@ class SimplyPrint : public PrintHost
|
||||
|
||||
void load_oauth_credential();
|
||||
|
||||
/**
|
||||
* \brief Call the given SimplyPrint API, and if the token expired do a token refresh then retry
|
||||
* \param build_request the http request builder
|
||||
* \param on_complete
|
||||
* \param on_error
|
||||
* \return whether the API call succeeded
|
||||
*/
|
||||
bool do_api_call(std::function<Http(bool /*is_retry*/)> build_request,
|
||||
std::function<bool(std::string /* body */, unsigned /* http_status */)> on_complete,
|
||||
std::function<bool(std::string /* body */, std::string /* error */, unsigned /* http_status */)> on_error) const;
|
||||
|
||||
/**
|
||||
* \brief Upload a temp file and open SimplyPrint panel for file importing
|
||||
* \param file_path for file smaller than 100MB, this is the file path, otherwise must left empty
|
||||
* \param chunk_id for file greater than 100MB, this is the chunk id returned by the ChunkReceive API, otherwise must left empty
|
||||
* \param filename the target file name
|
||||
* \param prorgess_fn
|
||||
* \param error_fn
|
||||
* \return whether upload succeeded
|
||||
*/
|
||||
bool do_temp_upload(const boost::filesystem::path& file_path,
|
||||
const std::string& chunk_id,
|
||||
const std::string& filename,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn) const;
|
||||
|
||||
bool do_chunk_upload(const boost::filesystem::path& file_path,
|
||||
const std::string& filename,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn) const;
|
||||
|
||||
public:
|
||||
SimplyPrint(DynamicPrintConfig* config);
|
||||
~SimplyPrint() override = default;
|
||||
|
||||
Reference in New Issue
Block a user