app updater feature

checks online version file
offers download of new version of slicer
download with notification
opens instalator or target folder
This commit is contained in:
David Kocik 2022-01-12 16:21:16 +01:00
parent 1d2339fbce
commit 43124979e5
13 changed files with 1250 additions and 36 deletions

View File

@ -213,6 +213,8 @@ set(SLIC3R_GUI_SOURCES
GUI/DesktopIntegrationDialog.hpp
GUI/HintNotification.cpp
GUI/HintNotification.hpp
Utils/AppUpdater.cpp
Utils/AppUpdater.hpp
Utils/Http.cpp
Utils/Http.hpp
Utils/FixModelByWin10.cpp
@ -250,6 +252,7 @@ if (APPLE)
list(APPEND SLIC3R_GUI_SOURCES
Utils/RetinaHelperImpl.mm
Utils/MacDarkMode.mm
Utils/MacUtils.mm
GUI/RemovableDriveManagerMM.mm
GUI/RemovableDriveManagerMM.h
GUI/Mouse3DHandlerMac.mm

View File

@ -57,6 +57,7 @@
#include "../Utils/PrintHost.hpp"
#include "../Utils/Process.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/AppUpdater.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp"
@ -816,18 +817,13 @@ void GUI_App::post_init()
CallAfter([this] {
bool cw_showed = this->config_wizard_startup();
this->preset_updater->sync(preset_bundle);
this->app_version_check();
if (! cw_showed) {
// The CallAfter is needed as well, without it, GL extensions did not show.
// Also, we only want to show this when the wizard does not, so the new user
// sees something else than "we want something" on the first start.
show_send_system_info_dialog_if_needed();
}
#ifdef _WIN32
// Run external updater on Windows if version check is enabled.
if (this->preset_updater->version_check_enabled() && ! run_updater_win())
// "prusaslicer-updater.exe" was not started, run our own update check.
#endif // _WIN32
this->preset_updater->slic3r_update_notify();
});
}
@ -853,6 +849,8 @@ GUI_App::GUI_App(EAppMode mode)
{
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
this->init_app_config();
// init app downloader after path to datadir is set
m_app_updater = std::make_unique<AppUpdater>();
}
GUI_App::~GUI_App()
@ -1242,21 +1240,7 @@ bool GUI_App::on_init_inner()
#endif // __WXMSW__
preset_updater = new PresetUpdater();
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
app_config->set("version_online", into_u8(evt.GetString()));
app_config->save();
std::string opt = app_config->get("notify_release");
if (this->plater_ != nullptr && (opt == "all" || opt == "release")) {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
, NotificationManager::NotificationLevel::ImportantNotificationLevel
, Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
, _u8L("See Download page.")
, [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
);
}
}
});
Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this);
Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
app_config->save();
if (this->plater_ != nullptr && app_config->get("notify_release") == "all") {
@ -1272,6 +1256,18 @@ bool GUI_App::on_init_inner()
}
}
});
Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) {
if (this->plater_ != nullptr)
this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f );
});
Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) {
if (this->plater_ != nullptr)
this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload);
show_error(nullptr, evt.GetString());
});
}
else {
#ifdef __WXMSW__
@ -2282,7 +2278,8 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application"));
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
//if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
@ -2324,9 +2321,12 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
case ConfigMenuWizard:
run_wizard(ConfigWizard::RR_USER);
break;
case ConfigMenuUpdate:
case ConfigMenuUpdateConf:
check_updates(true);
break;
case ConfigMenuUpdateApp:
app_updater(true);
break;
#ifdef __linux__
case ConfigMenuDesktopIntegration:
show_desktop_integration_dialog();
@ -3266,5 +3266,108 @@ void GUI_App::associate_gcode_files()
}
#endif // __WXMSW__
void GUI_App::on_version_read(wxCommandEvent& evt)
{
app_config->set("version_online", into_u8(evt.GetString()));
app_config->save();
std::string opt = app_config->get("notify_release");
if (this->plater_ == nullptr || (opt != "all" && opt != "release")) {
return;
}
if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) {
return;
}
// notification
/*
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
, NotificationManager::NotificationLevel::ImportantNotificationLevel
, Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
, _u8L("See Download page.")
, [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
);
*/
// updater
app_updater(false);
}
void GUI_App::app_updater(bool from_user)
{
DownloadAppData app_data = m_app_updater->get_app_data();
if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION)))
{
BOOST_LOG_TRIVIAL(info) << "There is no newer version online.";
MsgNoAppUpdates no_update_dialog;
no_update_dialog.ShowModal();
return;
}
assert(!app_data.url.empty());
assert(!app_data.target_path.empty());
// dialog with new version info
AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version);
auto dialog_result = dialog.ShowModal();
// checkbox "do not show again"
if (dialog.disable_version_check()) {
app_config->set("notify_release", "none");
}
// Doesn't wish to update
if (dialog_result != wxID_OK) {
return;
}
// dialog with new version download (installer or app dependent on system)
AppUpdateDownloadDialog dwnld_dlg(*app_data.version);
dialog_result = dwnld_dlg.ShowModal();
// Doesn't wish to download
if (dialog_result != wxID_OK) {
return;
}
// Save as dialog
if (dwnld_dlg.select_download_path()) {
std::string extension = app_data.target_path.filename().extension().string();
wxString wildcard;
if (!extension.empty()) {
extension = extension.substr(1);
wxString wxext = boost::nowide::widen(extension);
wildcard = GUI::format_wxstr("%1% Files (*.%2%)|*.%2%", wxext.Upper(), wxext);
}
wxFileDialog save_dlg(
plater()
, _L("Save as:")
, boost::nowide::widen(m_app_updater->get_default_dest_folder())
, boost::nowide::widen(AppUpdater::get_filename_from_url(app_data.url))
, wildcard
, wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
// Canceled
if (save_dlg.ShowModal() != wxID_OK) {
return;
// set path
} else {
app_data.target_path = boost::filesystem::path(save_dlg.GetPath().ToUTF8().data());
}
}
if (boost::filesystem::exists(app_data.target_path))
{
BOOST_LOG_TRIVIAL(error) << "App download: File on target path already exists and will be overwritten. Path: " << app_data.target_path;
}
// start download
this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get()));
app_data.start_after = dwnld_dlg.run_after_download();
m_app_updater->set_app_data(std::move(app_data));
m_app_updater->sync_download();
}
void GUI_App::app_version_check()
{
std::string version_check_url = app_config->version_check_url();
m_app_updater->sync_version(version_check_url);
}
} // GUI
} //Slic3r

