Improve progress bars (#1350)

* refactor progress bars
* use fmt to better format progress bars fields
* add a Chrono class to handle time and status/state
* add a ProgressBarRepr class to handle representation of the bars
* add capability to remove progress bars from a manager
* add capability to set width when computing bars repr and printing
* handle no_progress_bars context value in all situations
* also ensure quiet, not a tty, and json modes are handled
* add and update tests
* make it optional to display sub-bars in aggregated mode
* display a carrousel of downloading or extracting packages is the aggregated bar
* make sure extracting packages from cache are printed by the progress bars manager
* remove postfix when printing the completed download status
* protect chrono state against modifications when sorting bars
* fix to_human_readable_filesize free function
* only wait for watch_print thread to exit if started
* store speed, also compute average speed from member or Chrono elapsed time
* use the average speed on download aggregated bar if cURL info is missing
* allows to get average speed on total elapsed time
* improved color scheme of progress bars
* collect and show the in-progress status using cyan
* init the progress randomly when activating spinner
* control sorting of multi downloads using an option
* use C-style options also for fastfail
* make sure pbar manager is init on multi bars when dl channels
* refactor on api changes
This commit is contained in:
Adrien Delsalle 2022-01-26 15:47:13 +01:00 committed by GitHub
parent 8b3629130f
commit 129920a304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 3305 additions and 907 deletions

View File

@ -56,7 +56,7 @@ Here is an example usage of the mamba_api:
index.append((sd, channel))
dlist.add(sd)
is_downloaded = dlist.download(True)
is_downloaded = dlist.download(mamba_api.MAMBA_DOWNLOAD_FAILFAST)
if not is_downloaded:
raise RuntimeError("Error downloading repodata.")

View File

@ -17,7 +17,9 @@ extern "C"
#include <vector>
#include "nlohmann/json.hpp"
#include "output.hpp"
#include "progress_bar.hpp"
#include "validate.hpp"
namespace mamba
@ -43,6 +45,7 @@ namespace mamba
void set_expected_size(std::size_t size);
const std::string& name() const;
std::size_t expected_size() const;
void init_curl_target(const std::string& url);
@ -74,6 +77,9 @@ namespace mamba
bool can_retry();
CURL* retry();
std::chrono::steady_clock::time_point progress_throttle_time() const;
void set_progress_throttle_time(const std::chrono::steady_clock::time_point& time);
CURLcode result;
bool failed = false;
int http_status = 10000;
@ -108,6 +114,9 @@ namespace mamba
std::ofstream m_file;
static void init_curl_handle(CURL* handle, const std::string& url);
std::function<void(ProgressBarRepr&)> download_repr();
std::chrono::steady_clock::time_point m_progress_throttle_time;
};
class MultiDownloadTarget
@ -118,7 +127,7 @@ namespace mamba
void add(DownloadTarget* target);
bool check_msgs(bool failfast);
bool download(bool failfast);
bool download(int options);
private:
std::vector<DownloadTarget*> m_targets;
@ -126,6 +135,8 @@ namespace mamba
CURLM* m_handle;
};
const int MAMBA_DOWNLOAD_FAILFAST = 1 << 0;
const int MAMBA_DOWNLOAD_SORT = 1 << 1;
} // namespace mamba
#endif // MAMBA_FETCH_HPP

View File

@ -46,97 +46,6 @@
#define PREFIX_LENGTH 25
namespace cursor
{
class CursorMovementTriple
{
public:
CursorMovementTriple(const char* esc, int n, const char* mod)
: m_esc(esc)
, m_mod(mod)
, m_n(n)
{
}
const char* m_esc;
const char* m_mod;
int m_n;
};
inline std::ostream& operator<<(std::ostream& o, const CursorMovementTriple& m)
{
o << m.m_esc << m.m_n << m.m_mod;
return o;
}
class CursorMod
{
public:
CursorMod(const char* mod)
: m_mod(mod)
{
}
std::ostream& operator<<(std::ostream& o)
{
o << m_mod;
return o;
}
const char* m_mod;
};
inline auto up(int n)
{
return CursorMovementTriple("\x1b[", n, "A");
}
inline auto down(int n)
{
return CursorMovementTriple("\x1b[", n, "B");
}
inline auto forward(int n)
{
return CursorMovementTriple("\x1b[", n, "C");
}
inline auto back(int n)
{
return CursorMovementTriple("\x1b[", n, "D");
}
inline auto next_line(int n)
{
return CursorMovementTriple("\x1b[", n, "E");
}
inline auto prev_line(int n)
{
return CursorMovementTriple("\x1b[", n, "F");
}
inline auto horizontal_abs(int n)
{
return CursorMovementTriple("\x1b[", n, "G");
}
inline auto erase_line(int n = 0)
{
return CursorMovementTriple("\x1b[", n, "K");
}
inline auto show(int n)
{
return CursorMod("\x1b[?25h");
}
inline auto hide(int n)
{
return CursorMod("\x1b[?25l");
}
} // namespace cursor
namespace mamba
{
std::string cut_repo_name(const std::string& reponame);
@ -204,11 +113,6 @@ namespace mamba
std::ostringstream table_like(const std::vector<std::string>& data, std::size_t max_width);
} // namespace printers
// The next two functions / classes were ported from the awesome indicators
// library by p-ranav (MIT License) https://github.com/p-ranav/indicators
std::ostream& write_duration(std::ostream& os, std::chrono::nanoseconds ns);
int get_console_width();
// Todo: replace public inheritance with
// private one + using directives
class ConsoleStream : public std::stringstream
@ -236,7 +140,10 @@ namespace mamba
std::istream& input_stream = std::cin);
ProgressProxy add_progress_bar(const std::string& name, size_t expected_total = 0);
void init_multi_progress(ProgressBarMode mode = ProgressBarMode::multi);
void clear_progress_bars();
ProgressBarManager& init_progress_bar_manager(ProgressBarMode mode
= ProgressBarMode::multi);
ProgressBarManager& progress_bar_manager();
static std::string hide_secrets(const std::string_view& str);
@ -247,30 +154,26 @@ namespace mamba
void json_down(const std::string& key);
void json_up();
static void print_buffer(std::ostream& ostream);
private:
Console();
~Console() = default;
void deactivate_progress_bar(std::size_t idx, const std::string_view& msg = "");
void print_progress(std::size_t idx);
bool skip_progress_bars() const;
std::mutex m_mutex;
std::unique_ptr<ProgressBarManager> p_progress_manager;
std::unique_ptr<ProgressBarManager> p_progress_bar_manager;
std::string json_hier;
unsigned int json_index;
nlohmann::json json_log;
static std::vector<std::string> m_buffer;
friend class ProgressProxy;
};
inline void ProgressProxy::set_postfix(const std::string& s)
{
p_bar->set_postfix(s);
Console::instance().print_progress(m_idx);
}
class MessageLogger
{
public:
@ -279,11 +182,21 @@ namespace mamba
std::stringstream& stream();
static void activate_buffer();
static void deactivate_buffer();
static void print_buffer(std::ostream& ostream);
private:
std::string m_file;
int m_line;
spdlog::level::level_enum m_level;
std::stringstream m_stream;
static std::mutex m_mutex;
static bool use_buffer;
static std::vector<std::pair<std::string, spdlog::level::level_enum>> m_buffer;
static void emit(const std::string& msg, const spdlog::level::level_enum& level);
};

View File

