From b322ca76d3500efcc6482a0ba8dbfb4d8d74f9e3 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Fri, 15 Dec 2023 17:06:58 +0000 Subject: [PATCH 01/68] Structure find_by_* calls to avoid raw loops This set of changes is made as preparation for the addition of an Aviso attribute, and is expected to reduce the duplication of source code. Re ECFLOW-1931 --- libs/core/src/ecflow/core/Stl.hpp | 44 +++++ libs/node/src/ecflow/node/Jobs.cpp | 11 +- libs/node/src/ecflow/node/Node.cpp | 38 ++-- libs/node/src/ecflow/node/NodeAdd.cpp | 23 +-- libs/node/src/ecflow/node/NodeChange.cpp | 136 +++++++------- libs/node/src/ecflow/node/NodeDelete.cpp | 223 +++++++++++------------ libs/node/src/ecflow/node/NodeFind.cpp | 95 +++------- 7 files changed, 282 insertions(+), 288 deletions(-) diff --git a/libs/core/src/ecflow/core/Stl.hpp b/libs/core/src/ecflow/core/Stl.hpp index 7fbd18b50..efbd46d51 100644 --- a/libs/core/src/ecflow/core/Stl.hpp +++ b/libs/core/src/ecflow/core/Stl.hpp @@ -12,6 +12,7 @@ #define ecflow_core_Stl_HPP #include +#include namespace ecf { /// Helper struct that will aid the deletion of Pointer from a container @@ -57,6 +58,49 @@ void AssoDeletePtrs(Container& pContainer) { std::for_each(pContainer.begin(), pContainer.end(), TAsoDeletor()); pContainer.clear(); } + +namespace algorithm { + +namespace detail { + +template +struct is_shared_pointer : std::false_type +{ +}; + +template +struct is_shared_pointer> : std::true_type +{ +}; + +} // namespace detail + +template +constexpr bool is_shared_pointer_v = detail::is_shared_pointer::value; + +template +inline auto find_by(C& container, Predicate predicate) { + return std::find_if(std::begin(container), std::end(container), predicate); +} + +template +inline auto find_by_name(C& container, std::string_view name) { + // Important: special handling to seamlessly handle containers of std::shared_ptr. + if constexpr (is_shared_pointer_v) { + return find_by(container, [&](const auto& item) { return item->name() == name; }); + } + else { + return find_by(container, [&](const auto& item) { return item.name() == name; }); + } +} + +template +inline auto find_by_number(C& container, I number) { + return find_by(container, [&](const auto& item) { return item.number() == number; }); +} + +} // namespace algorithm + } // namespace ecf #endif /* ecflow_core_Stl_HPP */ diff --git a/libs/node/src/ecflow/node/Jobs.cpp b/libs/node/src/ecflow/node/Jobs.cpp index dd123ca66..20c3741ba 100644 --- a/libs/node/src/ecflow/node/Jobs.cpp +++ b/libs/node/src/ecflow/node/Jobs.cpp @@ -54,12 +54,11 @@ bool Jobs::generate(JobsParam& jobsParam) const { if (defs_) { if (defs_->server().get_state() == SState::RUNNING) { - const std::vector& suiteVec = defs_->suiteVec(); - size_t theSize = suiteVec.size(); - for (size_t i = 0; i < theSize; i++) { - // SuiteChanged moved internal to Suite::resolveDependencies. i.e on fast path - // and when suites not begun we save a constructor/destructor calls - (void)suiteVec[i]->resolveDependencies(jobsParam); + const std::vector& suites = defs_->suiteVec(); + for (const suite_ptr& suite: suites) { + // SuiteChanged moved into Suite::resolveDependencies. + // This ensures the fast path and when suite are not begun we save a ctor/dtor call + (void)suite->resolveDependencies(jobsParam); } } } diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 6d6276adc..165612137 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -20,6 +20,7 @@ #include "ecflow/core/Log.hpp" #include "ecflow/core/PrintStyle.hpp" #include "ecflow/core/Serialization.hpp" +#include "ecflow/core/Stl.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/core/cereal_boost_time.hpp" #include "ecflow/node/AbstractObserver.hpp" @@ -1045,11 +1046,9 @@ void Node::setStateOnly(NState::State newState, // Record state changes for verification if (misc_attrs_) { - size_t theSize = misc_attrs_->verifys_.size(); - for (size_t i = 0; i < theSize; i++) { - if (misc_attrs_->verifys_[i].state() == newState) { - // cout << "Verify: calendar " << to_simple_string(calendar.date()) << "\n"; - misc_attrs_->verifys_[i].incrementActual(); + for (auto& verify : misc_attrs_->verifys_) { + if (verify.state() == newState) { + verify.incrementActual(); } } } @@ -1092,22 +1091,27 @@ DState::State Node::dstate() const { } bool Node::set_event(const std::string& event_name_or_number) { - for (Event& e : events_) { - if (e.name_or_number() == event_name_or_number) { - e.set_value(true); - return true; - } + auto found = ecf::algorithm::find_by( + events_, [&](const auto& item) { return item.name_or_number() == event_name_or_number; }); + + if (found == std::end(events_)) { + return false; } - return false; + + found->set_value(true); + return true; } + bool Node::clear_event(const std::string& event_name_or_number) { - for (Event& e : events_) { - if (e.name_or_number() == event_name_or_number) { - e.set_value(false); - return true; - } + auto found = ecf::algorithm::find_by( + events_, [&](const auto& item) { return item.name_or_number() == event_name_or_number; }); + + if (found == std::end(events_)) { + return false; } - return false; + + found->set_value(false); + return true; } void Node::setRepeatToLastValue() { diff --git a/libs/node/src/ecflow/node/NodeAdd.cpp b/libs/node/src/ecflow/node/NodeAdd.cpp index a311671a9..9c103b51b 100644 --- a/libs/node/src/ecflow/node/NodeAdd.cpp +++ b/libs/node/src/ecflow/node/NodeAdd.cpp @@ -15,6 +15,7 @@ #include "ecflow/attribute/LateAttr.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" +#include "ecflow/core/Stl.hpp" #include "ecflow/node/AutoRestoreAttr.hpp" #include "ecflow/node/Expression.hpp" #include "ecflow/node/Limit.hpp" @@ -25,18 +26,18 @@ using namespace ecf; using namespace std; bool Node::update_variable(const std::string& name, const std::string& value) { - size_t theSize = vars_.size(); - for (size_t i = 0; i < theSize; i++) { - if (vars_[i].name() == name) { - // Variable already exist, *UPDATE* its value - vars_[i].set_value(value); - if (0 == Ecf::debug_level()) - std::cerr << "Node::addVariable: Variable of name '" << name << "' already exist for node " - << debugNodePath() << " updating with value '" << value << "'\n"; - return true; - } + auto found = ecf::algorithm::find_by_name(vars_, name); + + if (found == std::end(vars_)) { + return false; + } + + found->set_value(value); + if (0 == Ecf::debug_level()) { + std::cerr << "Node::addVariable: Variable of name '" << name << "' already exist for node " << debugNodePath() + << " updating with value '" << value << "'\n"; } - return false; + return true; } void Node::addVariable(const Variable& v) { diff --git a/libs/node/src/ecflow/node/NodeChange.cpp b/libs/node/src/ecflow/node/NodeChange.cpp index 34813124f..a284bbba3 100644 --- a/libs/node/src/ecflow/node/NodeChange.cpp +++ b/libs/node/src/ecflow/node/NodeChange.cpp @@ -13,6 +13,7 @@ #include "ecflow/attribute/LateAttr.hpp" #include "ecflow/core/Converter.hpp" #include "ecflow/core/Ecf.hpp" +#include "ecflow/core/Stl.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/ExprAst.hpp" #include "ecflow/node/Limit.hpp" @@ -22,15 +23,14 @@ using namespace ecf; using namespace std; void Node::changeVariable(const std::string& name, const std::string& value) { - size_t theSize = vars_.size(); - for (size_t i = 0; i < theSize; i++) { - if (vars_[i].name() == name) { - vars_[i].set_value(value); - variable_change_no_ = Ecf::incr_state_change_no(); - return; - } + auto found = ecf::algorithm::find_by_name(vars_, name); + + if (found == std::end(vars_)) { + throw std::runtime_error("Node::changeVariable: Could not find variable " + name); } - throw std::runtime_error("Node::changeVariable: Could not find variable " + name); + + found->set_value(value); + variable_change_no_ = Ecf::incr_state_change_no(); } bool Node::set_event(const std::string& event_name_or_number, bool value) { @@ -39,10 +39,10 @@ bool Node::set_event(const std::string& event_name_or_number, bool value) { } // find by name first - size_t theSize = events_.size(); - for (size_t i = 0; i < theSize; i++) { - if (events_[i].name() == event_name_or_number) { - events_[i].set_value(value); + { + auto found = ecf::algorithm::find_by_name(events_, event_name_or_number); + if (found != std::end(events_)) { + found->set_value(value); return true; } } @@ -50,12 +50,11 @@ bool Node::set_event(const std::string& event_name_or_number, bool value) { // Test for numeric, and then casting, is ****faster***** than relying on exception alone if (event_name_or_number.find_first_of(Str::NUMERIC()) == 0) { try { - auto eventNumber = ecf::convert_to(event_name_or_number); - for (size_t i = 0; i < theSize; i++) { - if (events_[i].number() == eventNumber) { - events_[i].set_value(value); - return true; - } + auto number = ecf::convert_to(event_name_or_number); + auto found = ecf::algorithm::find_by_number(events_, number); + if (found != std::end(events_)) { + found->set_value(value); + return true; } } catch (const ecf::bad_conversion&) { @@ -70,10 +69,10 @@ bool Node::set_event_used_in_trigger(const std::string& event_name_or_number) { } // find by name first - size_t theSize = events_.size(); - for (size_t i = 0; i < theSize; i++) { - if (events_[i].name() == event_name_or_number) { - events_[i].usedInTrigger(true); + { + auto found = ecf::algorithm::find_by_name(events_, event_name_or_number); + if (found != std::end(events_)) { + found->usedInTrigger(true); return true; } } @@ -81,18 +80,17 @@ bool Node::set_event_used_in_trigger(const std::string& event_name_or_number) { // Test for numeric, and then casting, is ****faster***** than relying on exception alone if (event_name_or_number.find_first_of(Str::NUMERIC()) == 0) { try { - auto eventNumber = ecf::convert_to(event_name_or_number); - for (size_t i = 0; i < theSize; i++) { - if (events_[i].number() == eventNumber) { - events_[i].usedInTrigger(true); - return true; - ; - } + auto number = ecf::convert_to(event_name_or_number); + auto found = ecf::algorithm::find_by_number(events_, number); + if (found != std::end(events_)) { + found->usedInTrigger(true); + return true; } } catch (const ecf::bad_conversion&) { } } + return false; } void Node::changeEvent(const std::string& event_name_or_number, const std::string& setOrClear) { @@ -116,25 +114,25 @@ void Node::changeEvent(const std::string& event_name_or_number, bool value) { throw std::runtime_error("Node::changeEvent: Could not find event " + event_name_or_number); } -bool Node::set_meter(const std::string& meter_name, int value) { - size_t the_meter_size = meters_.size(); - for (size_t i = 0; i < the_meter_size; ++i) { - if (meters_[i].name() == meter_name) { - meters_[i].set_value(value); - return true; - } +bool Node::set_meter(const std::string& name, int value) { + auto found = ecf::algorithm::find_by_name(meters_, name); + + if (found == std::end(meters_)) { + return false; } - return false; + + found->set_value(value); + return true; } -bool Node::set_meter_used_in_trigger(const std::string& meter_name) { - size_t the_meter_size = meters_.size(); - for (size_t i = 0; i < the_meter_size; ++i) { - if (meters_[i].name() == meter_name) { - meters_[i].usedInTrigger(true); - return true; - } +bool Node::set_meter_used_in_trigger(const std::string& name) { + auto found = ecf::algorithm::find_by_name(meters_, name); + + if (found == std::end(meters_)) { + return false; } - return false; + + found->usedInTrigger(true); + return true; } void Node::changeMeter(const std::string& meter_name, const std::string& value) { int theValue = 0; @@ -146,6 +144,7 @@ void Node::changeMeter(const std::string& meter_name, const std::string& value) } changeMeter(meter_name, theValue); } + void Node::changeMeter(const std::string& meter_name, int value) { if (set_meter(meter_name, value)) return; @@ -153,14 +152,13 @@ void Node::changeMeter(const std::string& meter_name, int value) { } void Node::changeLabel(const std::string& name, const std::string& value) { - size_t theSize = labels_.size(); - for (size_t i = 0; i < theSize; i++) { - if (labels_[i].name() == name) { - labels_[i].set_new_value(value); - return; - } + auto found = ecf::algorithm::find_by_name(labels_, name); + + if (found == std::end(labels_)) { + throw std::runtime_error("Node::changeLabel: Could not find label " + name); } - throw std::runtime_error("Node::changeLabel: Could not find label " + name); + + found->set_new_value(value); } void Node::changeTrigger(const std::string& expression) { @@ -241,30 +239,28 @@ void Node::change_time(const std::string& old, const std::string& new_time) { TimeAttr old_attr(TimeSeries::create(old)); // can throw if parse fails TimeAttr new_attr(TimeSeries::create(new_time)); // can throw if parse fails - size_t theSize = times_.size(); - for (size_t i = 0; i < theSize; i++) { - // Dont use '==' since that compares additional state like free_ - if (times_[i].structureEquals(old_attr)) { - times_[i] = new_attr; - state_change_no_ = Ecf::incr_state_change_no(); - return; - } + // Don't use '==' since that compares additional state like free_ + auto found = ecf::algorithm::find_by(times_, [&](const auto& item) { return item.structureEquals(old_attr); }); + + if (found == std::end(times_)) { + throw std::runtime_error("Node::change_time : Cannot find time attribute: "); } - throw std::runtime_error("Node::change_time : Cannot find time attribute: "); + + *found = new_attr; + state_change_no_ = Ecf::incr_state_change_no(); } void Node::change_today(const std::string& old, const std::string& new_time) { TodayAttr old_attr(TimeSeries::create(old)); // can throw if parse fails TodayAttr new_attr(TimeSeries::create(new_time)); // can throw if parse fails - size_t theSize = todays_.size(); - for (size_t i = 0; i < theSize; i++) { - // Dont use '==' since that compares additional state like free_ - if (todays_[i].structureEquals(old_attr)) { - todays_[i] = new_attr; - state_change_no_ = Ecf::incr_state_change_no(); - return; - } + // Don't use '==' since that compares additional state like free_ + auto found = ecf::algorithm::find_by(todays_, [&](const auto& item) { return item.structureEquals(old_attr); }); + + if (found == std::end(todays_)) { + throw std::runtime_error("Node::change_today : Cannot find time attribute: "); } - throw std::runtime_error("Node::change_today : Cannot find today attribute: "); + + *found = new_attr; + state_change_no_ = Ecf::incr_state_change_no(); } diff --git a/libs/node/src/ecflow/node/NodeDelete.cpp b/libs/node/src/ecflow/node/NodeDelete.cpp index 8b56b1f6d..8c0132bf5 100644 --- a/libs/node/src/ecflow/node/NodeDelete.cpp +++ b/libs/node/src/ecflow/node/NodeDelete.cpp @@ -14,6 +14,7 @@ #include "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/attribute/LateAttr.hpp" #include "ecflow/core/Ecf.hpp" +#include "ecflow/core/Stl.hpp" #include "ecflow/node/AutoRestoreAttr.hpp" #include "ecflow/node/Expression.hpp" #include "ecflow/node/Limit.hpp" @@ -37,20 +38,18 @@ void Node::deleteTime(const std::string& name) { } void Node::delete_time(const ecf::TimeAttr& attr) { - size_t theSize = times_.size(); - for (size_t i = 0; i < theSize; i++) { - // Dont use '==' since that compares additional state like free_ - if (times_[i].structureEquals(attr)) { - times_.erase(times_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by(times_, [&](const auto& item) { return item.structureEquals(attr); }); + + if (found == std::end(times_)) { + throw std::runtime_error("Node::delete_time: Cannot find time attribute: "); + } + + times_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::delete_time\n"; + std::cout << "Node::delete_time\n"; #endif - return; - } - } - throw std::runtime_error("Node::delete_time: Cannot find time attribute: "); } void Node::deleteToday(const std::string& name) { @@ -68,19 +67,19 @@ void Node::deleteToday(const std::string& name) { } void Node::delete_today(const ecf::TodayAttr& attr) { - size_t theSize = todays_.size(); - for (size_t i = 0; i < theSize; i++) { - // Dont use '==' since that compares additional state like free_ - if (todays_[i].structureEquals(attr)) { - todays_.erase(todays_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + // Don't use '==' since that compares additional state like free_ + auto found = ecf::algorithm::find_by(todays_, [&](const auto& item) { return item.structureEquals(attr); }); + + if (found == std::end(todays_)) { + throw std::runtime_error("Node::delete_today: Cannot find today attribute: " + attr.toString()); + } + + todays_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); + #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::delete_today\n"; + std::cout << "Node::delete_today\n"; #endif - return; - } - } - throw std::runtime_error("Node::delete_today: Cannot find today attribute: " + attr.toString()); } void Node::deleteDate(const std::string& name) { @@ -98,18 +97,18 @@ void Node::deleteDate(const std::string& name) { } void Node::delete_date(const DateAttr& attr) { - for (size_t i = 0; i < dates_.size(); i++) { - // Dont use '==' since that compares additional state like free_ - if (attr.structureEquals(dates_[i])) { - dates_.erase(dates_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + // Don't use '==' since that compares additional state like free_ + auto found = ecf::algorithm::find_by(dates_, [&](const auto& item) { return item.structureEquals(attr); }); + + if (found == std::end(dates_)) { + throw std::runtime_error("Node::delete_date: Cannot find date attribute: " + attr.toString()); + } + + dates_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::delete_date\n"; + std::cout << "Node::delete_date\n"; #endif - return; - } - } - throw std::runtime_error("Node::delete_date: Cannot find date attribute: " + attr.toString()); } void Node::deleteDay(const std::string& name) { @@ -127,18 +126,18 @@ void Node::deleteDay(const std::string& name) { } void Node::delete_day(const DayAttr& attr) { - for (size_t i = 0; i < days_.size(); i++) { - // Dont use '==' since that compares additional state like free_ - if (attr.structureEquals(days_[i])) { - days_.erase(days_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + // Don't use '==' since that compares additional state like free_ + auto found = ecf::algorithm::find_by(days_, [&](const auto& item) { return item.structureEquals(attr); }); + + if (found == std::end(days_)) { + throw std::runtime_error("Node::delete_day: Cannot find day attribute: " + attr.toString()); + } + + days_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::delete_day\n"; + std::cout << "Node::delete_day\n"; #endif - return; - } - } - throw std::runtime_error("Node::delete_day: Cannot find day attribute: " + attr.toString()); } void Node::deleteCron(const std::string& name) { @@ -156,18 +155,18 @@ void Node::deleteCron(const std::string& name) { } void Node::delete_cron(const ecf::CronAttr& attr) { - for (size_t i = 0; i < crons_.size(); i++) { - // Dont use '==' since that compares additional state like free_ - if (attr.structureEquals(crons_[i])) { - crons_.erase(crons_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + // Don't use '==' since that compares additional state like free_ + auto found = ecf::algorithm::find_by(crons_, [&](const auto& item) { return item.structureEquals(attr); }); + + if (found == std::end(crons_)) { + throw std::runtime_error("Node::delete_cron: Cannot find cron attribute: " + attr.toString()); + } + + crons_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::delete_cron\n"; + std::cout << "Node::delete_cron\n"; #endif - return; - } - } - throw std::runtime_error("Node::delete_cron: Cannot find cron attribute: " + attr.toString()); } void Node::deleteVariable(const std::string& name) { @@ -181,19 +180,17 @@ void Node::deleteVariable(const std::string& name) { return; } - size_t theSize = vars_.size(); - for (size_t i = 0; i < theSize; i++) { - if (vars_[i].name() == name) { - vars_.erase(vars_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by_name(vars_, name); + + if (found == std::end(vars_)) { + throw std::runtime_error("Node::deleteVariable: Cannot find 'user' variable of name " + name); + } + vars_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::deleteVariable\n"; + std::cout << "Node::deleteVariable\n"; #endif - return; - } - } - throw std::runtime_error("Node::deleteVariable: Cannot find 'user' variable of name " + name); } void Node::delete_variable_no_error(const std::string& name) { @@ -204,17 +201,14 @@ void Node::delete_variable_no_error(const std::string& name) { return; } - size_t theSize = vars_.size(); - for (size_t i = 0; i < theSize; i++) { - if (vars_[i].name() == name) { - vars_.erase(vars_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by_name(vars_, name); + if (found != std::end(vars_)) { + vars_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::delete_variable_no_error\n"; + std::cout << "Node::delete_variable_no_error\n"; #endif - return; - } } } @@ -228,18 +222,17 @@ void Node::deleteEvent(const std::string& name) { return; } - size_t theSize = events_.size(); - for (size_t i = 0; i < theSize; i++) { - if (events_[i].name_or_number() == name) { - events_.erase(events_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by(events_, [&](const auto& item) { return item.name_or_number() == name; }); + + if (found == std::end(events_)) { + throw std::runtime_error("Node::deleteEvent: Cannot find event: " + name); + } + + events_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::deleteEvent\n"; + std::cout << "Node::deleteEvent\n"; #endif - return; - } - } - throw std::runtime_error("Node::deleteEvent: Cannot find event: " + name); } void Node::deleteMeter(const std::string& name) { @@ -252,18 +245,17 @@ void Node::deleteMeter(const std::string& name) { return; } - size_t theSize = meters_.size(); - for (size_t i = 0; i < theSize; i++) { - if (meters_[i].name() == name) { - meters_.erase(meters_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by_name(meters_, name); + + if (found == std::end(meters_)) { + throw std::runtime_error("Node::deleteMeter: Cannot find meter: " + name); + } + + meters_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Expression::clearFree()\n"; + std::cout << "Expression::clearFree()\n"; #endif - return; - } - } - throw std::runtime_error("Node::deleteMeter: Cannot find meter: " + name); } void Node::deleteLabel(const std::string& name) { @@ -276,18 +268,17 @@ void Node::deleteLabel(const std::string& name) { return; } - size_t theSize = labels_.size(); - for (size_t i = 0; i < theSize; i++) { - if (labels_[i].name() == name) { - labels_.erase(labels_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by_name(labels_, name); + + if (found == std::end(labels_)) { + throw std::runtime_error("Node::deleteLabel: Cannot find label: " + name); + } + + labels_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::deleteLabel\n"; + std::cout << "Node::deleteLabel\n"; #endif - return; - } - } - throw std::runtime_error("Node::deleteLabel: Cannot find label: " + name); } void Node::delete_queue(const std::string& name) { @@ -346,18 +337,17 @@ void Node::deleteLimit(const std::string& name) { return; } - size_t theSize = limits_.size(); - for (size_t i = 0; i < theSize; i++) { - if (limits_[i]->name() == name) { - limits_.erase(limits_.begin() + i); - state_change_no_ = Ecf::incr_state_change_no(); + auto found = ecf::algorithm::find_by_name(limits_, name); + + if (found == std::end(limits_)) { + throw std::runtime_error("Node::deleteLimit: Cannot find limit: " + name); + } + + limits_.erase(found); + state_change_no_ = Ecf::incr_state_change_no(); #ifdef DEBUG_STATE_CHANGE_NO - std::cout << "Node::deleteLimit\n"; + std::cout << "Node::deleteLimit\n"; #endif - return; - } - } - throw std::runtime_error("Node::deleteLimit: Cannot find limit: " + name); } void Node::delete_limit_path(const std::string& name, const std::string& path) { @@ -368,14 +358,13 @@ void Node::delete_limit_path(const std::string& name, const std::string& path) { throw std::runtime_error("Node::delete_limit_path: the limit path must be provided"); } - size_t theSize = limits_.size(); - for (size_t i = 0; i < theSize; i++) { - if (limits_[i]->name() == name) { - limits_[i]->delete_path(path); // will update state change no - return; - } + auto found = ecf::algorithm::find_by_name(limits_, name); + + if (found == std::end(limits_)) { + throw std::runtime_error("Node::delete_limit_path: Cannot find limit: " + name); } - throw std::runtime_error("Node::delete_limit_path: Cannot find limit: " + name); + + (*found)->delete_path(path); } void Node::deleteInlimit(const std::string& name) { diff --git a/libs/node/src/ecflow/node/NodeFind.cpp b/libs/node/src/ecflow/node/NodeFind.cpp index 362d19f99..59aac5daf 100644 --- a/libs/node/src/ecflow/node/NodeFind.cpp +++ b/libs/node/src/ecflow/node/NodeFind.cpp @@ -10,6 +10,7 @@ #include "ecflow/core/Converter.hpp" #include "ecflow/core/NodePath.hpp" +#include "ecflow/core/Stl.hpp" #include "ecflow/core/Str.hpp" #include "ecflow/node/Defs.hpp" #include "ecflow/node/Limit.hpp" @@ -161,12 +162,8 @@ bool Node::user_variable_exists(const std::string& name) const { } const Variable& Node::findVariable(const std::string& name) const { - for (const auto& v : vars_) { - if (v.name() == name) { - return v; - } - } - return Variable::EMPTY(); + auto found = ecf::algorithm::find_by_name(vars_, name); + return found == std::end(vars_) ? Variable::EMPTY() : *found; } std::string Node::find_parent_variable_sub_value(const std::string& name) const { @@ -224,13 +221,14 @@ const Variable& Node::find_parent_variable(const std::string& name) const { } bool Node::findVariableValue(const std::string& name, std::string& returnedValue) const { - for (const auto& var : vars_) { - if (var.name() == name) { - returnedValue = var.theValue(); - return true; - } + auto found = ecf::algorithm::find_by_name(vars_, name); + + if (found == std::end(vars_)) { + return false; } - return false; + + returnedValue = found->theValue(); + return true; } bool Node::findGenVariableValue(const std::string& name, std::string& returnedValue) const { @@ -243,21 +241,13 @@ bool Node::findGenVariableValue(const std::string& name, std::string& returnedVa } bool Node::findLimit(const Limit& theLimit) const { - for (const auto& lim : limits_) { - if (lim->name() == theLimit.name()) { - return true; - } - } - return false; + auto found = ecf::algorithm::find_by_name(limits_, theLimit.name()); + return found != std::end(limits_); } limit_ptr Node::find_limit(const std::string& theName) const { - for (const auto& lim : limits_) { - if (lim->name() == theName) { - return lim; - } - } - return limit_ptr(); + auto found = ecf::algorithm::find_by_name(limits_, theName); + return found == std::end(limits_) ? limit_ptr() : *found; } limit_ptr Node::findLimitUpNodeTree(const std::string& name) const { @@ -278,31 +268,18 @@ limit_ptr Node::findLimitUpNodeTree(const std::string& name) const { } const Event& Node::findEvent(const Event& theEvent) const { - for (const auto& e : events_) { - // compare ignores state like value_ and initial_value - if (e.compare(theEvent)) { - return e; - } - } - return Event::EMPTY(); + auto found = ecf::algorithm::find_by(events_, [&](const auto& item) { return item.compare(theEvent); }); + return found == std::end(events_) ? Event::EMPTY() : *found; } const Event& Node::findEventByNumber(int number) const { - for (const auto& e : events_) { - if (e.number() == number) { - return e; - } - } - return Event::EMPTY(); + auto found = ecf::algorithm::find_by_number(events_, number); + return found == std::end(events_) ? Event::EMPTY() : *found; } const Event& Node::findEventByName(const std::string& event_name) const { - for (const auto& e : events_) { - if (e.name() == event_name) { - return e; - } - } - return Event::EMPTY(); + auto found = ecf::algorithm::find_by_name(events_, event_name); + return found == std::end(events_) ? Event::EMPTY() : *found; } const Event& Node::findEventByNameOrNumber(const std::string& theName) const { @@ -324,39 +301,23 @@ const Event& Node::findEventByNameOrNumber(const std::string& theName) const { } const Meter& Node::findMeter(const std::string& name) const { - for (const auto& m : meters_) { - if (m.name() == name) { - return m; - } - } - return Meter::EMPTY(); + auto found = ecf::algorithm::find_by_name(meters_, name); + return found == std::end(meters_) ? Meter::EMPTY() : *found; } Meter& Node::find_meter(const std::string& name) { - for (auto& m : meters_) { - if (m.name() == name) { - return m; - } - } - return const_cast(Meter::EMPTY()); + auto found = ecf::algorithm::find_by_name(meters_, name); + return found == std::end(meters_) ? const_cast(Meter::EMPTY()) : *found; } bool Node::findLabel(const std::string& name) const { - for (const auto& l : labels_) { - if (l.name() == name) { - return true; - } - } - return false; + auto found = ecf::algorithm::find_by_name(labels_, name); + return found != std::end(labels_); } const Label& Node::find_label(const std::string& name) const { - for (const auto& l : labels_) { - if (l.name() == name) { - return l; - } - } - return Label::EMPTY(); + auto found = ecf::algorithm::find_by_name(labels_, name); + return found == std::end(labels_) ? Label::EMPTY() : *found; } bool Node::findVerify(const VerifyAttr& v) const { From c287bc0f23c399d2d05245bfbecd3eae23a2047b Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Tue, 19 Dec 2023 09:10:07 +0000 Subject: [PATCH 02/68] Create Message Message is a wrapper/utility class that allows to easily create and store a string based on a set of given arguments. Re ECFLOW-1931 --- libs/core/CMakeLists.txt | 2 + libs/core/src/ecflow/core/Message.hpp | 46 +++++++++++++++++++++++ libs/core/test/TestMessage.cpp | 54 +++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 libs/core/src/ecflow/core/Message.hpp create mode 100644 libs/core/test/TestMessage.cpp diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index f2fbe3f2c..99cc698d6 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -40,6 +40,7 @@ set(srcs src/ecflow/core/Indentor.hpp src/ecflow/core/Log.hpp src/ecflow/core/LogVerification.hpp + src/ecflow/core/Message.hpp src/ecflow/core/NOrder.hpp src/ecflow/core/NState.hpp src/ecflow/core/NodePath.hpp @@ -153,6 +154,7 @@ set(test_srcs test/TestFile.cpp test/TestGetUserDetails.cpp test/TestLog.cpp + test/TestMessage.cpp test/TestMigration.cpp test/TestNodePath.cpp test/TestPasswdFile.cpp diff --git a/libs/core/src/ecflow/core/Message.hpp b/libs/core/src/ecflow/core/Message.hpp new file mode 100644 index 000000000..c8d79ae6c --- /dev/null +++ b/libs/core/src/ecflow/core/Message.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_core_Message_HPP +#define ecflow_core_Message_HPP + +#include +#include + +namespace ecf { + +/// +/// \brief Message is a utility class that easily creates a string-based Message from an arbitrary set of inputs +/// +/// The goal is to facilitate the construction/formatting of messages for error handling and logging. +/// A Message implicitly allows conversion to std::string, and provides the streaming operator<<. +/// +class Message { +public: + template + explicit Message(ARGS&&... args) { + ((buffer << std::forward(args)), ...); + } + + [[nodiscard]] std::string str() const { return buffer.str(); } + [[nodiscard]] operator std::string() const { return buffer.str(); } + +private: + std::ostringstream buffer; +}; + +inline std::ostream& operator<<(std::ostream& o, const Message& m) { + o << std::string(m); + return o; +} + +} // namespace ecf + +#endif /* ecflow_core_Message_HPP */ diff --git a/libs/core/test/TestMessage.cpp b/libs/core/test/TestMessage.cpp new file mode 100644 index 000000000..80fda2d55 --- /dev/null +++ b/libs/core/test/TestMessage.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include + +#include + +#include "ecflow/core/Message.hpp" + +BOOST_AUTO_TEST_SUITE(U_Core) + +BOOST_AUTO_TEST_SUITE(T_Message) + +BOOST_AUTO_TEST_CASE(test_message_can_create_from_various_arguments) { + using namespace ecf; + using namespace std::string_literals; + + BOOST_CHECK_EQUAL(Message().str(), ""s); + BOOST_CHECK_EQUAL(Message("a", ' ', "1").str(), "a 1"s); + BOOST_CHECK_EQUAL(Message("a", ' ', "1.01").str(), "a 1.01"s); + BOOST_CHECK_EQUAL(Message("a", ' ', 1).str(), "a 1"s); + BOOST_CHECK_EQUAL(Message("a", ' ', 1.01).str(), "a 1.01"s); + BOOST_CHECK_EQUAL(Message("a", ' ', true).str(), "a 1"s); + BOOST_CHECK_EQUAL(Message("a", Message("b", 'c')).str(), "abc"s); +} + +BOOST_AUTO_TEST_CASE(test_message_can_convert_to_string) { + using namespace ecf; + using namespace std::string_literals; + + std::string s = Message("a", Message("b", 'c')); + BOOST_CHECK_EQUAL(s, "abc"s); +} + +BOOST_AUTO_TEST_CASE(test_message_can_stream) { + using namespace ecf; + using namespace std::string_literals; + + std::ostringstream oss; + oss << Message("a", Message("b", 'c')); + + BOOST_CHECK_EQUAL(oss.str(), "abc"s); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() From 7d5d241fbd3c7edb571854e68826852f1973bee0 Mon Sep 17 00:00:00 2001 From: Marcos Bento Date: Tue, 19 Dec 2023 15:06:14 +0000 Subject: [PATCH 03/68] Enable handling of Aviso attribute - Add Aviso attribute - Add exceptions for Aviso error handling - Enable DefsParser to handle Aviso attribute - Enable Defsformatter to handle Aviso attribute - Update Node to contain Aviso attributes - Enable AlterCmd to change Aviso attributes - Add Aviso 'Updater' service, including periodic scheduler - Enable Memento for Aviso attributes - Display Aviso attributes in UI Re ECFLOW-1931 --- Viewer/ecflowUI/src/CMakeLists.txt | 2 + Viewer/ecflowUI/src/NodeViewDelegate.cpp | 149 ++++++++++ Viewer/ecflowUI/src/NodeViewDelegate.hpp | 1 + Viewer/ecflowUI/src/VAttributeType.cpp | 2 + Viewer/ecflowUI/src/VAvisoAttr.cpp | 130 +++++++++ Viewer/ecflowUI/src/VAvisoAttr.hpp | 54 ++++ Viewer/ecflowUI/src/VModelData.cpp | 3 +- Viewer/ecflowUI/src/VNode.hpp | 1 + Viewer/ecflowUI/src/Viewer.hpp | 1 + libs/attribute/CMakeLists.txt | 5 + .../src/ecflow/attribute/AvisoAttr.cpp | 46 ++++ .../src/ecflow/attribute/AvisoAttr.hpp | 80 ++++++ .../src/ecflow/attribute/AvisoService.cpp | 47 ++++ .../src/ecflow/attribute/AvisoService.hpp | 97 +++++++ .../src/ecflow/base/cts/user/AlterCmd.cpp | 60 +++- .../src/ecflow/base/cts/user/AlterCmd.hpp | 3 + libs/core/CMakeLists.txt | 1 + libs/core/src/ecflow/core/Str.cpp | 39 +++ libs/core/src/ecflow/core/Str.hpp | 2 + .../src/ecflow/core/exceptions/Exceptions.hpp | 32 +++ libs/node/CMakeLists.txt | 3 + libs/node/src/ecflow/node/Aspect.hpp | 3 +- libs/node/src/ecflow/node/Memento.cpp | 7 + libs/node/src/ecflow/node/Memento.hpp | 18 ++ libs/node/src/ecflow/node/Node.cpp | 12 + libs/node/src/ecflow/node/Node.hpp | 8 + libs/node/src/ecflow/node/NodeAdd.cpp | 11 + libs/node/src/ecflow/node/NodeChange.cpp | 10 + libs/node/src/ecflow/node/NodeDelete.cpp | 23 ++ libs/node/src/ecflow/node/NodeFind.cpp | 5 + libs/node/src/ecflow/node/NodeFwd.hpp | 1 + libs/node/src/ecflow/node/NodeMemento.cpp | 22 ++ libs/node/src/ecflow/node/NodeTime.cpp | 22 +- .../ecflow/node/formatter/AvisoFormatter.hpp | 43 +++ .../node/src/ecflow/node/formatter/Format.hpp | 25 ++ .../src/ecflow/node/formatter/Formatter.hpp | 29 ++ .../src/ecflow/node/parser/AvisoParser.cpp | 49 ++++ .../src/ecflow/node/parser/AvisoParser.hpp | 23 ++ .../src/ecflow/node/parser/DefsParser.cpp | 5 + libs/server/CMakeLists.txt | 28 ++ .../server/src/ecflow/server/AvisoUpdater.cpp | 37 +++ .../server/src/ecflow/server/AvisoUpdater.hpp | 39 +++ libs/server/src/ecflow/server/BaseServer.cpp | 8 + libs/server/src/ecflow/server/BaseServer.hpp | 4 + .../src/ecflow/server/PeriodicScheduler.cpp | 11 + .../src/ecflow/server/PeriodicScheduler.hpp | 258 ++++++++++++++++++ libs/server/test/TestPeriodicScheduler.cpp | 108 ++++++++ .../test/TestPeriodicScheduler_main.cpp | 12 + .../ecflow/etc/ecflowview_attribute_conf.json | 3 + 49 files changed, 1576 insertions(+), 6 deletions(-) create mode 100644 Viewer/ecflowUI/src/VAvisoAttr.cpp create mode 100644 Viewer/ecflowUI/src/VAvisoAttr.hpp create mode 100644 libs/attribute/src/ecflow/attribute/AvisoAttr.cpp create mode 100644 libs/attribute/src/ecflow/attribute/AvisoAttr.hpp create mode 100644 libs/attribute/src/ecflow/attribute/AvisoService.cpp create mode 100644 libs/attribute/src/ecflow/attribute/AvisoService.hpp create mode 100644 libs/core/src/ecflow/core/exceptions/Exceptions.hpp create mode 100644 libs/node/src/ecflow/node/formatter/AvisoFormatter.hpp create mode 100644 libs/node/src/ecflow/node/formatter/Format.hpp create mode 100644 libs/node/src/ecflow/node/formatter/Formatter.hpp create mode 100644 libs/node/src/ecflow/node/parser/AvisoParser.cpp create mode 100644 libs/node/src/ecflow/node/parser/AvisoParser.hpp create mode 100644 libs/server/src/ecflow/server/AvisoUpdater.cpp create mode 100644 libs/server/src/ecflow/server/AvisoUpdater.hpp create mode 100644 libs/server/src/ecflow/server/PeriodicScheduler.cpp create mode 100644 libs/server/src/ecflow/server/PeriodicScheduler.hpp create mode 100644 libs/server/test/TestPeriodicScheduler.cpp create mode 100644 libs/server/test/TestPeriodicScheduler_main.cpp diff --git a/Viewer/ecflowUI/src/CMakeLists.txt b/Viewer/ecflowUI/src/CMakeLists.txt index 7f4441a5f..4e842a365 100644 --- a/Viewer/ecflowUI/src/CMakeLists.txt +++ b/Viewer/ecflowUI/src/CMakeLists.txt @@ -215,6 +215,7 @@ set(viewer_srcs VAutoArchiveAttr.hpp VAutoCancelAttr.hpp VAutoRestoreAttr.hpp + VAvisoAttr.hpp VConfig.hpp VConfigLoader.hpp VDateAttr.hpp @@ -491,6 +492,7 @@ set(viewer_srcs VAutoArchiveAttr.cpp VAutoCancelAttr.cpp VAutoRestoreAttr.cpp + VAvisoAttr.cpp VConfig.cpp VConfigLoader.cpp VDateAttr.cpp diff --git a/Viewer/ecflowUI/src/NodeViewDelegate.cpp b/Viewer/ecflowUI/src/NodeViewDelegate.cpp index 03baff8cb..6a6691207 100644 --- a/Viewer/ecflowUI/src/NodeViewDelegate.cpp +++ b/Viewer/ecflowUI/src/NodeViewDelegate.cpp @@ -120,6 +120,7 @@ NodeViewDelegate::NodeViewDelegate(QWidget* parent) : QStyledItemDelegate(parent attrRenderers_["meter"] = &NodeViewDelegate::renderMeter; attrRenderers_["label"] = &NodeViewDelegate::renderLabel; + attrRenderers_["aviso"] = &NodeViewDelegate::renderAviso; attrRenderers_["event"] = &NodeViewDelegate::renderEvent; attrRenderers_["var"] = &NodeViewDelegate::renderVar; attrRenderers_["genvar"] = &NodeViewDelegate::renderGenvar; @@ -661,6 +662,154 @@ void NodeViewDelegate::renderLabel(QPainter* painter, size = QSize(totalWidth, labelHeight(multiCnt + 1)); } +void NodeViewDelegate::renderAviso(QPainter* painter, + QStringList data, + const QStyleOptionViewItem& option, + QSize& size) const { + int totalWidth = 0; + size = QSize(totalWidth, attrBox_->fullHeight); + + if (data.count() < 2) + return; + + QString name = data.at(1) + ":"; + QString val; + if (data.count() > 2) + val = data.at(2); + + bool selected = option.state & QStyle::State_Selected; + + // The border rect (we will adjust its width) + QRect contRect = option.rect.adjusted(attrBox_->leftMargin, + attrBox_->topMargin + attrBox_->topPadding, + 0, + -attrBox_->bottomMargin - attrBox_->bottomPadding); + + int currentRight = contRect.x(); + int multiCnt = val.count('\n'); + + QRect nameRect; + QRect valRect, valRestRect; + + QFont nameFont = attrFont_; + nameFont.setBold(true); + QFont valFont = attrFont_; + QString valFirst, valRest; + QString full; + + if (multiCnt == 0) { + // The text rectangle + QFontMetrics fm(nameFont); + int nameWidth = ViewerUtil::textWidth(fm, name); + nameRect = contRect.adjusted(attrBox_->leftPadding, 0, 0, 0); + nameRect.setWidth(nameWidth); + + // The value rectangle + fm = QFontMetrics(valFont); + int valWidth = ViewerUtil::textWidth(fm, val); + valRect = nameRect; + valRect.setX(nameRect.x() + nameRect.width() + attrBox_->spacing); + valRect.setWidth(valWidth); + + // Adjust the filled rect width + currentRight = valRect.x() + valRect.width(); + } + else { + // The text rectangle + QFontMetrics fm(nameFont); + int nameWidth = ViewerUtil::textWidth(fm, name); + nameRect = contRect.adjusted(attrBox_->leftPadding, 0, 0, 0); + nameRect.setWidth(nameWidth); + nameRect.setHeight(attrBox_->height - attrBox_->topPadding - attrBox_->bottomPadding); + + // The value rectangles + fm = QFontMetrics(valFont); + + // First row comes after the name rect! + QStringList valLst = val.split("\n"); + Q_ASSERT(valLst.count() > 0); + valFirst = valLst[0]; + + valRect = nameRect; + valRect.setX(nameRect.x() + nameRect.width() + attrBox_->spacing); + valRect.setWidth(ViewerUtil::textWidth(fm, valFirst)); + + // The rest of the rows + valLst.takeFirst(); + valRest = valLst.join("\n"); + QSize valSize = fm.size(0, valRest); + + valRestRect = QRect(nameRect.x(), nameRect.y() + nameRect.height() + 2, valSize.width(), valSize.height()); + + currentRight = qMax(valRect.x() + valRect.width(), valRestRect.x() + valRestRect.width()); + + val = valFirst + " " + valRest; + } + + // Define clipping + int rightPos = currentRight + attrBox_->rightPadding + attrBox_->rightMargin; + totalWidth = rightPos - option.rect.left(); + const bool setClipRect = rightPos > option.rect.right(); + if (setClipRect) { + painter->save(); + painter->setClipRect(option.rect); + } + + QPen fontPen(Qt::black); + LabelStyle* labelStyle = labelStyle_[DefaultLabel]; + QList types; + types << ErrorLabel << WarningLabel << InfoLabel; + full = name + " " + val; + Q_FOREACH (LabelType t, types) { + if (labelStyle_[t]->enabled_) { + if (full.contains(labelStyle_[t]->regex_)) { + labelStyle = labelStyle_[t]; + break; + } + } + } + + if (labelStyle) { + if (labelStyle->enabled_) { + fontPen = labelStyle->fontPen_; + // draw bg + if (labelStyle->enabledBg_) { + QRect sr = option.rect; + sr.setWidth(rightPos - sr.x()); + painter->fillRect(attrBox_->adjustSelectionRect(sr), labelStyle->bgBrush_); + } + } + } + + // Draw name + painter->setPen(fontPen); + painter->setFont(nameFont); + painter->drawText(attrBox_->adjustTextRect(nameRect), Qt::AlignLeft | Qt::AlignVCenter, name); + + // Draw value + painter->setPen(fontPen); + painter->setFont(valFont); + + if (multiCnt == 0) + painter->drawText(attrBox_->adjustTextRect(valRect), Qt::AlignLeft | Qt::AlignVCenter, val); + else { + painter->drawText(attrBox_->adjustTextRect(valRect), Qt::AlignLeft | Qt::AlignVCenter, valFirst); + painter->drawText(valRestRect, Qt::AlignLeft | Qt::AlignVCenter, valRest); + } + + if (selected && drawAttrSelectionRect_) { + QRect sr = option.rect; + sr.setWidth(rightPos - sr.x()); + renderSelectionRect(painter, attrBox_->adjustSelectionRect(sr)); + } + + if (setClipRect) { + painter->restore(); + } + + size = QSize(totalWidth, labelHeight(multiCnt + 1)); +} + void NodeViewDelegate::labelSize(QStringList data, int& totalWidth, int& totalHeight) const { if (data.count() < 2) return; diff --git a/Viewer/ecflowUI/src/NodeViewDelegate.hpp b/Viewer/ecflowUI/src/NodeViewDelegate.hpp index b3dc11144..2c60c52e1 100644 --- a/Viewer/ecflowUI/src/NodeViewDelegate.hpp +++ b/Viewer/ecflowUI/src/NodeViewDelegate.hpp @@ -172,6 +172,7 @@ class NodeViewDelegate : public QStyledItemDelegate, public VPropertyObserver { virtual void renderMeter(QPainter* painter, QStringList data, const QStyleOptionViewItem& option, QSize&) const; virtual void renderLabel(QPainter* painter, QStringList data, const QStyleOptionViewItem& option, QSize&) const; + virtual void renderAviso(QPainter* painter, QStringList data, const QStyleOptionViewItem& option, QSize&) const; virtual void renderEvent(QPainter* painter, QStringList data, const QStyleOptionViewItem& option, QSize&) const; virtual void renderVar(QPainter* painter, QStringList data, const QStyleOptionViewItem& option, QSize&) const; virtual void renderGenvar(QPainter* painter, QStringList data, const QStyleOptionViewItem& option, QSize&) const; diff --git a/Viewer/ecflowUI/src/VAttributeType.cpp b/Viewer/ecflowUI/src/VAttributeType.cpp index 90a122570..d8c30a675 100644 --- a/Viewer/ecflowUI/src/VAttributeType.cpp +++ b/Viewer/ecflowUI/src/VAttributeType.cpp @@ -156,6 +156,7 @@ static SimpleLoader loader("attribute"); #include "VAutoRestoreAttr.hpp" #include "VDateAttr.hpp" #include "VEventAttr.hpp" +#include "VAvisoAttr.hpp" #include "VGenVarAttr.hpp" #include "VLabelAttr.hpp" #include "VLateAttr.hpp" @@ -171,6 +172,7 @@ static SimpleLoader loader("attribute"); static VLabelAttrType labelAttrType; static VMeterAttrType meterAttType; static VEventAttrType eventAttrType; +static VAvisoAttrType avisoAttrType; static VLimitAttrType limitAttrType; static VLimiterAttrType limiterAttrType; static VRepeatAttrType repeatAttrType; diff --git a/Viewer/ecflowUI/src/VAvisoAttr.cpp b/Viewer/ecflowUI/src/VAvisoAttr.cpp new file mode 100644 index 000000000..f6dbc0a13 --- /dev/null +++ b/Viewer/ecflowUI/src/VAvisoAttr.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "VAvisoAttr.hpp" + +#include "VAttributeType.hpp" +#include "VNode.hpp" +#include "ecflow/attribute/AvisoAttr.hpp" + +//================================ +// VAvisoAttrType +//================================ + +VAvisoAttrType::VAvisoAttrType() : VAttributeType("aviso") { + dataCount_ = 3; + searchKeyToData_["aviso_name"] = NameIndex; + searchKeyToData_["aviso_handle"] = ValueIndex; + searchKeyToData_["name"] = NameIndex; + scanProc_ = VAvisoAttr::scan; +} + +QString VAvisoAttrType::toolTip(QStringList d) const { + QString t = "Type: Aviso
"; + if (d.count() == dataCount_) { + t += "Name: " + d[NameIndex] + "
"; + t += "Value: " + d[ValueIndex]; + } + return t; +} + +QString VAvisoAttrType::definition(QStringList d) const { + QString t = "aviso"; + if (d.count() == dataCount_) { + t += " " + d[NameIndex] + " '" + d[ValueIndex] + "'"; + } + return t; +} + +void VAvisoAttrType::encode(const ecf::AvisoAttr& aviso, QStringList& data, bool firstLine) const { + std::string val = aviso.listener(); + + if (firstLine) { + std::size_t pos = val.find("\n"); + if (pos != std::string::npos) { + val.resize(pos); + } + } + + data << qName_ << QString::fromStdString(aviso.name()) << QString::fromStdString(val); +} + +void VAvisoAttrType::encode_empty(QStringList& data) const { + data << qName_ << "" + << ""; +} + +//===================================================== +// +// VAvisoAttr +// +//===================================================== + +VAvisoAttr::VAvisoAttr(VNode* parent, [[maybe_unused]] const ecf::AvisoAttr& aviso, int index) + : VAttribute(parent, index) { +} + +int VAvisoAttr::lineNum() const { + if (parent_->node_) { + const std::vector& v = parent_->node_->avisos(); + std::string val = v[index_].listener(); + return std::count(val.begin(), val.end(), '\n') + 1; + } + + return 1; +} + +VAttributeType* VAvisoAttr::type() const { + static VAttributeType* atype = VAttributeType::find("aviso"); + return atype; +} + +QStringList VAvisoAttr::data(bool firstLine) const { + static auto* atype = static_cast(type()); + QStringList s; + if (parent_->node_) { + const std::vector& v = parent_->node_->avisos(); + if (index_ < static_cast(v.size())) + atype->encode(v[index_], s, firstLine); + + // this can happen temporarily during update when: + // + // - an attribute was already deleted + // - the notification was emitted from the update thread, but + // has not yet reached the main thread + // - here, in the main thread, we still have the old (incorrect) attribute number + // + // * In this case, as safety measure, we encode an empty attribute. + // When the notification arrives all the attributes of the given node will be rescanned + // and will be set with the correct state. + else + atype->encode_empty(s); + } + return s; +} + +std::string VAvisoAttr::strName() const { + if (parent_->node_) { + const std::vector& v = parent_->node_->avisos(); + return v[index_].name(); + } + return {}; +} + +void VAvisoAttr::scan(VNode* vnode, std::vector& vec) { + if (vnode->node_) { + const std::vector& v = vnode->node_->avisos(); + + auto n = static_cast(v.size()); + for (int i = 0; i < n; i++) { + vec.push_back(new VAvisoAttr(vnode, v[i], i)); + } + } +} diff --git a/Viewer/ecflowUI/src/VAvisoAttr.hpp b/Viewer/ecflowUI/src/VAvisoAttr.hpp new file mode 100644 index 000000000..5719bab0f --- /dev/null +++ b/Viewer/ecflowUI/src/VAvisoAttr.hpp @@ -0,0 +1,54 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_viewer_VAvisoAttr_HPP +#define ecflow_viewer_VAvisoAttr_HPP + +#include +#include + +#include + +#include "VAttribute.hpp" +#include "VAttributeType.hpp" + +class AttributeFilter; +class VAttributeType; +class VNode; + +namespace ecf { +class AvisoAttr; +} + +class VAvisoAttrType : public VAttributeType { +public: + explicit VAvisoAttrType(); + QString toolTip(QStringList d) const override; + QString definition(QStringList d) const override; + void encode(const ecf::AvisoAttr& aviso, QStringList& data, bool firstLine) const; + void encode_empty(QStringList& data) const; + +private: + enum DataIndex { TypeIndex = 0, NameIndex = 1, ValueIndex = 2 }; +}; + +class VAvisoAttr : public VAttribute { +public: + VAvisoAttr(VNode* parent, const ecf::AvisoAttr&, int index); + + int lineNum() const override; + VAttributeType* type() const override; + QStringList data(bool firstLine) const override; + std::string strName() const override; + + static void scan(VNode* vnode, std::vector& vec); +}; + +#endif /* ecflow_viewer_VAvisoAttr_HPP */ diff --git a/Viewer/ecflowUI/src/VModelData.cpp b/Viewer/ecflowUI/src/VModelData.cpp index 75b1c17b7..4c6cdb2c6 100644 --- a/Viewer/ecflowUI/src/VModelData.cpp +++ b/Viewer/ecflowUI/src/VModelData.cpp @@ -319,7 +319,8 @@ void VTreeServer::notifyBeginNodeChange(const VNode* vnode, it == ecf::Aspect::LIMIT || it == ecf::Aspect::EXPR_TRIGGER || it == ecf::Aspect::EXPR_COMPLETE || it == ecf::Aspect::REPEAT || it == ecf::Aspect::REPEAT_INDEX || it == ecf::Aspect::NODE_VARIABLE || it == ecf::Aspect::LATE || it == ecf::Aspect::TODAY || it == ecf::Aspect::TIME || - it == ecf::Aspect::DAY || it == ecf::Aspect::CRON || it == ecf::Aspect::DATE) { + it == ecf::Aspect::DAY || it == ecf::Aspect::CRON || it == ecf::Aspect::DATE || + it == ecf::Aspect::AVISO) { if (node && node->isAttrInitialised()) { Q_EMIT attributesChanged(this, node); } diff --git a/Viewer/ecflowUI/src/VNode.hpp b/Viewer/ecflowUI/src/VNode.hpp index 3cd23fd5c..74d994a2c 100644 --- a/Viewer/ecflowUI/src/VNode.hpp +++ b/Viewer/ecflowUI/src/VNode.hpp @@ -88,6 +88,7 @@ class VNode : public VItem { friend class VLabelAttr; friend class VMeterAttr; friend class VEventAttr; + friend class VAvisoAttr; friend class VRepeatAttr; friend class VTriggerAttr; friend class VLimitAttr; diff --git a/Viewer/ecflowUI/src/Viewer.hpp b/Viewer/ecflowUI/src/Viewer.hpp index 61e6c17d4..d9504668e 100644 --- a/Viewer/ecflowUI/src/Viewer.hpp +++ b/Viewer/ecflowUI/src/Viewer.hpp @@ -31,6 +31,7 @@ enum AttributeType { LabelAttribute, MeterAttribute, EventAttribute, + AvisoAttribute, RepeatAttribute, TimeAttribute, DateAttribute, diff --git a/libs/attribute/CMakeLists.txt b/libs/attribute/CMakeLists.txt index 76b56b3a6..7919dcc41 100644 --- a/libs/attribute/CMakeLists.txt +++ b/libs/attribute/CMakeLists.txt @@ -12,6 +12,8 @@ set(srcs # Headers src/ecflow/attribute/AutoArchiveAttr.hpp src/ecflow/attribute/AutoCancelAttr.hpp + src/ecflow/attribute/AvisoAttr.hpp + src/ecflow/attribute/AvisoService.hpp src/ecflow/attribute/ClockAttr.hpp src/ecflow/attribute/CronAttr.hpp src/ecflow/attribute/DateAttr.hpp @@ -30,6 +32,8 @@ set(srcs # Sources src/ecflow/attribute/AutoArchiveAttr.cpp src/ecflow/attribute/AutoCancelAttr.cpp + src/ecflow/attribute/AvisoAttr.cpp + src/ecflow/attribute/AvisoService.cpp src/ecflow/attribute/ClockAttr.cpp src/ecflow/attribute/CronAttr.cpp src/ecflow/attribute/DateAttr.cpp @@ -59,6 +63,7 @@ ecbuild_add_library( PUBLIC_LIBS core Boost::date_time + nlohmann::json ) target_clangformat(attributes) diff --git a/libs/attribute/src/ecflow/attribute/AvisoAttr.cpp b/libs/attribute/src/ecflow/attribute/AvisoAttr.cpp new file mode 100644 index 000000000..d71e189ad --- /dev/null +++ b/libs/attribute/src/ecflow/attribute/AvisoAttr.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/attribute/AvisoAttr.hpp" + +#include + +#include "ecflow/attribute/AvisoService.hpp" +#include "ecflow/core/Message.hpp" +#include "ecflow/core/exceptions/Exceptions.hpp" + +namespace ecf { + +AvisoAttr::AvisoAttr(name_t name, listener_t listener) : name_{std::move(name)}, listener_{std::move(listener)} { + if (!ecf::Str::valid_name(name_)) { + throw ecf::InvalidArgument(ecf::Message("Invalid AvisoAttr name :", name_)); + } +}; + +bool AvisoAttr::why(std::string& theReasonWhy) const { + if (isFree()) { + return false; + } + + theReasonWhy += ecf::Message(" is Aviso dependent (", listener_, "), but no notification received"); + return true; +}; + +bool AvisoAttr::isFree() const { + using namespace ecf; + auto& registry = AvisoService::service(); + LOG(Log::DBG, Message("******************** Check Aviso attribute (name: ", name_, ", listener: ", listener_, ")")); + return registry.check_notification(name_); +} + +void AvisoAttr::requeue() { +} + +} // namespace ecf diff --git a/libs/attribute/src/ecflow/attribute/AvisoAttr.hpp b/libs/attribute/src/ecflow/attribute/AvisoAttr.hpp new file mode 100644 index 000000000..bb28f7569 --- /dev/null +++ b/libs/attribute/src/ecflow/attribute/AvisoAttr.hpp @@ -0,0 +1,80 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_attribute_AvisoAttr_HPP +#define ecflow_attribute_AvisoAttr_HPP + +#include +#include +#include + +#include "ecflow/core/Log.hpp" +#include "ecflow/core/Str.hpp" + +namespace cereal { +class access; +} + +namespace ecf { + +/// +/// \brief AvisoAttr represents an attribute, attached to a \ref Node. +/// +/// The Aviso attribute effectively acts as a trigger for the Node, granting +/// the Node to be (re)queued as soon as a related notification is received. +/// +/// \see https://github.com/ecmwf/aviso +/// + +class AvisoAttr { +public: + using name_t = std::string; + using listener_t = std::string; + + /** + * Creates a(n invalid) Aviso + * + * Note: this is required by Cereal serialization + * Cereal invokes the default ctor to create the object and only then proceeds to member-wise serialization. + */ + AvisoAttr() = default; + AvisoAttr(std::string name, listener_t handle); + AvisoAttr(const AvisoAttr& rhs) = default; + + AvisoAttr& operator=(const AvisoAttr& rhs) = default; + + [[nodiscard]] inline const std::string& name() const { return name_; } + [[nodiscard]] inline const std::string& listener() const { return listener_; } + + void set_listener(std::string_view listener) { listener_ = listener; } + + bool why(std::string& theReasonWhy) const; + + [[nodiscard]] bool isFree() const; + + void requeue(); + + template + friend void serialize(Archive& ar, AvisoAttr& aviso, std::uint32_t version); + +private: + name_t name_; + listener_t listener_; +}; + +template +void serialize(Archive& ar, AvisoAttr& aviso, [[maybe_unused]] std::uint32_t version) { + ar & aviso.name_; + ar & aviso.listener_; +} + +} // namespace ecf + +#endif /* ecflow_attribute_AvisoAttr_HPP */ diff --git a/libs/attribute/src/ecflow/attribute/AvisoService.cpp b/libs/attribute/src/ecflow/attribute/AvisoService.cpp new file mode 100644 index 000000000..38cb87706 --- /dev/null +++ b/libs/attribute/src/ecflow/attribute/AvisoService.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/attribute/AvisoService.hpp" + +#include +#include + +#include "ecflow/core/Filesystem.hpp" +#include "nlohmann/json.hpp" + +namespace ecf { + +void AvisoAPI::check_aviso_server_for_notifications(AvisoRegistry& registry) const { + std::lock_guard guard(m_); + + using json = nlohmann::ordered_json; + + // TODO[MB]: Actually implement retrieval of notification from Aviso server + + fs::path f = "notification_api.json"; + boost::system::error_code ec; + if (fs::exists(f, ec)) { + + json object; + { + std::ifstream ifs("notification_api.json"); + object = json::parse(ifs); + } + + fs::remove(f); + + for (const auto& handle : object["handlers"]) { + auto name = handle["name"]; + registry.notify(std::string{name}); + } + } +} + +} // namespace ecf diff --git a/libs/attribute/src/ecflow/attribute/AvisoService.hpp b/libs/attribute/src/ecflow/attribute/AvisoService.hpp new file mode 100644 index 000000000..269e31856 --- /dev/null +++ b/libs/attribute/src/ecflow/attribute/AvisoService.hpp @@ -0,0 +1,97 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_attribute_AvisoService_HPP +#define ecflow_attribute_AvisoService_HPP + +#include +#include +#include +#include +#include +#include + +namespace ecf { + +struct AvisoRegistry; + +/// +/// \brief AvisoAPI allows checking if a notification has been registered in the Aviso server +/// + +struct AvisoAPI +{ + AvisoAPI() = default; + + void check_aviso_server_for_notifications(AvisoRegistry& registry) const; + +private: + mutable std::mutex m_; +}; + +/// +/// \brief BaseService manages a \a service object +/// +/// \tparam T the type of the \a service object +/// +template +struct BaseService +{ + static T& service() { + static T instance; + return instance; + } + +private: + BaseService() = default; +}; + +/// +/// \brief AvisoRegistry allows \b global access to a singleton instance of \ref AvisoRegistry +/// +struct AvisoRegistry +{ + AvisoRegistry() = default; + + bool check_notification(const std::string& name) { + std::lock_guard guard(m_); + std::cout << "AvisoRegistry: check notification for name: " << name << std::endl; + + if (auto found = roster_.find(name); found != std::end(roster_)) { + roster_.erase(found); + std::cout << "Notification found! Reseting for next time!..." << std::endl; + return true; + } + + std::cout << ". No notification found!" << std::endl; + + return false; + } + + void notify(const std::string& name) { + std::lock_guard guard(m_); + + std::cout << "AvisoRegistry: Registered notification for name: " << name << std::endl; + roster_[name] = true; + } + +private: + mutable std::mutex m_; + std::unordered_map roster_; +}; + +/// +/// \brief AvisoService allows \b global access to a singleton instance of \ref AvisoRegistry +/// +using AvisoService = BaseService; + +} // namespace ecf + +#endif /* ecflow_attribute_AvisoService_HPP */ diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp index 07f979347..9ec0abe24 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.cpp @@ -75,7 +75,8 @@ struct EnumTraits std::make_pair(AlterCmd::DEL_ZOMBIE, "zombie"), std::make_pair(AlterCmd::DEL_LATE, "late"), std::make_pair(AlterCmd::DEL_QUEUE, "queue"), - std::make_pair(AlterCmd::DEL_GENERIC, "generic") + std::make_pair(AlterCmd::DEL_GENERIC, "generic"), + std::make_pair(AlterCmd::DEL_AVISO, "aviso") // clang-format on }; static constexpr size_t size = map.size(); @@ -99,7 +100,8 @@ struct EnumTraits std::make_pair(AlterCmd::ADD_LATE, "late"), std::make_pair(AlterCmd::ADD_LIMIT, "limit"), std::make_pair(AlterCmd::ADD_INLIMIT, "inlimit"), - std::make_pair(AlterCmd::ADD_LABEL, "label") + std::make_pair(AlterCmd::ADD_LABEL, "label"), + std::make_pair(AlterCmd::ADD_AVISO, "aviso") // clang-format on }; static constexpr size_t size = map.size(); @@ -130,7 +132,8 @@ struct EnumTraits std::make_pair(AlterCmd::DEFSTATUS, "defstatus"), std::make_pair(AlterCmd::LATE, "late"), std::make_pair(AlterCmd::TIME, "time"), - std::make_pair(AlterCmd::TODAY, "today") + std::make_pair(AlterCmd::TODAY, "today"), + std::make_pair(AlterCmd::AVISO, "aviso") // clang-format on }; static constexpr size_t size = map.size(); @@ -366,6 +369,9 @@ STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const { case AlterCmd::DEL_LABEL: node->deleteLabel(name_); break; + case AlterCmd::DEL_AVISO: + node->deleteAviso(name_); + break; case AlterCmd::DEL_TRIGGER: node->deleteTrigger(); break; @@ -432,6 +438,9 @@ STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const { case AlterCmd::LABEL: node->changeLabel(name_, value_); break; + case AlterCmd::AVISO: + node->changeAviso(name_, value_); + break; case AlterCmd::TRIGGER: node->changeTrigger(name_); break; // expression must parse @@ -483,6 +492,9 @@ STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const { case AlterCmd::ADD_DAY: node->addDay(DayAttr::create(name_)); break; + case AlterCmd::ADD_AVISO: + node->addAviso(AvisoAttr(name_, value_)); + break; case AlterCmd::ADD_ZOMBIE: node->addZombie(ZombieAttr::create(name_)); break; @@ -800,6 +812,20 @@ void AlterCmd::extract_name_and_value_for_add(AlterCmd::Add_attr_type theAttrTyp value = options[3]; break; } + case AlterCmd::ADD_AVISO: { + if (options.size() == 3 && paths.size() > 1) { + // label value may be a path, hence it will be in the paths parameter + options.push_back(paths[0]); + paths.erase(paths.begin()); + } + if (options.size() < 4) { + ss << "AlterCmd: add: Expected 'add aviso . Not enough arguments\n" + << dump_args(options, paths) << "\n"; + throw std::runtime_error(ss.str()); + } + value = options[3]; + break; + } case AlterCmd::ADD_LIMIT: { if (options.size() < 4) { ss << "AlterCmd: add: Expected 'add limit int. Not enough arguments\n" @@ -852,6 +878,11 @@ void AlterCmd::check_for_add(AlterCmd::Add_attr_type theAttrType, case AlterCmd::ADD_DAY: (void)DayAttr::create(name); break; + case AlterCmd::ADD_AVISO: { + // Create an Aviso to check if name is valid + AvisoAttr check(name, value); + break; + } case AlterCmd::ADD_ZOMBIE: (void)ZombieAttr::create(name); break; @@ -1041,6 +1072,12 @@ void AlterCmd::check_for_delete(AlterCmd::Delete_attr_type theAttrType, } break; } + case AlterCmd::DEL_AVISO: { + if (!name.empty()) { + AvisoAttr check(name, "value"); // will throw if not valid + } + break; + } case AlterCmd::DEL_EVENT: { if (!name.empty()) { Event check(name); // will throw if not valid @@ -1284,6 +1321,23 @@ void AlterCmd::extract_name_and_value_for_change(AlterCmd::Change_attr_type theA break; } + case AlterCmd::AVISO: { + if (options.size() != 4) { + ss << "AlterCmd: change aviso expects 4 arguments : change aviso but found " + << (options.size()) << ".\n"; + ss << dump_args(options, paths) << "\n"; + throw std::runtime_error(ss.str()); + } + if (paths.empty()) { + ss << "AlterCmd: change aviso expects at least 1 node path but none was provided.\n"; + ss << dump_args(options, paths) << "\n"; + throw std::runtime_error(ss.str()); + } + name = options[2]; + value = options[3]; + break; + } + case AlterCmd::LATE: { if (options.size() != 3) { ss << "AlterCmd: change: expected three args: change late \"late -s +00:15 -a 20:00 -c +02:00\" " diff --git a/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp b/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp index 38632e78a..6f3de18e5 100644 --- a/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp +++ b/libs/base/src/ecflow/base/cts/user/AlterCmd.hpp @@ -37,6 +37,7 @@ class AlterCmd final : public UserCmd { DEL_LATE, DEL_QUEUE, DEL_GENERIC, + DEL_AVISO, }; enum Change_attr_type { @@ -58,6 +59,7 @@ class AlterCmd final : public UserCmd { LATE, TIME, TODAY, + AVISO, }; enum Add_attr_type { @@ -72,6 +74,7 @@ class AlterCmd final : public UserCmd { ADD_LIMIT, ADD_INLIMIT, ADD_LABEL, + ADD_AVISO, }; // Python diff --git a/libs/core/CMakeLists.txt b/libs/core/CMakeLists.txt index 99cc698d6..677288792 100644 --- a/libs/core/CMakeLists.txt +++ b/libs/core/CMakeLists.txt @@ -64,6 +64,7 @@ set(srcs src/ecflow/core/cereal_boost_time.hpp src/ecflow/core/cereal_optional_nvp.hpp src/ecflow/core/perf_timer.hpp + src/ecflow/core/exceptions/Exceptions.hpp # Sources src/ecflow/core/AssertTimer.cpp src/ecflow/core/Cal.cpp diff --git a/libs/core/src/ecflow/core/Str.cpp b/libs/core/src/ecflow/core/Str.cpp index e9626e549..593022c41 100644 --- a/libs/core/src/ecflow/core/Str.cpp +++ b/libs/core/src/ecflow/core/Str.cpp @@ -355,6 +355,45 @@ void Str::split_using_string_view2(std::string_view strv, std::vector Str::tokenize_quotation(const std::string& s, std::string_view quotes) { + + std::vector tokens; + + std::string levels; + + const char* current = &s[0]; + const char* start = current; + while (*current != 0) { + if (*current == ' ' && levels.empty()) { + if (start != current) { + tokens.emplace_back(start, static_cast(current - start)); + } + start = current + 1; + } + else { + if (std::any_of( + std::begin(quotes), std::end(quotes), [¤t](char quote) { return *current == quote; })) { + if (!levels.empty() && (levels.back() == *current)) { + levels.pop_back(); + } + else { + if (levels.empty()) { + start = current; + } + levels.push_back(*current); + } + } + } + ++current; + } + + if (start != current) { + tokens.emplace_back(start, static_cast(current - start)); + } + + return tokens; +} + bool Str::get_token(std::string_view str, size_t pos, std::string& token, std::string_view delims) { // Time for StringSplitter::get_token 250000 times = 1.457s wall, (1.460s user + 0.000s system = 1.460s) CPU // (100.2%) Time for Str::get_token 250000 times = 0.566s wall, (0.560s user + 0.000s system = 0.560s) diff --git a/libs/core/src/ecflow/core/Str.hpp b/libs/core/src/ecflow/core/Str.hpp index b516c255d..2de926364 100644 --- a/libs/core/src/ecflow/core/Str.hpp +++ b/libs/core/src/ecflow/core/Str.hpp @@ -108,6 +108,8 @@ class Str { /// This function is used to choose the fastest implementation static void split(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); + static std::vector tokenize_quotation(const std::string& s, std::string_view quotes); + static void split_orig(const std::string& line, std::vector& tokens, const std::string& delimiters = " \t"); diff --git a/libs/core/src/ecflow/core/exceptions/Exceptions.hpp b/libs/core/src/ecflow/core/exceptions/Exceptions.hpp new file mode 100644 index 000000000..5719ce0d3 --- /dev/null +++ b/libs/core/src/ecflow/core/exceptions/Exceptions.hpp @@ -0,0 +1,32 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_core_exceptions_Exceptions_HPP +#define ecflow_core_exceptions_Exceptions_HPP + +#include + +namespace ecf { + +struct Exception : public std::runtime_error +{ + explicit Exception(const char* what) : std::runtime_error(what) {} + explicit Exception(const std::string& what) : std::runtime_error(what) {} +}; + +struct InvalidArgument : public Exception +{ + explicit InvalidArgument(const char* what) : Exception(what) {} + explicit InvalidArgument(const std::string& what) : Exception(what) {} +}; + +} // namespace ecf + +#endif // ecflow_core_exceptions_Exceptions_HPP diff --git a/libs/node/CMakeLists.txt b/libs/node/CMakeLists.txt index 7b193d44f..ab2a580ae 100644 --- a/libs/node/CMakeLists.txt +++ b/libs/node/CMakeLists.txt @@ -55,9 +55,11 @@ set(srcs src/ecflow/node/Task.hpp src/ecflow/node/TaskScriptGenerator.hpp src/ecflow/node/move_peer.hpp + src/ecflow/node/formatter/Formatter.hpp src/ecflow/node/parser/AutoArchiveParser.hpp src/ecflow/node/parser/AutoCancelParser.hpp src/ecflow/node/parser/AutoRestoreParser.hpp + src/ecflow/node/parser/AvisoParser.hpp src/ecflow/node/parser/CalendarParser.hpp src/ecflow/node/parser/ClockParser.hpp src/ecflow/node/parser/CronParser.hpp @@ -132,6 +134,7 @@ set(srcs src/ecflow/node/parser/AutoArchiveParser.cpp src/ecflow/node/parser/AutoCancelParser.cpp src/ecflow/node/parser/AutoRestoreParser.cpp + src/ecflow/node/parser/AvisoParser.cpp src/ecflow/node/parser/CalendarParser.cpp src/ecflow/node/parser/ClockParser.cpp src/ecflow/node/parser/CronParser.cpp diff --git a/libs/node/src/ecflow/node/Aspect.hpp b/libs/node/src/ecflow/node/Aspect.hpp index 7a1e64f70..19c0ff156 100644 --- a/libs/node/src/ecflow/node/Aspect.hpp +++ b/libs/node/src/ecflow/node/Aspect.hpp @@ -53,7 +53,8 @@ class Aspect { ALIAS_NUMBER, QUEUE, QUEUE_INDEX, - GENERIC + GENERIC, + AVISO }; // Disable default construction diff --git a/libs/node/src/ecflow/node/Memento.cpp b/libs/node/src/ecflow/node/Memento.cpp index 9b771ba8e..654aab816 100644 --- a/libs/node/src/ecflow/node/Memento.cpp +++ b/libs/node/src/ecflow/node/Memento.cpp @@ -208,6 +208,11 @@ void NodeLabelMemento::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(label_)); } +template +void NodeAvisoMemento::serialize(Archive& ar, std::uint32_t const version) { + ar(cereal::base_class(this), CEREAL_NVP(aviso_)); +} + template void NodeQueueMemento::serialize(Archive& ar, std::uint32_t const version) { ar(cereal::base_class(this), CEREAL_NVP(queue_)); @@ -335,6 +340,7 @@ CEREAL_TEMPLATE_SPECIALIZE_V(ServerVariableMemento); CEREAL_TEMPLATE_SPECIALIZE_V(NodeEventMemento); CEREAL_TEMPLATE_SPECIALIZE_V(NodeMeterMemento); CEREAL_TEMPLATE_SPECIALIZE_V(NodeLabelMemento); +CEREAL_TEMPLATE_SPECIALIZE_V(NodeAvisoMemento); CEREAL_TEMPLATE_SPECIALIZE_V(NodeQueueMemento); CEREAL_TEMPLATE_SPECIALIZE_V(NodeGenericMemento); CEREAL_TEMPLATE_SPECIALIZE_V(NodeQueueIndexMemento); @@ -372,6 +378,7 @@ CEREAL_REGISTER_TYPE(ServerVariableMemento) CEREAL_REGISTER_TYPE(NodeEventMemento) CEREAL_REGISTER_TYPE(NodeMeterMemento) CEREAL_REGISTER_TYPE(NodeLabelMemento) +CEREAL_REGISTER_TYPE(NodeAvisoMemento) CEREAL_REGISTER_TYPE(NodeQueueMemento) CEREAL_REGISTER_TYPE(NodeGenericMemento) CEREAL_REGISTER_TYPE(NodeQueueIndexMemento) diff --git a/libs/node/src/ecflow/node/Memento.hpp b/libs/node/src/ecflow/node/Memento.hpp index 3e60d72da..37656881d 100644 --- a/libs/node/src/ecflow/node/Memento.hpp +++ b/libs/node/src/ecflow/node/Memento.hpp @@ -651,6 +651,24 @@ class NodeCronMemento : public Memento { void serialize(Archive& ar, std::uint32_t const version); }; +class NodeAvisoMemento : public Memento { +public: + NodeAvisoMemento() = default; + explicit NodeAvisoMemento(const ecf::AvisoAttr& a) : aviso_(a) {} + +private: + void do_incremental_node_sync(Node* n, std::vector& aspects, bool f) const override { + n->set_memento(this, aspects, f); + } + + ecf::AvisoAttr aviso_; + friend class Node; + + friend class cereal::access; + template + void serialize(Archive& ar, std::uint32_t const version); +}; + class NodeDateMemento : public Memento { public: explicit NodeDateMemento(const DateAttr& attr) : attr_(attr) {} diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 165612137..1c118292f 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -36,6 +36,7 @@ #include "ecflow/node/Suite.hpp" #include "ecflow/node/SuiteChanged.hpp" #include "ecflow/node/Task.hpp" +#include "ecflow/node/formatter/Format.hpp" #include "ecflow/node/parser/DefsStructureParser.hpp" using namespace ecf; @@ -1777,6 +1778,9 @@ void Node::print(std::string& os) const { for (const CronAttr& cron : crons_) { cron.print(os); } + for (const AvisoAttr& a : avisos_) { + ecf::format_as_defs(a, os); + } if (auto_cancel_) auto_cancel_->print(os); @@ -2345,6 +2349,13 @@ bool Node::why(std::vector& vec, bool html) const { why_found = true; } } + for (const auto& aviso : avisos_) { + postFix.clear(); + if (aviso.why(postFix)) { + vec.push_back(prefix + postFix); + why_found = true; + } + } } // ************************************************************************************** @@ -2885,6 +2896,7 @@ void Node::serialize(Archive& ar, std::uint32_t const version) { CEREAL_OPTIONAL_NVP(ar, meters_, [this]() { return !meters_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, events_, [this]() { return !events_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, labels_, [this]() { return !labels_.empty(); }); // conditionally save + CEREAL_OPTIONAL_NVP(ar, avisos_, [this]() { return !avisos_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, times_, [this]() { return !times_.empty(); }); // conditionally save CEREAL_OPTIONAL_NVP(ar, todays_, [this]() { return !todays_.empty(); }); // conditionally save diff --git a/libs/node/src/ecflow/node/Node.hpp b/libs/node/src/ecflow/node/Node.hpp index 4de1ab325..90abc1891 100644 --- a/libs/node/src/ecflow/node/Node.hpp +++ b/libs/node/src/ecflow/node/Node.hpp @@ -27,6 +27,7 @@ #include #include +#include "ecflow/attribute/AvisoAttr.hpp" #include "ecflow/attribute/CronAttr.hpp" #include "ecflow/attribute/DateAttr.hpp" #include "ecflow/attribute/DayAttr.hpp" @@ -392,6 +393,7 @@ class Node : public std::enable_shared_from_this { const std::vector& dates() const { return dates_; } const std::vector& days() const { return days_; } const std::vector& crons() const { return crons_; } + const std::vector& avisos() const { return avisos_; } const std::vector& verifys() const; const std::vector& zombies() const; @@ -456,6 +458,7 @@ class Node : public std::enable_shared_from_this { void addDate(const DateAttr&); void addDay(const DayAttr&); void addCron(const ecf::CronAttr&); + void addAviso(const ecf::AvisoAttr&); void addLimit(const Limit&, bool check = true); // will throw std::runtime_error if duplicate void addInLimit(const InLimit& l, bool check = true); // will throw std::runtime_error if duplicate @@ -507,6 +510,7 @@ class Node : public std::enable_shared_from_this { void deleteEvent(const std::string& name); void deleteMeter(const std::string& name); void deleteLabel(const std::string& name); + void deleteAviso(const std::string& name); void delete_queue(const std::string& name); void delete_generic(const std::string& name); void deleteTrigger(); @@ -529,6 +533,7 @@ class Node : public std::enable_shared_from_this { void changeMeter(const std::string& name, const std::string& value); void changeMeter(const std::string& name, int value); void changeLabel(const std::string& name, const std::string& value); + void changeAviso(const std::string& name, const std::string& value); void changeTrigger(const std::string& expression); void changeComplete(const std::string& expression); void changeRepeat(const std::string& value); @@ -557,6 +562,7 @@ class Node : public std::enable_shared_from_this { void set_memento(const NodeEventMemento*, std::vector& aspects, bool f); void set_memento(const NodeMeterMemento*, std::vector& aspects, bool f); void set_memento(const NodeLabelMemento*, std::vector& aspects, bool f); + void set_memento(const NodeAvisoMemento*, std::vector& aspects, bool f); void set_memento(const NodeQueueMemento*, std::vector& aspects, bool f); void set_memento(const NodeGenericMemento*, std::vector& aspects, bool f); void set_memento(const NodeQueueIndexMemento*, std::vector& aspects, bool f); @@ -626,6 +632,7 @@ class Node : public std::enable_shared_from_this { bool findLimit(const Limit&) const; bool findLabel(const std::string& name) const; const Label& find_label(const std::string& name) const; + bool findAviso(const std::string& name) const; const QueueAttr& find_queue(const std::string& name) const; QueueAttr& findQueue(const std::string& name); const GenericAttr& find_generic(const std::string& name) const; @@ -880,6 +887,7 @@ class Node : public std::enable_shared_from_this { std::vector meters_; std::vector events_; std::vector