mirror of
https://github.com/genodelabs/genode.git
synced 2025-01-18 10:46:25 +00:00
sculpt: browse depot index files in '+' menu
This commit turns the '+' menu into a tool for the following tasks: - Selecting and downloading of depot index files - Browsing of the hierarchical depot index files - Installation of packages found in the index files - Interactive routing configuration of a selected package - Deployment of configured component
This commit is contained in:
parent
b7f5aae64a
commit
c0b93190d0
@ -140,6 +140,7 @@
|
||||
</vfs>
|
||||
<policy label="log" decoration="yes"/>
|
||||
<policy label="runtime -> leitzentrale -> runtime_view" decoration="no" motion="20"/>
|
||||
<policy label_prefix="gui -> popup" decoration="no" motion="20"/>
|
||||
<policy label_prefix="gui" decoration="no"/>
|
||||
<default-policy/>
|
||||
</config>
|
||||
|
@ -261,16 +261,22 @@ class Depot_deploy::Child : public List_model<Child>::Element
|
||||
}
|
||||
}
|
||||
|
||||
void gen_query(Xml_generator &xml) const
|
||||
bool blueprint_needed() const
|
||||
{
|
||||
if (_configured() || _pkg_incomplete)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (_defined_by_launcher() && !_launcher_xml.constructed())
|
||||
return;
|
||||
return false;
|
||||
|
||||
xml.node("blueprint", [&] () {
|
||||
xml.attribute("pkg", _blueprint_pkg_path); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void gen_query(Xml_generator &xml) const
|
||||
{
|
||||
if (blueprint_needed())
|
||||
xml.node("blueprint", [&] () {
|
||||
xml.attribute("pkg", _blueprint_pkg_path); });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,6 +141,14 @@ class Depot_deploy::Children
|
||||
return result;
|
||||
}
|
||||
|
||||
bool any_blueprint_needed() const
|
||||
{
|
||||
bool result = false;
|
||||
_children.for_each([&] (Child const &child) {
|
||||
result |= child.blueprint_needed(); });
|
||||
return result;
|
||||
}
|
||||
|
||||
bool exists(Child::Name const &name) const
|
||||
{
|
||||
bool result = false;
|
||||
|
@ -73,6 +73,14 @@ class Depot_download_manager::Import
|
||||
|
||||
State state = DOWNLOAD_IN_PROGRESS;
|
||||
|
||||
bool in_progress() const
|
||||
{
|
||||
return state == DOWNLOAD_IN_PROGRESS
|
||||
|| state == DOWNLOAD_COMPLETE
|
||||
|| state == VERIFICATION_IN_PROGRESS
|
||||
|| state == VERIFIED;
|
||||
}
|
||||
|
||||
Item(Registry<Item> ®istry, Archive::Path const &path)
|
||||
:
|
||||
_element(registry, *this), path(path)
|
||||
@ -281,6 +289,15 @@ class Depot_download_manager::Import
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool in_progress() const
|
||||
{
|
||||
bool result = false;
|
||||
_items.for_each([&] (Item const &item) {
|
||||
result |= item.in_progress(); });
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _IMPORT_H_ */
|
||||
|
@ -174,6 +174,8 @@ struct Depot_download_manager::Main : Import::Download_progress
|
||||
Job::Update_policy policy(_heap);
|
||||
_jobs.update_from_xml(policy, _installation.xml());
|
||||
|
||||
_depot_query_count.value++;
|
||||
|
||||
_generate_init_config();
|
||||
}
|
||||
|
||||
@ -378,7 +380,6 @@ void Depot_download_manager::Main::_handle_init_state()
|
||||
_verified.update();
|
||||
|
||||
bool reconfigure_init = false;
|
||||
bool import_finished = false;
|
||||
|
||||
if (!_import.constructed())
|
||||
return;
|
||||
@ -450,14 +451,8 @@ void Depot_download_manager::Main::_handle_init_state()
|
||||
if (extract_state.exited && extract_state.code != 0)
|
||||
error("extract failed with exit code ", extract_state.code);
|
||||
|
||||
if (extract_state.exited && extract_state.code == 0) {
|
||||
if (extract_state.exited && extract_state.code == 0)
|
||||
import.all_verified_archives_extracted();
|
||||
import_finished = true;
|
||||
|
||||
/* kill extract, re-issue new depot query to start next iteration */
|
||||
_depot_query_count.value++;
|
||||
reconfigure_init = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* flag failed jobs to prevent re-attempts in subsequent import iterations */
|
||||
@ -470,9 +465,14 @@ void Depot_download_manager::Main::_handle_init_state()
|
||||
if (reconfigure_init)
|
||||
_update_state_report();
|
||||
|
||||
if (import_finished)
|
||||
if (!import.in_progress()) {
|
||||
_import.destruct();
|
||||
|
||||
/* re-issue new depot query to start next iteration */
|
||||
_depot_query_count.value++;
|
||||
reconfigure_init = true;
|
||||
}
|
||||
|
||||
if (reconfigure_init)
|
||||
_generate_init_config();
|
||||
}
|
||||
|
@ -98,37 +98,24 @@ void Sculpt::Deploy::handle_deploy()
|
||||
});
|
||||
});
|
||||
|
||||
/* update query for blueprints of all unconfigured start nodes */
|
||||
if (_arch.valid()) {
|
||||
_depot_query_reporter.generate([&] (Xml_generator &xml) {
|
||||
xml.attribute("arch", _arch);
|
||||
xml.attribute("version", _query_version.value);
|
||||
_children.gen_queries(xml);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply blueprint after 'gen_queries'. Otherwise the application of a
|
||||
* stale blueprint may flag children whose pkgs have been installed in the
|
||||
* meanwhile as incomplete, suppressing their respective queries.
|
||||
*/
|
||||
try {
|
||||
Xml_node const blueprint = _blueprint_rom.xml();
|
||||
|
||||
/* apply blueprint, except when stale */
|
||||
typedef String<32> Version;
|
||||
Version const version = blueprint.attribute_value("version", Version());
|
||||
if (version == Version(_query_version.value))
|
||||
if (version == Version(_depot_query.depot_query_version().value))
|
||||
_children.apply_blueprint(_blueprint_rom.xml());
|
||||
}
|
||||
catch (...) {
|
||||
error("spurious exception during deploy update (apply_blueprint)"); }
|
||||
|
||||
/* update query for blueprints of all unconfigured start nodes */
|
||||
if (_children.any_blueprint_needed())
|
||||
_depot_query.trigger_depot_query();
|
||||
|
||||
/* feed missing packages to installation queue */
|
||||
if (!_installation.try_generate_manually_managed())
|
||||
_installation.generate([&] (Xml_generator &xml) {
|
||||
xml.attribute("arch", _arch);
|
||||
_children.gen_installation_entries(xml); });
|
||||
update_installation();
|
||||
|
||||
/* apply runtime condition checks */
|
||||
update_child_conditions();
|
||||
|
@ -23,10 +23,12 @@
|
||||
|
||||
/* local includes */
|
||||
#include <model/launchers.h>
|
||||
#include <model/download_queue.h>
|
||||
#include <types.h>
|
||||
#include <runtime.h>
|
||||
#include <managed_config.h>
|
||||
#include <view/dialog.h>
|
||||
#include <depot_query.h>
|
||||
|
||||
namespace Sculpt { struct Deploy; }
|
||||
|
||||
@ -43,23 +45,22 @@ struct Sculpt::Deploy
|
||||
|
||||
Runtime_config_generator &_runtime_config_generator;
|
||||
|
||||
Depot_query &_depot_query;
|
||||
|
||||
Attached_rom_dataspace const &_launcher_listing_rom;
|
||||
Attached_rom_dataspace const &_blueprint_rom;
|
||||
|
||||
Download_queue const &_download_queue;
|
||||
|
||||
typedef String<16> Arch;
|
||||
Arch _arch { };
|
||||
|
||||
struct Query_version { unsigned value; } _query_version { 0 };
|
||||
|
||||
Child_state cached_depot_rom_state {
|
||||
"depot_rom", Ram_quota{24*1024*1024}, Cap_quota{200} };
|
||||
|
||||
Child_state uncached_depot_rom_state {
|
||||
"dynamic_depot_rom", Ram_quota{8*1024*1024}, Cap_quota{200} };
|
||||
|
||||
Attached_rom_dataspace _blueprint_rom { _env, "report -> runtime/depot_query/blueprint" };
|
||||
|
||||
Expanding_reporter _depot_query_reporter { _env, "query", "depot_query"};
|
||||
|
||||
/*
|
||||
* Report written to '/config/managed/deploy'
|
||||
*
|
||||
@ -122,7 +123,9 @@ struct Sculpt::Deploy
|
||||
|
||||
bool update_needed() const
|
||||
{
|
||||
return _manual_installation_scheduled || _children.any_incomplete();
|
||||
return _manual_installation_scheduled
|
||||
|| _children.any_incomplete()
|
||||
|| _download_queue.any_active_download();
|
||||
}
|
||||
|
||||
void handle_deploy();
|
||||
@ -130,14 +133,8 @@ struct Sculpt::Deploy
|
||||
void _handle_managed_deploy()
|
||||
{
|
||||
_managed_deploy_rom.update();
|
||||
_query_version.value++;
|
||||
handle_deploy();
|
||||
}
|
||||
|
||||
void _handle_blueprint()
|
||||
{
|
||||
_blueprint_rom.update();
|
||||
handle_deploy();
|
||||
_depot_query.trigger_depot_query();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -191,13 +188,10 @@ struct Sculpt::Deploy
|
||||
Signal_handler<Deploy> _managed_deploy_handler {
|
||||
_env.ep(), *this, &Deploy::_handle_managed_deploy };
|
||||
|
||||
Signal_handler<Deploy> _blueprint_handler {
|
||||
_env.ep(), *this, &Deploy::_handle_blueprint };
|
||||
|
||||
void restart()
|
||||
{
|
||||
/* ignore stale query results */
|
||||
_query_version.value++;
|
||||
_depot_query.trigger_depot_query();
|
||||
|
||||
_children.apply_config(Xml_node("<config/>"));
|
||||
}
|
||||
@ -208,18 +202,41 @@ struct Sculpt::Deploy
|
||||
handle_deploy();
|
||||
}
|
||||
|
||||
void gen_depot_query(Xml_generator &xml) const
|
||||
{
|
||||
_children.gen_queries(xml);
|
||||
}
|
||||
|
||||
void update_installation()
|
||||
{
|
||||
/* feed missing packages to installation queue */
|
||||
if (_installation.try_generate_manually_managed())
|
||||
return;
|
||||
|
||||
_installation.generate([&] (Xml_generator &xml) {
|
||||
xml.attribute("arch", _arch);
|
||||
_children.gen_installation_entries(xml);
|
||||
_download_queue.gen_installation_entries(xml);
|
||||
});
|
||||
}
|
||||
|
||||
Deploy(Env &env, Allocator &alloc, Runtime_info const &runtime_info,
|
||||
Dialog::Generator &dialog_generator,
|
||||
Runtime_config_generator &runtime_config_generator,
|
||||
Attached_rom_dataspace const &launcher_listing_rom)
|
||||
Depot_query &depot_query,
|
||||
Attached_rom_dataspace const &launcher_listing_rom,
|
||||
Attached_rom_dataspace const &blueprint_rom,
|
||||
Download_queue const &download_queue)
|
||||
:
|
||||
_env(env), _alloc(alloc), _runtime_info(runtime_info),
|
||||
_dialog_generator(dialog_generator),
|
||||
_runtime_config_generator(runtime_config_generator),
|
||||
_launcher_listing_rom(launcher_listing_rom)
|
||||
_depot_query(depot_query),
|
||||
_launcher_listing_rom(launcher_listing_rom),
|
||||
_blueprint_rom(blueprint_rom),
|
||||
_download_queue(download_queue)
|
||||
{
|
||||
_managed_deploy_rom.sigh(_managed_deploy_handler);
|
||||
_blueprint_rom.sigh(_blueprint_handler);
|
||||
}
|
||||
};
|
||||
|
||||
|
74
repos/gems/src/app/sculpt_manager/depot_query.h
Normal file
74
repos/gems/src/app/sculpt_manager/depot_query.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* \brief Interface for querying information about the depot
|
||||
* \author Norman Feske
|
||||
* \date 2019-02-22
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _DEPOT_QUERY_H_
|
||||
#define _DEPOT_QUERY_H_
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace Sculpt {
|
||||
|
||||
struct Depot_query;
|
||||
|
||||
static inline bool blueprint_missing (Xml_node, Path const &);
|
||||
static inline bool blueprint_any_missing (Xml_node);
|
||||
static inline bool blueprint_any_rom_missing(Xml_node);
|
||||
}
|
||||
|
||||
|
||||
struct Sculpt::Depot_query : Interface
|
||||
{
|
||||
struct Version { unsigned value; };
|
||||
|
||||
virtual Version depot_query_version() const = 0;
|
||||
|
||||
virtual void trigger_depot_query() = 0;
|
||||
};
|
||||
|
||||
|
||||
static inline bool Sculpt::blueprint_missing(Xml_node blueprint, Path const &path)
|
||||
{
|
||||
bool result = false;
|
||||
blueprint.for_each_sub_node("missing", [&] (Xml_node missing) {
|
||||
if (missing.attribute_value("path", Path()) == path)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static inline bool Sculpt::blueprint_any_missing(Xml_node blueprint)
|
||||
{
|
||||
return blueprint.has_sub_node("missing");
|
||||
}
|
||||
|
||||
|
||||
static inline bool Sculpt::blueprint_any_rom_missing(Xml_node blueprint)
|
||||
{
|
||||
bool result = false;
|
||||
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
|
||||
pkg.for_each_sub_node("missing_rom", [&] (Xml_node missing_rom) {
|
||||
|
||||
/* ld.lib.so is always taken from the base system */
|
||||
Label const label = missing_rom.attribute_value("label", Label());
|
||||
if (label == "ld.lib.so")
|
||||
return;
|
||||
|
||||
/* some ingredient is not extracted yet, or actually missing */
|
||||
result = true;
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif /* _DEPOT_QUERY_H_ */
|
@ -42,7 +42,9 @@ struct Sculpt::Main : Input_event_handler,
|
||||
Runtime_config_generator,
|
||||
Storage::Target_user,
|
||||
Graph::Action,
|
||||
Popup_dialog::Action
|
||||
Popup_dialog::Action,
|
||||
Popup_dialog::Construction_info,
|
||||
Depot_query
|
||||
{
|
||||
Env &_env;
|
||||
|
||||
@ -184,6 +186,67 @@ struct Sculpt::Main : Input_event_handler,
|
||||
&& _deploy.update_needed();
|
||||
}
|
||||
|
||||
Download_queue _download_queue { _heap };
|
||||
|
||||
|
||||
/*****************
|
||||
** Depot query **
|
||||
*****************/
|
||||
|
||||
Depot_query::Version _query_version { 0 };
|
||||
|
||||
Expanding_reporter _depot_query_reporter { _env, "query", "depot_query"};
|
||||
|
||||
/**
|
||||
* Depot_query interface
|
||||
*/
|
||||
Depot_query::Version depot_query_version() const override
|
||||
{
|
||||
return _query_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Depot_query interface
|
||||
*/
|
||||
void trigger_depot_query() override
|
||||
{
|
||||
_query_version.value++;
|
||||
|
||||
if (_deploy._arch.valid()) {
|
||||
_depot_query_reporter.generate([&] (Xml_generator &xml) {
|
||||
xml.attribute("arch", _deploy._arch);
|
||||
xml.attribute("version", _query_version.value);
|
||||
|
||||
_popup_dialog.gen_depot_query(xml);
|
||||
|
||||
/* update query for blueprints of all unconfigured start nodes */
|
||||
_deploy.gen_depot_query(xml);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********************
|
||||
** Blueprint query **
|
||||
*********************/
|
||||
|
||||
Attached_rom_dataspace _blueprint_rom { _env, "report -> runtime/depot_query/blueprint" };
|
||||
|
||||
Signal_handler<Main> _blueprint_handler {
|
||||
_env.ep(), *this, &Main::_handle_blueprint };
|
||||
|
||||
void _handle_blueprint()
|
||||
{
|
||||
_blueprint_rom.update();
|
||||
|
||||
Xml_node const blueprint = _blueprint_rom.xml();
|
||||
|
||||
_runtime_state.apply_to_construction([&] (Component &component) {
|
||||
_popup_dialog.apply_blueprint(component, blueprint); });
|
||||
|
||||
_deploy.handle_deploy();
|
||||
}
|
||||
|
||||
|
||||
/************
|
||||
** Deploy **
|
||||
@ -214,7 +277,8 @@ struct Sculpt::Main : Input_event_handler,
|
||||
}
|
||||
|
||||
|
||||
Deploy _deploy { _env, _heap, _runtime_state, *this, *this, _launcher_listing_rom };
|
||||
Deploy _deploy { _env, _heap, _runtime_state, *this, *this, *this,
|
||||
_launcher_listing_rom, _blueprint_rom, _download_queue };
|
||||
|
||||
Attached_rom_dataspace _manual_deploy_rom { _env, "config -> deploy" };
|
||||
|
||||
@ -418,7 +482,8 @@ struct Sculpt::Main : Input_event_handler,
|
||||
&& !_graph.add_button_hovered()) {
|
||||
|
||||
_popup.state = Popup::OFF;
|
||||
_popup_dialog.reset_hover();
|
||||
_popup_dialog.reset();
|
||||
discard_construction();
|
||||
|
||||
/* de-select '+' button */
|
||||
_graph._gen_graph_dialog();
|
||||
@ -427,15 +492,15 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_handle_window_layout();
|
||||
}
|
||||
|
||||
if (_graph.hovered()) _graph.click(*this);
|
||||
|
||||
if (_graph.hovered()) _graph.click(*this);
|
||||
if (_popup_dialog.hovered()) _popup_dialog.click(*this);
|
||||
}
|
||||
|
||||
if (ev.key_release(Input::BTN_LEFT)) {
|
||||
if (_hovered_dialog == Hovered::STORAGE) _storage.dialog.clack(_storage);
|
||||
|
||||
if (_graph.hovered()) _graph.clack(*this);
|
||||
if (_graph.hovered()) _graph.clack(*this);
|
||||
if (_popup_dialog.hovered()) _popup_dialog.clack(*this);
|
||||
}
|
||||
|
||||
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
|
||||
@ -469,6 +534,17 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_handle_window_layout();
|
||||
}
|
||||
|
||||
void _close_popup_dialog()
|
||||
{
|
||||
/* close popup menu */
|
||||
_popup.state = Popup::OFF;
|
||||
_popup_dialog.reset();
|
||||
_handle_window_layout();
|
||||
|
||||
/* reset state of the '+' button */
|
||||
_graph._gen_graph_dialog();
|
||||
}
|
||||
|
||||
/*
|
||||
* Popup_dialog::Action interface
|
||||
*/
|
||||
@ -476,19 +552,57 @@ struct Sculpt::Main : Input_event_handler,
|
||||
{
|
||||
_runtime_state.launch(launcher, launcher);
|
||||
|
||||
/* close popup menu */
|
||||
_popup.state = Popup::OFF;
|
||||
_popup_dialog.reset_hover();
|
||||
_handle_window_layout();
|
||||
|
||||
/* reset state of the '+' button */
|
||||
_graph._gen_graph_dialog();
|
||||
_close_popup_dialog();
|
||||
|
||||
/* trigger change of the deployment */
|
||||
_deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
|
||||
}
|
||||
|
||||
Popup_dialog _popup_dialog { _env, _launchers, _runtime_state };
|
||||
Start_name new_construction(Component::Path const &pkg,
|
||||
Component::Info const &info) override
|
||||
{
|
||||
return _runtime_state.new_construction(pkg, info);
|
||||
}
|
||||
|
||||
void _apply_to_construction(Popup_dialog::Action::Apply_to &fn) override
|
||||
{
|
||||
_runtime_state.apply_to_construction([&] (Component &c) { fn.apply_to(c); });
|
||||
}
|
||||
|
||||
void discard_construction() override { _runtime_state.discard_construction(); }
|
||||
|
||||
void launch_construction() override
|
||||
{
|
||||
_runtime_state.launch_construction();
|
||||
|
||||
_close_popup_dialog();
|
||||
|
||||
/* trigger change of the deployment */
|
||||
_deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
|
||||
}
|
||||
|
||||
void trigger_download(Path const &path) override
|
||||
{
|
||||
_download_queue.add(path);
|
||||
|
||||
/* incorporate new download-queue content into update */
|
||||
_deploy.update_installation();
|
||||
|
||||
generate_runtime_config();
|
||||
}
|
||||
|
||||
/**
|
||||
* Popup_dialog::Construction_info interface
|
||||
*/
|
||||
void _with_construction(Popup_dialog::Construction_info::With const &fn) const override
|
||||
{
|
||||
_runtime_state.with_construction([&] (Component const &c) { fn.with(c); });
|
||||
}
|
||||
|
||||
Popup_dialog _popup_dialog { _env, _heap, _launchers,
|
||||
_network._nic_state, _network._nic_target,
|
||||
_runtime_state, _cached_runtime_config,
|
||||
_download_queue, *this, *this };
|
||||
|
||||
Managed_config<Main> _fb_drv_config {
|
||||
_env, "config", "fb_drv", *this, &Main::_handle_fb_drv_config };
|
||||
@ -591,6 +705,7 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_window_list .sigh(_window_list_handler);
|
||||
_decorator_margins .sigh(_decorator_margins_handler);
|
||||
_launcher_listing_rom.sigh(_launcher_listing_handler);
|
||||
_blueprint_rom .sigh(_blueprint_handler);
|
||||
|
||||
/*
|
||||
* Generate initial configurations
|
||||
@ -886,11 +1001,33 @@ void Sculpt::Main::_handle_update_state()
|
||||
_update_state_rom.update();
|
||||
generate_dialog();
|
||||
|
||||
bool const installation_complete =
|
||||
!_update_state_rom.xml().attribute_value("progress", false);
|
||||
Xml_node const update_state = _update_state_rom.xml();
|
||||
|
||||
if (installation_complete)
|
||||
if (update_state.num_sub_nodes() == 0)
|
||||
return;
|
||||
|
||||
bool const popup_watches_downloads =
|
||||
_popup_dialog.interested_in_download();
|
||||
|
||||
_download_queue.apply_update_state(update_state);
|
||||
_download_queue.remove_inactive_downloads();
|
||||
|
||||
Xml_node const blueprint = _blueprint_rom.xml();
|
||||
bool const new_depot_query_needed = popup_watches_downloads
|
||||
|| blueprint_any_missing(blueprint)
|
||||
|| blueprint_any_rom_missing(blueprint);
|
||||
if (new_depot_query_needed)
|
||||
trigger_depot_query();
|
||||
|
||||
if (popup_watches_downloads)
|
||||
_deploy.update_installation();
|
||||
|
||||
bool const installation_complete =
|
||||
!update_state.attribute_value("progress", false);
|
||||
|
||||
if (installation_complete) {
|
||||
_deploy.reattempt_after_installation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
83
repos/gems/src/app/sculpt_manager/model/component.h
Normal file
83
repos/gems/src/app/sculpt_manager/model/component.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* \brief Representation of a component
|
||||
* \author Norman Feske
|
||||
* \date 2019-02-25
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__COMPONENT_H_
|
||||
#define _MODEL__COMPONENT_H_
|
||||
|
||||
#include <types.h>
|
||||
#include <model/route.h>
|
||||
|
||||
namespace Sculpt { struct Component; }
|
||||
|
||||
|
||||
struct Sculpt::Component : Noncopyable
|
||||
{
|
||||
Route::Update_policy _route_update_policy;
|
||||
|
||||
typedef Depot::Archive::Path Path;
|
||||
typedef Depot::Archive::Name Name;
|
||||
typedef String<100> Info;
|
||||
typedef Start_name Service;
|
||||
|
||||
/* defined at construction time */
|
||||
Path const path;
|
||||
Info const info;
|
||||
|
||||
/* defined when blueprint arrives */
|
||||
uint64_t ram { };
|
||||
size_t caps { };
|
||||
|
||||
bool blueprint_known = false;
|
||||
|
||||
List_model<Route> routes { };
|
||||
|
||||
Component(Allocator &alloc, Path const &path, Info const &info)
|
||||
: _route_update_policy(alloc), path(path), info(info) { }
|
||||
|
||||
~Component()
|
||||
{
|
||||
routes.update_from_xml(_route_update_policy, Xml_node("<empty/>"));
|
||||
}
|
||||
|
||||
void try_apply_blueprint(Xml_node blueprint)
|
||||
{
|
||||
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
|
||||
|
||||
if (path != pkg.attribute_value("path", Path()))
|
||||
return;
|
||||
|
||||
pkg.with_sub_node("runtime", [&] (Xml_node runtime) {
|
||||
|
||||
ram = runtime.attribute_value("ram", Number_of_bytes());
|
||||
caps = runtime.attribute_value("caps", 0UL);
|
||||
|
||||
runtime.with_sub_node("requires", [&] (Xml_node requires) {
|
||||
routes.update_from_xml(_route_update_policy, requires); });
|
||||
});
|
||||
|
||||
blueprint_known = true;
|
||||
});
|
||||
}
|
||||
|
||||
bool all_routes_defined() const
|
||||
{
|
||||
bool result = true;
|
||||
routes.for_each([&] (Route const &route) {
|
||||
if (!route.selected_service.constructed())
|
||||
result = false; });
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__COMPONENT_H_ */
|
125
repos/gems/src/app/sculpt_manager/model/download_queue.h
Normal file
125
repos/gems/src/app/sculpt_manager/model/download_queue.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* \brief List of depot downloads that are currently in flight
|
||||
* \author Norman Feske
|
||||
* \date 2019-02-25
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__DOWNLOAD_QUEUE_H_
|
||||
#define _MODEL__DOWNLOAD_QUEUE_H_
|
||||
|
||||
#include <base/registry.h>
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Download_queue; }
|
||||
|
||||
|
||||
struct Sculpt::Download_queue : Noncopyable
|
||||
{
|
||||
struct Download : Interface
|
||||
{
|
||||
Path const path;
|
||||
|
||||
enum class State { DOWNLOADING, FAILED, DONE } state;
|
||||
|
||||
Download(Path const &path) : path(path), state(State::DOWNLOADING) { }
|
||||
|
||||
void gen_installation_entry(Xml_generator &xml) const
|
||||
{
|
||||
if (state != State::DOWNLOADING)
|
||||
return;
|
||||
|
||||
if (Depot::Archive::index(path))
|
||||
xml.node("index", [&] () {
|
||||
xml.attribute("path", path); });
|
||||
else
|
||||
xml.node("archive", [&] () {
|
||||
xml.attribute("path", path);
|
||||
xml.attribute("source", "no"); });
|
||||
}
|
||||
};
|
||||
|
||||
Allocator &_alloc;
|
||||
|
||||
Registry<Registered<Download> > _downloads { };
|
||||
|
||||
Download_queue(Allocator &alloc) : _alloc(alloc) { }
|
||||
|
||||
void add(Path const &path)
|
||||
{
|
||||
log("add to download queue: ", path);
|
||||
bool already_exists = false;
|
||||
_downloads.for_each([&] (Download const &download) {
|
||||
if (download.path == path)
|
||||
already_exists = true; });
|
||||
|
||||
if (already_exists)
|
||||
return;
|
||||
|
||||
new (_alloc) Registered<Download>(_downloads, path);
|
||||
}
|
||||
|
||||
bool in_progress(Path const &path) const
|
||||
{
|
||||
bool result = false;
|
||||
_downloads.for_each([&] (Download const &download) {
|
||||
if (download.path == path && download.state == Download::State::DOWNLOADING)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void apply_update_state(Xml_node state)
|
||||
{
|
||||
/* 'elem' may be of type 'index' or 'archive' */
|
||||
state.for_each_sub_node([&] (Xml_node elem) {
|
||||
|
||||
Path const path = elem.attribute_value("path", Path());
|
||||
|
||||
_downloads.for_each([&] (Download &download) {
|
||||
|
||||
if (download.path != path)
|
||||
return;
|
||||
|
||||
typedef String<16> State;
|
||||
State const state = elem.attribute_value("state", State());
|
||||
|
||||
if (state == "done") download.state = Download::State::DONE;
|
||||
if (state == "failed") download.state = Download::State::FAILED;
|
||||
if (state == "unavailable") download.state = Download::State::FAILED;
|
||||
if (state == "corrupted") download.state = Download::State::FAILED;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void remove_inactive_downloads()
|
||||
{
|
||||
_downloads.for_each([&] (Download &download) {
|
||||
if (download.state != Download::State::DOWNLOADING)
|
||||
destroy(_alloc, &download); });
|
||||
}
|
||||
|
||||
void gen_installation_entries(Xml_generator &xml) const
|
||||
{
|
||||
_downloads.for_each([&] (Download const &download) {
|
||||
download.gen_installation_entry(xml); });
|
||||
}
|
||||
|
||||
bool any_active_download() const
|
||||
{
|
||||
bool result = false;
|
||||
_downloads.for_each([&] (Download const &download) {
|
||||
if (!result && download.state == Download::State::DOWNLOADING)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__ROUTE_H_ */
|
162
repos/gems/src/app/sculpt_manager/model/route.h
Normal file
162
repos/gems/src/app/sculpt_manager/model/route.h
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* \brief Representation of a route to a service
|
||||
* \author Norman Feske
|
||||
* \date 2019-02-25
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__ROUTE_H_
|
||||
#define _MODEL__ROUTE_H_
|
||||
|
||||
#include <types.h>
|
||||
#include <model/service.h>
|
||||
|
||||
namespace Sculpt { struct Route; }
|
||||
|
||||
|
||||
struct Sculpt::Route : List_model<Route>::Element
|
||||
{
|
||||
typedef String<32> Id;
|
||||
typedef String<80> Info;
|
||||
|
||||
static char const *xml_type(Service::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Service::Type::AUDIO_IN: return "audio_in";
|
||||
case Service::Type::AUDIO_OUT: return "audio_out";
|
||||
case Service::Type::BLOCK: return "block";
|
||||
case Service::Type::FILE_SYSTEM: return "file_system";
|
||||
case Service::Type::NIC: return "nic";
|
||||
case Service::Type::NITPICKER: return "nitpicker";
|
||||
case Service::Type::RM: return "rm";
|
||||
case Service::Type::IO_MEM: return "io_mem";
|
||||
case Service::Type::IO_PORT: return "io_port";
|
||||
case Service::Type::IRQ: return "irq";
|
||||
case Service::Type::REPORT: return "report";
|
||||
case Service::Type::ROM: return "rom";
|
||||
case Service::Type::TERMINAL: return "terminal";
|
||||
case Service::Type::TRACE: return "trace";
|
||||
case Service::Type::USB: return "usb";
|
||||
case Service::Type::RTC: return "rtc";
|
||||
case Service::Type::PLATFORM: return "platform";
|
||||
case Service::Type::VM: return "vm";
|
||||
case Service::Type::UNDEFINED: break;
|
||||
}
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
static char const *_pretty_name(Service::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Service::Type::AUDIO_IN: return "Audio input";
|
||||
case Service::Type::AUDIO_OUT: return "Audio output";
|
||||
case Service::Type::BLOCK: return "Block device";
|
||||
case Service::Type::FILE_SYSTEM: return "File system";
|
||||
case Service::Type::NIC: return "Network";
|
||||
case Service::Type::NITPICKER: return "GUI";
|
||||
case Service::Type::RM: return "Region maps";
|
||||
case Service::Type::IO_MEM: return "Direct memory-mapped I/O";
|
||||
case Service::Type::IO_PORT: return "Direct port I/O";
|
||||
case Service::Type::IRQ: return "Direct device interrupts";
|
||||
case Service::Type::REPORT: return "Report";
|
||||
case Service::Type::ROM: return "ROM";
|
||||
case Service::Type::TERMINAL: return "Terminal";
|
||||
case Service::Type::TRACE: return "Tracing";
|
||||
case Service::Type::USB: return "USB";
|
||||
case Service::Type::RTC: return "Real-time clock";
|
||||
case Service::Type::PLATFORM: return "Device access";
|
||||
case Service::Type::VM: return "Hardware-based virtualization";
|
||||
case Service::Type::UNDEFINED: break;
|
||||
}
|
||||
return "<undefined>";
|
||||
}
|
||||
|
||||
static Service::Type _required(Xml_node node)
|
||||
{
|
||||
for (unsigned i = 0; i < (unsigned)Service::Type::UNDEFINED; i++) {
|
||||
Service::Type const s = (Service::Type)i;
|
||||
if (node.has_type(xml_type(s)))
|
||||
return s;
|
||||
}
|
||||
|
||||
return Service::Type::UNDEFINED;
|
||||
}
|
||||
|
||||
Service::Type const required;
|
||||
Label const required_label;
|
||||
|
||||
Constructible<Service> selected_service { };
|
||||
|
||||
Id selected_service_id { };
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* \param required sub node of a runtime's <requires> node
|
||||
*/
|
||||
Route(Xml_node node)
|
||||
:
|
||||
required(_required(node)),
|
||||
required_label(node.attribute_value("label", Label()))
|
||||
{ }
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
Genode::print(out, _pretty_name(required));
|
||||
if (required_label.valid())
|
||||
Genode::print(out, " (", required_label, ") ");
|
||||
}
|
||||
|
||||
void gen_xml(Xml_generator &xml) const
|
||||
{
|
||||
if (!selected_service.constructed()) {
|
||||
warning("no service assigned to route ", *this);
|
||||
return;
|
||||
}
|
||||
|
||||
gen_named_node(xml, "service", Service::name_attr(required), [&] () {
|
||||
|
||||
if (required_label.valid())
|
||||
xml.attribute("label", required_label);
|
||||
|
||||
selected_service->gen_xml(xml);
|
||||
});
|
||||
}
|
||||
|
||||
struct Update_policy
|
||||
{
|
||||
typedef Route Element;
|
||||
|
||||
Allocator &_alloc;
|
||||
|
||||
Update_policy(Allocator &alloc) : _alloc(alloc) { }
|
||||
|
||||
void destroy_element(Route &elem) { destroy(_alloc, &elem); }
|
||||
|
||||
Route &create_element(Xml_node node)
|
||||
{
|
||||
return *new (_alloc) Route(node);
|
||||
}
|
||||
|
||||
void update_element(Route &, Xml_node) { }
|
||||
|
||||
static bool element_matches_xml_node(Route const &elem, Xml_node node)
|
||||
{
|
||||
return elem.required == _required(node)
|
||||
&& elem.required_label == node.attribute_value("label", Label());
|
||||
}
|
||||
|
||||
static bool node_is_element(Xml_node node)
|
||||
{
|
||||
return _required(node) != Service::Type::UNDEFINED;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* _MODEL__ROUTE_H_ */
|
@ -17,12 +17,15 @@
|
||||
/* Genode includes */
|
||||
#include <util/xml_node.h>
|
||||
#include <util/list_model.h>
|
||||
#include <base/registry.h>
|
||||
|
||||
/* local includes */
|
||||
#include <types.h>
|
||||
#include <model/service.h>
|
||||
|
||||
namespace Sculpt { class Runtime_config; }
|
||||
|
||||
|
||||
class Sculpt::Runtime_config
|
||||
{
|
||||
private:
|
||||
@ -52,9 +55,8 @@ class Sculpt::Runtime_config
|
||||
|
||||
node.with_sub_node("parent", [&] (Xml_node parent) {
|
||||
|
||||
typedef String<16> Service;
|
||||
Service const service =
|
||||
node.attribute_value("name", Service());
|
||||
Service::Type_name const service =
|
||||
node.attribute_value("name", Service::Type_name());
|
||||
|
||||
Label const dst_label =
|
||||
parent.attribute_value("label", Label());
|
||||
@ -123,6 +125,54 @@ class Sculpt::Runtime_config
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Child_service : Service, List_model<Child_service>::Element
|
||||
{
|
||||
static Service::Type type_from_xml(Xml_node service)
|
||||
{
|
||||
auto const name = service.attribute_value("name", Service::Type_name());
|
||||
for (unsigned i = 0; i < (unsigned)Type::UNDEFINED; i++) {
|
||||
Type const t = (Type)i;
|
||||
if (name == Service::name_attr(t))
|
||||
return t;
|
||||
}
|
||||
|
||||
return Type::UNDEFINED;
|
||||
}
|
||||
|
||||
Child_service(Start_name server, Xml_node provides)
|
||||
: Service(server, type_from_xml(provides), Label()) { }
|
||||
|
||||
struct Update_policy
|
||||
{
|
||||
typedef Child_service Element;
|
||||
|
||||
Start_name _server;
|
||||
Allocator &_alloc;
|
||||
|
||||
Update_policy(Start_name const &server, Allocator &alloc)
|
||||
: _server(server), _alloc(alloc) { }
|
||||
|
||||
void destroy_element(Element &elem) { destroy(_alloc, &elem); }
|
||||
|
||||
Element &create_element(Xml_node node)
|
||||
{
|
||||
return *new (_alloc) Child_service(_server, node);
|
||||
}
|
||||
|
||||
void update_element(Element &, Xml_node) { }
|
||||
|
||||
static bool element_matches_xml_node(Element const &elem, Xml_node node)
|
||||
{
|
||||
return type_from_xml(node) == elem.type;
|
||||
}
|
||||
|
||||
static bool node_is_element(Xml_node node)
|
||||
{
|
||||
return type_from_xml(node) != Service::Type::UNDEFINED;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
struct Component : List_model<Component>::Element
|
||||
@ -177,21 +227,29 @@ class Sculpt::Runtime_config
|
||||
fn(dep.to_name); });
|
||||
}
|
||||
|
||||
List_model<Child_service> _child_services { };
|
||||
|
||||
Component(Start_name const &name) : name(name) { }
|
||||
|
||||
template <typename FN>
|
||||
void for_each_service(FN const &fn) const
|
||||
{
|
||||
_child_services.for_each(fn);
|
||||
}
|
||||
|
||||
struct Update_policy
|
||||
{
|
||||
typedef Component Element;
|
||||
|
||||
Allocator &_alloc;
|
||||
|
||||
Dep::Update_policy _dep_update_policy { _alloc };
|
||||
|
||||
Update_policy(Allocator &alloc) : _alloc(alloc) { }
|
||||
|
||||
void destroy_element(Component &elem)
|
||||
{
|
||||
elem.deps.update_from_xml(_dep_update_policy, Xml_node("<route/>"));
|
||||
/* flush list models */
|
||||
update_element(elem, Xml_node("<start> <route/> <provides/> </start>"));
|
||||
|
||||
destroy(_alloc, &elem);
|
||||
}
|
||||
|
||||
@ -205,8 +263,20 @@ class Sculpt::Runtime_config
|
||||
{
|
||||
elem.primary_dependency = _primary_dependency(node);
|
||||
|
||||
node.with_sub_node("route", [&] (Xml_node route) {
|
||||
elem.deps.update_from_xml(_dep_update_policy, route); });
|
||||
{
|
||||
Dep::Update_policy policy { _alloc };
|
||||
|
||||
node.with_sub_node("route", [&] (Xml_node route) {
|
||||
elem.deps.update_from_xml(policy, route); });
|
||||
}
|
||||
|
||||
{
|
||||
Child_service::Update_policy policy { elem.name, _alloc };
|
||||
|
||||
node.with_sub_node("provides", [&] (Xml_node provides) {
|
||||
elem._child_services.update_from_xml(policy,
|
||||
provides); });
|
||||
}
|
||||
}
|
||||
|
||||
static bool element_matches_xml_node(Component const &elem, Xml_node node)
|
||||
@ -222,6 +292,43 @@ class Sculpt::Runtime_config
|
||||
|
||||
List_model<Component> _components { };
|
||||
|
||||
struct Parent_services
|
||||
{
|
||||
typedef Registered_no_delete<Service> Parent_service;
|
||||
typedef Service::Type Type;
|
||||
|
||||
Registry<Parent_service> _r { };
|
||||
|
||||
Parent_service const
|
||||
_focus { _r, Type::NITPICKER, "keyboard focus", "focus" },
|
||||
_backdrop { _r, Type::NITPICKER, "desktop background", "backdrop" },
|
||||
_nitpicker { _r, Type::NITPICKER, "system GUI server" },
|
||||
_config_fs { _r, Type::FILE_SYSTEM, "writeable system configuration", "config" },
|
||||
_report_fs { _r, Type::FILE_SYSTEM, "read-only system reports", "report" },
|
||||
_capslock { _r, Type::ROM, "global capslock state", "capslock" },
|
||||
_vimrc { _r, Type::ROM, "default vim configuration", "config -> vimrc" },
|
||||
_fonts { _r, Type::ROM, "system font configuration", "config -> managed/fonts" },
|
||||
_pf_info { _r, Type::ROM, "platform information", "platform_info" },
|
||||
_report { _r, Type::REPORT, "system reports" },
|
||||
_rm { _r, Type::RM, "custom virtual memory objects" },
|
||||
_io_mem { _r, Type::IO_MEM, "raw hardware access" },
|
||||
_io_port { _r, Type::IO_PORT, "raw hardware access" },
|
||||
_irq { _r, Type::IRQ, "raw hardware access" },
|
||||
_rtc { _r, Type::RTC, "system clock" },
|
||||
_block { _r, Type::BLOCK, "direct block-device access" },
|
||||
_usb { _r, Type::USB, "direct USB-device access" },
|
||||
_pci_wifi { _r, Type::PLATFORM, "wifi hardware", "wifi" },
|
||||
_pci_net { _r, Type::PLATFORM, "network hardware", "nic" },
|
||||
_pci_audio { _r, Type::PLATFORM, "audio hardware", "audio" },
|
||||
_pci_acpi { _r, Type::PLATFORM, "ACPI", "acpica" },
|
||||
_trace { _r, Type::TRACE, "system-global tracing" },
|
||||
_vm { _r, Type::VM, "virtualization hardware" };
|
||||
|
||||
template <typename FN>
|
||||
void for_each(FN const &fn) const { _r.for_each(fn); }
|
||||
|
||||
} _parent_services { };
|
||||
|
||||
public:
|
||||
|
||||
Runtime_config(Allocator &alloc) : _alloc(alloc) { }
|
||||
@ -246,6 +353,15 @@ class Sculpt::Runtime_config
|
||||
component.deps.for_each([&] (Component::Dep const &dep) {
|
||||
fn(dep.to_name); }); } });
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_service(FN const &fn) const
|
||||
{
|
||||
_components.for_each([&] (Component const &component) {
|
||||
component.for_each_service(fn); });
|
||||
|
||||
_parent_services.for_each(fn);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__RUNTIME_CONFIG_H_ */
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <types.h>
|
||||
#include <runtime.h>
|
||||
#include <model/runtime_config.h>
|
||||
#include <model/component.h>
|
||||
|
||||
namespace Sculpt { class Runtime_state; }
|
||||
|
||||
@ -85,12 +86,64 @@ class Sculpt::Runtime_state : public Runtime_info
|
||||
{
|
||||
Start_name const name;
|
||||
Path const launcher;
|
||||
|
||||
Constructible<Component> construction { };
|
||||
|
||||
bool launched;
|
||||
|
||||
/**
|
||||
* Constructor used for child started via launcher
|
||||
*/
|
||||
Launched_child(Start_name const &name, Path const &launcher)
|
||||
: name(name), launcher(launcher) { };
|
||||
: name(name), launcher(launcher), launched(true) { };
|
||||
|
||||
/**
|
||||
* Constructor used for interactively configured child
|
||||
*/
|
||||
Launched_child(Allocator &alloc, Start_name const &name,
|
||||
Component::Path const &pkg_path,
|
||||
Component::Info const &info)
|
||||
:
|
||||
name(name), launcher(), launched(false)
|
||||
{
|
||||
construction.construct(alloc, pkg_path, info);
|
||||
}
|
||||
|
||||
void gen_deploy_start_node(Xml_generator &xml) const
|
||||
{
|
||||
if (!launched)
|
||||
return;
|
||||
|
||||
gen_named_node(xml, "start", name, [&] () {
|
||||
|
||||
/* interactively constructed */
|
||||
if (construction.constructed()) {
|
||||
xml.attribute("pkg", construction->path);
|
||||
|
||||
xml.node("route", [&] () {
|
||||
construction->routes.for_each([&] (Route const &route) {
|
||||
route.gen_xml(xml); }); });
|
||||
}
|
||||
|
||||
/* created via launcher */
|
||||
else {
|
||||
if (name != launcher)
|
||||
xml.attribute("launcher", launcher);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Registry<Registered<Launched_child> > _launched_children { };
|
||||
|
||||
Registered<Launched_child> *_currently_constructed = nullptr;
|
||||
|
||||
bool _construction_in_progress() const
|
||||
{
|
||||
return _currently_constructed
|
||||
&& _currently_constructed->construction.constructed();
|
||||
}
|
||||
|
||||
struct Update_policy : List_model<Child>::Update_policy
|
||||
{
|
||||
Allocator &_alloc;
|
||||
@ -133,6 +186,12 @@ class Sculpt::Runtime_state : public Runtime_info
|
||||
static bool node_is_element(Xml_node node) { return node.has_type("child"); }
|
||||
};
|
||||
|
||||
/*
|
||||
* Noncopyable
|
||||
*/
|
||||
Runtime_state(Runtime_state const &);
|
||||
Runtime_state &operator = (Runtime_state const &);
|
||||
|
||||
public:
|
||||
|
||||
Runtime_state(Allocator &alloc) : _alloc(alloc) { }
|
||||
@ -151,9 +210,15 @@ class Sculpt::Runtime_state : public Runtime_info
|
||||
bool present_in_runtime(Start_name const &name) const override
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
_children.for_each([&] (Child const &child) {
|
||||
if (!result && child.name == name)
|
||||
result = true; });
|
||||
|
||||
_launched_children.for_each([&] (Launched_child const &child) {
|
||||
if (!result && child.name == name)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -175,9 +240,7 @@ class Sculpt::Runtime_state : public Runtime_info
|
||||
void gen_launched_deploy_start_nodes(Xml_generator &xml) const override
|
||||
{
|
||||
_launched_children.for_each([&] (Launched_child const &child) {
|
||||
gen_named_node(xml, "start", child.name, [&] () {
|
||||
if (child.name != child.launcher)
|
||||
xml.attribute("launcher", child.launcher); }); });
|
||||
child.gen_deploy_start_node(xml); });
|
||||
}
|
||||
|
||||
Info info(Start_name const &name) const
|
||||
@ -243,7 +306,7 @@ class Sculpt::Runtime_state : public Runtime_info
|
||||
* entry from '_launched_children'.
|
||||
*/
|
||||
bool was_interactively_launched = false;
|
||||
_launched_children.for_each([&] (Launched_child &child) {
|
||||
_launched_children.for_each([&] (Registered<Launched_child> &child) {
|
||||
if (child.name == name) {
|
||||
was_interactively_launched = true;
|
||||
destroy(_alloc, &child);
|
||||
@ -264,6 +327,55 @@ class Sculpt::Runtime_state : public Runtime_info
|
||||
new (_alloc) Registered<Launched_child>(_launched_children, name, launcher);
|
||||
}
|
||||
|
||||
Start_name new_construction(Component::Path const pkg,
|
||||
Component::Info const &info)
|
||||
{
|
||||
/* allow only one construction at a time */
|
||||
discard_construction();
|
||||
|
||||
/* determine unique name for new child */
|
||||
Depot::Archive::Name const archive_name = Depot::Archive::name(pkg);
|
||||
Start_name unique_name = archive_name;
|
||||
unsigned cnt = 1;
|
||||
while (present_in_runtime(unique_name))
|
||||
unique_name = Start_name(archive_name, ".", ++cnt);
|
||||
|
||||
_currently_constructed = new (_alloc)
|
||||
Registered<Launched_child>(_launched_children, _alloc,
|
||||
unique_name, pkg, info);
|
||||
return unique_name;
|
||||
}
|
||||
|
||||
void discard_construction()
|
||||
{
|
||||
if (_currently_constructed) {
|
||||
destroy(_alloc, _currently_constructed);
|
||||
_currently_constructed = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void apply_to_construction(FN const &fn)
|
||||
{
|
||||
if (_construction_in_progress())
|
||||
fn(*_currently_constructed->construction);
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void with_construction(FN const &fn) const
|
||||
{
|
||||
if (_construction_in_progress())
|
||||
fn(*_currently_constructed->construction);
|
||||
}
|
||||
|
||||
void launch_construction()
|
||||
{
|
||||
if (_currently_constructed)
|
||||
_currently_constructed->launched = true;
|
||||
|
||||
_currently_constructed = nullptr;
|
||||
}
|
||||
|
||||
void reset_abandoned_and_launched_children()
|
||||
{
|
||||
_abandoned_children.for_each([&] (Abandoned_child &child) {
|
||||
|
28
repos/gems/src/app/sculpt_manager/model/sculpt_version.h
Normal file
28
repos/gems/src/app/sculpt_manager/model/sculpt_version.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* \brief Sculpt version
|
||||
* \author Norman Feske
|
||||
* \date 2019-02-24
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__SCULPT_VERSION_H_
|
||||
#define _MODEL__SCULPT_VERSION_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Sculpt_version; }
|
||||
|
||||
struct Sculpt::Sculpt_version : String<6>
|
||||
{
|
||||
Sculpt_version(Env &env)
|
||||
: String<6>(Attached_rom_dataspace(env, "VERSION").local_addr<char const>())
|
||||
{ }
|
||||
};
|
||||
|
||||
#endif /* _MODEL__SCULPT_VERSION_H_ */
|
93
repos/gems/src/app/sculpt_manager/model/service.h
Normal file
93
repos/gems/src/app/sculpt_manager/model/service.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* \brief Representation of service that can be targeted by a route
|
||||
* \author Norman Feske
|
||||
* \date 2019-02-25
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2019 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__SERVICE_H_
|
||||
#define _MODEL__SERVICE_H_
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace Sculpt { struct Service; }
|
||||
|
||||
|
||||
struct Sculpt::Service
|
||||
{
|
||||
typedef String<16> Type_name;
|
||||
typedef String<32> Info;
|
||||
|
||||
enum class Type {
|
||||
AUDIO_IN, AUDIO_OUT, BLOCK, FILE_SYSTEM, NIC, NITPICKER,
|
||||
RM, IO_MEM, IO_PORT, IRQ, REPORT, ROM, TERMINAL, TRACE,
|
||||
USB, RTC, PLATFORM, VM, UNDEFINED };
|
||||
|
||||
Start_name server { }; /* invalid for parent service */
|
||||
Type type;
|
||||
Label label;
|
||||
Info info;
|
||||
|
||||
/**
|
||||
* Return name attribute value of <service name="..."> node
|
||||
*/
|
||||
static char const *name_attr(Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Type::AUDIO_IN: return "Audio_in";
|
||||
case Type::AUDIO_OUT: return "Audio_out";
|
||||
case Type::BLOCK: return "Block";
|
||||
case Type::FILE_SYSTEM: return "File_system";
|
||||
case Type::NIC: return "Nic";
|
||||
case Type::NITPICKER: return "Nitpicker";
|
||||
case Type::RM: return "RM";
|
||||
case Type::IO_MEM: return "IO_MEM";
|
||||
case Type::IO_PORT: return "IO_PORT";
|
||||
case Type::IRQ: return "IRQ";
|
||||
case Type::REPORT: return "Report";
|
||||
case Type::ROM: return "ROM";
|
||||
case Type::TERMINAL: return "Terminal";
|
||||
case Type::TRACE: return "TRACE";
|
||||
case Type::USB: return "Usb";
|
||||
case Type::RTC: return "Rtc";
|
||||
case Type::PLATFORM: return "Platform";
|
||||
case Type::VM: return "VM";
|
||||
case Type::UNDEFINED: break;
|
||||
}
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for child service
|
||||
*/
|
||||
Service(Start_name const &server, Type type, Label const &label)
|
||||
: server(server), type(type), label(label), info(server) { }
|
||||
|
||||
/**
|
||||
* Constructor for parent service
|
||||
*/
|
||||
Service(Type type, Info const &info, Label const &label = Label())
|
||||
: type(type), label(label), info(info) { }
|
||||
|
||||
void gen_xml(Xml_generator &xml) const
|
||||
{
|
||||
bool const parent = !server.valid();
|
||||
|
||||
xml.node(parent ? "parent" : "child", [&] () {
|
||||
|
||||
if (!parent)
|
||||
xml.attribute("name", server);
|
||||
|
||||
if (label.valid())
|
||||
xml.attribute("label", label);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__SERVICE_H_ */
|
438
repos/gems/src/app/sculpt_manager/view/popup_dialog.cc
Normal file
438
repos/gems/src/app/sculpt_manager/view/popup_dialog.cc
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
* \brief Popup dialog
|
||||
* \author Norman Feske
|
||||
* \date 2018-09-12
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#include <view/popup_dialog.h>
|
||||
|
||||
using namespace Sculpt;
|
||||
|
||||
|
||||
void Popup_dialog::_gen_pkg_info(Xml_generator &xml,
|
||||
Component const &component) const
|
||||
{
|
||||
gen_named_node(xml, "label", "info", [&] () {
|
||||
xml.attribute("text", Component::Info(" ", component.info, " ")); });
|
||||
|
||||
_gen_info_label(xml, "pad1", "");
|
||||
_gen_info_label(xml, "path", component.path);
|
||||
}
|
||||
|
||||
|
||||
void Popup_dialog::_gen_pkg_elements(Xml_generator &xml,
|
||||
Component const &component) const
|
||||
{
|
||||
typedef Component::Info Info;
|
||||
|
||||
_gen_sub_menu_title(xml, "back", Menu::Name("Add ", _construction_name));
|
||||
|
||||
_gen_pkg_info(xml, component);
|
||||
|
||||
_gen_info_label(xml, "resources", Info(Capacity{component.ram}, " ",
|
||||
component.caps, " caps"));
|
||||
_gen_info_label(xml, "pad2", "");
|
||||
|
||||
unsigned cnt = 0;
|
||||
component.routes.for_each([&] (Route const &route) {
|
||||
|
||||
Route::Id const id(cnt++);
|
||||
|
||||
gen_named_node(xml, "frame", id, [&] () {
|
||||
|
||||
xml.node("vbox", [&] () {
|
||||
|
||||
bool const selected = _route_selected(id);
|
||||
bool const defined = route.selected_service.constructed();
|
||||
|
||||
if (!selected) {
|
||||
_gen_route_entry(xml, id,
|
||||
defined ? Info(route.selected_service->info)
|
||||
: Info(route),
|
||||
defined);
|
||||
}
|
||||
|
||||
/*
|
||||
* List of routing options
|
||||
*/
|
||||
if (selected) {
|
||||
_gen_route_entry(xml, "back", Info(route), true, "back");
|
||||
|
||||
unsigned cnt = 0;
|
||||
_runtime_config.for_each_service([&] (Service const &service) {
|
||||
|
||||
Hoverable_item::Id const id("service.", cnt++);
|
||||
|
||||
bool const service_selected =
|
||||
route.selected_service.constructed() &&
|
||||
id == route.selected_service_id;
|
||||
|
||||
if (service.type == route.required)
|
||||
_gen_route_entry(xml, id, service.info, service_selected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* Display "Add component" button once all routes are defined
|
||||
*/
|
||||
if (component.all_routes_defined()) {
|
||||
gen_named_node(xml, "button", "launch", [&] () {
|
||||
_action_item.gen_button_attr(xml, "launch");
|
||||
xml.node("label", [&] () {
|
||||
xml.attribute("text", "Add component"); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Popup_dialog::_gen_menu_elements(Xml_generator &xml) const
|
||||
{
|
||||
/*
|
||||
* Lauchers
|
||||
*/
|
||||
if (_state == TOP_LEVEL || _state < DEPOT_SHOWN) {
|
||||
_launchers.for_each([&] (Launchers::Info const &info) {
|
||||
|
||||
/* allow each launcher to be used only once */
|
||||
if (_runtime_info.present_in_runtime(info.path))
|
||||
return;
|
||||
|
||||
_gen_menu_entry(xml, info.path, info.path, false);
|
||||
});
|
||||
|
||||
_gen_menu_entry(xml, "depot", "Depot ...", false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Depot users with an available index
|
||||
*/
|
||||
if (_state == DEPOT_SHOWN || _state == INDEX_REQUESTED) {
|
||||
_gen_sub_menu_title(xml, "back", "Depot");
|
||||
|
||||
_scan_rom.xml().for_each_sub_node("user", [&] (Xml_node user) {
|
||||
|
||||
User const name = user.attribute_value("name", User());
|
||||
bool const selected = (_selected_user == name);
|
||||
|
||||
if (_index_avail(name))
|
||||
_gen_menu_entry(xml, name, User(name, " ..."), selected);
|
||||
});
|
||||
|
||||
/*
|
||||
* Depot selection menu item
|
||||
*/
|
||||
if (_state == DEPOT_SHOWN || _state == INDEX_REQUESTED) {
|
||||
|
||||
if (_nic_ready())
|
||||
_gen_menu_entry(xml, "selection", "Selection ...", false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* List of depot users for removing/adding indices
|
||||
*/
|
||||
if (_state == DEPOT_SELECTION) {
|
||||
_gen_sub_menu_title(xml, "back", "Selection");
|
||||
|
||||
_scan_rom.xml().for_each_sub_node("user", [&] (Xml_node user) {
|
||||
|
||||
User const name = user.attribute_value("name", User());
|
||||
bool const selected = _index_avail(name);
|
||||
|
||||
String<32> const suffix = _download_queue.in_progress(_index_path(name))
|
||||
? " fetch... " : " ";
|
||||
|
||||
_gen_menu_entry(xml, name, User(name, suffix), selected, "checkbox");
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Title of index
|
||||
*/
|
||||
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
|
||||
Menu::Name title("Depot ", _selected_user);
|
||||
if (_menu._level)
|
||||
title = Menu::Name(title, " ", _menu, " ");
|
||||
|
||||
_gen_sub_menu_title(xml, "back", title);
|
||||
}
|
||||
|
||||
/*
|
||||
* Index menu
|
||||
*/
|
||||
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
|
||||
|
||||
unsigned cnt = 0;
|
||||
_for_each_menu_item([&] (Xml_node item) {
|
||||
|
||||
Hoverable_item::Id const id(cnt);
|
||||
|
||||
if (item.has_type("index")) {
|
||||
auto const name = item.attribute_value("name", Menu::Name());
|
||||
_gen_menu_entry(xml, id, Menu::Name(name, " ..."), false);
|
||||
}
|
||||
|
||||
if (item.has_type("pkg")) {
|
||||
auto const path = item.attribute_value("path", Depot::Archive::Path());
|
||||
auto const name = Depot::Archive::name(path);
|
||||
auto const version = Depot::Archive::version(path);
|
||||
|
||||
bool selected = false;
|
||||
|
||||
bool const installing = _download_queue.in_progress(path);
|
||||
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
|
||||
if (component.path == path)
|
||||
selected = true;
|
||||
});
|
||||
|
||||
String<100> const text(name, " " "(", version, ")",
|
||||
installing ? " installing... " : "...");
|
||||
|
||||
_gen_menu_entry(xml, id, text, selected);
|
||||
|
||||
if (selected && _pkg_missing && !installing) {
|
||||
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
|
||||
gen_named_node(xml, "float", "install", [&] () {
|
||||
|
||||
gen_named_node(xml, "vbox", "vbox", [&] () {
|
||||
|
||||
if (_nic_ready()) {
|
||||
|
||||
_gen_pkg_info(xml, component);
|
||||
_gen_info_label(xml, "pad2", "");
|
||||
|
||||
gen_named_node(xml, "float", "install", [&] () {
|
||||
xml.node("button", [&] () {
|
||||
_install_item.gen_button_attr(xml, "install");
|
||||
xml.node("label", [&] () {
|
||||
xml.attribute("text", "Install");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
_gen_info_label(xml, "pad2", "");
|
||||
_gen_info_label(xml, "path", component.path);
|
||||
_gen_info_label(xml, "pad3", "");
|
||||
xml.node("label", [&] () {
|
||||
xml.attribute("text", "not installed"); });
|
||||
}
|
||||
|
||||
_gen_info_label(xml, "pad4", "");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
cnt++;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Pkg configuration
|
||||
*/
|
||||
if (_state >= PKG_SHOWN)
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
_gen_pkg_elements(xml, component); });
|
||||
}
|
||||
|
||||
|
||||
void Popup_dialog::click(Action &action)
|
||||
{
|
||||
Hoverable_item::Id const clicked = _item._hovered;
|
||||
_item._hovered = Hoverable_item::Id();
|
||||
|
||||
_action_item .propose_activation_on_click();
|
||||
_install_item.propose_activation_on_click();
|
||||
|
||||
Route::Id const clicked_route = _route_item._hovered;
|
||||
|
||||
auto back_to_index = [&] ()
|
||||
{
|
||||
_state = INDEX_SHOWN;
|
||||
action.discard_construction();
|
||||
_selected_route.destruct();
|
||||
};
|
||||
|
||||
if (_state == TOP_LEVEL) {
|
||||
|
||||
if (clicked == "depot") {
|
||||
_state = DEPOT_REQUESTED;
|
||||
_depot_query.trigger_depot_query();
|
||||
} else {
|
||||
action.launch_global(clicked);
|
||||
}
|
||||
}
|
||||
|
||||
else if (_state == DEPOT_SHOWN) {
|
||||
|
||||
/* back to top-level menu */
|
||||
if (clicked == "back") {
|
||||
_state = TOP_LEVEL;
|
||||
|
||||
} else if (clicked == "selection") {
|
||||
_state = DEPOT_SELECTION;
|
||||
|
||||
} else {
|
||||
|
||||
/* enter depot users menu */
|
||||
_selected_user = clicked;
|
||||
_state = INDEX_REQUESTED;
|
||||
_depot_query.trigger_depot_query();
|
||||
}
|
||||
}
|
||||
|
||||
else if (_state == DEPOT_SELECTION) {
|
||||
|
||||
/* back to depot users */
|
||||
if (clicked == "back") {
|
||||
_state = DEPOT_SHOWN;
|
||||
} else {
|
||||
|
||||
if (!_index_avail(clicked))
|
||||
action.trigger_download(_index_path(clicked));
|
||||
}
|
||||
}
|
||||
|
||||
else if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
|
||||
|
||||
/* back to depot users */
|
||||
if (_menu._level == 0 && clicked == "back") {
|
||||
_state = DEPOT_SHOWN;
|
||||
_selected_user = User();
|
||||
} else {
|
||||
|
||||
/* go one menu up */
|
||||
if (clicked == "back") {
|
||||
_menu._selected[_menu._level] = Menu::Name();
|
||||
_menu._level--;
|
||||
action.discard_construction();
|
||||
} else {
|
||||
|
||||
/* enter sub menu of index */
|
||||
if (_menu._level < Menu::MAX_LEVELS - 1) {
|
||||
|
||||
unsigned cnt = 0;
|
||||
_for_each_menu_item([&] (Xml_node item) {
|
||||
|
||||
if (clicked == Hoverable_item::Id(cnt)) {
|
||||
|
||||
if (item.has_type("index")) {
|
||||
|
||||
Menu::Name const name =
|
||||
item.attribute_value("name", Menu::Name());
|
||||
|
||||
_menu._selected[_menu._level] = name;
|
||||
_menu._level++;
|
||||
|
||||
} else if (item.has_type("pkg")) {
|
||||
|
||||
auto path = item.attribute_value("path", Component::Path());
|
||||
auto info = item.attribute_value("info", Component::Info());
|
||||
|
||||
_construction_name = action.new_construction(path, info);
|
||||
|
||||
_state = PKG_REQUESTED;
|
||||
_pkg_missing = false;
|
||||
_depot_query.trigger_depot_query();
|
||||
}
|
||||
}
|
||||
cnt++;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (_state == PKG_SHOWN) {
|
||||
|
||||
/* back to index */
|
||||
if (clicked == "back") {
|
||||
back_to_index();
|
||||
|
||||
} else {
|
||||
|
||||
/* select route to present routing options */
|
||||
if (clicked_route.valid()) {
|
||||
_state = ROUTE_SELECTED;
|
||||
_selected_route.construct(clicked_route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (_state == ROUTE_SELECTED) {
|
||||
|
||||
/* back to index */
|
||||
if (clicked == "back") {
|
||||
back_to_index();
|
||||
|
||||
} else {
|
||||
|
||||
/* close selected route */
|
||||
if (clicked_route == "back") {
|
||||
_state = PKG_SHOWN;
|
||||
_selected_route.destruct();
|
||||
|
||||
} else {
|
||||
|
||||
bool clicked_on_selected_route = false;
|
||||
|
||||
_apply_to_selected_route(action, [&] (Route &route) {
|
||||
|
||||
unsigned cnt = 0;
|
||||
_runtime_config.for_each_service([&] (Service const &service) {
|
||||
|
||||
Hoverable_item::Id const id("service.", cnt++);
|
||||
|
||||
if (clicked_route == id) {
|
||||
|
||||
bool const clicked_service_already_selected =
|
||||
route.selected_service.constructed() &&
|
||||
id == route.selected_service_id;
|
||||
|
||||
if (clicked_service_already_selected) {
|
||||
|
||||
/* clear selection */
|
||||
route.selected_service.destruct();
|
||||
route.selected_service_id = Hoverable_item::Id();
|
||||
|
||||
} else {
|
||||
|
||||
/* select different service */
|
||||
route.selected_service.construct(service);
|
||||
route.selected_service_id = id;
|
||||
}
|
||||
|
||||
_state = PKG_SHOWN;
|
||||
_selected_route.destruct();
|
||||
|
||||
clicked_on_selected_route = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* select different route */
|
||||
if (!clicked_on_selected_route && clicked_route.valid()) {
|
||||
_state = ROUTE_SELECTED;
|
||||
_selected_route.construct(clicked_route);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generate();
|
||||
}
|
@ -14,10 +14,21 @@
|
||||
#ifndef _VIEW__POPUP_DIALOG_H_
|
||||
#define _VIEW__POPUP_DIALOG_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <os/reporter.h>
|
||||
#include <depot/archive.h>
|
||||
|
||||
/* local includes */
|
||||
#include <types.h>
|
||||
#include <model/launchers.h>
|
||||
#include <model/sculpt_version.h>
|
||||
#include <model/component.h>
|
||||
#include <model/runtime_config.h>
|
||||
#include <model/download_queue.h>
|
||||
#include <model/nic_state.h>
|
||||
#include <view/dialog.h>
|
||||
#include <view/selectable_item.h>
|
||||
#include <view/activatable_item.h>
|
||||
#include <depot_query.h>
|
||||
|
||||
namespace Sculpt { struct Popup_dialog; }
|
||||
|
||||
@ -26,9 +37,70 @@ struct Sculpt::Popup_dialog
|
||||
{
|
||||
Env &_env;
|
||||
|
||||
Launchers const &_launchers;
|
||||
Allocator &_alloc;
|
||||
|
||||
Runtime_info const &_runtime_info;
|
||||
Sculpt_version const _sculpt_version { _env };
|
||||
|
||||
Launchers const &_launchers;
|
||||
Nic_state const &_nic_state;
|
||||
Nic_target const &_nic_target;
|
||||
|
||||
bool _nic_ready() const { return _nic_target.ready() && _nic_state.ready(); }
|
||||
|
||||
Runtime_info const &_runtime_info;
|
||||
Runtime_config const &_runtime_config;
|
||||
Download_queue const &_download_queue;
|
||||
|
||||
Depot_query &_depot_query;
|
||||
|
||||
struct Construction_info : Interface
|
||||
{
|
||||
struct With : Interface { virtual void with(Component const &) const = 0; };
|
||||
|
||||
virtual void _with_construction(With const &) const = 0;
|
||||
|
||||
template <typename FN>
|
||||
void with_construction(FN const &fn) const
|
||||
{
|
||||
struct _With : With {
|
||||
FN const &_fn;
|
||||
_With(FN const &fn) : _fn(fn) { }
|
||||
void with(Component const &c) const override { _fn(c); }
|
||||
};
|
||||
_with_construction(_With(fn));
|
||||
}
|
||||
};
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void launch_global(Path const &launcher) = 0;
|
||||
|
||||
virtual Start_name new_construction(Component::Path const &pkg,
|
||||
Component::Info const &info) = 0;
|
||||
|
||||
struct Apply_to : Interface { virtual void apply_to(Component &) = 0; };
|
||||
|
||||
virtual void _apply_to_construction(Apply_to &) = 0;
|
||||
|
||||
template <typename FN>
|
||||
void apply_to_construction(FN const &fn)
|
||||
{
|
||||
struct _Apply_to : Apply_to {
|
||||
FN const &_fn;
|
||||
_Apply_to(FN const &fn) : _fn(fn) { }
|
||||
void apply_to(Component &c) override { _fn(c); }
|
||||
} apply_fn(fn);
|
||||
|
||||
_apply_to_construction(apply_fn);
|
||||
}
|
||||
|
||||
virtual void discard_construction() = 0;
|
||||
virtual void launch_construction() = 0;
|
||||
|
||||
virtual void trigger_download(Path const &) = 0;
|
||||
};
|
||||
|
||||
Construction_info const &_construction_info;
|
||||
|
||||
Expanding_reporter _dialog_reporter { _env, "dialog", "popup_dialog" };
|
||||
|
||||
@ -37,10 +109,85 @@ struct Sculpt::Popup_dialog
|
||||
Signal_handler<Popup_dialog> _hover_handler {
|
||||
_env.ep(), *this, &Popup_dialog::_handle_hover };
|
||||
|
||||
Hoverable_item _item { };
|
||||
Hoverable_item _item { };
|
||||
Activatable_item _action_item { };
|
||||
Activatable_item _install_item { };
|
||||
Hoverable_item _route_item { };
|
||||
|
||||
bool _hovered = false;
|
||||
|
||||
enum State { TOP_LEVEL, DEPOT_REQUESTED, DEPOT_SHOWN, DEPOT_SELECTION,
|
||||
INDEX_REQUESTED, INDEX_SHOWN,
|
||||
PKG_REQUESTED, PKG_SHOWN, ROUTE_SELECTED };
|
||||
|
||||
State _state { TOP_LEVEL };
|
||||
|
||||
typedef Depot::Archive::User User;
|
||||
User _selected_user { };
|
||||
|
||||
bool _pkg_missing = false;
|
||||
|
||||
Component::Name _construction_name { };
|
||||
|
||||
Constructible<Route::Id> _selected_route { };
|
||||
|
||||
bool _route_selected(Route::Id const &id) const
|
||||
{
|
||||
return _selected_route.constructed() && id == _selected_route->string();
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void _apply_to_selected_route(Action &action, FN const &fn)
|
||||
{
|
||||
unsigned cnt = 0;
|
||||
action.apply_to_construction([&] (Component &component) {
|
||||
component.routes.for_each([&] (Route &route) {
|
||||
if (_route_selected(Route::Id(cnt++)))
|
||||
fn(route); }); });
|
||||
}
|
||||
|
||||
struct Menu
|
||||
{
|
||||
enum { MAX_LEVELS = 5 };
|
||||
|
||||
unsigned _level = 0;
|
||||
|
||||
typedef String<64> Name;
|
||||
|
||||
Name _selected[MAX_LEVELS] { };
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
using Genode::print;
|
||||
for (unsigned i = 0; i < _level; i++) {
|
||||
print(out, _selected[i]);
|
||||
if (i + 1 < _level)
|
||||
print(out, " ");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void _for_each_item(Xml_node index, FN const &fn, unsigned level) const
|
||||
{
|
||||
if (level == _level) {
|
||||
index.for_each_sub_node(fn);
|
||||
return;
|
||||
}
|
||||
|
||||
index.for_each_sub_node("index", [&] (Xml_node index) {
|
||||
if (index.attribute_value("name", Name()) == _selected[level])
|
||||
_for_each_item(index, fn, level + 1); });
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_item(Xml_node index, FN const &fn) const
|
||||
{
|
||||
_for_each_item(index, fn, 0);
|
||||
}
|
||||
};
|
||||
|
||||
Menu _menu { };
|
||||
|
||||
void _handle_hover()
|
||||
{
|
||||
_hover_rom.update();
|
||||
@ -49,7 +196,11 @@ struct Sculpt::Popup_dialog
|
||||
|
||||
_hovered = hover.has_sub_node("dialog");
|
||||
|
||||
bool const changed = _item.match(hover, "dialog", "frame", "vbox", "hbox", "name");
|
||||
bool const changed =
|
||||
_item .match(hover, "dialog", "frame", "vbox", "hbox", "name") |
|
||||
_action_item .match(hover, "dialog", "frame", "vbox", "button", "name") |
|
||||
_install_item.match(hover, "dialog", "frame", "vbox", "float", "vbox", "float", "button", "name") |
|
||||
_route_item .match(hover, "dialog", "frame", "vbox", "frame", "vbox", "hbox", "name");
|
||||
|
||||
if (changed)
|
||||
generate();
|
||||
@ -57,78 +208,279 @@ struct Sculpt::Popup_dialog
|
||||
|
||||
bool hovered() const { return _hovered; };
|
||||
|
||||
Attached_rom_dataspace _scan_rom { _env, "report -> runtime/depot_query/scan" };
|
||||
|
||||
Signal_handler<Popup_dialog> _scan_handler {
|
||||
_env.ep(), *this, &Popup_dialog::_handle_scan };
|
||||
|
||||
void _handle_scan()
|
||||
{
|
||||
_scan_rom.update();
|
||||
|
||||
if (_state == DEPOT_REQUESTED)
|
||||
_state = DEPOT_SHOWN;
|
||||
|
||||
if (_state != TOP_LEVEL)
|
||||
generate();
|
||||
}
|
||||
|
||||
Attached_rom_dataspace _index_rom { _env, "report -> runtime/depot_query/index" };
|
||||
|
||||
Signal_handler<Popup_dialog> _index_handler {
|
||||
_env.ep(), *this, &Popup_dialog::_handle_index };
|
||||
|
||||
void _handle_index()
|
||||
{
|
||||
/* prevent modifications of index while browing it */
|
||||
if (_state >= INDEX_SHOWN)
|
||||
return;
|
||||
|
||||
_index_rom.update();
|
||||
|
||||
if (_state == INDEX_REQUESTED)
|
||||
_state = INDEX_SHOWN;
|
||||
|
||||
generate();
|
||||
}
|
||||
|
||||
bool _index_avail(User const &user) const
|
||||
{
|
||||
bool result = false;
|
||||
_index_rom.xml().for_each_sub_node("index", [&] (Xml_node index) {
|
||||
if (index.attribute_value("user", User()) == user)
|
||||
result = true; });
|
||||
return result;
|
||||
};
|
||||
|
||||
Path _index_path(User const &user) const
|
||||
{
|
||||
return Path(user, "/index/", _sculpt_version);
|
||||
}
|
||||
|
||||
void _gen_sub_menu_title(Xml_generator &xml,
|
||||
Start_name const &name,
|
||||
Start_name const &text) const
|
||||
{
|
||||
gen_named_node(xml, "hbox", name, [&] () {
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", "yes");
|
||||
xml.node("hbox", [&] () {
|
||||
gen_named_node(xml, "button", "back", [&] () {
|
||||
xml.attribute("selected", "yes");
|
||||
xml.attribute("style", "back");
|
||||
_item.gen_button_attr(xml, name);
|
||||
xml.node("hbox", [&] () { });
|
||||
});
|
||||
gen_named_node(xml, "label", "label", [&] () {
|
||||
xml.attribute("font", "title/regular");
|
||||
xml.attribute("text", Path(" ", text));
|
||||
});
|
||||
});
|
||||
});
|
||||
gen_named_node(xml, "hbox", "right", [&] () { });
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_menu_entry(Xml_generator &xml, Start_name const &name,
|
||||
Component::Info const &text, bool selected,
|
||||
char const *style = "radio") const
|
||||
{
|
||||
gen_named_node(xml, "hbox", name, [&] () {
|
||||
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", "yes");
|
||||
|
||||
xml.node("hbox", [&] () {
|
||||
gen_named_node(xml, "button", "button", [&] () {
|
||||
|
||||
if (selected)
|
||||
xml.attribute("selected", "yes");
|
||||
|
||||
xml.attribute("style", style);
|
||||
_item.gen_button_attr(xml, name);
|
||||
xml.node("hbox", [&] () { });
|
||||
});
|
||||
gen_named_node(xml, "label", "name", [&] () {
|
||||
xml.attribute("text", Path(" ", text)); });
|
||||
});
|
||||
});
|
||||
|
||||
gen_named_node(xml, "hbox", "right", [&] () { });
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_route_entry(Xml_generator &xml,
|
||||
Start_name const &name,
|
||||
Start_name const &text,
|
||||
bool selected, char const *style = "radio") const
|
||||
{
|
||||
gen_named_node(xml, "hbox", name, [&] () {
|
||||
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", "yes");
|
||||
|
||||
xml.node("hbox", [&] () {
|
||||
gen_named_node(xml, "button", "button", [&] () {
|
||||
|
||||
if (selected)
|
||||
xml.attribute("selected", "yes");
|
||||
|
||||
xml.attribute("style", style);
|
||||
_route_item.gen_button_attr(xml, name);
|
||||
xml.node("hbox", [&] () { });
|
||||
});
|
||||
gen_named_node(xml, "label", "name", [&] () {
|
||||
xml.attribute("text", Path(" ", text)); });
|
||||
});
|
||||
});
|
||||
|
||||
gen_named_node(xml, "hbox", "right", [&] () { });
|
||||
});
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void _for_each_menu_item(FN const &fn) const
|
||||
{
|
||||
Xml_node index = _index_rom.xml();
|
||||
|
||||
/*
|
||||
* The index may contain duplicates, evaluate only the first match.
|
||||
*/
|
||||
bool first = true;
|
||||
index.for_each_sub_node("index", [&] (Xml_node index) {
|
||||
|
||||
if (index.attribute_value("user", User()) != _selected_user)
|
||||
return;
|
||||
|
||||
if (first)
|
||||
_menu.for_each_item(index, fn);
|
||||
|
||||
first = false;
|
||||
});
|
||||
}
|
||||
|
||||
static void _gen_info_label(Xml_generator &xml, char const *name,
|
||||
Component::Info const &info)
|
||||
{
|
||||
gen_named_node(xml, "label", name, [&] () {
|
||||
xml.attribute("font", "annotation/regular");
|
||||
xml.attribute("text", Component::Info(" ", info, " ")); });
|
||||
}
|
||||
|
||||
void _gen_pkg_info (Xml_generator &, Component const &) const;
|
||||
void _gen_pkg_elements (Xml_generator &, Component const &) const;
|
||||
void _gen_menu_elements(Xml_generator &) const;
|
||||
|
||||
void generate()
|
||||
{
|
||||
_dialog_reporter.generate([&] (Xml_generator &xml) {
|
||||
xml.node("frame", [&] () {
|
||||
xml.node("vbox", [&] () {
|
||||
_gen_menu_elements(xml); }); }); });
|
||||
}
|
||||
|
||||
_launchers.for_each([&] (Launchers::Info const &info) {
|
||||
void click(Action &action);
|
||||
|
||||
/* allow each launcher to be used only once */
|
||||
if (_runtime_info.present_in_runtime(info.path))
|
||||
return;
|
||||
void clack(Action &action)
|
||||
{
|
||||
_action_item.confirm_activation_on_clack();
|
||||
_install_item.confirm_activation_on_clack();
|
||||
|
||||
gen_named_node(xml, "hbox", info.path, [&] () {
|
||||
if (_action_item.activated("launch")) {
|
||||
action.launch_construction();
|
||||
reset();
|
||||
}
|
||||
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", "yes");
|
||||
|
||||
xml.node("hbox", [&] () {
|
||||
gen_named_node(xml, "button", "button", [&] () {
|
||||
xml.attribute("style", "radio");
|
||||
_item.gen_button_attr(xml, info.path);
|
||||
xml.node("hbox", [&] () { });
|
||||
});
|
||||
gen_named_node(xml, "label", "name", [&] () {
|
||||
xml.attribute("text", Path(" ", info.path)); });
|
||||
});
|
||||
});
|
||||
|
||||
gen_named_node(xml, "hbox", "right", [&] () { });
|
||||
});
|
||||
});
|
||||
});
|
||||
if (_pkg_missing && _install_item.activated("install")) {
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
action.trigger_download(component.path);
|
||||
_install_item.reset();
|
||||
generate();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void launch_global(Path const &launcher) = 0;
|
||||
};
|
||||
|
||||
void click(Action &action)
|
||||
{
|
||||
action.launch_global(_item._hovered);
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX
|
||||
*
|
||||
* This method should not be needed. However, in some situations,
|
||||
* the popup menu_view does not receive a leave event when the popup
|
||||
* is closed, to the effect of maintaining stale hover information
|
||||
* instead of generating an empty hover report. So from the sculpt-
|
||||
* manager's point of view, the popup is still hovered even if it is
|
||||
* actually closed. The 'reset_hover' shortcuts the regular hover
|
||||
* update in this situation.
|
||||
*/
|
||||
void reset_hover()
|
||||
void reset()
|
||||
{
|
||||
_item._hovered = Hoverable_item::Id();
|
||||
_route_item._hovered = Hoverable_item::Id();
|
||||
_action_item.reset();
|
||||
_install_item.reset();
|
||||
_hovered = false;
|
||||
_state = TOP_LEVEL;
|
||||
_selected_user = User();
|
||||
_selected_route.destruct();
|
||||
_menu._level = 0;
|
||||
}
|
||||
|
||||
Popup_dialog(Env &env, Launchers const &launchers,
|
||||
Runtime_info const &runtime_info)
|
||||
Popup_dialog(Env &env, Allocator &alloc,
|
||||
Launchers const &launchers,
|
||||
Nic_state const &nic_state,
|
||||
Nic_target const &nic_target,
|
||||
Runtime_info const &runtime_info,
|
||||
Runtime_config const &runtime_config,
|
||||
Download_queue const &download_queue,
|
||||
Depot_query &depot_query,
|
||||
Construction_info const &construction_info)
|
||||
:
|
||||
_env(env), _launchers(launchers), _runtime_info(runtime_info)
|
||||
_env(env), _alloc(alloc), _launchers(launchers),
|
||||
_nic_state(nic_state), _nic_target(nic_target),
|
||||
_runtime_info(runtime_info), _runtime_config(runtime_config),
|
||||
_download_queue(download_queue), _depot_query(depot_query),
|
||||
_construction_info(construction_info)
|
||||
{
|
||||
_hover_rom.sigh(_hover_handler);
|
||||
_scan_rom.sigh(_scan_handler);
|
||||
_index_rom.sigh(_index_handler);
|
||||
|
||||
generate();
|
||||
}
|
||||
|
||||
void gen_depot_query(Xml_generator &xml) const
|
||||
{
|
||||
if (_state >= TOP_LEVEL)
|
||||
xml.node("scan", [&] () {
|
||||
xml.attribute("users", "yes"); });
|
||||
|
||||
if (_state >= TOP_LEVEL)
|
||||
_scan_rom.xml().for_each_sub_node("user", [&] (Xml_node user) {
|
||||
xml.node("index", [&] () {
|
||||
User const name = user.attribute_value("name", User());
|
||||
xml.attribute("user", name);
|
||||
xml.attribute("version", _sculpt_version);
|
||||
if (_state >= INDEX_REQUESTED && _selected_user == name)
|
||||
xml.attribute("content", "yes");
|
||||
});
|
||||
});
|
||||
|
||||
if (_state >= PKG_REQUESTED)
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
xml.node("blueprint", [&] () {
|
||||
xml.attribute("pkg", component.path); }); });
|
||||
}
|
||||
|
||||
void apply_blueprint(Component &construction, Xml_node blueprint)
|
||||
{
|
||||
if (_state < PKG_REQUESTED)
|
||||
return;
|
||||
|
||||
_pkg_missing = blueprint_missing(blueprint, construction.path)
|
||||
|| blueprint_any_rom_missing(blueprint);
|
||||
|
||||
construction.try_apply_blueprint(blueprint);
|
||||
if (construction.blueprint_known)
|
||||
_state = PKG_SHOWN;
|
||||
|
||||
generate();
|
||||
}
|
||||
|
||||
bool interested_in_download() const
|
||||
{
|
||||
if (_state == DEPOT_SELECTION)
|
||||
return true;
|
||||
|
||||
return _state >= PKG_REQUESTED && _pkg_missing;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__POPUP_DIALOG_H_ */
|
||||
|
@ -121,7 +121,7 @@ namespace Sculpt {
|
||||
}
|
||||
|
||||
/**
|
||||
* Query attribute value from XML sub nodd
|
||||
* Query attribute value from XML sub node
|
||||
*
|
||||
* The list of arguments except for the last one refer to XML path into the
|
||||
* XML structure. The last argument denotes the queried attribute name.
|
||||
|
Loading…
Reference in New Issue
Block a user