@ -7,12 +7,291 @@
#ifndef MAMBA_CORE_PROGRESS_BAR_HPP
#define MAMBA_CORE_PROGRESS_BAR_HPP
#include <string_view>
#include "spdlog/fmt/fmt.h"
#include "spdlog/fmt/bundled/color.h"
#include <iomanip>
#include <iostream>
#include <mutex>
#include <set>
#include <string_view>
#include <vector>
#include <map>
#include <functional>
namespace cursor
{
class CursorMovementTriple
{
public:
CursorMovementTriple(const char* esc, int n, const char* mod)
: m_esc(esc)
, m_mod(mod)
, m_n(n)
{
}
const char* m_esc;
const char* m_mod;
int m_n;
};
inline std::ostream& operator<<(std::ostream& o, const CursorMovementTriple& m)
{
o << m.m_esc << m.m_n << m.m_mod;
return o;
}
class CursorMod
{
public:
CursorMod(const char* mod)
: m_mod(mod)
{
}
std::ostream& operator<<(std::ostream& o)
{
o << m_mod;
return o;
}
const char* m_mod;
};
inline std::ostream& operator<<(std::ostream& o, const CursorMod& m)
{
o << m.m_mod;
return o;
}
inline auto up(int n)
{
return CursorMovementTriple("\x1b[", n, "A");
}
inline auto down(int n)
{
return CursorMovementTriple("\x1b[", n, "B");
}
inline auto forward(int n)
{
return CursorMovementTriple("\x1b[", n, "C");
}
inline auto back(int n)
{
return CursorMovementTriple("\x1b[", n, "D");
}
inline auto next_line(int n)
{
return CursorMovementTriple("\x1b[", n, "E");
}
inline auto prev_line(int n)
{
return CursorMovementTriple("\x1b[", n, "F");
}
inline auto horizontal_abs(int n)
{
return CursorMovementTriple("\x1b[", n, "G");
}
inline auto home()
{
return CursorMod("\x1b[H");
}
inline auto erase_display(int n = 0)
{
return CursorMovementTriple("\x1b[", n, "J");
}
inline auto erase_line(int n = 0)
{
return CursorMovementTriple("\x1b[", n, "K");
}
inline auto scroll_up(int n = 1)
{
return CursorMovementTriple("\x1b[", n, "S");
}
inline auto scroll_down(int n = 1)
{
return CursorMovementTriple("\x1b[", n, "T");
}
inline auto show()
{
return CursorMod("\x1b[?25h");
}
inline auto hide()
{
return CursorMod("\x1b[?25l");
}
inline auto pos()
{
return CursorMod("\x1b[R");
}
inline auto delete_line(int n = 1)
{
return CursorMovementTriple("\x1b[", n, "M");
}
inline auto alternate_screen()
{
return CursorMod("\x1b[?1049h");
}
inline auto main_screen()
{
return CursorMod("\x1b[?1049l");
}
} // namespace cursor
namespace mamba
{
void to_human_readable_filesize(std::ostream& o, double bytes, std::size_t precision = 0);
std::string to_human_readable_filesize(double bytes, std::size_t precision);
std::ostream& write_duration(std::ostream& os, std::chrono::nanoseconds ns);
std::stringstream duration_stream(std::chrono::nanoseconds ns);
std::string duration_str(std::chrono::nanoseconds ns);
int get_console_width();
int get_console_height();
enum ChronoState
{
unset = 0,
started = 1,
paused = 2,
stopped = 3,
terminated = 4
};
class Chrono
{
public:
using duration_t = std::chrono::milliseconds;
using time_point_t
= std::chrono::time_point<std::chrono::high_resolution_clock, duration_t>;
Chrono() = default;
ChronoState status() const;
bool started() const;
bool paused() const;
bool stopped() const;
bool terminated() const;
bool unset() const;
void start();
void start(const time_point_t& time_point);
void pause();
void resume();
void stop();
void terminate();
void set_start_time(const time_point_t& time_point);
time_point_t start_time() const;
time_point_t last_active_time();
void set_elapsed_time(const duration_t& time);
std::string elapsed_time_to_str();
duration_t elapsed();
std::unique_lock<std::mutex> chrono_lock();
private:
time_point_t m_start;
duration_t m_elapsed_ns = duration_t::zero();
ChronoState m_state = ChronoState::unset;
std::mutex m_mutex;
void compute_elapsed();
protected:
static time_point_t now();
};
class FieldRepr
{
public:
bool active() const;
bool defined() const;
operator bool() const;
bool overflow() const;
std::string formatted_value(bool allow_overflow = false) const;
std::size_t width(bool allow_overflow = true) const;
std::size_t stored_width() const;
std::string value() const;
FieldRepr& activate();
FieldRepr& deactivate();
FieldRepr& set_format(const std::string& str);
FieldRepr& set_format(const std::string& str, std::size_t size);
FieldRepr& set_value(const std::string& str);
FieldRepr& set_width(std::size_t size);
FieldRepr& reset_width();
FieldRepr& resize(std::size_t size);
private:
std::string m_value = "";
std::size_t m_width = 0;
std::string m_format = "";
bool m_active = true;
static std::string resize(const std::string& str, std::size_t size);
};
class ProgressBar;
class ProgressBarManager;
class ProgressBarRepr
{
public:
ProgressBarRepr() = default;
ProgressBarRepr(ProgressBar* pbar);
FieldRepr prefix, progress, current, separator, total, speed, postfix, elapsed;
fmt::text_style style;
void print(std::ostream& ostream, std::size_t width = 0, bool with_endl = true);
void compute_progress();
void compute_progress_width();
void compute_progress_value();
ProgressBarRepr& set_width(std::size_t width);
std::size_t width() const;
ProgressBarRepr& reset_fields();
const ProgressBar& progress_bar() const;
private:
ProgressBar* p_progress_bar;
std::size_t m_width = 0;
void set_same_widths(const ProgressBarRepr& r);
void deactivate_empty_fields();
std::vector<FieldRepr*> fields();
friend class ProgressBar;
friend class ProgressBarManager;
};
class ProgressBarManager;
/*******************************
* Public API of progress bars *
@ -22,7 +301,7 @@ namespace mamba
{
public:
ProgressProxy() = default;
ProgressProxy(ProgressBar* ptr, std::size_t idx);
ProgressProxy(ProgressBar* ptr);
~ProgressProxy() = default;
ProgressProxy(const ProgressProxy&) = default;
@ -30,22 +309,65 @@ namespace mamba
ProgressProxy(ProgressProxy&&) = default;
ProgressProxy& operator=(ProgressProxy&&) = default;
void set_full();
void set_progress(size_t current, size_t total);
void elapsed_time_to_stream(std::stringstream& s);
void set_postfix(const std::string& s);
void mark_as_completed(const std::string_view& final_message = "");
void mark_as_extracted();
bool defined() const;
operator bool() const;
ProgressProxy& set_bar(ProgressBar* ptr);
ProgressProxy& set_progress(std::size_t current, std::size_t total);
ProgressProxy& update_progress(std::size_t current, std::size_t total);
ProgressProxy& set_progress(double progress);
ProgressProxy& set_current(std::size_t current);
ProgressProxy& set_in_progress(std::size_t in_progress);
ProgressProxy& update_current(std::size_t current);
ProgressProxy& set_total(std::size_t total);
ProgressProxy& set_speed(std::size_t speed);
ProgressProxy& set_full();
ProgressProxy& activate_spinner();
ProgressProxy& deactivate_spinner();
std::size_t current() const;
std::size_t in_progress() const;
std::size_t total() const;
std::size_t speed() const;
std::size_t avg_speed(const std::chrono::milliseconds& ref_duration
= std::chrono::milliseconds::max());
double progress() const;
bool completed() const;
ProgressProxy& set_prefix(const std::string& text);
ProgressProxy& set_postfix(const std::string& text);
ProgressProxy& set_repr_hook(std::function<void(ProgressBarRepr&)> f);
ProgressProxy& set_progress_hook(std::function<void(ProgressProxy&)> f);
ProgressProxy& mark_as_completed(const std::chrono::milliseconds& delay
= std::chrono::milliseconds::zero());
std::string elapsed_time_to_str() const;
std::string prefix() const;
ProgressBarRepr& update_repr(bool compute_progress = true);
const ProgressBarRepr& repr() const;
ProgressBarRepr& repr();
ProgressProxy& print(std::ostream& stream, std::size_t width = 0, bool with_endl = true);
ProgressProxy& start();
ProgressProxy& pause();
ProgressProxy& resume();
ProgressProxy& stop();
bool started() const;
int width() const;
private:
ProgressBar* p_bar;
std::size_t m_idx;
friend class ProgressBarManager;
};
class ProgressBarManager
class ProgressBarManager : public Chrono
{
public:
virtual ~ProgressBarManager() = default;
virtual ~ProgressBarManager();
ProgressBarManager(const ProgressBarManager&) = delete;
ProgressBarManager& operator=(const ProgressBarManager&) = delete;
@ -54,13 +376,53 @@ namespace mamba
virtual ProgressProxy add_progress_bar(const std::string& name, size_t expected_total = 0)
= 0;
virtual void print_progress(std::size_t idx) = 0;
virtual void deactivate_progress_bar(std::size_t idx, const std::string_view& msg = "") = 0;
virtual void clear_progress_bars();
virtual void add_label(const std::string& label, const ProgressProxy& progress_bar);
virtual void print(const std::string_view& str, bool skip_progress_bars) = 0;
void watch_print(const duration_t& period = std::chrono::milliseconds(100));
virtual std::size_t print(std::ostream& os,
std::size_t width = 0,
std::size_t max_lines = std::numeric_limits<std::size_t>::max(),
bool with_endl = true)
= 0;
void start();
void terminate();
void register_print_hook(std::function<void(std::ostream&)> f);
void register_pre_start_hook(std::function<void()> f);
void register_post_stop_hook(std::function<void()> f);
void compute_bars_progress(std::vector<ProgressBar*>& bars);
void activate_sorting();
void deactivate_sorting();
protected:
using progress_bar_ptr = std::unique_ptr<ProgressBar>;
ProgressBarManager() = default;
ProgressBarManager(std::size_t width);
duration_t m_period = std::chrono::milliseconds(100);
std::vector<progress_bar_ptr> m_progress_bars = {};
std::map<std::string, std::vector<ProgressBar*>> m_labels;
bool m_marked_to_terminate = false, m_watch_print_started = false;
bool m_sort_bars = false;
std::size_t m_width = 0;
std::mutex m_mutex;
ProgressBar* raw_bar(const ProgressProxy& progress_bar);
void run();
std::vector<std::function<void(std::ostream&)>> m_print_hooks;
std::vector<std::function<void()>> m_pre_start_hooks;
std::vector<std::function<void()>> m_post_stop_hooks;
void erase_lines(std::ostream& ostream, std::size_t count);
void call_print_hooks(std::ostream& ostream);
void sort_bars(bool max_height_exceeded);
};
enum ProgressBarMode
@ -71,110 +433,158 @@ namespace mamba
std::unique_ptr<ProgressBarManager> make_progress_bar_manager(ProgressBarMode);
/******************************************
* Internal use of progress bars, *
* should not be used directly by clients *
******************************************/
/*********************************
* Internal use of progress bars *
*********************************/
class MultiBarManager : public ProgressBarManager
{
public:
MultiBarManager();
MultiBarManager(std::size_t width);
virtual ~MultiBarManager() = default;
ProgressProxy add_progress_bar(const std::string& name, size_t expected_total) override;
void print_progress(std::size_t idx) override;
void deactivate_progress_bar(std::size_t idx, const std::string_view& msg = "") override;
void print(const std::string_view& str, bool skip_progress_bars) override;
private:
void print_progress();
using progress_bar_ptr = std::unique_ptr<ProgressBar>;
std::vector<progress_bar_ptr> m_progress_bars;
std::vector<ProgressBar*> m_active_progress_bars;
bool m_progress_started;
std::size_t print(std::ostream& os,
std::size_t width = 0,
std::size_t max_lines = std::numeric_limits<std::size_t>::max(),
bool with_endl = true) override;
};
class AggregatedBarManager : public ProgressBarManager
{
public:
AggregatedBarManager();
AggregatedBarManager(std::size_t width);
virtual ~AggregatedBarManager() = default;
ProgressProxy add_progress_bar(const std::string& name, size_t expected_total) override;
void print_progress(std::size_t idx) override;
void deactivate_progress_bar(std::size_t idx, const std::string_view& msg = "") override;
void print(const std::string_view& str, bool skip_progress_bars) override;
ProgressProxy add_progress_bar(const std::string& name,
std::size_t expected_total) override;
void update_download_bar(std::size_t current_diff);
void update_extract_bar();
void add_label(const std::string& label, const ProgressProxy& progress_bar) override;
void clear_progress_bars() override;
void activate_sub_bars();
void deactivate_sub_bars();
ProgressBar* aggregated_bar(const std::string& label);
std::size_t print(std::ostream& os,
std::size_t width = 0,
std::size_t max_lines = std::numeric_limits<std::size_t>::max(),
bool with_endl = true) override;
private:
void print_progress();
std::map<std::string, progress_bar_ptr> m_aggregated_bars;
bool m_print_sub_bars = false;
bool is_complete() const;
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
using progress_bar_ptr = std::unique_ptr<ProgressBar>;
std::vector<progress_bar_ptr> m_progress_bars;
progress_bar_ptr p_download_bar;
progress_bar_ptr p_extract_bar;
size_t m_completed;
size_t m_extracted;
std::mutex m_main_mutex;
size_t m_current;
size_t m_total;
bool m_progress_started;
void update_aggregates_progress();
};
class ProgressBar
class ProgressBar : public Chrono
{
public:
virtual ~ProgressBar() = default;
virtual ~ProgressBar();
ProgressBar(const ProgressBar&) = delete;
ProgressBar& operator=(const ProgressBar&) = delete;
ProgressBar(ProgressBar&&) = delete;
ProgressBar& operator=(ProgressBar&&) = delete;
virtual void print() = 0;
virtual void set_full() = 0;
virtual void set_progress(size_t current, size_t total) = 0;
virtual void set_extracted() = 0;
void set_start();
void set_postfix(const std::string& postfix_text);
void elapsed_time_to_stream(std::stringstream& s);
const std::string& prefix() const;
virtual void print(std::ostream& stream, std::size_t width = 0, bool with_endl = true) = 0;
ProgressBarRepr& update_repr(bool compute_bar = true);
const ProgressBarRepr& repr() const;
ProgressBarRepr& repr();
ProgressBar& set_progress(std::size_t current, std::size_t total);
ProgressBar& update_progress(std::size_t current, std::size_t total);
ProgressBar& set_progress(double progress);
ProgressBar& set_current(std::size_t current);
ProgressBar& set_in_progress(std::size_t in_progress);
ProgressBar& update_current(std::size_t current);
ProgressBar& set_total(std::size_t total);
ProgressBar& set_speed(std::size_t speed);
ProgressBar& set_full();
ProgressBar& activate_spinner();
ProgressBar& deactivate_spinner();
ProgressBar& mark_as_completed(const std::chrono::milliseconds& delay
= std::chrono::milliseconds::zero());
std::size_t current() const;
std::size_t in_progress() const;
std::size_t total() const;
std::size_t speed() const;
std::size_t avg_speed(const std::chrono::milliseconds& ref_duration
= std::chrono::milliseconds::max());
double progress() const;
bool completed() const;
bool is_spinner() const;
const std::set<std::string>& active_tasks() const;
std::set<std::string>& active_tasks();
const std::set<std::string>& all_tasks() const;
std::set<std::string>& all_tasks();
ProgressBar& add_active_task(const std::string& name);
ProgressBar& add_task(const std::string& name);
std::string last_active_task();
ProgressBar& set_repr_hook(std::function<void(ProgressBarRepr&)> f);
ProgressBar& set_progress_hook(std::function<void(ProgressProxy&)> f);
ProgressBar& set_prefix(const std::string& str);
ProgressBar& set_postfix(const std::string& str);
std::string prefix() const;
int width() const;
protected:
ProgressBar(const std::string& prefix);
ProgressBar(const std::string& prefix, std::size_t total, int width = 0);
std::chrono::nanoseconds m_elapsed_ns;
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
double m_progress = 0.;
std::size_t m_current = 0;
std::size_t m_in_progress = 0;
std::size_t m_total = 0;
std::size_t m_speed = 0, m_avg_speed = 0, m_current_avg = 0;
int m_width = 0;
std::string m_prefix;
std::string m_postfix;
bool m_start_time_saved;
bool m_activate_bob;
std::set<std::string> m_active_tasks = {};
std::set<std::string> m_all_tasks = {};
std::string m_last_active_task = "";
time_point_t m_task_time, m_avg_speed_time;
ProgressBarRepr m_repr;
bool m_is_spinner;
bool m_completed = false;
std::mutex m_mutex;
void run();
std::function<void(ProgressBarRepr&)> p_repr_hook;
std::function<void(ProgressProxy&)> p_progress_hook;
ProgressBar& call_progress_hook();
ProgressBar& call_repr_hook();
};
class DefaultProgressBar : public ProgressBar
{
public:
DefaultProgressBar(const std::string& prefix, int width_cap = 20);
DefaultProgressBar(const std::string& prefix, std::size_t total, int width = 0);
virtual ~DefaultProgressBar() = default;
void print() override;
void set_full() override;
void set_progress(size_t current, size_t total) override;
void set_extracted() override;
private:
size_t m_progress;
int m_width_cap;
void print(std::ostream& stream, std::size_t width = 0, bool with_endl = true) override;
};
class HiddenProgressBar : public ProgressBar
@ -182,19 +592,12 @@ namespace mamba
public:
HiddenProgressBar(const std::string& prefix,
AggregatedBarManager* manager,
size_t expected_total);
std::size_t total,
int width = 0);
virtual ~HiddenProgressBar() = default;
void print() override;
void set_full() override;
void set_progress(size_t current, size_t total) override;
void set_extracted() override;
private:
AggregatedBarManager* p_manager;
std::size_t m_current;
std::size_t m_total;
void print(std::ostream& stream, std::size_t width = 0, bool with_endl = true) override;
};
}
} // namespace mamba
#endif

