Pause / continue download

This commit is contained in:
David Kocik 2022-06-01 11:30:01 +02:00
parent 648e56d8bf
commit 16b0654a6a
8 changed files with 277 additions and 56 deletions

View File

@ -32,4 +32,16 @@ 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();
}
}

View File

@ -9,12 +9,12 @@ namespace Downloader {
enum DownloadState
{
DownloadPending,
DownloadPending = 0,
DownloadOngoing,
DownloadStopped,
DownloadDone,
DownloadError,
DownloadPaused
};
class Download {
@ -22,7 +22,8 @@ public:
Download(int ID, std::string url, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
void start();
void cancel();
// void pause();
void pause();
void resume();
int get_id() const { return m_id; }
boost::filesystem::path get_final_path() const { return m_final_path; }

View File

@ -181,6 +181,15 @@ DownloadFrame::DownloadFrame(const wxString& title, const wxPoint& pos, const wx
btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_cancel_button(evt); });
btn_sizer->Add(btn_cancel, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
wxButton* btn_pause = new wxButton(this, wxID_EDIT, "Pause");
btn_pause->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_pause_button(evt); });
btn_sizer->Add(btn_pause, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
wxButton* btn_resume = new wxButton(this, wxID_EDIT, "Resume");
btn_resume->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_resume_button(evt); });
btn_sizer->Add(btn_resume, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
//main_sizer->Add(data_sizer);
//main_sizer->Add(btn_sizer);
@ -245,25 +254,53 @@ void DownloadFrame::log(const wxString& msg)
void DownloadFrame::on_progress(wxCommandEvent& event)
{
//log(std::to_string(event.GetInt()) + ": " + event.GetString());
m_dataview->SetValue(std::stoi(boost::nowide::narrow(event.GetString())), event.GetInt() - 1, 2);
m_dataview->SetValue("Downloading", event.GetInt() -1, 3);
for (size_t i = 0; i < m_dataview->GetItemCount(); i++) {
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(i, 0)));
if (id == event.GetInt()) {
if (!is_in_state(id, DownloadState::DownloadOngoing))
return;
m_dataview->SetValue(std::stoi(boost::nowide::narrow(event.GetString())), i, 2);
m_dataview->SetValue("Downloading", i, 3);
return;
}
}
}
void DownloadFrame::on_error(wxCommandEvent& event)
{
set_download_state(event.GetInt(), DownloadState::DownloadError);
m_dataview->SetValue("Error", event.GetInt() - 1, 3);
for (size_t i = 0; i < m_dataview->GetItemCount(); i++) {
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(i, 0)));
if (id == event.GetInt()) {
m_dataview->SetValue("Error - " + event.GetString(), i, 3);
return;
}
}
}
void DownloadFrame::on_complete(wxCommandEvent& event)
{
set_download_state(event.GetInt(), DownloadState::DownloadDone);
m_dataview->SetValue("Done", event.GetInt() - 1, 3);
for (size_t i = 0; i < m_dataview->GetItemCount(); i++) {
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(i, 0)));
if (id == event.GetInt()) {
m_dataview->SetValue("Done", i, 3);
return;
}
}
start_next();
}
void DownloadFrame::on_name_change(wxCommandEvent& event)
{
m_dataview->SetValue(event.GetString(), event.GetInt() - 1, 1);
for (size_t i = 0; i < m_dataview->GetItemCount(); i++) {
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(i, 0)));
if (id == event.GetInt()) {
m_dataview->SetValue(event.GetString(), i, 1);
return;
}
}
}
void DownloadFrame::start_next()
{
@ -313,7 +350,29 @@ void DownloadFrame::on_cancel_button(wxCommandEvent& event)
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(selected, 0)));
if(cancel_download(id))
m_dataview->SetValue("Canceled", id - 1, 3);
m_dataview->SetValue("Canceled", selected, 3);
if (delete_download(id))
m_dataview->DeleteItem(selected);
}
void DownloadFrame::on_pause_button(wxCommandEvent& event)
{
int selected = m_dataview->GetSelectedRow();
if (selected == wxNOT_FOUND) { return; }
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(selected, 0)));
if (pause_download(id))
m_dataview->SetValue("Paused", selected, 3);
}
void DownloadFrame::on_resume_button(wxCommandEvent& event)
{
int selected = m_dataview->GetSelectedRow();
if (selected == wxNOT_FOUND) { return; }
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(selected, 0)));
if (resume_download(id))
m_dataview->SetValue("Paused", selected, 3);
}
void DownloadFrame::set_download_state(int id, DownloadState state)
@ -326,7 +385,15 @@ void DownloadFrame::set_download_state(int id, DownloadState state)
}
}
bool DownloadFrame::is_in_state(int id, DownloadState state) const
void DownloadFrame::update_state_labels()
{
for (size_t i = 0; i < m_dataview->GetItemCount(); i++) {
int id = std::stoi(boost::nowide::narrow(m_dataview->GetTextValue(i, 0)));
m_dataview->SetValue(c_state_labels.at(get_download_state(id)), i, 1);
}
}
DownloadState DownloadFrame::get_download_state(int id) const
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
@ -335,6 +402,15 @@ bool DownloadFrame::is_in_state(int id, DownloadState state) const
}
}
bool DownloadFrame::is_in_state(int id, DownloadState state) const
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
return m_downloads[i]->get_state() == state;
}
}
}
bool DownloadFrame::cancel_download(int id)
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
@ -349,6 +425,45 @@ bool DownloadFrame::cancel_download(int id)
return false;
}
bool DownloadFrame::pause_download(int id)
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
if (m_downloads[i]->get_state() == DownloadState::DownloadOngoing) {
m_downloads[i]->pause();
return true;
}
return false;
}
}
return false;
}
bool DownloadFrame::resume_download(int id)
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
if (m_downloads[i]->get_state() == DownloadState::DownloadPaused) {
m_downloads[i]->resume();
return true;
}
return false;
}
}
return false;
}
bool DownloadFrame::delete_download(int id)
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
m_downloads.erase(m_downloads.begin() + i);
return true;
}
}
return false;
}
wxString DownloadFrame::get_path_of(int id) const
{
for (size_t i = 0; i < m_downloads.size(); ++i) {

View File

@ -4,6 +4,7 @@
#include "InstanceSend.hpp"
#include "Download.hpp"
#include <vector>
#include <map>
#include <wx/wx.h>
#include <wx/dataview.h>
@ -29,12 +30,18 @@ private:
void on_open_in_new_slicer(wxCommandEvent& event);
void on_open_in_explorer(wxCommandEvent& event);
void on_cancel_button(wxCommandEvent& event);
void on_pause_button(wxCommandEvent& event);
void on_resume_button(wxCommandEvent& event);
void update_state_labels();
void start_next();
void set_download_state(int id, DownloadState state);
bool is_in_state(int id, DownloadState state) const;
DownloadState get_download_state(int id) const;
bool cancel_download(int id);
bool pause_download(int id);
bool resume_download(int id);
bool delete_download(int id);
wxString get_path_of(int id) const;
wxString get_folder_path_of(int id) const;
@ -55,6 +62,19 @@ private:
boost::filesystem::path m_dest_folder;
std::vector<std::unique_ptr<Download>> m_downloads;
/* DownloadPending = 0,
DownloadOngoing,
DownloadStopped,
DownloadDone,
DownloadError,
DownloadPaused*/
const std::map<DownloadState, wxString> c_state_labels = {
{DownloadPending, "Pending"},
{DownloadStopped, "Canceled"},
{DownloadDone, "Done"},
{DownloadError, "Error"},
{DownloadPaused, "Paused"},
};
};
class DownloadApp : public wxApp

View File

@ -55,7 +55,10 @@ struct FileGet::priv
std::thread m_io_thread;
wxEvtHandler* m_evt_handler;
boost::filesystem::path m_dest_folder;
std::atomic_bool m_cancel = false;
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();
@ -79,68 +82,91 @@ void FileGet::priv::get_perform()
assert(boost::filesystem::is_directory(m_dest_folder));
// open dest file
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")))
if (m_written == 0)
{
++version;
final_filename = just_filename + "(" + std::to_string(version) + ")";
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);
}
m_filename = final_filename + extension;
boost::filesystem::path tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
dest_path = m_dest_folder / m_filename;
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;
FILE* file;
// open file for writting
FILE* file = fopen(tmp_path.string().c_str(), "wb");
size_t written = 0;
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(tmp_path.string().c_str());
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)
/*if (progress.dlnow == 0 && m_written == 0) {
evt->SetString("0");
else {
if (progress.dlnow - written > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
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, progress.dlnow);
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&)
catch (const std::exception& e)
{
// fclose(file); do it?
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_ERROR);
evt->SetString("Failed to write progress.");
evt->SetString(e.what());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
cancel = true;
return;
}
written = progress.dlnow;
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);
}
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
fclose(file);
@ -158,14 +184,16 @@ void FileGet::priv::get_perform()
//}
try
{
if (written < body.size())
/*
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(written);
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(tmp_path, dest_path);
boost::filesystem::rename(m_tmp_path, dest_path);
}
catch (const std::exception& e)
{
@ -196,18 +224,25 @@ FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}
FileGet::~FileGet()
{
if (p && p->m_io_thread.joinable()) {
p->m_io_thread.detach();
p->m_cancel = true;
p->m_io_thread.join();
}
}
void FileGet::get()
{
if (p) {
auto io_thread = std::thread([&priv = p]() {
priv->get_perform();
});
p->m_io_thread = std::move(io_thread);
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()
@ -217,4 +252,26 @@ void FileGet::cancel()
}
}
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();
});
}
}

View File

@ -20,6 +20,8 @@ public:
void get();
void cancel();
void pause();
void resume();
static std::string escape_url(const std::string& url);
private:
std::unique_ptr<priv> p;

View File

@ -144,6 +144,7 @@ struct Http::priv
void set_post_body(const fs::path &path);
void set_post_body(const std::string &body);
void set_put_body(const fs::path &path);
void set_range(const std::string& range);
std::string curl_error(CURLcode curlcode);
std::string body_size_error();
@ -313,6 +314,11 @@ void Http::priv::set_put_body(const fs::path &path)
}
}
void Http::priv::set_range(const std::string& range)
{
::curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
}
std::string Http::priv::curl_error(CURLcode curlcode)
{
return (boost::format("%1%:\n%2%\n[Error %3%]")
@ -438,6 +444,12 @@ Http& Http::size_limit(size_t sizeLimit)
return *this;
}
Http& Http::set_range(const std::string& range)
{
if (p) { p->set_range(range); }
return *this;
}
Http& Http::header(std::string name, const std::string &value)
{
if (!p) { return * this; }

View File

@ -67,6 +67,8 @@ public:
// Sets a maximum size of the data that can be received.
// A value of zero sets the default limit, which is is 5MB.
Http& size_limit(size_t sizeLimit);
// range of donloaded bytes. example: curl_easy_setopt(curl, CURLOPT_RANGE, "0-199");
Http& set_range(const std::string& range);
// Sets a HTTP header field.
Http& header(std::string name, const std::string &value);
// Removes a header field.