View File

@ -32,6 +32,7 @@ class PresetUpdater;
class ModelObject;
class PrintHostJobQueue;
class Model;
class AppUpdater;
namespace GUI{
@ -81,7 +82,8 @@ enum ConfigMenuIDs {
ConfigMenuWizard,
ConfigMenuSnapshots,
ConfigMenuTakeSnapshot,
ConfigMenuUpdate,
ConfigMenuUpdateConf,
ConfigMenuUpdateApp,
ConfigMenuDesktopIntegration,
ConfigMenuPreferences,
ConfigMenuModeSimple,
@ -156,6 +158,7 @@ private:
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
std::unique_ptr <AppUpdater> m_app_updater;
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
std::string m_instance_hash_string;
size_t m_instance_hash_int;
@ -360,6 +363,11 @@ private:
// Returns true if the configuration is fine.
// Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring.
bool check_updates(const bool verbose);
void on_version_read(wxCommandEvent& evt);
// if the data from version file are already downloaded, shows dialogs to start download of new version of app
void app_updater(bool from_user);
// inititate read of version file online in separate thread
void app_version_check();
bool m_datadir_redefined { false };
};

View File

@ -819,6 +819,120 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu
imgui.text(text.c_str());
}
}
//------ProgressBarWithCancelNotification----------------
void NotificationManager::ProgressBarWithCancelNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
if (m_percentage < 0.f || m_percentage >= 1.f)
render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
else
render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
}
void NotificationManager::ProgressBarWithCancelNotification::render_close_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
button_text = ImGui::CloseNotifButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
true))
{
button_text = ImGui::CloseNotifHoverButton;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
close();
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
{
close();
}
ImGui::PopStyleColor(5);
}
void NotificationManager::ProgressBarWithCancelNotification::render_cancel_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
button_text = ImGui::CancelButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
true))
{
button_text = ImGui::CancelHoverButton;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
on_cancel_button();
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
{
on_cancel_button();
}
ImGui::PopStyleColor(5);
}
void NotificationManager::ProgressBarWithCancelNotification::on_cancel_button()
{
if (m_cancel_callback) {
if (m_cancel_callback()) {
close();
}
}
}
void NotificationManager::ProgressBarWithCancelNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
std::string text;
if (m_percentage < 0.f) {
text = _u8L("ERROR");
} else {
std::stringstream stream;
stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%";
text = stream.str();
}
ImGui::SetCursorPosX(m_left_indentation);
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4));
imgui.text(text.c_str());
}
//------PrintHostUploadNotification----------------
void NotificationManager::PrintHostUploadNotification::init()
{
@ -1653,6 +1767,32 @@ void NotificationManager::upload_job_notification_show_error(int id, const std::
}
}
void NotificationManager::push_download_progress_notification(const std::string& text, std::function<bool()> cancel_callback)
{
// If already exists, change text and reset progress
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::AppDownload) {
notification->update({ NotificationType::AppDownload, NotificationLevel::ProgressBarNotificationLevel, 10, text });
auto* pbwcn = dynamic_cast<ProgressBarWithCancelNotification*>(notification.get());
pbwcn->set_percentage(0.0f);
pbwcn->set_cancel_callback(cancel_callback);
return;
}
}
// push new one
NotificationData data{ NotificationType::AppDownload, NotificationLevel::ProgressBarNotificationLevel, 10, text };
push_notification_data(std::make_unique<NotificationManager::ProgressBarWithCancelNotification>(data, m_id_provider, m_evt_handler, cancel_callback), 0);
}
void NotificationManager::set_download_progress_percentage(float percentage)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::AppDownload) {
dynamic_cast<ProgressBarWithCancelNotification*>(notification.get())->set_percentage(percentage);
return;
}
}
}
void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {

View File

@ -78,6 +78,8 @@ enum class NotificationType
ProgressBar,
// Progress bar with info from Print Host Upload Queue dialog.
PrintHostUpload,
// Progress bar of download next version app.
AppDownload,
// Progress bar with cancel button, cannot be closed
// On end of slicing and G-code processing (the full G-code preview is available),
// contains a hyperlink to export the G-code to a removable media or hdd.
@ -203,6 +205,9 @@ public:
void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage);
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
// Download App progress
void push_download_progress_notification(const std::string& text, std::function<bool()> cancel_callback);
void set_download_progress_percentage(float percentage);
// slicing progress
void init_slicing_progress_notification(std::function<bool()> cancel_callback);
void set_slicing_progress_began();
@ -459,7 +464,34 @@ private:
};
class ProgressBarWithCancelNotification : public ProgressBarNotification
{
public:
ProgressBarWithCancelNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, std::function<bool()> cancel_callback)
: ProgressBarNotification(n, id_provider, evt_handler)
, m_cancel_callback(cancel_callback)
{
}
void set_percentage(float percent) override { m_percentage = percent; if(m_percentage >= 1.f) m_state = EState::FadingOut; else m_state = EState::NotFading; }
void set_cancel_callback(std::function<bool()> cancel_callback) { m_cancel_callback = cancel_callback; }
protected:
void render_close_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void render_close_button_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_cancel_button_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_bar(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void on_cancel_button();
std::function<bool()> m_cancel_callback;
long m_hover_time{ 0 };
};
class PrintHostUploadNotification : public ProgressBarNotification
{

View File

@ -88,6 +88,89 @@ bool MsgUpdateSlic3r::disable_version_check() const
return cbox->GetValue();
}
wxSize AppUpdateAvailableDialog::AUAD_size;
// AppUpdater
AppUpdateAvailableDialog::AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online)
: MsgDialog(nullptr, _(L("App Update available")), wxString::Format(_(L("New version of %s is available.\nDo you wish to download it?")), SLIC3R_APP_NAME))
{
auto* versions = new wxFlexGridSizer(1, 0, VERT_SPACING);
versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string()));
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
content_sizer->Add(versions);
content_sizer->AddSpacer(VERT_SPACING);
cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more")));
content_sizer->Add(cbox);
content_sizer->AddSpacer(VERT_SPACING);
AUAD_size = content_sizer->GetSize();
add_button(wxID_CANCEL);
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
btn_ok->SetLabel(_L("Next"));
}
finalize();
}
AppUpdateAvailableDialog::~AppUpdateAvailableDialog() {}
bool AppUpdateAvailableDialog::disable_version_check() const
{
return cbox->GetValue();
}
// AppUpdateDownloadDialog
AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online)
: MsgDialog(nullptr, _(L("App Update download")), wxString::Format(_(L("New version of %s is available.")), SLIC3R_APP_NAME))
{
auto* versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
content_sizer->Add(versions);
content_sizer->AddSpacer(VERT_SPACING);
#ifndef __linux__
cbox_run = new wxCheckBox(this, wxID_ANY, _(L("Run installer after download.")));
content_sizer->Add(cbox_run);
#endif
content_sizer->AddSpacer(VERT_SPACING);
cbox_path = new wxCheckBox(this, wxID_ANY, _(L("Select path for downloaded file.")));
content_sizer->Add(cbox_path);
content_sizer->AddSpacer(VERT_SPACING);
content_sizer->SetMinSize(AppUpdateAvailableDialog::AUAD_size);
add_button(wxID_CANCEL);
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
btn_ok->SetLabel(_L("Download"));
}
finalize();
}
AppUpdateDownloadDialog::~AppUpdateDownloadDialog() {}
bool AppUpdateDownloadDialog::run_after_download() const
{
#ifndef __linux__
return cbox_run->GetValue();
#endif
return false;
}
bool AppUpdateDownloadDialog::select_download_path() const
{
return cbox_path->GetValue();
}
// MsgUpdateConfig
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
@ -314,5 +397,25 @@ MsgNoUpdates::MsgNoUpdates() :
MsgNoUpdates::~MsgNoUpdates() {}
// MsgNoAppUpdates
MsgNoAppUpdates::MsgNoAppUpdates() :
MsgDialog(nullptr, _(L("App update")), _(L("No updates available")), wxICON_ERROR | wxOK)
{
auto* text = new wxStaticText(this, wxID_ANY, wxString::Format(
_(L(
"%s has no version updates available."
)),
SLIC3R_APP_NAME
));
text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
finalize();
}
MsgNoAppUpdates::~MsgNoAppUpdates() {}
}
}