View File

@ -24,6 +24,7 @@
#include "package_cache.hpp"
#include "package_handling.hpp"
#include "prefix_data.hpp"
#include "progress_bar.hpp"
#include "repo.hpp"
#include "thread_utils.hpp"
#include "transaction_context.hpp"
@ -56,6 +57,7 @@ namespace mamba
bool extract_from_cache();
bool validate_extract();
const std::string& name() const;
std::size_t expected_size() const;
auto validation_result() const;
void clear_cache() const;
@ -80,7 +82,8 @@ namespace mamba
std::string m_sha256, m_md5;
std::size_t m_expected_size;
ProgressProxy m_progress_proxy;
bool m_has_progress_bars = false;
ProgressProxy m_download_bar, m_extract_bar;
std::unique_ptr<DownloadTarget> m_target;
std::string m_url, m_name, m_channel, m_filename;
@ -89,6 +92,9 @@ namespace mamba
std::future<bool> m_extract_future;
VALIDATION_RESULT m_validation_result = VALIDATION_RESULT::UNDEFINED;
std::function<void(ProgressBarRepr&)> extract_repr();
std::function<void(ProgressProxy&)> extract_progress_callback();
};
class DownloadExtractSemaphore
@ -115,8 +121,9 @@ namespace mamba
MTransaction(MPool& pool,
const std::vector<MatchSpec>& specs_to_remove,
const std::vector<MatchSpec>& specs_to_install,
MultiPackageCache& caches);
MTransaction(MSolver& solver, MultiPackageCache& caches);
MultiPackageCache& caches,
std::vector<MRepo*> repos);
MTransaction(MSolver& solver, MultiPackageCache& caches, std::vector<MRepo*> repos);
~MTransaction();
@ -133,9 +140,9 @@ namespace mamba
void init();
to_conda_type to_conda();
void log_json();
bool fetch_extract_packages(std::vector<MRepo*>& repos);
bool fetch_extract_packages();
bool empty();
bool prompt(std::vector<MRepo*>& repos);
bool prompt();
void print();
bool execute(PrefixData& prefix);
bool filter(Solvable* s);
@ -150,6 +157,8 @@ namespace mamba
MultiPackageCache m_multi_cache;
const fs::path m_cache_path;
std::vector<Solvable*> m_to_install, m_to_remove;
std::vector<MRepo*> m_repos;
History::UserRequest m_history_entry;
Transaction* m_transaction;
@ -160,7 +169,8 @@ namespace mamba
MTransaction create_explicit_transaction_from_urls(MPool& pool,
const std::vector<std::string>& urls,
MultiPackageCache& package_caches);
MultiPackageCache& package_caches,
std::vector<MRepo*>& repos);
} // namespace mamba
#endif // MAMBA_TRANSACTION_HPP

View File

@ -59,7 +59,6 @@ namespace mamba
bool is_package_file(const std::string_view& fn);
void to_human_readable_filesize(std::ostream& o, double bytes, std::size_t precision = 0);
bool lexists(const fs::path& p);
bool lexists(const fs::path& p, std::error_code& ec);
std::vector<fs::path> filter_dir(const fs::path& dir, const std::string& suffix);

View File

@ -46,6 +46,8 @@ namespace mamba
int max_prio = static_cast<int>(channel_urls.size());
std::string prev_channel_name;
Console::instance().init_progress_bar_manager(ProgressBarMode::multi);
if (ctx.experimental)
{
load_tokens();
@ -86,7 +88,7 @@ namespace mamba
// TODO load local channels even when offline
if (!ctx.offline)
{
multi_dl.download(true);
multi_dl.download(MAMBA_DOWNLOAD_FAILFAST);
}
std::vector<MRepo> repos;

View File

@ -348,7 +348,6 @@ namespace mamba
LOG_WARNING << "No 'channels' specified";
}
std::vector<MRepo> repos;
MPool pool;
if (ctx.offline)
@ -431,23 +430,20 @@ namespace mamba
throw std::runtime_error("UnsatisfiableError");
}
MTransaction trans(solver, package_caches);
std::vector<MRepo*> repo_ptrs;
for (auto& r : repos)
repo_ptrs.push_back(&r);
MTransaction trans(solver, package_caches, repo_ptrs);
if (ctx.json)
{
trans.log_json();
}
// TODO this is not so great
std::vector<MRepo*> repo_ptrs;
for (auto& r : repos)
{
repo_ptrs.push_back(&r);
}
Console::stream();
bool yes = trans.prompt(repo_ptrs);
if (yes)
if (trans.prompt())
{
if (create_env && !Context::instance().dry_run)
detail::create_target_directory(ctx.target_prefix);
@ -470,19 +466,18 @@ namespace mamba
PrefixData prefix_data(ctx.target_prefix);
fs::path pkgs_dirs(Context::instance().root_prefix / "pkgs");
MultiPackageCache pkg_caches({ pkgs_dirs });
auto transaction = create_explicit_transaction_from_urls(pool, specs, pkg_caches);
std::vector<MRepo*> repos = {};
auto transaction = create_explicit_transaction_from_urls(pool, specs, pkg_caches, repos);
prefix_data.load();
prefix_data.add_virtual_packages(get_virtual_packages());
MRepo(pool, prefix_data);
std::vector<MRepo*> repo_ptrs;
if (ctx.json)
transaction.log_json();
bool yes = transaction.prompt(repo_ptrs);
if (yes)
if (transaction.prompt())
{
if (create_env && !Context::instance().dry_run)
detail::create_target_directory(ctx.target_prefix);

View File

@ -75,29 +75,25 @@ namespace mamba
auto repo = MRepo(pool, prefix_data);
repos.push_back(repo);
const fs::path pkgs_dirs(ctx.root_prefix / "pkgs");
MultiPackageCache package_caches({ pkgs_dirs });
// TODO this is not so great
std::vector<MRepo*> repo_ptrs;
for (auto& r : repos)
{
repo_ptrs.push_back(&r);
}
const fs::path pkgs_dirs(ctx.root_prefix / "pkgs");
MultiPackageCache package_caches({ pkgs_dirs });
auto execute_transaction = [&](MTransaction& transaction) {
if (ctx.json)
transaction.log_json();
bool yes = transaction.prompt(repo_ptrs);
if (yes)
if (transaction.prompt())
transaction.execute(prefix_data);
};
if (force)
{
std::vector<MatchSpec> mspecs(specs.begin(), specs.end());
auto transaction = MTransaction(pool, mspecs, {}, package_caches);
auto transaction = MTransaction(pool, mspecs, {}, package_caches, repo_ptrs);
execute_transaction(transaction);
}
else
@ -124,7 +120,7 @@ namespace mamba
solver.add_jobs(specs, solver_flag);
solver.solve();
MTransaction transaction(solver, package_caches);
MTransaction transaction(solver, package_caches, repo_ptrs);
execute_transaction(transaction);
}
}

