Skip to content

Commit

Permalink
client/gui: Add polling timer to check for instance state changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Townsend committed Feb 20, 2019
1 parent 0dbc69b commit a59c2a4
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 58 deletions.
159 changes: 105 additions & 54 deletions src/client/gui/gui_cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,63 +75,66 @@ void cmd::GuiCmd::create_actions()

void cmd::GuiCmd::update_menu()
{
std::vector<std::string> instances_to_remove;
tray_icon_menu.removeAction(retrieving_action);

auto reply = list_future.result();
instances_menus.clear();

for (const auto& instance : reply.instances())
for (auto it = instances_entries.cbegin(); it != instances_entries.cend(); ++it)
{
auto state = QString::fromStdString(mp::format::status_string_for(instance.instance_status()));
auto instance = std::find_if(reply.instances().cbegin(), reply.instances().cend(),
[it](const ListVMInstance& instance) { return it->first == instance.name(); });

if (state != "DELETED")
if (instance == reply.instances().cend())
{
auto name = instance.name();
auto action_string = QString("%1 (%2)").arg(QString::fromStdString(name)).arg(state);
instances_to_remove.push_back(it->first);
}
}

instances_menus[name].instance_menu = std::make_unique<QMenu>(action_string);
auto& instance_menu = instances_menus.at(name).instance_menu;
auto& instance_actions = instances_menus.at(name).instance_actions;
for (const auto instance : instances_to_remove)
{
instances_entries.erase(instance);
}

instance_actions.push_back(instance_menu->addAction("Open shell"));
QObject::connect(instance_actions.back(), &QAction::triggered, [name] {
fmt::print("Opening shell for {}\n", name);
mp::cli::platform::open_multipass_shell(QString::fromStdString(name));
});
for (const auto& instance : reply.instances())
{
auto name = instance.name();
auto state = QString::fromStdString(mp::format::status_string_for(instance.instance_status()));

if (state != "RUNNING")
{
instance_actions.back()->setDisabled(true);
}
auto it = instances_entries.find(name);

if (state == "RUNNING")
if (it != instances_entries.end())
{
auto instance_state = it->second.state;
if (state == "DELETED")
{
instance_actions.push_back(instance_menu->addAction("Suspend"));
QObject::connect(instance_actions.back(), &QAction::triggered, [this, name] {
fmt::print("Suspending {}\n", name);
future_synchronizer.addFuture(QtConcurrent::run(this, &GuiCmd::suspend_instance_for, name));
});

instance_actions.push_back(instance_menu->addAction("Stop"));
QObject::connect(instance_actions.back(), &QAction::triggered, [this, name] {
fmt::print("Stopping {}\n", name);
future_synchronizer.addFuture(QtConcurrent::run(this, &GuiCmd::stop_instance_for, name));
});
instances_entries.erase(name);
}
else if (state == "STOPPED" || state == "SUSPENDED")
else if (instance_state != state)
{
instance_actions.push_back(instance_menu->addAction("Start"));
QObject::connect(instance_actions.back(), &QAction::triggered, [this, name] {
fmt::print("Started {}\n", name);
future_synchronizer.addFuture(QtConcurrent::run(this, &GuiCmd::start_instance_for, name));
});
auto& instance_menu = instances_entries.at(name).menu;
auto action_string = QString("%1 (%2)").arg(QString::fromStdString(name)).arg(state);

instance_menu->setTitle(action_string);
instance_menu->clear();
set_menu_actions_for(name, state);
instances_entries[name].state = state;
}

tray_icon_menu.insertMenu(about_separator, instance_menu.get());
continue;
}

if (state != "DELETED")
{
auto action_string = QString("%1 (%2)").arg(QString::fromStdString(name)).arg(state);

instances_entries[name].menu = std::make_unique<QMenu>(action_string);
set_menu_actions_for(name, state);
instances_entries[name].state = state;
}
}

if (instances_menus.empty())
if (instances_entries.empty())
{
about_separator->setVisible(false);
}
Expand All @@ -149,28 +152,35 @@ void cmd::GuiCmd::create_menu()

QObject::connect(&list_watcher, &QFutureWatcher<ListReply>::finished, this, &GuiCmd::update_menu);

QObject::connect(&tray_icon_menu, &QMenu::aboutToShow, [this]() {
if (failure_action.isVisible())
{
tray_icon_menu.removeAction(&failure_action);
}

if (instances_menus.empty())
tray_icon_menu.insertAction(about_separator, retrieving_action);

if (!list_future.isRunning())
{
list_future = QtConcurrent::run(this, &GuiCmd::retrieve_all_instances);
future_synchronizer.addFuture(list_future);
list_watcher.setFuture(list_future);
}
});
QObject::connect(&menu_update_timer, &QTimer::timeout, [this]() { initiate_menu_layout(); });

// Use a singleShot here to make sure the event loop is running before the quit() runs
QObject::connect(quit_action, &QAction::triggered, [this] {
future_synchronizer.waitForFinished();
QTimer::singleShot(0, [] { QCoreApplication::quit(); });
});

initiate_menu_layout();

menu_update_timer.start(1000);
}