View File

@ -37,6 +37,42 @@ private:
};
class AppUpdateAvailableDialog : public MsgDialog
{
public:
AppUpdateAvailableDialog(const Semver& ver_current, const Semver& ver_online);
AppUpdateAvailableDialog(AppUpdateAvailableDialog&&) = delete;
AppUpdateAvailableDialog(const AppUpdateAvailableDialog&) = delete;
AppUpdateAvailableDialog& operator=(AppUpdateAvailableDialog&&) = delete;
AppUpdateAvailableDialog& operator=(const AppUpdateAvailableDialog&) = delete;
virtual ~AppUpdateAvailableDialog();
// Tells whether the user checked the "don't bother me again" checkbox
bool disable_version_check() const;
static wxSize AUAD_size;
private:
wxCheckBox* cbox;
};
class AppUpdateDownloadDialog : public MsgDialog
{
public:
AppUpdateDownloadDialog(const Semver& ver_online);
AppUpdateDownloadDialog(AppUpdateDownloadDialog&&) = delete;
AppUpdateDownloadDialog(const AppUpdateDownloadDialog&) = delete;
AppUpdateDownloadDialog& operator=(AppUpdateDownloadDialog&&) = delete;
AppUpdateDownloadDialog& operator=(const AppUpdateDownloadDialog&) = delete;
virtual ~AppUpdateDownloadDialog();
// Tells whether the user checked the "don't bother me again" checkbox
bool run_after_download() const;
bool select_download_path() const;
private:
wxCheckBox* cbox_run;
wxCheckBox* cbox_path;
};
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
class MsgUpdateConfig : public MsgDialog
{
@ -129,6 +165,18 @@ public:
~MsgNoUpdates();
};
// Informs about absence of new version online.
class MsgNoAppUpdates : public MsgDialog
{
public:
MsgNoAppUpdates();
MsgNoAppUpdates(MsgNoAppUpdates&&) = delete;
MsgNoAppUpdates(const MsgNoAppUpdates&) = delete;
MsgNoAppUpdates& operator=(MsgNoUpdates&&) = delete;
MsgNoAppUpdates& operator=(const MsgNoAppUpdates&) = delete;
~MsgNoAppUpdates();
};
}
}