View File

@ -106,20 +106,19 @@ namespace mamba
solver.solve();
MTransaction transaction(solver, package_caches);
// TODO this is not so great
std::vector<MRepo*> repo_ptrs;
for (auto& r : repos)
{
repo_ptrs.push_back(&r);
}
MTransaction transaction(solver, package_caches, repo_ptrs);
auto execute_transaction = [&](MTransaction& transaction) {
if (ctx.json)
transaction.log_json();
bool yes = transaction.prompt(repo_ptrs);
bool yes = transaction.prompt();
if (yes)
transaction.execute(prefix_data);
};

View File

@ -347,41 +347,62 @@ namespace mamba
return nitems * size;
}
std::function<void(ProgressBarRepr&)> DownloadTarget::download_repr()
{
return [&](ProgressBarRepr& r) -> void {
r.current.set_value(
fmt::format("{:>7}", to_human_readable_filesize(m_progress_bar.current(), 1)));
std::string total_str;
if (!m_progress_bar.total()
|| (m_progress_bar.total() == std::numeric_limits<std::size_t>::max()))
total_str = "??.?MB";
else
total_str = to_human_readable_filesize(m_progress_bar.total(), 1);
r.total.set_value(fmt::format("{:>7}", total_str));
auto speed = m_progress_bar.speed();
r.speed.set_value(
fmt::format("@ {:>7}/s", speed ? to_human_readable_filesize(speed, 1) : "??.?MB"));
r.separator.set_value("/");
};
}
std::chrono::steady_clock::time_point DownloadTarget::progress_throttle_time() const
{
return m_progress_throttle_time;
}
void DownloadTarget::set_progress_throttle_time(
const std::chrono::steady_clock::time_point& time)
{
m_progress_throttle_time = time;
}
int DownloadTarget::progress_callback(
void* f, curl_off_t total_to_download, curl_off_t now_downloaded, curl_off_t, curl_off_t)
{
auto* target = static_cast<DownloadTarget*>(f);
if (Context::instance().quiet || Context::instance().json)
{
auto now = std::chrono::steady_clock::now();
if (now - target->progress_throttle_time() < std::chrono::milliseconds(50))
return 0;
}
if ((total_to_download != 0 || target->m_expected_size != 0))
{
std::stringstream postfix;
postfix << std::setw(6);
to_human_readable_filesize(postfix, now_downloaded);
postfix << " / ";
postfix << std::setw(6);
to_human_readable_filesize(postfix, total_to_download);
postfix << " (";
postfix << std::setw(6);
to_human_readable_filesize(postfix, target->get_speed(), 2);
postfix << "/s)";
target->m_progress_bar.set_progress(now_downloaded, total_to_download);
target->m_progress_bar.set_postfix(postfix.str());
}
else
{
std::stringstream postfix;
to_human_readable_filesize(postfix, now_downloaded);
postfix << " / ?? (";
to_human_readable_filesize(postfix, target->get_speed(), 2);
postfix << "/s)";
target->m_progress_bar.set_progress(SIZE_MAX, SIZE_MAX);
target->m_progress_bar.set_postfix(postfix.str());
}
target->set_progress_throttle_time(now);
if (!total_to_download && !target->expected_size())
target->m_progress_bar.activate_spinner();
else
target->m_progress_bar.deactivate_spinner();
if (!total_to_download && target->expected_size())
target->m_progress_bar.update_current(now_downloaded);
else
target->m_progress_bar.update_progress(now_downloaded, total_to_download);
target->m_progress_bar.set_speed(target->get_speed());
return 0;
}
@ -407,6 +428,8 @@ namespace mamba
{
m_has_progress_bar = true;
m_progress_bar = progress_proxy;
m_progress_bar.set_repr_hook(download_repr());
curl_easy_setopt(m_handle, CURLOPT_XFERINFOFUNCTION, &DownloadTarget::progress_callback);
curl_easy_setopt(m_handle, CURLOPT_XFERINFODATA, this);
curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0L);
@ -422,12 +445,16 @@ namespace mamba
return m_name;
}
std::size_t DownloadTarget::expected_size() const
{
return m_expected_size;
}
static size_t discard(char* ptr, size_t size, size_t nmemb, void*)
{
return size * nmemb;
}
bool DownloadTarget::resource_exists()
{
auto handle = curl_easy_init();
@ -474,7 +501,14 @@ namespace mamba
{
curl_off_t speed;
CURLcode res = curl_easy_getinfo(m_handle, CURLINFO_SPEED_DOWNLOAD_T, &speed);
return res == CURLE_OK ? speed : 0;
if (res != CURLE_OK)
{
if (m_has_progress_bar)
speed = m_progress_bar.avg_speed();
else
speed = 0;
}
return speed;
}
void DownloadTarget::set_result(CURLcode r)
@ -499,7 +533,8 @@ namespace mamba
if (m_has_progress_bar)
{
m_progress_bar.set_progress(0, 1);
m_progress_bar.update_progress(0, 1);
// m_progress_bar.set_elapsed_time();
m_progress_bar.set_postfix(curl_easy_strerror(result));
}
if (!m_ignore_failure && !can_retry())
@ -513,17 +548,12 @@ namespace mamba
{
char* effective_url = nullptr;
auto cres = curl_easy_getinfo(m_handle, CURLINFO_SPEED_DOWNLOAD_T, &avg_speed);
if (cres != CURLE_OK)
{
avg_speed = 0;
}
avg_speed = get_speed();
curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &http_status);
curl_easy_getinfo(m_handle, CURLINFO_EFFECTIVE_URL, &effective_url);
curl_easy_getinfo(m_handle, CURLINFO_SIZE_DOWNLOAD_T, &downloaded_size);
LOG_INFO << "Transfer finalized, status: " << http_status << " [" << effective_url << "] "
LOG_INFO << "Transfer finalized, status " << http_status << " [" << effective_url << "] "
<< downloaded_size << " bytes";
if (http_status >= 500 && can_retry())
@ -533,26 +563,53 @@ namespace mamba
= std::chrono::steady_clock::now() + std::chrono::seconds(m_retry_wait_seconds);
std::stringstream msg;
msg << "Failed (" << http_status << "), retry in " << m_retry_wait_seconds << "s";
m_progress_bar.set_progress(0, downloaded_size);
m_progress_bar.set_postfix(msg.str());
if (m_has_progress_bar)
{
m_progress_bar.update_progress(0, downloaded_size);
m_progress_bar.set_postfix(msg.str());
}
return false;
}
m_file.close();
final_url = effective_url;
if (m_finalize_callback)
if (m_has_progress_bar)
{
return m_finalize_callback();
m_progress_bar.set_speed(avg_speed);
m_progress_bar.set_total(downloaded_size);
m_progress_bar.set_full();
m_progress_bar.set_postfix("downloaded");
}
bool ret = true;
if (m_finalize_callback)
ret = m_finalize_callback();
else
{
if (m_has_progress_bar)
{
m_progress_bar.mark_as_completed("Downloaded " + m_name);
}
m_progress_bar.mark_as_completed();
else
Console::instance().print(name() + " completed");
}
return true;
if (m_has_progress_bar)
{
// make sure total value is up-to-date
m_progress_bar.update_repr(false);
// select field to display and make sure they are
// properly set if not yet printed by the progress bar manager
ProgressBarRepr r = m_progress_bar.repr();
r.prefix.set_format("{:<35}").reset_width();
r.progress.deactivate();
r.current.deactivate();
r.separator.deactivate();
auto console_stream = Console::stream();
r.print(console_stream, 0, false);
}
return ret;
}
/**************************************
@ -622,7 +679,7 @@ namespace mamba
if (msg->msg == CURLMSG_DONE)
{
LOG_INFO << "Transfer done ...";
LOG_INFO << "Transfer done for '" << current_target->name() << "'";
// We are only interested in messages about finished transfers
curl_multi_remove_handle(m_handle, current_target->handle());
@ -632,7 +689,7 @@ namespace mamba
// transfer did not work! can we retry?
if (current_target->can_retry())
{
LOG_INFO << "Adding target to retry!";
LOG_INFO << "Setting retry for '" << current_target->name() << "'";
m_retry_targets.push_back(current_target);
}
else
@ -648,10 +705,40 @@ namespace mamba
return true;
}
bool MultiDownloadTarget::download(bool failfast)
bool MultiDownloadTarget::download(int options)
{
bool failfast = options & MAMBA_DOWNLOAD_FAILFAST;
bool sort = options & MAMBA_DOWNLOAD_SORT;
auto& ctx = Context::instance();
if (m_targets.empty())
{
LOG_INFO << "All targets to download are cached";
return true;
}
if (sort)
std::sort(m_targets.begin(),
m_targets.end(),
[](DownloadTarget* a, DownloadTarget* b) -> bool {
return a->expected_size() > b->expected_size();
});
LOG_INFO << "Starting to download targets";
auto& pbar_manager = Console::instance().progress_bar_manager();
interruption_guard g([]() { Console::instance().progress_bar_manager().terminate(); });
// be sure the progress bar manager was not already started
// it would mean this code is part of a larger process using progress bars
bool pbar_manager_started = pbar_manager.started();
if (!(ctx.no_progress_bars || ctx.json || ctx.quiet || pbar_manager_started))
{
pbar_manager.start();
pbar_manager.watch_print();
}
int still_running, repeats = 0;
const long max_wait_msecs = 1000;
do
@ -723,6 +810,13 @@ namespace mamba
curl_multi_cleanup(m_handle);
return false;
}
if (!(ctx.no_progress_bars || ctx.json || ctx.quiet || pbar_manager_started))
{
pbar_manager.terminate();
pbar_manager.clear_progress_bars();
}
return true;
}
} // namespace mamba

View File

@ -26,53 +26,6 @@
namespace mamba
{
std::ostream& write_duration(std::ostream& os, std::chrono::nanoseconds ns)
{
using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::hours;
using std::chrono::minutes;
using std::chrono::seconds;
using days = duration<int, std::ratio<86400>>;
char fill = os.fill();
os.fill('0');
auto d = duration_cast<days>(ns);
ns -= d;
auto h = duration_cast<hours>(ns);
ns -= h;
auto m = duration_cast<minutes>(ns);
ns -= m;
auto s = duration_cast<seconds>(ns);
if (d.count() > 0)
{
os << std::setw(2) << d.count() << "d:";
}
if (h.count() > 0)
{
os << std::setw(2) << h.count() << "h:";
}
os << std::setw(2) << m.count() << "m:" << std::setw(2) << s.count() << 's';
os.fill(fill);
return os;
}
int get_console_width()
{
#ifndef _WIN32
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
return w.ws_col;
#else
CONSOLE_SCREEN_BUFFER_INFO coninfo;
auto res = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
return coninfo.dwSize.X;
#endif
return -1;
}
std::string cut_repo_name(const std::string& full_url)
{
std::string remaining_url, scheme, auth, token;
@ -297,8 +250,8 @@ namespace mamba
Console::Console()
: m_mutex()
, p_progress_manager(make_progress_bar_manager(ProgressBarMode::multi))
{
init_progress_bar_manager(ProgressBarMode::multi);
#ifdef _WIN32
// initialize ANSI codes on Win terminals
auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
@ -339,10 +292,10 @@ namespace mamba
if (!(Context::instance().quiet || Context::instance().json) || force_print)
{
const std::lock_guard<std::mutex> lock(instance().m_mutex);
if (instance().p_progress_manager)
if (instance().p_progress_bar_manager && instance().p_progress_bar_manager->started())
{
instance().p_progress_manager->print(instance().hide_secrets(str),
instance().skip_progress_bars());
instance().m_buffer.push_back(instance().hide_secrets(str));
}
else
{
@ -351,6 +304,17 @@ namespace mamba
}
}
std::vector<std::string> Console::m_buffer({});
void Console::print_buffer(std::ostream& ostream)
{
for (auto& message : m_buffer)
ostream << message << "\n";
const std::lock_guard<std::mutex> lock(instance().m_mutex);
m_buffer.clear();
}
bool Console::prompt(const std::string_view& message, char fallback, std::istream& input_stream)
{
if (Context::instance().always_yes)
@ -399,35 +363,31 @@ namespace mamba
ProgressProxy Console::add_progress_bar(const std::string& name, size_t expected_total)
{
return p_progress_manager->add_progress_bar(name, expected_total);
if (Context::instance().no_progress_bars)
return ProgressProxy();
else
return p_progress_bar_manager->add_progress_bar(name, expected_total);
}
void Console::init_multi_progress(ProgressBarMode mode)
void Console::clear_progress_bars()
{
p_progress_manager = make_progress_bar_manager(mode);
return p_progress_bar_manager->clear_progress_bars();
}
void Console::deactivate_progress_bar(std::size_t idx, const std::string_view& msg)
ProgressBarManager& Console::init_progress_bar_manager(ProgressBarMode mode)
{
std::lock_guard<std::mutex> lock(instance().m_mutex);
p_progress_manager->deactivate_progress_bar(idx, msg);
p_progress_bar_manager = make_progress_bar_manager(mode);
p_progress_bar_manager->register_print_hook(Console::print_buffer);
p_progress_bar_manager->register_print_hook(MessageLogger::print_buffer);
p_progress_bar_manager->register_pre_start_hook(MessageLogger::activate_buffer);
p_progress_bar_manager->register_post_stop_hook(MessageLogger::deactivate_buffer);
return *p_progress_bar_manager;
}
void Console::print_progress(std::size_t idx)
ProgressBarManager& Console::progress_bar_manager()
{
if (skip_progress_bars())
{
return;
}
std::lock_guard<std::mutex> lock(instance().m_mutex);
p_progress_manager->print_progress(idx);
}
bool Console::skip_progress_bars() const
{
return Context::instance().quiet || Context::instance().json
|| Context::instance().no_progress_bars;
return *p_progress_bar_manager;
}
std::string strip_file_prefix(const std::string& file)
@ -455,8 +415,25 @@ namespace mamba
MessageLogger::~MessageLogger()
{
auto str = Console::hide_secrets(m_stream.str());
switch (m_level)
if (!use_buffer)
emit(m_stream.str(), m_level);
else
{
const std::lock_guard<std::mutex> lock(m_mutex);
m_buffer.push_back({ m_stream.str(), m_level });
}
}
std::mutex MessageLogger::m_mutex;
bool MessageLogger::use_buffer(false);
std::vector<std::pair<std::string, spdlog::level::level_enum>> MessageLogger::m_buffer({});
void MessageLogger::emit(const std::string& msg, const spdlog::level::level_enum& level)
{
auto str = Console::hide_secrets(msg);
switch (level)
{
case spdlog::level::critical:
SPDLOG_CRITICAL(prepend(str, "", std::string(4, ' ').c_str()));
@ -488,11 +465,31 @@ namespace mamba
return m_stream;
}
void MessageLogger::activate_buffer()
{
use_buffer = true;
}
void MessageLogger::deactivate_buffer()
{
use_buffer = false;
}
void MessageLogger::print_buffer(std::ostream& /*ostream*/)
{
for (auto& [msg, level] : m_buffer)
emit(msg, level);
spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->flush(); });
const std::lock_guard<std::mutex> lock(m_mutex);
m_buffer.clear();
}
Logger::Logger(const std::string& pattern)
: spdlog::logger(std::string("mamba"),
std::make_shared<spdlog::sinks::stderr_color_sink_mt>())
{
// set_pattern("%^[%L %Y-%m-%d %T:%e]%$ %v");
set_pattern(pattern);
}