void cmd::GuiCmd::initiate_menu_layout()
{
if (failure_action.isVisible())
{
tray_icon_menu.removeAction(&failure_action);
}

if (instances_entries.empty())
tray_icon_menu.insertAction(about_separator, retrieving_action);

if (!list_future.isRunning())
{
list_future = QtConcurrent::run(this, &GuiCmd::retrieve_all_instances);
future_synchronizer.addFuture(list_future);
list_watcher.setFuture(list_future);
}
}

mp::ListReply cmd::GuiCmd::retrieve_all_instances()
Expand All @@ -195,6 +205,47 @@ mp::ListReply cmd::GuiCmd::retrieve_all_instances()
return list_reply;
}

void cmd::GuiCmd::set_menu_actions_for(const std::string& instance_name, const QString& state)
{
auto& instance_menu = instances_entries.at(instance_name).menu;

instance_menu->addAction("Open shell");
QObject::connect(instance_menu->actions().back(), &QAction::triggered, [this, instance_name] {
fmt::print("Opening shell for {}\n", instance_name);
mp::cli::platform::open_multipass_shell(QString::fromStdString(instance_name));
});

if (state != "RUNNING")
{
instance_menu->actions().back()->setDisabled(true);
}

if (state == "RUNNING")
{
instance_menu->addAction("Suspend");
QObject::connect(instance_menu->actions().back(), &QAction::triggered, [this, instance_name] {
fmt::print("Suspending {}\n", instance_name);
future_synchronizer.addFuture(QtConcurrent::run(this, &GuiCmd::suspend_instance_for, instance_name));
});

instance_menu->addAction("Stop");
QObject::connect(instance_menu->actions().back(), &QAction::triggered, [this, instance_name] {
fmt::print("Stopping {}\n", instance_name);
future_synchronizer.addFuture(QtConcurrent::run(this, &GuiCmd::stop_instance_for, instance_name));
});
}
else if (state == "STOPPED" || state == "SUSPENDED")
{
instance_menu->addAction("Start");
QObject::connect(instance_menu->actions().back(), &QAction::triggered, [this, instance_name] {
fmt::print("Started {}\n", instance_name);
future_synchronizer.addFuture(QtConcurrent::run(this, &GuiCmd::start_instance_for, instance_name));
});
}

tray_icon_menu.insertMenu(about_separator, instance_menu.get());
}

void cmd::GuiCmd::start_instance_for(const std::string& instance_name)
{
auto on_success = [](mp::StartReply& reply) { return ReturnCode::Ok; };
Expand Down
13 changes: 9 additions & 4 deletions src/client/gui/gui_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <QMenu>
#include <QObject>
#include <QSystemTrayIcon>
#include <QTimer>

#include <memory>
#include <unordered_map>
Expand Down Expand Up @@ -52,7 +53,9 @@ class GuiCmd final : public QObject, public Command
void create_actions();
void create_menu();
void update_menu();
void initiate_menu_layout();
ListReply retrieve_all_instances();
void set_menu_actions_for(const std::string& instance_name, const QString& state);
void start_instance_for(const std::string& instance_name);
void suspend_instance_for(const std::string& instance_name);
void stop_instance_for(const std::string& instance_name);
Expand All @@ -66,16 +69,18 @@ class GuiCmd final : public QObject, public Command
QAction* quit_action;
QAction failure_action{"Failure retrieving instances"};

struct MenuEntry
struct InstanceEntry
{
std::unique_ptr<QMenu> instance_menu;
std::vector<QAction*> instance_actions;
QString state;
std::unique_ptr<QMenu> menu;
};
std::unordered_map<std::string, MenuEntry> instances_menus;
std::unordered_map<std::string, InstanceEntry> instances_entries;

QFuture<ListReply> list_future;
QFutureWatcher<ListReply> list_watcher;
QFutureSynchronizer<void> future_synchronizer;

QTimer menu_update_timer;
};
} // namespace cmd
} // namespace multipass
Expand Down

0 comments on commit a59c2a4

Please sign in to comment.