View File

@ -0,0 +1,694 @@
#include "AppUpdater.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <curl/curl.h>
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/Utils/Http.hpp"
#ifdef _WIN32
#include <shellapi.h>
#include <Shlobj_core.h>
#include <windows.h>
#include <KnownFolders.h>
#include <shlobj.h>
#endif // _WIN32
namespace Slic3r {
namespace {
#ifdef _WIN32
bool run_file(const boost::filesystem::path& path)
{
// find updater exe
if (boost::filesystem::exists(path)) {
// run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst
// Using quoted string as mentioned in CreateProcessW docs, silent execution parameter.
std::wstring wcmd = L"\"" + path.wstring();
// additional information
STARTUPINFOW si;
PROCESS_INFORMATION pi;
// set the size of the structures
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// start the program up
if (CreateProcessW(NULL, // the path
wcmd.data(), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
)) {
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
else {
BOOST_LOG_TRIVIAL(error) << "Failed to run " << wcmd;
}
}
return false;
}
std::string get_downloads_path()
{
std::string ret;
PWSTR path = NULL;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path);
if (SUCCEEDED(hr)) {
ret = boost::nowide::narrow(path);
}
CoTaskMemFree(path);
return ret;
}
bool open_folder(const boost::filesystem::path& path)
{
// this command can run the installer exe as well, but is it better than CreateProcessW?
ShellExecuteW(NULL, NULL, path.wstring().c_str(), NULL, NULL, SW_SHOWNORMAL);
return true;
}
#elif __linux__
bool run_file(const boost::filesystem::path& path)
{
return false;
}
std::string get_downloads_path()
{
wxString command = "xdg-user-dir DOWNLOAD";
wxArrayString output;
//Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
// because they may mess up the environment expected by the file manager.
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
if (wxGetEnv("APPIMAGE", nullptr)) {
// We're running from AppImage
wxEnvVariableHashMap env_vars;
wxGetEnvMap(&env_vars);
env_vars.erase("APPIMAGE");
env_vars.erase("APPDIR");
env_vars.erase("LD_LIBRARY_PATH");
env_vars.erase("LD_PRELOAD");
env_vars.erase("UNION_PRELOAD");
wxExecuteEnv exec_env;
exec_env.env = std::move(env_vars);
wxString owd;
if (wxGetEnv("OWD", &owd)) {
// This is the original work directory from which the AppImage image was run,
// set it as CWD for the child process:
exec_env.cwd = std::move(owd);
}
::wxExecute(command, output, 0, &exec_env);
} else {
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
::wxExecute(command, output);
}
if (output.GetCount() > 0) {
return boost::nowide::narrow(output[0]);
}
return std::string();
}
bool open_folder(const boost::filesystem::path& path)
{
if (boost::filesystem::is_directory(path)) {
const char *argv[] = { "xdg-open", path.string().c_str(), nullptr };
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
// because they may mess up the environment expected by the file manager.
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
if (wxGetEnv("APPIMAGE", nullptr)) {
// We're running from AppImage
wxEnvVariableHashMap env_vars;
wxGetEnvMap(&env_vars);
env_vars.erase("APPIMAGE");
env_vars.erase("APPDIR");
env_vars.erase("LD_LIBRARY_PATH");
env_vars.erase("LD_PRELOAD");
env_vars.erase("UNION_PRELOAD");
wxExecuteEnv exec_env;
exec_env.env = std::move(env_vars);
wxString owd;
if (wxGetEnv("OWD", &owd)) {
// This is the original work directory from which the AppImage image was run,
// set it as CWD for the child process:
exec_env.cwd = std::move(owd);
}
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
} else {
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
}
return true;
}
return false;
}
#elif __APPLE__
bool run_file(const boost::filesystem::path& path)
{
if (boost::filesystem::exists(path)) {
// attach downloaded dmg file
const char* argv1[] = { "hdiutil", "attach", path.string().c_str(), nullptr };
::wxExecute(const_cast<char**>(argv1), wxEXEC_ASYNC, nullptr);
// open inside attached as a folder in finder
const char* argv2[] = { "open", "/Volumes/PrusaSlicer", nullptr };
::wxExecute(const_cast<char**>(argv2), wxEXEC_ASYNC, nullptr);
return true;
}
return false;
}
std::string get_downloads_path()
{
// call objective-c implementation
return get_downloads_path_mac();
}
bool open_folder(const boost::filesystem::path& path)
{
if (boost::filesystem::is_directory(path)) {
const char* argv[] = { "open", path.string().c_str(), nullptr };
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
return true;
}
return false;
}
#endif
} // namespace
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_FAILED, wxCommandEvent);
// priv handles all operations in separate thread
// 1) download version file and parse it.
// 2) download new app file and open in folder / run it.
struct AppUpdater::priv {
priv();
// Download file. What happens with the data is specified in completefn.
bool http_get_file(const std::string& url
, size_t size_limit
, std::function<bool(Http::Progress)> progress_fn
, std::function<bool(std::string /*body*/, std::string& error_message)> completefn
, std::string& error_message
) const;
// Download installer / app
boost::filesystem::path download_file(const DownloadAppData& data) const;
// Run file in m_last_dest_path
bool run_downloaded_file(boost::filesystem::path path);
// gets version file via http
void version_check(const std::string& version_check_url);
#if 0
// parsing of Prusaslicer.version2
void parse_version_string_old(const std::string& body) const;
#endif
// parses ini tree of version file, saves to m_online_version_data and queue event(s) to UI
void parse_version_string(const std::string& body);
// thread
std::thread m_thread;
std::atomic_bool m_cancel;
std::mutex m_data_mutex;
// read only variable used to init m_online_version_data.target_path
boost::filesystem::path m_default_dest_folder; // readonly
// DownloadAppData read / write needs to be locked by m_data_mutex
DownloadAppData m_online_version_data;
DownloadAppData get_app_data();
void set_app_data(DownloadAppData data);
};
AppUpdater::priv::priv() :
m_cancel (false)
#ifdef __linux__
, m_default_dest_folder (boost::filesystem::path("/tmp"))
#else
, m_default_dest_folder (boost::filesystem::path(data_dir()) / "cache")
#endif //_WIN32
{
boost::filesystem::path downloads_path = boost::filesystem::path(get_downloads_path());
if (!downloads_path.empty()) {
m_default_dest_folder = std::move(downloads_path);
}
BOOST_LOG_TRIVIAL(error) << "Default download path: " << m_default_dest_folder;
}
bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit, std::function<bool(Http::Progress)> progress_fn, std::function<bool(std::string /*body*/, std::string& error_message)> complete_fn, std::string& error_message) const
{
bool res = false;
Http::get(url)
.size_limit(size_limit)
.on_progress([&, progress_fn](Http::Progress progress, bool& cancel) {
// progress function returns true as success (to continue)
cancel = (this->m_cancel ? true : !progress_fn(std::move(progress)));
if (cancel) {
error_message = GUI::format("Error getting: `%1%`: Download was canceled.",
url);
BOOST_LOG_TRIVIAL(debug) << "AppUpdater::priv::http_get_file message: "<< error_message;
}
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
error_message = GUI::format("Error getting: `%1%`: HTTP %2%, %3%",
url,
http_status,
error);
BOOST_LOG_TRIVIAL(error) << error_message;
})
.on_complete([&](std::string body, unsigned /* http_status */) {
assert(complete_fn != nullptr);
res = complete_fn(body, error_message);
})
.perform_sync();
return res;
}
boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& data) const
{
boost::filesystem::path dest_path;
size_t last_gui_progress = 0;
size_t expected_size = data.size;
dest_path = data.target_path;
assert(!dest_path.empty());
if (dest_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "Download from " << data.url << " could not start. Destination path is empty.";
return boost::filesystem::path();
}
std::string error_message;
bool res = http_get_file(data.url, 80 * 1024 * 1024 //TODO: what value here
// on_progress
, [&last_gui_progress, expected_size](Http::Progress progress) {
// size check
if (progress.dltotal > 0 && progress.dltotal > expected_size) {
std::string message = GUI::format("Downloading new %1% has failed. The file has incorrect file size. Aborting download.\nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal);
BOOST_LOG_TRIVIAL(error) << message;
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
evt->SetString(message);
GUI::wxGetApp().QueueEvent(evt);
return false;
} else if (progress.dltotal > 0 && progress.dltotal < expected_size) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Downloading new %1% has incorrect size. The download will continue. \nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal);;
}
// progress event
size_t gui_progress = progress.dltotal > 0 ? 100 * progress.dlnow / progress.dltotal : 0;
//BOOST_LOG_TRIVIAL(error) << "App download " << gui_progress << "% " << progress.dlnow << " of " << progress.dltotal;
if (last_gui_progress < gui_progress && (last_gui_progress != 0 || gui_progress != 100)) {
last_gui_progress = gui_progress;
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS);
evt->SetString(GUI::from_u8(std::to_string(gui_progress)));
GUI::wxGetApp().QueueEvent(evt);
}
return true;
}
// on_complete
, [dest_path, expected_size](std::string body, std::string& error_message){
// Size check. Does always 1 char == 1 byte?
size_t body_size = body.size();
if (body_size != expected_size) {
BOOST_LOG_TRIVIAL(error) << "Downloaded file has wrong size. Expected size: " << expected_size << " Downloaded size: " << body_size;
return false;
}
boost::filesystem::path tmp_path = dest_path;
tmp_path += format(".%1%%2%", get_current_pid(), ".download");
try
{
boost::filesystem::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.close();
boost::filesystem::rename(tmp_path, dest_path);
}
catch (const std::exception&)
{
BOOST_LOG_TRIVIAL(error) << "Failed to write and move " << tmp_path << " to " << dest_path;
return false;
}
return true;
}
, error_message
);
if (!res)
{
if (this->m_cancel)
{
BOOST_LOG_TRIVIAL(error) << error_message;
} else {
std::string message = GUI::format("Downloading new %1% has failed:\n%2%", SLIC3R_APP_NAME, error_message);
BOOST_LOG_TRIVIAL(error) << message;
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
evt->SetString(message);
GUI::wxGetApp().QueueEvent(evt);
}
return boost::filesystem::path();
}
return dest_path;
}
bool AppUpdater::priv::run_downloaded_file(boost::filesystem::path path)
{
assert(!path.empty());
bool res = run_file(path);
BOOST_LOG_TRIVIAL(error) << "Running "<< path.string() << " was " << res;
return res;
}
void AppUpdater::priv::version_check(const std::string& version_check_url)
{
assert(!version_check_url.empty());
std::string error_message;
bool res = http_get_file(version_check_url, 1024
// on_progress
, [](Http::Progress progress) { return true; }
// on_complete
, [&](std::string body, std::string& error_message) {
boost::trim(body);
parse_version_string(body);
return true;
}
, error_message
);
if (!res)
BOOST_LOG_TRIVIAL(error) << "Failed to download version file: " << error_message;
}
void AppUpdater::priv::parse_version_string(const std::string& body)
{
size_t start = body.find('[');
if (start == std::string::npos) {
#if 0
BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Starting old parsing.";
parse_version_string_old(body);
return;
#endif // 0
BOOST_LOG_TRIVIAL(error) << "Could not find property tree in version file. Checking for application update has failed.";
return;
}
std::string tree_string = body.substr(start);
boost::property_tree::ptree tree;
std::stringstream ss(tree_string);
try {
boost::property_tree::read_ini(ss, tree);
} catch (const boost::property_tree::ini_parser::ini_parser_error& err) {
//throw Slic3r::RuntimeError(format("Failed reading version file property tree Error: \"%1%\" at line %2%. \nTree:\n%3%", err.message(), err.line(), tree_string).c_str());
BOOST_LOG_TRIVIAL(error) << format("Failed reading version file property tree Error: \"%1%\" at line %2%. \nTree:\n%3%", err.message(), err.line(), tree_string);
return;
}
DownloadAppData new_data;
for (const auto& section : tree) {
std::string section_name = section.first;
// online release version info
if (section_name ==
#ifdef _WIN32
"release:win64"
#elif __linux__
"release:linux"
#else
"release:osx"
#endif
) {
for (const auto& data : section.second) {
if (data.first == "url") {
new_data.url = data.second.data();
new_data.target_path = m_default_dest_folder / AppUpdater::get_filename_from_url(new_data.url);
BOOST_LOG_TRIVIAL(error) << format("parsing version string: url: %1%", new_data.url);
} else if (data.first == "size"){
new_data.size = std::stoi(data.second.data());
BOOST_LOG_TRIVIAL(error) << format("parsing version string: expected size: %1%", new_data.size);
}
}
}
// released versions - to be send to UI layer
if (section_name == "common") {
std::vector<std::string> prerelease_versions;
for (const auto& data : section.second) {
// release version - save and send to UI layer
if (data.first == "release") {
std::string version = data.second.data();
boost::optional<Semver> release_version = Semver::parse(version);
if (!release_version) {
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from version file: Not a correct semver: `%1%`", version);
return;
}
new_data.version = release_version;
// Send after all data is read
/*
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
*/
// prerelease versions - write down to be sorted and send to UI layer
} else if (data.first == "alpha") {
prerelease_versions.emplace_back(data.second.data());
} else if (data.first == "beta") {
prerelease_versions.emplace_back(data.second.data());
} else if (data.first == "rc") {
prerelease_versions.emplace_back(data.second.data());
}
}
// find recent version that is newer than last full release.
boost::optional<Semver> recent_version;
std::string version_string;
for (const std::string& ver_string : prerelease_versions) {
boost::optional<Semver> ver = Semver::parse(ver_string);
if (ver && *new_data.version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
recent_version = ver;
version_string = ver_string;
}
}
// send prerelease version to UI layer
if (recent_version) {
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version_string);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version_string));
GUI::wxGetApp().QueueEvent(evt);
}
}
}
assert(!new_data.url.empty());
assert(new_data.version);
// save
set_app_data(new_data);
// send
std::string version = new_data.version.get().to_string();
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
#if 0
void AppUpdater::priv::parse_version_string_old(const std::string& body) const
{
// release version
std::string version;
const auto first_nl_pos = body.find_first_of("\n\r");
if (first_nl_pos != std::string::npos)
version = body.substr(0, first_nl_pos);
else
version = body;
boost::optional<Semver> release_version = Semver::parse(version);
if (!release_version) {
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
// alpha / beta version
std::vector<std::string> prerelease_versions;
size_t nexn_nl_pos = first_nl_pos;
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
const auto last_nl_pos = nexn_nl_pos;
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
std::string line;
if (nexn_nl_pos == std::string::npos)
line = body.substr(last_nl_pos + 1);
else
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
// alpha
if (line.substr(0, 6) == "alpha=") {
version = line.substr(6);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
prerelease_versions.emplace_back(version);
// beta
}
else if (line.substr(0, 5) == "beta=") {
version = line.substr(5);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
prerelease_versions.emplace_back(version);
}
}
// find recent version that is newer than last full release.
boost::optional<Semver> recent_version;
for (const std::string& ver_string : prerelease_versions) {
boost::optional<Semver> ver = Semver::parse(ver_string);
if (ver && *release_version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
recent_version = ver;
version = ver_string;
}
}
if (recent_version) {
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
}
#endif // 0
DownloadAppData AppUpdater::priv::get_app_data()
{
const std::lock_guard<std::mutex> lock(m_data_mutex);
DownloadAppData ret_val(m_online_version_data);
return ret_val;
}
void AppUpdater::priv::set_app_data(DownloadAppData data)
{
const std::lock_guard<std::mutex> lock(m_data_mutex);
m_online_version_data = data;
}
AppUpdater::AppUpdater()
:p(new priv())
{
}
AppUpdater::~AppUpdater()
{
if (p && p->m_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_thread.join();
}
}
void AppUpdater::sync_download()
{
assert(p);
// join thread first - it could have been in sync_version
if (p->m_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_thread.join();
}
p->m_cancel = false;
DownloadAppData input_data = p->get_app_data();
assert(!input_data.url.empty());
p->m_thread = std::thread(
[this, input_data]() {
if (boost::filesystem::path dest_path = p->download_file(input_data); boost::filesystem::exists(dest_path)){
if (input_data.start_after) {
p->run_downloaded_file(std::move(dest_path));
} else {
open_folder(dest_path.parent_path());
}
}
});
}
void AppUpdater::sync_version(const std::string& version_check_url)
{
assert(p);
// join thread first - it could have been in sync_download
if (p->m_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_thread.join();
}
p->m_cancel = false;
p->m_thread = std::thread(
[this, version_check_url]() {
p->version_check(version_check_url);
});
}
void AppUpdater::cancel()
{
p->m_cancel = true;
}
bool AppUpdater::cancel_callback()
{
cancel();
return true;
}
std::string AppUpdater::get_default_dest_folder()
{
return p->m_default_dest_folder.string();
}
std::string AppUpdater::get_filename_from_url(const std::string& url)
{
size_t slash = url.rfind('/');
return (slash != std::string::npos ? url.substr(slash + 1) : url);
}
std::string AppUpdater::get_file_extension_from_url(const std::string& url)
{
size_t dot = url.rfind('.');
return (dot != std::string::npos ? url.substr(dot) : url);
}
void AppUpdater::set_app_data(DownloadAppData data)
{
p->set_app_data(std::move(data));
}
DownloadAppData AppUpdater::get_app_data()
{
return p->get_app_data();
}
} //namespace Slic3r