File diff suppressed because it is too large Load Diff

View File

@ -68,7 +68,8 @@ namespace mamba
const std::string& repodata_fn,
MultiPackageCache& caches,
bool is_noarch)
: m_loaded(false)
: m_progress_bar(ProgressProxy())
, m_loaded(false)
, m_download_complete(false)
, m_repodata_url(repodata_url)
, m_name(name)
@ -198,7 +199,7 @@ namespace mamba
{
std::string prefix = m_name;
prefix.resize(PREFIX_LENGTH - 1, ' ');
Console::stream() << prefix << " Using cache";
Console::stream() << fmt::format("{:<35} Using cache", prefix);
}
else
{
@ -242,9 +243,13 @@ namespace mamba
{
LOG_INFO << "Unable to retrieve repodata (response: " << m_target->http_status
<< ") for '" << m_repodata_url << "'";
m_progress_bar.set_postfix(std::to_string(m_target->http_status) + " Failed");
m_progress_bar.set_full();
m_progress_bar.mark_as_completed();
if (m_progress_bar)
{
m_progress_bar.set_postfix(std::to_string(m_target->http_status) + " failed");
m_progress_bar.set_full();
m_progress_bar.mark_as_completed();
}
m_loaded = false;
return false;
}
@ -314,7 +319,6 @@ namespace mamba
m_valid_cache_path = writable_cache_path;
}
{
LOG_TRACE << "Refreshing '" << json_file.string() << "'";
auto lock = LockFile(json_file);
@ -328,9 +332,17 @@ namespace mamba
m_solv_cache_valid = true;
}
m_progress_bar.set_postfix("No change");
m_progress_bar.set_full();
m_progress_bar.mark_as_completed();
if (m_progress_bar)
{
m_progress_bar.set_postfix("No change");
m_progress_bar.mark_as_completed();
auto& r = m_progress_bar.repr();
r.prefix.set_format("{:<35}");
r.total.deactivate();
r.speed.deactivate();
r.elapsed.deactivate();
}
m_json_cache_valid = true;
m_loaded = true;
@ -370,11 +382,13 @@ namespace mamba
if (ends_with(m_repodata_url, ".bz2"))
{
m_progress_bar.set_postfix("Decomp...");
if (m_progress_bar)
m_progress_bar.set_postfix("decompressing");
decompress();
}
m_progress_bar.set_postfix("Finalizing...");
if (m_progress_bar)
m_progress_bar.set_postfix("finalizing");
std::ifstream temp_file = open_ifstream(m_temp_file->path());
std::stringstream temp_json;
@ -397,9 +411,11 @@ namespace mamba
exit(1);
}
m_progress_bar.set_postfix("Done");
m_progress_bar.set_full();
m_progress_bar.mark_as_completed();
if (m_progress_bar)
{
m_progress_bar.repr().postfix.set_value("downloaded").deactivate();
m_progress_bar.mark_as_completed();
}
m_valid_cache_path = writable_cache_path;
m_json_cache_valid = true;
@ -429,10 +445,14 @@ namespace mamba
void MSubdirData::create_target(nlohmann::json& mod_etag)
{
auto& ctx = Context::instance();
m_temp_file = std::make_unique<TemporaryFile>();
m_progress_bar = Console::instance().add_progress_bar(m_name);
m_target = std::make_unique<DownloadTarget>(m_name, m_repodata_url, m_temp_file->path());
m_target->set_progress_bar(m_progress_bar);
if (!(ctx.no_progress_bars || ctx.quiet || ctx.json))
{
m_progress_bar = Console::instance().add_progress_bar(m_name);
m_target->set_progress_bar(m_progress_bar);
}
// if we get something _other_ than the noarch, we DO NOT throw if the file
// can't be retrieved
if (!m_is_noarch)

View File

@ -75,6 +75,9 @@ namespace mamba
m_expected_size = pkg_info.size;
m_sha256 = pkg_info.sha256;
m_md5 = pkg_info.md5;
auto& ctx = Context::instance();
m_has_progress_bars = !(ctx.no_progress_bars || ctx.quiet || ctx.json);
}
void PackageDownloadExtractTarget::write_repodata_record(const fs::path& base_path)
@ -114,7 +117,12 @@ namespace mamba
LOG_ERROR << "File not valid: file size doesn't match expectation " << m_tarball_path
<< "\nExpected: " << m_expected_size
<< "\nActual: " << size_t(m_target->downloaded_size) << "\n";
m_progress_proxy.mark_as_completed("File size validation error.");
if (m_has_progress_bars)
{
m_download_bar.set_postfix("validation failed");
m_download_bar.mark_as_completed();
}
Console::instance().print(m_filename + " tarball has incorrect size");
m_validation_result = SIZE_ERROR;
return;
}
@ -126,7 +134,12 @@ namespace mamba
if (m_sha256 != sha256sum)
{
m_validation_result = SHA256_ERROR;
m_progress_proxy.mark_as_completed("SHA256 sum validation error.");
if (m_has_progress_bars)
{
m_download_bar.set_postfix("validation failed");
m_download_bar.mark_as_completed();
}
Console::instance().print(m_filename + " tarball has incorrect checksum");
LOG_ERROR << "File not valid: SHA256 sum doesn't match expectation "
<< m_tarball_path << "\nExpected: " << m_sha256
<< "\nActual: " << sha256sum << "\n";
@ -139,23 +152,50 @@ namespace mamba
if (m_md5 != md5sum)
{
m_validation_result = MD5SUM_ERROR;
m_progress_proxy.mark_as_completed("MD5 sum validation error.");
if (m_has_progress_bars)
{
m_download_bar.set_postfix("validation failed");
m_download_bar.mark_as_completed();
}
Console::instance().print(m_filename + " tarball has incorrect checksum");
LOG_ERROR << "File not valid: MD5 sum doesn't match expectation " << m_tarball_path
<< "\nExpected: " << m_md5 << "\nActual: " << md5sum << "\n";
}
}
}
std::function<void(ProgressBarRepr&)> PackageDownloadExtractTarget::extract_repr()
{
return [&](ProgressBarRepr& r) -> void {
if (r.progress_bar().started())
r.postfix.set_value("extracting");
else
r.postfix.set_value("extracted");
};
}
std::function<void(ProgressProxy&)> PackageDownloadExtractTarget::extract_progress_callback()
{
return [&](ProgressProxy& bar) -> void {
if (bar.started())
bar.set_progress(0, 1);
};
}
bool PackageDownloadExtractTarget::extract()
{
// Extracting is __not__ yet thread safe it seems...
interruption_point();
if (m_has_progress_bars)
m_extract_bar.start();
LOG_DEBUG << "Waiting for decompression " << m_tarball_path;
m_progress_proxy.set_postfix("Waiting...");
if (m_has_progress_bars)
m_extract_bar.update_progress(0, 1);
{
std::lock_guard<counting_semaphore> lock(DownloadExtractSemaphore::semaphore);
interruption_point();
m_progress_proxy.set_postfix("Decompressing...");
LOG_DEBUG << "Decompressing '" << m_tarball_path.string() << "'";
fs::path extract_path;
try
@ -185,69 +225,77 @@ namespace mamba
LOG_DEBUG << "Extracted to '" << extract_path.string() << "'";
write_repodata_record(extract_path);
add_url();
if (m_has_progress_bars)
{
m_extract_bar.set_full();
m_extract_bar.mark_as_completed();
}
}
catch (std::exception& e)
{
Console::instance().print(m_filename + " extraction failed");
LOG_ERROR << "Error when extracting package: " << e.what();
m_decompress_exception = e;
m_validation_result = VALIDATION_RESULT::EXTRACT_ERROR;
m_finished = true;
m_progress_proxy.mark_as_completed("Extraction error");
if (m_has_progress_bars)
{
m_extract_bar.set_postfix("extraction failed");
m_extract_bar.mark_as_completed();
}
return false;
}
}
m_finished = true;
return m_finished;
return true;
}
bool PackageDownloadExtractTarget::extract_from_cache()
{
bool result = this->extract();
if (result)
{
std::stringstream final_msg;
final_msg << "Extracted " << std::left << std::setw(30) << m_name;
m_progress_proxy.mark_as_completed(final_msg.str());
return result;
}
// currently we always return true
this->extract();
m_finished = true;
return true;
}
bool PackageDownloadExtractTarget::validate_extract()
{
using std::chrono::nanoseconds;
if (m_has_progress_bars)
{
m_extract_bar.start();
m_extract_bar.set_postfix("validating");
}
validate();
// Validation
if (m_validation_result != VALIDATION_RESULT::VALID)
{
if (m_has_progress_bars)
m_extract_bar.set_postfix("validation failed");
LOG_WARNING << "'" << m_tarball_path.string() << "' validation failed";
// abort here, but set finished to true
m_finished = true;
return true;
}
std::stringstream final_msg;
final_msg << "Finished " << std::left << std::setw(30) << m_name << std::right
<< std::setw(8);
m_progress_proxy.elapsed_time_to_stream(final_msg);
final_msg << " " << std::setw(12 + 2);
to_human_readable_filesize(final_msg, m_expected_size);
final_msg << " " << std::setw(6);
to_human_readable_filesize(final_msg, m_target->avg_speed);
final_msg << "/s";
m_progress_proxy.mark_as_completed(final_msg.str());
if (m_has_progress_bars)
m_extract_bar.set_postfix("validated");
LOG_DEBUG << "'" << m_tarball_path.string() << "' successfully validated";
bool result = this->extract();
m_progress_proxy.mark_as_extracted();
m_finished = true;
return result;
}
bool PackageDownloadExtractTarget::finalize_callback()
{
m_progress_proxy.set_full();
m_progress_proxy.set_postfix("Validating...");
if (m_has_progress_bars)
{
m_download_bar.repr().postfix.set_value("downloaded").deactivate();
m_download_bar.mark_as_completed();
}
LOG_INFO << "Download finished, validating '" << m_tarball_path.string() << "'";
thread v(&PackageDownloadExtractTarget::validate_extract, this);
v.detach();
@ -279,6 +327,11 @@ namespace mamba
return m_name;
}
std::size_t PackageDownloadExtractTarget::expected_size() const
{
return m_expected_size;
}
// todo remove cache from this interface
DownloadTarget* PackageDownloadExtractTarget::target(MultiPackageCache& caches)
{
@ -296,15 +349,25 @@ namespace mamba
caches.first_writable_cache(true).clear_query_cache(m_package_info);
m_cache_path = caches.first_writable_path();
if (m_has_progress_bars)
{
m_extract_bar = Console::instance().add_progress_bar(m_name, 1);
m_extract_bar.activate_spinner();
m_extract_bar.set_progress_hook(extract_progress_callback());
m_extract_bar.set_repr_hook(extract_repr());
Console::instance().progress_bar_manager().add_label("Extract", m_extract_bar);
}
if (!tarball_cache.empty())
{
LOG_DEBUG << "Found valid tarball cache at '" << tarball_cache.string() << "'";
m_tarball_path = tarball_cache / m_filename;
m_progress_proxy = Console::instance().add_progress_bar(m_name);
m_validation_result = VALIDATION_RESULT::VALID;
thread v(&PackageDownloadExtractTarget::extract_from_cache, this);
v.detach();
LOG_DEBUG << "Using cached tarball '" << m_filename << "'";
return nullptr;
}
else
{
@ -313,16 +376,21 @@ namespace mamba
LOG_DEBUG << "Adding '" << m_name << "' to download targets from '" << m_url << "'";
m_tarball_path = m_cache_path / m_filename;
m_progress_proxy = Console::instance().add_progress_bar(m_name, m_expected_size);
m_target = std::make_unique<DownloadTarget>(m_name, m_url, m_tarball_path);
m_target->set_finalize_callback(&PackageDownloadExtractTarget::finalize_callback,
this);
m_target->set_expected_size(m_expected_size);
m_target->set_progress_bar(m_progress_proxy);
if (m_has_progress_bars)
{
m_download_bar = Console::instance().add_progress_bar(m_name, m_expected_size);
m_target->set_progress_bar(m_download_bar);
Console::instance().progress_bar_manager().add_label("Download",
m_download_bar);
}
return m_target.get();
}
}
LOG_INFO << "Using cached '" << m_name << "'";
LOG_DEBUG << "Using cached '" << m_name << "'";
m_finished = true;
return nullptr;
}
@ -350,8 +418,10 @@ namespace mamba
MTransaction::MTransaction(MPool& pool,
const std::vector<MatchSpec>& specs_to_remove,
const std::vector<MatchSpec>& specs_to_install,
MultiPackageCache& caches)
MultiPackageCache& caches,
std::vector<MRepo*> repos)
: m_multi_cache(caches)
, m_repos(repos)
{
// auto& ctx = Context::instance();
std::vector<PackageInfo> pi_result;
@ -453,8 +523,11 @@ namespace mamba
}
MTransaction::MTransaction(MSolver& solver, MultiPackageCache& caches)
MTransaction::MTransaction(MSolver& solver,
MultiPackageCache& caches,
std::vector<MRepo*> repos)
: m_multi_cache(caches)
, m_repos(repos)
{
if (!solver.is_solved())
{
@ -696,10 +769,12 @@ namespace mamba
}
LockFile lf(ctx.target_prefix / "conda-meta");
clean_trash_files(ctx.target_prefix, false);
Console::stream() << "\nTransaction starting";
fetch_extract_packages();
// m_transaction_context = TransactionContext(prefix.path(), find_python_version());
History::UserRequest ur = History::UserRequest::prefilled();
TransactionRollback rollback;
@ -875,12 +950,14 @@ namespace mamba
add_json(to_unlink, "UNLINK");
}
bool MTransaction::fetch_extract_packages(std::vector<MRepo*>& repos)
bool MTransaction::fetch_extract_packages()
{
std::vector<std::unique_ptr<PackageDownloadExtractTarget>> targets;
MultiDownloadTarget multi_dl;
Console::instance().init_multi_progress(ProgressBarMode::aggregated);
auto& pbar_manager
= Console::instance().init_progress_bar_manager(ProgressBarMode::aggregated);
auto& aggregated_pbar_manager = dynamic_cast<AggregatedBarManager&>(pbar_manager);
auto& ctx = Context::instance();
DownloadExtractSemaphore::set_max(ctx.extract_threads);
@ -892,7 +969,7 @@ namespace mamba
{
std::string url;
MRepo* mamba_repo = nullptr;
for (auto& r : repos)
for (auto& r : m_repos)
{
if (r->repo() == s->repo)
{
@ -900,6 +977,7 @@ namespace mamba
break;
}
}
if (mamba_repo == nullptr || mamba_repo->url() == "")
{
// use fallback mediadir / mediafile
@ -923,7 +1001,9 @@ namespace mamba
}
targets.emplace_back(std::make_unique<PackageDownloadExtractTarget>(s));
multi_dl.add(targets[targets.size() - 1]->target(m_multi_cache));
DownloadTarget* download_target = targets.back()->target(m_multi_cache);
if (download_target != nullptr)
multi_dl.add(download_target);
}
if (ctx.experimental && ctx.verify_artifacts)
@ -932,9 +1012,75 @@ namespace mamba
<< "package(s) are trusted " << termcolor::reset;
LOG_INFO << "All package(s) are trusted";
}
interruption_guard g([]() { Console::instance().init_multi_progress(); });
bool downloaded = multi_dl.download(true);
if (!(ctx.no_progress_bars || ctx.json || ctx.quiet))
{
interruption_guard g([]() { Console::instance().progress_bar_manager().terminate(); });
auto* dl_bar = aggregated_pbar_manager.aggregated_bar("Download");
if (dl_bar)
dl_bar->set_repr_hook([&](ProgressBarRepr& repr) -> void {
auto active_tasks = dl_bar->active_tasks().size();
if (active_tasks == 0)
{
repr.prefix.set_value(fmt::format("{:<16}", "Downloading"));
repr.postfix.set_value(fmt::format("{:<25}", ""));
}
else
{
repr.prefix.set_value(fmt::format(
"{:<11} {:>4}", "Downloading", fmt::format("({})", active_tasks)));
repr.postfix.set_value(fmt::format("{:<25}", dl_bar->last_active_task()));
}
repr.current.set_value(
fmt::format("{:>7}", to_human_readable_filesize(dl_bar->current(), 1)));
repr.separator.set_value("/");
std::string total_str;
if (dl_bar->total() == std::numeric_limits<std::size_t>::max())
total_str = "??.?MB";
else
total_str = to_human_readable_filesize(dl_bar->total(), 1);
repr.total.set_value(fmt::format("{:>7}", total_str));
auto speed = dl_bar->avg_speed(std::chrono::milliseconds(500));
repr.speed.set_value(
speed ? fmt::format("@ {:>7}/s", to_human_readable_filesize(speed, 1))
: "");
});
auto* extract_bar = aggregated_pbar_manager.aggregated_bar("Extract");
if (extract_bar)
extract_bar->set_repr_hook([&](ProgressBarRepr& repr) -> void {
auto active_tasks = extract_bar->active_tasks().size();
if (active_tasks == 0)
{
repr.prefix.set_value(fmt::format("{:<16}", "Extracting"));
repr.postfix.set_value(fmt::format("{:<25}", ""));
}
else
{
repr.prefix.set_value(fmt::format(
"{:<11} {:>4}", "Extracting", fmt::format("({})", active_tasks)));
repr.postfix.set_value(
fmt::format("{:<25}", extract_bar->last_active_task()));
}
repr.current.set_value(fmt::format("{:>3}", extract_bar->current()));
repr.separator.set_value("/");
std::string total_str;
if (extract_bar->total() == std::numeric_limits<std::size_t>::max())
total_str = "?";
else
total_str = std::to_string(extract_bar->total());
repr.total.set_value(fmt::format("{:>3}", total_str));
});
pbar_manager.start();
pbar_manager.watch_print();
}
bool downloaded = multi_dl.download(MAMBA_DOWNLOAD_FAILFAST | MAMBA_DOWNLOAD_SORT);
bool all_valid = true;
if (!downloaded)
@ -961,6 +1107,12 @@ namespace mamba
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (!(ctx.no_progress_bars || ctx.json || ctx.quiet))
{
pbar_manager.terminate();
pbar_manager.clear_progress_bars();
}
for (const auto& t : targets)
{
if (t->validation_result() != PackageDownloadExtractTarget::VALIDATION_RESULT::VALID
@ -982,20 +1134,13 @@ namespace mamba
return m_to_install.size() == 0 && m_to_remove.size() == 0;
}
bool MTransaction::prompt(std::vector<MRepo*>& repos)
bool MTransaction::prompt()
{
print();
if (Context::instance().dry_run || empty())
{
return true;
}
bool res = Console::prompt("Confirm changes", 'y');
if (res)
{
return fetch_extract_packages(repos);
}
return res;
return Console::prompt("Confirm changes", 'y');
}
void MTransaction::print()
@ -1241,7 +1386,8 @@ namespace mamba
MTransaction create_explicit_transaction_from_urls(MPool& pool,
const std::vector<std::string>& urls,
MultiPackageCache& package_caches)
MultiPackageCache& package_caches,
std::vector<MRepo*>& repos)
{
std::vector<MatchSpec> specs_to_install;
for (auto& u : urls)
@ -1267,6 +1413,6 @@ namespace mamba
}
specs_to_install.push_back(ms);
}
return MTransaction(pool, {}, specs_to_install, package_caches);
return MTransaction(pool, {}, specs_to_install, package_caches, repos);
}
} // namespace mamba

