| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 | /* <!-- copyright *//* * aria2 - The high speed download utility * * Copyright (C) 2013 Tatsuhiro Tsujikawa * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL.  If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so.  If you * do not wish to do so, delete this exception statement from your * version.  If you delete this exception statement from all source * files in the program, then also delete it here. *//* copyright --> *///// Multi-threaded GUI program example for libaria2.  The downloads can// be added using Download -> Add URI menu. The progress is shown in// the main window.//// Compile and link like this:// $ g++ -O2 -Wall -g -std=c++11 `wx-config --cflags` -o libaria2wx// libaria2wx.cc `wx-config --libs` -laria2 -pthread#include <iostream>#include <chrono>#include <thread>#include <mutex>#include <queue>#include <wx/wx.h>#include <aria2/aria2.h>// Interface to send message to downloader thread from UI threadstruct Job {  virtual ~Job(){};  virtual void execute(aria2::Session* session) = 0;};class MainFrame;// Interface to report back to UI thread from downloader threadstruct Notification {  virtual ~Notification(){};  virtual void notify(MainFrame* frame) = 0;};// std::queue<T> wrapper synchronized by mutex. In this example// program, only one thread consumes from the queue, so separating// empty() and pop() is not a problem.template <typename T> class SynchronizedQueue {public:  SynchronizedQueue() {}  ~SynchronizedQueue() {}  void push(std::unique_ptr<T>&& t)  {    std::lock_guard<std::mutex> l(m_);    q_.push(std::move(t));  }  std::unique_ptr<T> pop()  {    std::lock_guard<std::mutex> l(m_);    std::unique_ptr<T> t = std::move(q_.front());    q_.pop();    return t;  }  bool empty()  {    std::lock_guard<std::mutex> l(m_);    return q_.empty();  }private:  std::queue<std::unique_ptr<T>> q_;  std::mutex m_;};typedef SynchronizedQueue<Job> JobQueue;typedef SynchronizedQueue<Notification> NotifyQueue;// Job to shutdown downloader threadstruct ShutdownJob : public Job {  ShutdownJob(bool force) : force(force) {}  virtual void execute(aria2::Session* session)  {    aria2::shutdown(session, force);  }  bool force;};// Job to send URI to download and options to downloader threadstruct AddUriJob : public Job {  AddUriJob(std::vector<std::string>&& uris, aria2::KeyVals&& options)      : uris(uris), options(options)  {  }  virtual void execute(aria2::Session* session)  {    // TODO check return value    aria2::addUri(session, 0, uris, options);  }  std::vector<std::string> uris;  aria2::KeyVals options;};int downloaderJob(JobQueue& jobq, NotifyQueue& notifyq);// This struct is used to report download progress for active// downloads from downloader thread to UI thread.struct DownloadStatus {  aria2::A2Gid gid;  int64_t totalLength;  int64_t completedLength;  int downloadSpeed;  int uploadSpeed;  std::string filename;};class Aria2App : public wxApp {public:  virtual bool OnInit();  virtual int OnExit();};class MainFrame : public wxFrame {public:  MainFrame(const wxString& title);  void OnQuit(wxCommandEvent& event);  void OnAbout(wxCommandEvent& event);  void OnCloseWindow(wxCloseEvent& event);  void OnTimer(wxTimerEvent& event);  void OnAddUri(wxCommandEvent& event);  void UpdateActiveStatus(const std::vector<DownloadStatus>& v);private:  wxTextCtrl* text_;  wxTimer timer_;  JobQueue jobq_;  NotifyQueue notifyq_;  std::thread downloaderThread_;  DECLARE_EVENT_TABLE()};enum { TIMER_ID = 1 };enum { MI_ADD_URI = 1 };BEGIN_EVENT_TABLE(MainFrame, wxFrame)EVT_CLOSE(MainFrame::OnCloseWindow)EVT_TIMER(TIMER_ID, MainFrame::OnTimer)EVT_MENU(MI_ADD_URI, MainFrame::OnAddUri)END_EVENT_TABLE()class AddUriDialog : public wxDialog {public:  AddUriDialog(wxWindow* parent);  void OnButton(wxCommandEvent& event);  wxString GetUri();  wxString GetOption();private:  wxTextCtrl* uriText_;  wxTextCtrl* optionText_;  wxButton* okBtn_;  wxButton* cancelBtn_;  DECLARE_EVENT_TABLE()};BEGIN_EVENT_TABLE(AddUriDialog, wxDialog)EVT_BUTTON(wxID_ANY, AddUriDialog::OnButton)END_EVENT_TABLE()IMPLEMENT_APP(Aria2App)bool Aria2App::OnInit(){  if (!wxApp::OnInit())    return false;  aria2::libraryInit();  MainFrame* frame = new MainFrame(wxT("libaria2 GUI example"));  frame->Show(true);  return true;}int Aria2App::OnExit(){  aria2::libraryDeinit();  return wxApp::OnExit();}MainFrame::MainFrame(const wxString& title)    : wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(640, 400)),      timer_(this, TIMER_ID),      downloaderThread_(downloaderJob, std::ref(jobq_), std::ref(notifyq_)){  wxMenu* downloadMenu = new wxMenu;  downloadMenu->Append(MI_ADD_URI, wxT("&Add URI"), wxT("Add URI to download"));  wxMenuBar* menuBar = new wxMenuBar();  menuBar->Append(downloadMenu, wxT("&Download"));  SetMenuBar(menuBar);  // Show active downloads in textual manner  wxPanel* panel = new wxPanel(this, wxID_ANY);  wxBoxSizer* box = new wxBoxSizer(wxVERTICAL);  box->Add(new wxStaticText(panel, wxID_ANY, wxT("Active Download(s)")));  text_ = new wxTextCtrl(panel, wxID_ANY, wxT(""), wxDefaultPosition,                         wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);  box->Add(text_, wxSizerFlags().Expand().Proportion(1));  panel->SetSizer(box);  // Finally start time here  timer_.Start(900);}void MainFrame::OnAddUri(wxCommandEvent& WXUNUSED(event)){  AddUriDialog dlg(this);  int ret = dlg.ShowModal();  if (ret == 0) {    if (dlg.GetUri().IsEmpty()) {      return;    }    std::vector<std::string> uris = {std::string(dlg.GetUri().mb_str())};    std::string optstr(dlg.GetOption().mb_str());    aria2::KeyVals options;    int keyfirst = 0;    for (int i = 0; i < (int)optstr.size(); ++i) {      if (optstr[i] == '\n') {        keyfirst = i + 1;      }      else if (optstr[i] == '=') {        int j;        for (j = i + 1; j < (int)optstr.size(); ++j) {          if (optstr[j] == '\n') {            break;          }        }        if (i - keyfirst > 0) {          options.push_back(              std::make_pair(optstr.substr(keyfirst, i - keyfirst),                             optstr.substr(i + 1, j - i - 1)));        }        keyfirst = j + 1;        i = j;      }    }    jobq_.push(std::unique_ptr<Job>(        new AddUriJob(std::move(uris), std::move(options))));  }}void MainFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event)){  // On exit, we have to shutdown downloader thread and wait for it to  // join. This is needed to execute graceful shutdown sequence of  // aria2 session.  jobq_.push(std::unique_ptr<Job>(new ShutdownJob(true)));  downloaderThread_.join();  Destroy();}void MainFrame::OnTimer(wxTimerEvent& event){  while (!notifyq_.empty()) {    std::unique_ptr<Notification> nt = notifyq_.pop();    nt->notify(this);  }}template <typename T> std::string abbrevsize(T size){  if (size >= 1024 * 1024 * 1024) {    return std::to_string(size / 1024 / 1024 / 1024) + "G";  }  else if (size >= 1024 * 1024) {    return std::to_string(size / 1024 / 1024) + "M";  }  else if (size >= 1024) {    return std::to_string(size / 1024) + "K";  }  else {    return std::to_string(size);  }}wxString towxs(const std::string& s) { return wxString(s.c_str(), wxConvUTF8); }void MainFrame::UpdateActiveStatus(const std::vector<DownloadStatus>& v){  text_->Clear();  for (auto& a : v) {    *text_ << wxT("[") << towxs(aria2::gidToHex(a.gid)) << wxT("] ")           << towxs(abbrevsize(a.completedLength)) << wxT("/")           << towxs(abbrevsize(a.totalLength)) << wxT("(")           << (a.totalLength != 0 ? a.completedLength * 100 / a.totalLength : 0)           << wxT("%)") << wxT(" D:") << towxs(abbrevsize(a.downloadSpeed))           << wxT(" U:") << towxs(abbrevsize(a.uploadSpeed)) << wxT("\n")           << wxT("File:") << towxs(a.filename) << wxT("\n");  }}AddUriDialog::AddUriDialog(wxWindow* parent)    : wxDialog(parent, wxID_ANY, wxT("Add URI"), wxDefaultPosition,               wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){  wxPanel* panel = new wxPanel(this, wxID_ANY);  wxBoxSizer* box = new wxBoxSizer(wxVERTICAL);  // URI text input  box->Add(new wxStaticText(panel, wxID_ANY, wxT("URI")));  uriText_ = new wxTextCtrl(panel, wxID_ANY);  box->Add(uriText_, wxSizerFlags().Align(wxGROW));  // Option multi text input  box->Add(new wxStaticText(      panel, wxID_ANY, wxT("Options (key=value pair per line, e.g. dir=/tmp")));  optionText_ = new wxTextCtrl(panel, wxID_ANY, wxT(""), wxDefaultPosition,                               wxDefaultSize, wxTE_MULTILINE);  box->Add(optionText_, wxSizerFlags().Align(wxGROW));  // buttons  wxPanel* btnpanel = new wxPanel(panel, wxID_ANY);  box->Add(btnpanel);  wxBoxSizer* btnbox = new wxBoxSizer(wxHORIZONTAL);  // OK button  okBtn_ = new wxButton(btnpanel, wxID_ANY, wxT("OK"));  btnbox->Add(okBtn_);  // Cancel button  cancelBtn_ = new wxButton(btnpanel, wxID_ANY, wxT("Cancel"));  btnbox->Add(cancelBtn_);  panel->SetSizer(box);  btnpanel->SetSizer(btnbox);}void AddUriDialog::OnButton(wxCommandEvent& event){  int ret = -1;  if (event.GetEventObject() == okBtn_) {    ret = 0;  }  EndModal(ret);}wxString AddUriDialog::GetUri() { return uriText_->GetValue(); }wxString AddUriDialog::GetOption() { return optionText_->GetValue(); }struct DownloadStatusNotification : public Notification {  DownloadStatusNotification(std::vector<DownloadStatus>&& v) : v(v) {}  virtual void notify(MainFrame* frame) { frame->UpdateActiveStatus(v); }  std::vector<DownloadStatus> v;};struct ShutdownNotification : public Notification {  ShutdownNotification() {}  virtual void notify(MainFrame* frame) { frame->Close(); }};int downloaderJob(JobQueue& jobq, NotifyQueue& notifyq){  // session is actually singleton: 1 session per process  aria2::Session* session;  // Use default configuration  aria2::SessionConfig config;  config.keepRunning = true;  session = aria2::sessionNew(aria2::KeyVals(), config);  auto start = std::chrono::steady_clock::now();  for (;;) {    int rv = aria2::run(session, aria2::RUN_ONCE);    if (rv != 1) {      break;    }    auto now = std::chrono::steady_clock::now();    auto count =        std::chrono::duration_cast<std::chrono::milliseconds>(now - start)            .count();    while (!jobq.empty()) {      std::unique_ptr<Job> job = jobq.pop();      job->execute(session);    }    if (count >= 900) {      start = now;      std::vector<aria2::A2Gid> gids = aria2::getActiveDownload(session);      std::vector<DownloadStatus> v;      for (auto gid : gids) {        aria2::DownloadHandle* dh = aria2::getDownloadHandle(session, gid);        if (dh) {          DownloadStatus st;          st.gid = gid;          st.totalLength = dh->getTotalLength();          st.completedLength = dh->getCompletedLength();          st.downloadSpeed = dh->getDownloadSpeed();          st.uploadSpeed = dh->getUploadSpeed();          if (dh->getNumFiles() > 0) {            aria2::FileData file = dh->getFile(1);            st.filename = file.path;          }          v.push_back(std::move(st));          aria2::deleteDownloadHandle(dh);        }      }      notifyq.push(std::unique_ptr<Notification>(          new DownloadStatusNotification(std::move(v))));    }  }  int rv = aria2::sessionFinal(session);  // Report back to the UI thread that this thread is going to  // exit. This is needed when user pressed ctrl-C in the terminal.  notifyq.push(std::unique_ptr<Notification>(new ShutdownNotification()));  return rv;}
 |