View File

@ -0,0 +1,62 @@
#ifndef slic3r_AppUpdate_hpp_
#define slic3r_AppUpdate_hpp_
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include "libslic3r/Utils.hpp"
#include "wx/event.h"
//class boost::filesystem::path;
namespace Slic3r {
#ifdef __APPLE__
// implmented at MacUtils.mm
std::string get_downloads_path_mac();
#endif //__APPLE__
struct DownloadAppData
{
std::string url;
bool start_after;
boost::optional<Semver> version;
size_t size;
boost::filesystem::path target_path;
};
class AppUpdater
{
public:
AppUpdater();
~AppUpdater();
AppUpdater(AppUpdater&&) = delete;
AppUpdater(const AppUpdater&) = delete;
AppUpdater& operator=(AppUpdater&&) = delete;
AppUpdater& operator=(const AppUpdater&) = delete;
// downloads app file
void sync_download();
// downloads version file
void sync_version(const std::string& version_check_url);
void cancel();
bool cancel_callback();
std::string get_default_dest_folder();
static std::string get_filename_from_url(const std::string& url);
static std::string get_file_extension_from_url(const std::string& url);
// mutex access
void set_app_data(DownloadAppData data);
DownloadAppData get_app_data();
private:
struct priv;
std::unique_ptr<priv> p;
};
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_APP_DOWNLOAD_FAILED, wxCommandEvent);
} //namespace Slic3r
#endif