View File

@ -74,18 +74,6 @@ namespace mamba
return status.type() != fs::file_type::not_found || status.type() == fs::file_type::symlink;
}
void to_human_readable_filesize(std::ostream& o, double bytes, std::size_t precision)
{
const char* sizes[] = { " B", " KB", " MB", " GB", " TB" };
int order = 0;
while (bytes >= 1024 && order < (5 - 1))
{
order++;
bytes = bytes / 1024;
}
o << std::fixed << std::setprecision(precision) << bytes << sizes[order];
}
std::vector<fs::path> filter_dir(const fs::path& dir, const std::string& suffix)
{
std::vector<fs::path> result;

View File

@ -6,23 +6,25 @@ find_package(Threads REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS} SYSTEM)
set(TEST_SRCS
test_activation.cpp
test_channel.cpp
test_configuration.cpp
test_cpp.cpp
test_url.cpp
history_test/test_history.cpp
test_shell_init.cpp
test_activation.cpp
test_string_methods.cpp
test_env_file_reading.cpp
test_environments_manager.cpp
test_transfer.cpp
test_thread_utils.cpp
test_graph.cpp
history_test/test_history.cpp
test_lockfile.cpp
test_pinning.cpp
test_output.cpp
test_progress_bar.cpp
test_shell_init.cpp
test_string_methods.cpp
test_thread_utils.cpp
test_transfer.cpp
test_url.cpp
test_validate.cpp
test_virtual_packages.cpp
test_env_file_reading.cpp
test_lockfile.cpp
)
message(STATUS "Building libmamba C++ tests")

