/* * Copyright (C) 2022 Paul Davis * * 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. */ #include #include #include #include "pbd/downloader.h" #include "pbd/inflater.h" #include "pbd/i18n.h" #include #include "ardour/rc_configuration.h" #include "ardour/library.h" #include "gui_thread.h" #include "library_download_dialog.h" #include "ui_config.h" using namespace PBD; using namespace ARDOUR; using std::string; LibraryDownloadDialog::LibraryDownloadDialog () : ArdourDialog (_("Loop Download Manager"), true) /* modal */ , inflater(0) { /* pick some vaguely reasonable window size so that we do not just * grow to show all available rows in the listview */ set_size_request (-1, 600); _model = Gtk::ListStore::create (_columns); _display.set_model (_model); _display.append_column (_("Name"), _columns.name); _display.append_column (_("Author"), _columns.author); _display.append_column (_("License"), _columns.license); _display.append_column (_("Size"), _columns.size); append_install_column (); append_progress_column (); _display.set_headers_visible (true); _display.set_hover_selection (true); _display.signal_button_press_event().connect (sigc::mem_fun (*this, &LibraryDownloadDialog::display_button_press), false); scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); scroller.add (_display); Gtk::HBox* h = new Gtk::HBox; h->set_spacing (8); h->set_border_width (8); h->pack_start (scroller); get_vbox()->set_spacing (8); get_vbox()->set_border_width (12); get_vbox()->pack_start (*Gtk::manage (h)); _display.get_selection()->set_mode (Gtk::SELECTION_SINGLE); _display.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LibraryDownloadDialog::library_selected)); description_view.set_size_request (-1, 80); description_view.set_wrap_mode (Gtk::WRAP_WORD); get_vbox()->pack_start (description_view, Gtk::PACK_SHRINK); } LibraryDownloadDialog::~LibraryDownloadDialog () { delete inflater; } void LibraryDownloadDialog::refill () { ARDOUR::LibraryFetcher lf; lf.get_descriptions (); if (lf.n_descriptions()) { _model->clear (); } lf.foreach_description (std::bind (&LibraryDownloadDialog::add_library, this, _1)); } void LibraryDownloadDialog::on_show () { ArdourDialog::on_show (); refill (); } void LibraryDownloadDialog::library_selected () { if (_display.get_selection()->count_selected_rows() == 0) { description_view.get_buffer()->set_text (string()); return; } Gtk::TreeModel::iterator row = _display.get_selection()->get_selected(); string txt = (*row)[_columns.description]; description_view.get_buffer()->set_text (txt); } void LibraryDownloadDialog::append_progress_column () { progress_renderer = new Gtk::CellRendererProgress(); progress_renderer->property_width() = 100; Gtk::TreeViewColumn* tvc = manage (new Gtk::TreeViewColumn ("", *progress_renderer)); tvc->add_attribute (*progress_renderer, "value", _columns.progress); _display.append_column (*tvc); } void LibraryDownloadDialog::append_install_column () { install_renderer = new Gtk::CellRendererText(); Gtk::TreeViewColumn* tvc = manage (new Gtk::TreeViewColumn ("", *install_renderer)); tvc->set_data (X_("index"), (void*) (intptr_t (_columns.install.index()))); tvc->add_attribute (*install_renderer, "text", _columns.install); _display.append_column (*tvc); } void LibraryDownloadDialog::add_library (ARDOUR::LibraryDescription const & ld) { Gtk::TreeModel::iterator i = _model->append(); (*i)[_columns.name] = ld.name(); (*i)[_columns.author] = ld.author(); (*i)[_columns.license] = ld.license(); (*i)[_columns.size] = ld.size(); (*i)[_columns.installed] = ld.installed(); (*i)[_columns.url] = ld.url(); (*i)[_columns.toplevel] = ld.toplevel_dir(); if (ld.installed()) { (*i)[_columns.install] = string (_("Installed")); } else { (*i)[_columns.install] = string (_("Install")); } /* tooltip must be escape for pango markup */ (*i)[_columns.description] = Glib::Markup::escape_text (ld.description()); } void LibraryDownloadDialog::install (std::string const & path, Gtk::TreePath const & treepath) { std::string destdir = Glib::path_get_dirname (path); inflater = new Inflater (path, destdir); inflater->Progress.connect (install_connection, invalidator(*this), std::bind (&LibraryDownloadDialog::install_progress, this, _1, path, treepath), gui_context()); inflater->start (); /* starts unpacking in a thread */ } void LibraryDownloadDialog::install_progress (float p, std::string path, Gtk::TreePath treepath) { Gtk::TreeModel::iterator row = _model->get_iter (treepath); if (!inflater) { return; } if (inflater->status() >= 0) { LibraryDownloadDialog::install_finished (row, path, inflater->status()); return; } (*row)[_columns.progress] = (int) round (100.0 * p); } void LibraryDownloadDialog::install_finished (Gtk::TreeModel::iterator row, std::string path, int status) { if (status == 0) { std::string toplevel = (*row)[_columns.toplevel]; toplevel = Glib::build_filename (Glib::path_get_dirname (path), toplevel); LibraryFetcher lf; lf.add (toplevel); (*row)[_columns.installed] = true; (*row)[_columns.install] = string (_("Installed")); (*row)[_columns.progress] = 100; } else { (*row)[_columns.installed] = false; (*row)[_columns.install] = _("Install"); (*row)[_columns.progress] = 0; } /* Always unlink (remove) the downloaded archive */ ::g_unlink (path.c_str()); /* reap thread */ install_connection.disconnect (); delete inflater; inflater = 0; } void LibraryDownloadDialog::download (Gtk::TreePath const & path) { Gtk::TreeModel::iterator row = _model->get_iter (path); std::string url = (*row)[_columns.url]; PBD::Downloader* downloader; try { downloader = new PBD::Downloader (url, ARDOUR::Config->get_clip_library_dir()); } catch (...) { (*row)[_columns.install] = _("Error"); return; } /* setup timer callback to update progressbar */ Glib::signal_timeout().connect (sigc::bind (sigc::mem_fun (*this, &LibraryDownloadDialog::dl_timer_callback), downloader, path), 40); (*row)[_columns.downloader] = downloader; /* and go ... */ downloader->start (); /* and back to the GUI event loop, though we're modal so not much is possible */ } bool LibraryDownloadDialog::dl_timer_callback (Downloader* dl, Gtk::TreePath treepath) { Gtk::TreeModel::iterator row = _model->get_iter (treepath); /* zero status indicates still running; positive status indicates * success; negative value indicates failure */ if (dl->status() == 0) { (*row)[_columns.progress] = (int) round (dl->progress() * 100.0); return true; /* call again */ } (*row)[_columns.progress] = 0.; (*row)[_columns.downloader] = 0; if (dl->status() < 0) { (*row)[_columns.install] = _("Install");; } else { (*row)[_columns.install] = _("Installing"); install (dl->download_path(), treepath); } delete dl; return false; /* no more calls, done or cancelled */ } bool LibraryDownloadDialog::display_button_press (GdkEventButton* ev) { if ((ev->type == GDK_BUTTON_PRESS) && (ev->button == 1)) { Gtk::TreeModel::Path path; Gtk::TreeViewColumn* column; int cellx, celly; if (!_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) { return false; } int col = (intptr_t) column->get_data (X_("index")); if (col != _columns.install.index()) { return false; } Gtk::TreeIter iter = _model->get_iter (path); string cur = (*iter)[_columns.install]; if (cur == _("Install")) { if (!(*iter)[_columns.installed]) { (*iter)[_columns.install] = _("Cancel"); download (path); } } else { Downloader* dl = (*iter)[_columns.downloader]; if (dl) { dl->cancel (); } (*iter)[_columns.install] = _("Install"); } return true; } return false; }