View File

@ -207,7 +207,6 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
auto self = static_cast<priv*>(userp);
const char *cdata = static_cast<char*>(data);
const size_t realsize = size * nmemb;
const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
if (self->buffer.size() + realsize > limit) {
// This makes curl_easy_perform return CURLE_WRITE_ERROR

View File

@ -0,0 +1,18 @@
#import "AppUpdater.hpp"
#import <Foundation/Foundation.h>
namespace Slic3r {
// AppUpdater.hpp
std::string get_downloads_path_mac()
{
// 1)
NSArray * paths = NSSearchPathForDirectoriesInDomains (NSDownloadsDirectory, NSUserDomainMask, YES);
NSString * desktopPath = [paths objectAtIndex:0];
return std::string([desktopPath UTF8String]);
// 2)
//[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"]];
//return std::string();
}
}

View File

@ -136,8 +136,8 @@ struct Updates
};
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
//wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
//wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
struct PresetUpdater::priv
{
@ -162,8 +162,8 @@ struct PresetUpdater::priv
void set_download_prefs(AppConfig *app_config);
bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const;
void sync_version() const;
void parse_version_string(const std::string& body) const;
// void sync_version() const;
// void parse_version_string(const std::string& body) const;
void sync_config(const VendorMap vendors);
void check_install_indices() const;
@ -238,6 +238,8 @@ void PresetUpdater::priv::prune_tmps() const
}
}
// moved to app updater
/*
// Get Slic3rPE version available online, save in AppConfig.
void PresetUpdater::priv::sync_version() const
{
@ -257,7 +259,7 @@ void PresetUpdater::priv::sync_version() const
http_status,
error);
})
.on_complete([&](std::string body, unsigned /* http_status */) {
.on_complete([&](std::string body, unsigned ) {
boost::trim(body);
parse_version_string(body);
})
@ -268,6 +270,7 @@ void PresetUpdater::priv::sync_version() const
// Version string must contain release version on first line. Follows non-mandatory alpha / beta releases on following lines (alpha=2.0.0-alpha1).
void PresetUpdater::priv::parse_version_string(const std::string& body) const
{
// release version
std::string version;
const auto first_nl_pos = body.find_first_of("\n\r");
@ -331,8 +334,9 @@ void PresetUpdater::priv::parse_version_string(const std::string& body) const
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
}
}
*/
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache.
void PresetUpdater::priv::sync_config(const VendorMap vendors)
@ -743,7 +747,7 @@ void PresetUpdater::sync(PresetBundle *preset_bundle)
p->thread = std::thread([this, vendors]() {
this->p->prune_tmps();
this->p->sync_version();
// this->p->sync_version();
this->p->sync_config(std::move(vendors));
});
}

View File

@ -65,7 +65,7 @@ private:
std::unique_ptr<priv> p;
};
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
//wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
//wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
}
#endif