View File

@ -255,19 +255,6 @@ namespace mamba
"http://root:*****@myweb.com/test.repo\nhttp://myweb.com/t/*****/test.repo http://myweb.com/t/*****/test.repo http://root:*****@myweb.com/test.repo");
}
TEST(output, no_progress_bars)
{
Context::instance().no_progress_bars = true;
testing::internal::CaptureStdout();
auto proxy = Console::instance().add_progress_bar("conda-forge");
proxy.set_progress(50, 100);
proxy.set_postfix("Downloading");
proxy.mark_as_completed("conda-forge channel downloaded");
std::string output = testing::internal::GetCapturedStdout();
EXPECT_TRUE(ends_with(output, "conda-forge channel downloaded\n"));
Context::instance().no_progress_bars = false;
}
class OutputPromptTests : public testing::TestWithParam<std::tuple<std::string, char, bool>>
{

View File

@ -0,0 +1,24 @@
#include <gtest/gtest.h>
#include <sstream>
#include <tuple>
#include "mamba/core/context.hpp"
#include "mamba/core/output.hpp"
namespace mamba
{
TEST(output, no_progress_bars)
{
Context::instance().no_progress_bars = true;
auto proxy = Console::instance().add_progress_bar("conda-forge");
EXPECT_FALSE(proxy.defined());
EXPECT_FALSE(proxy);
Context::instance().no_progress_bars = false;
proxy = Console::instance().add_progress_bar("conda-forge");
EXPECT_TRUE(proxy.defined());
EXPECT_TRUE(proxy);
}
} // namespace mamba

View File

@ -0,0 +1,381 @@
#include <gtest/gtest.h>
#include <sstream>
#include <tuple>
#include "mamba/core/progress_bar.hpp"
namespace mamba
{
class progress_bar : public ::testing::Test
{
public:
progress_bar()
{
p_progress_bar_manager = std::make_unique<MultiBarManager>();
proxy = p_progress_bar_manager->add_progress_bar("conda-forge");
auto& r = proxy.repr();
r.progress.set_value("??");
r.current.set_value("foo");
r.separator.set_value("-");
r.total.set_value("bar");
r.speed.set_value("@10");
r.postfix.set_value("downloading");
r.elapsed.set_value("0.1s");
}
protected:
std::unique_ptr<ProgressBarManager> p_progress_bar_manager;
ProgressProxy proxy;
std::ostringstream ostream;
};
TEST_F(progress_bar, print)
{
auto& r = proxy.repr();
EXPECT_TRUE(r.prefix.active());
EXPECT_EQ(r.prefix.value(), "conda-forge");
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_TRUE(r.progress);
EXPECT_EQ(r.progress.value(), "??");
EXPECT_EQ(r.progress.width(), 2);
EXPECT_TRUE(r.separator);
EXPECT_EQ(r.separator.value(), "-");
EXPECT_EQ(r.separator.width(), 1);
EXPECT_TRUE(r.total);
EXPECT_EQ(r.total.value(), "bar");
EXPECT_EQ(r.total.width(), 3);
EXPECT_TRUE(r.speed);
EXPECT_EQ(r.speed.value(), "@10");
EXPECT_EQ(r.speed.width(), 3);
EXPECT_TRUE(r.postfix.active());
EXPECT_EQ(r.postfix.value(), "downloading");
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_TRUE(r.elapsed.active());
EXPECT_EQ(r.elapsed.value(), "0.1s");
EXPECT_EQ(r.elapsed.width(), 4);
proxy.print(ostream, 0, false);
EXPECT_EQ(ostream.str(), "conda-forge ?? foo - bar @10 downloading 0.1s");
ostream.str("");
r.set_width(21); // no impact if 'update_repr' not called
proxy.print(ostream, 0, false);
EXPECT_EQ(ostream.str(), "conda-forge ?? foo - bar @10 downloading 0.1s");
ostream.str("");
}
TEST_F(progress_bar, print_no_resize)
{
auto& r = proxy.repr();
r.set_width(150);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_TRUE(r.separator);
EXPECT_TRUE(r.total);
EXPECT_TRUE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 106);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.separator.width(), 1);
EXPECT_EQ(r.total.width(), 3);
EXPECT_EQ(r.speed.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
}
TEST_F(progress_bar, print_reduce_bar)
{
auto& r = proxy.repr();
r.set_width(84);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_TRUE(r.separator);
EXPECT_TRUE(r.total);
EXPECT_TRUE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 40);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.separator.width(), 1);
EXPECT_EQ(r.total.width(), 3);
EXPECT_EQ(r.speed.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
// 1: reduce bar width
// available space redistributed to the bar
r.set_width(83);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_TRUE(r.separator);
EXPECT_TRUE(r.total);
EXPECT_TRUE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 39);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.separator.width(), 1);
EXPECT_EQ(r.total.width(), 3);
EXPECT_EQ(r.speed.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
}
TEST_F(progress_bar, print_remove_total_sep)
{
auto& r = proxy.repr();
r.set_width(59);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_TRUE(r.separator);
EXPECT_TRUE(r.total);
EXPECT_TRUE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 15);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.separator.width(), 1);
EXPECT_EQ(r.total.width(), 3);
EXPECT_EQ(r.speed.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
// 2: remove the total value and the separator
// available space redistributed to the bar
r.set_width(58);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_TRUE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 20);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.speed.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
}
TEST_F(progress_bar, print_remove_speed)
{
auto& r = proxy.repr();
r.set_width(53);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_TRUE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 15);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.speed.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
// 3: remove the speed
// available space redistributed to the bar
r.set_width(52);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 18);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
}
TEST_F(progress_bar, print_remove_postfix)
{
auto& r = proxy.repr();
r.set_width(49);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_TRUE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 15);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.postfix.width(), 11);
EXPECT_EQ(r.elapsed.width(), 5);
// 4: remove the postfix
// available space redistributed to the bar
r.set_width(48);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_FALSE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 26);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.elapsed.width(), 5);
}
TEST_F(progress_bar, print_truncate_prefix)
{
auto& r = proxy.repr();
proxy.set_prefix("some_very_very_long_prefix");
r.set_width(52);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_FALSE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 26);
EXPECT_EQ(r.progress.width(), 15);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.elapsed.width(), 5);
// 5: truncate the prefix if too long
// available space redistributed to the prefix
r.set_width(51);
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_FALSE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 25);
EXPECT_EQ(r.progress.width(), 15);
EXPECT_EQ(r.current.width(), 3);
EXPECT_EQ(r.elapsed.width(), 5);
}
TEST_F(progress_bar, print_without_bar)
{
auto& r = proxy.repr();
r.set_width(34).reset_fields();
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_TRUE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_FALSE(r.postfix);
EXPECT_TRUE(r.elapsed);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 12);
EXPECT_EQ(r.current.width(), 3);
EXPECT_TRUE(r.progress.overflow());
EXPECT_EQ(r.elapsed.width(), 5);
// 6: display progress without a bar
r.set_width(33);
proxy.update_repr();
proxy.print(ostream, 0, false);
EXPECT_EQ(ostream.str(), "conda-forge 0% foo --");
ostream.str("");
}
TEST_F(progress_bar, print_remove_current)
{
auto& r = proxy.repr();
r.set_width(26).reset_fields();
proxy.update_repr();
proxy.print(ostream, 0, false);
EXPECT_EQ(ostream.str(), "conda-forge 0% foo --");
ostream.str("");
// 7: remove the current value
r.set_width(25).reset_fields();
proxy.update_repr();
proxy.print(ostream, 0, false);
EXPECT_EQ(ostream.str(), "conda-forge 0% --");
ostream.str("");
}
TEST_F(progress_bar, print_remove_elapsed)
{
auto& r = proxy.repr();
r.set_width(22).reset_fields();
proxy.update_repr();
EXPECT_TRUE(r.prefix);
EXPECT_TRUE(r.progress);
EXPECT_FALSE(r.current);
EXPECT_FALSE(r.separator);
EXPECT_FALSE(r.total);
EXPECT_FALSE(r.speed);
EXPECT_FALSE(r.postfix);
EXPECT_TRUE(r.elapsed);
proxy.print(ostream, 0, false);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 4);
EXPECT_EQ(r.elapsed.width(), 5);
EXPECT_EQ(ostream.str(), "conda-forge 0% --");
ostream.str("");
// 8: remove the elapsed time
r.set_width(21);
proxy.update_repr();
proxy.print(ostream, 0, false);
EXPECT_EQ(r.prefix.width(), 11);
EXPECT_EQ(r.progress.width(), 9);
EXPECT_EQ(ostream.str(), "conda-forge 0%");
ostream.str("");
}
} // namespace mamba

View File

@ -17,12 +17,12 @@ namespace mamba
// Ensures the compiler doe snot optimize away Context::instance()
std::string current_command = Context::instance().current_command;
EXPECT_EQ(current_command, "mamba");
Console::instance().init_multi_progress();
Console::instance().init_progress_bar_manager(ProgressBarMode::multi);
{
interruption_guard g([&res]() {
// Test for double free (segfault if that happens)
std::cout << "Interruption guard is interrupting" << std::endl;
Console::instance().init_multi_progress();
Console::instance().init_progress_bar_manager(ProgressBarMode::multi);
{
std::unique_lock<std::mutex> lk(res_mutex);
res -= 100;

View File

@ -23,7 +23,7 @@ namespace mamba
// file:// url should not retry
EXPECT_EQ(cf.target()->can_retry(), false);
multi_dl.download(true);
multi_dl.download(MAMBA_DOWNLOAD_FAILFAST);
// File does not exist
EXPECT_EQ(cf.target()->result, 37);
@ -38,7 +38,7 @@ namespace mamba
true);
cf.load();
multi_dl.add(cf.target());
EXPECT_THROW(multi_dl.download(true), std::runtime_error);
EXPECT_THROW(multi_dl.download(MAMBA_DOWNLOAD_FAILFAST), std::runtime_error);
}
Context::instance().quiet = false;
#endif

View File

@ -82,16 +82,14 @@ PYBIND11_MODULE(bindings, m)
.def("clear", &MRepo::clear);
py::class_<MTransaction>(m, "Transaction")
.def(py::init<MSolver&, MultiPackageCache&>())
.def(py::init<MSolver&, MultiPackageCache&, std::vector<MRepo*>&>())
.def("to_conda", &MTransaction::to_conda)
.def("log_json", &MTransaction::log_json)
.def("print", &MTransaction::print)
.def("fetch_extract_packages", &MTransaction::fetch_extract_packages)
.def("prompt", &MTransaction::prompt)
.def("find_python_version", &MTransaction::find_python_version)
.def("execute", [](MTransaction& self, PrefixData& target_prefix) -> bool {
return self.execute(target_prefix);
});
.def("execute", &MTransaction::execute);
py::class_<MSolver>(m, "Solver")
.def(py::init<MPool&, std::vector<std::pair<int, int>>>())
@ -505,6 +503,10 @@ PYBIND11_MODULE(bindings, m)
m.attr("MAMBA_ONLY_DEPS") = MAMBA_ONLY_DEPS;
m.attr("MAMBA_FORCE_REINSTALL") = MAMBA_FORCE_REINSTALL;
// DOWNLOAD FLAGS
m.attr("MAMBA_DOWNLOAD_FAILFAST") = MAMBA_DOWNLOAD_FAILFAST;
m.attr("MAMBA_DOWNLOAD_SORT") = MAMBA_DOWNLOAD_SORT;
// CLEAN FLAGS
m.attr("MAMBA_CLEAN_ALL") = MAMBA_CLEAN_ALL;
m.attr("MAMBA_CLEAN_INDEX") = MAMBA_CLEAN_INDEX;

View File

@ -8,6 +8,7 @@ import os
import sys
from logging import getLogger
from os.path import isdir, isfile, join
from pathlib import Path
# create support
from conda.base.constants import ChannelPriority, DepsModifier, UpdateModifier
@ -217,10 +218,12 @@ def remove(args, parser):
return exit_code
package_cache = api.MultiPackageCache(context.pkgs_dirs)
transaction = api.Transaction(solver, package_cache)
downloaded = transaction.prompt(repos)
if not downloaded:
transaction = api.Transaction(solver, package_cache, repos)
if not transaction.prompt():
exit(0)
elif not context.dry_run:
transaction.fetch_extract_packages()
mmb_specs, to_link, to_unlink = transaction.to_conda()
transaction.log_json()
@ -552,23 +555,30 @@ def install(args, parser, command="install"):
return exit_code
package_cache = api.MultiPackageCache(context.pkgs_dirs)
transaction = api.Transaction(solver, package_cache)
transaction = api.Transaction(solver, package_cache, repos)
mmb_specs, to_link, to_unlink = transaction.to_conda()
specs_to_add = [MatchSpec(m) for m in mmb_specs[0]]
specs_to_remove = [MatchSpec(m) for m in mmb_specs[1]]
transaction.log_json()
downloaded = transaction.prompt(repos)
if not downloaded:
exit(0)
# if use_mamba_experimental and not os.name == "nt":
if use_mamba_experimental:
if newenv and not isdir(context.target_prefix) and not context.dry_run:
mkdir_p(prefix)
transaction.execute(prefix_data)
if transaction.prompt():
if (
newenv
and not isdir(Path(prefix) / "conda-meta")
and not context.dry_run
):
mkdir_p(Path(prefix) / "conda-meta")
transaction.execute(prefix_data)
else:
if not transaction.prompt():
exit(0)
elif not context.dry_run:
transaction.fetch_extract_packages()
conda_transaction = to_txn(
specs_to_add,
specs_to_remove,

View File

@ -136,15 +136,17 @@ def mamba_install(prefix, specs, args, env, dry_run=False, *_, **kwargs):
exit(1)
package_cache = api.MultiPackageCache(context.pkgs_dirs)
transaction = api.Transaction(solver, package_cache)
transaction = api.Transaction(solver, package_cache, repos)
mmb_specs, to_link, to_unlink = transaction.to_conda()
specs_to_add = [MatchSpec(m) for m in mmb_specs[0]]
transaction.log_json()
downloaded = transaction.prompt(repos)
if not downloaded:
if not transaction.prompt():
exit(0)
elif not context.dry_run:
transaction.fetch_extract_packages()
if prune:
history = api.History(prefix)
history_map = history.get_requested_specs_map()
@ -162,7 +164,9 @@ def mamba_install(prefix, specs, args, env, dry_run=False, *_, **kwargs):
conda_transaction = to_txn(
specs_to_add, [], prefix, to_link, to_unlink, installed_pkg_recs, index
)
handle_txn(conda_transaction, prefix, args, True)
try:
installed_json_f.close()
os.unlink(installed_json_f.name)

View File

@ -100,7 +100,7 @@ def get_index(
)
dlist.add(sd)
is_downloaded = dlist.download(True)
is_downloaded = dlist.download(api.MAMBA_DOWNLOAD_FAILFAST)
if not is_downloaded:
raise RuntimeError("Error downloading repodata.")

View File

@ -115,8 +115,7 @@ def install(*args, default_channel=True, no_rc=True, no_dry_run=False):
cmd += ["--dry-run"]
cmd += ["--log-level=info"]
print(f"Running command {' '.join(cmd)}", file=sys.stderr)
res = subprocess.check_output(cmd, stderr=sys.stderr)
res = subprocess.check_output(cmd)
if "--json" in args:
try: