X-Git-Url: https://git.stg.codes/stg.git/blobdiff_plain/4271ab433cd55bbd2612292bcf39e4dc3d7274f1..0907aa4037b12b6b88ee24495d4577a064d4f8db:/projects/sgconf/tariffs.cpp diff --git a/projects/sgconf/tariffs.cpp b/projects/sgconf/tariffs.cpp new file mode 100644 index 00000000..6c5ab3b7 --- /dev/null +++ b/projects/sgconf/tariffs.cpp @@ -0,0 +1,449 @@ +#include "tariffs.h" + +#include "api_action.h" +#include "options.h" +#include "config.h" +#include "utils.h" + +#include "stg/servconf.h" +#include "stg/servconf_types.h" +#include "stg/tariff_conf.h" +#include "stg/common.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +std::string Indent(size_t level, bool dash = false) +{ +if (level == 0) + return ""; +return dash ? std::string(level * 4 - 2, ' ') + "- " : std::string(level * 4, ' '); +} + +std::string ChangePolicyToString(STG::Tariff::ChangePolicy changePolicy) +{ +switch (changePolicy) + { + case STG::Tariff::ALLOW: return "allow"; + case STG::Tariff::TO_CHEAP: return "to_cheap"; + case STG::Tariff::TO_EXPENSIVE: return "to_expensive"; + case STG::Tariff::DENY: return "deny"; + } +return "unknown"; +} + +std::string PeriodToString(STG::Tariff::Period period) +{ +switch (period) + { + case STG::Tariff::DAY: + return "daily"; + case STG::Tariff::MONTH: + return "monthly"; + } +return "unknown"; +} + +std::string TraffTypeToString(STG::Tariff::TraffType traffType) +{ +switch (traffType) + { + case STG::Tariff::TRAFF_UP: + return "upload"; + case STG::Tariff::TRAFF_DOWN: + return "download"; + case STG::Tariff::TRAFF_UP_DOWN: + return "upload + download"; + case STG::Tariff::TRAFF_MAX: + return "max(upload, download)"; + } +return "unknown"; +} + +void ConvPeriod(const std::string & value, STG::Optional & res) +{ +std::string lowered = ToLower(value); +if (lowered == "daily") + res = STG::Tariff::DAY; +else if (lowered == "monthly") + res = STG::Tariff::MONTH; +else + throw SGCONF::ACTION::ERROR("Period should be 'daily' or 'monthly'. Got: '" + value + "'"); +} + +void ConvChangePolicy(const std::string & value, STG::Optional & res) +{ +std::string lowered = ToLower(value); +if (lowered == "allow") + res = STG::Tariff::ALLOW; +else if (lowered == "to_cheap") + res = STG::Tariff::TO_CHEAP; +else if (lowered == "to_expensive") + res = STG::Tariff::TO_EXPENSIVE; +else if (lowered == "deny") + res = STG::Tariff::DENY; +else + throw SGCONF::ACTION::ERROR("Change policy should be 'allow', 'to_cheap', 'to_expensive' or 'deny'. Got: '" + value + "'"); +} + +void ConvChangePolicyTimeout(const std::string & value, STG::Optional & res) +{ +struct tm brokenTime; +if (stg_strptime(value.c_str(), "%Y-%m-%d %H:%M:%S", &brokenTime) == NULL) + throw SGCONF::ACTION::ERROR("Credit expiration should be in format 'YYYY-MM-DD HH:MM:SS'. Got: '" + value + "'"); +res = stg_timegm(&brokenTime); +} + +void ConvTraffType(const std::string & value, STG::Optional & res) +{ +std::string lowered = ToLower(value); +lowered.erase(std::remove(lowered.begin(), lowered.end(), ' '), lowered.end()); +if (lowered == "upload") + res = STG::Tariff::TRAFF_UP; +else if (lowered == "download") + res = STG::Tariff::TRAFF_DOWN; +else if (lowered == "upload+download") + res = STG::Tariff::TRAFF_UP_DOWN; +else if (lowered.substr(0, 3) == "max") + res = STG::Tariff::TRAFF_MAX; +else + throw SGCONF::ACTION::ERROR("Traff type should be 'upload', 'download', 'upload + download' or 'max'. Got: '" + value + "'"); +} + +STG::DirPriceDataOpt ConvTimeSpan(const std::string & value) +{ +size_t dashPos = value.find_first_of('-'); +if (dashPos == std::string::npos) + throw SGCONF::ACTION::ERROR("Time span should be in format 'hh:mm-hh:mm'. Got: '" + value + "'"); +size_t fromColon = value.find_first_of(':'); +if (fromColon == std::string::npos || fromColon > dashPos) + throw SGCONF::ACTION::ERROR("Time span should be in format 'hh:mm-hh:mm'. Got: '" + value + "'"); +size_t toColon = value.find_first_of(':', dashPos); +if (toColon == std::string::npos) + throw SGCONF::ACTION::ERROR("Time span should be in format 'hh:mm-hh:mm'. Got: '" + value + "'"); +STG::DirPriceDataOpt res; +res.hDay = FromString(value.substr(0, fromColon)); +if (res.hDay.data() < 0 || res.hDay.data() > 23) + throw SGCONF::ACTION::ERROR("Invalid 'from' hours. Got: '" + value.substr(0, fromColon) + "'"); +res.mDay = FromString(value.substr(fromColon + 1, dashPos - fromColon - 1)); +if (res.mDay.data() < 0 || res.mDay.data() > 59) + throw SGCONF::ACTION::ERROR("Invalid 'from' minutes. Got: '" + value.substr(fromColon + 1, dashPos - fromColon - 1) + "'"); +res.hNight = FromString(value.substr(dashPos + 1, toColon - dashPos - 1)); +if (res.hNight.data() < 0 || res.hNight.data() > 23) + throw SGCONF::ACTION::ERROR("Invalid 'to' hours. Got: '" + value.substr(dashPos + 1, toColon - dashPos - 1) + "'"); +res.mNight = FromString(value.substr(toColon + 1, value.length() - toColon)); +if (res.mNight.data() < 0 || res.mNight.data() > 59) + throw SGCONF::ACTION::ERROR("Invalid 'to' minutes. Got: '" + value.substr(toColon + 1, value.length() - toColon) + "'"); +return res; +} + +void splice(std::vector & lhs, const std::vector & rhs) +{ +for (size_t i = 0; i < lhs.size() && i < rhs.size(); ++i) + lhs[i].splice(rhs[i]); +} + +void ConvTimes(std::string value, std::vector & res) +{ +value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); +splice(res, Split >(value, ',', ConvTimeSpan)); +} + +struct ConvPrice : public std::unary_function +{ + typedef STG::Optional (STG::DirPriceDataOpt::* MemPtr); + ConvPrice(MemPtr before, MemPtr after) + : m_before(before), m_after(after) + {} + + STG::DirPriceDataOpt operator()(const std::string & value) + { + STG::DirPriceDataOpt res; + size_t slashPos = value.find_first_of('/'); + if (slashPos == std::string::npos) + { + double price = 0; + if (str2x(value, price) < 0) + throw SGCONF::ACTION::ERROR("Price should be a floating point number. Got: '" + value + "'"); + (res.*m_before) = (res.*m_after) = price; + res.noDiscount = true; + } + else + { + double price = 0; + if (str2x(value.substr(0, slashPos), price) < 0) + throw SGCONF::ACTION::ERROR("Price should be a floating point number. Got: '" + value.substr(0, slashPos) + "'"); + (res.*m_before) = price; + if (str2x(value.substr(slashPos + 1, value.length() - slashPos), price) < 0) + throw SGCONF::ACTION::ERROR("Price should be a floating point number. Got: '" + value.substr(slashPos + 1, value.length() - slashPos) + "'"); + (res.*m_after) = price; + res.noDiscount = false; + } + return res; + } + + MemPtr m_before; + MemPtr m_after; +}; + +void ConvDayPrices(std::string value, std::vector & res) +{ +value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); +splice(res, Split >(value, ',', ConvPrice(&STG::DirPriceDataOpt::priceDayA, &STG::DirPriceDataOpt::priceDayB))); +} + +void ConvNightPrices(std::string value, std::vector & res) +{ +value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); +splice(res, Split >(value, ',', ConvPrice(&STG::DirPriceDataOpt::priceNightA, &STG::DirPriceDataOpt::priceNightB))); +} + +STG::DirPriceDataOpt ConvThreshold(std::string value) +{ + STG::DirPriceDataOpt res; +double threshold = 0; +if (str2x(value, threshold) < 0) + throw SGCONF::ACTION::ERROR("Threshold should be a floating point value. Got: '" + value + "'"); +res.threshold = threshold; +return res; +} + +void ConvThresholds(std::string value, std::vector & res) +{ +value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); +splice(res, Split >(value, ',', ConvThreshold)); +} + +std::string TimeToString(int h, int m) +{ +std::ostringstream stream; +stream << (h < 10 ? "0" : "") << h << ":" + << (m < 10 ? "0" : "") << m; +return stream.str(); +} + +void PrintDirPriceData(size_t dir, const STG::DirPriceData & data, size_t level) +{ +std::string night = TimeToString(data.hNight, data.mNight); +std::string day = TimeToString(data.hDay, data.mDay); +std::cout << Indent(level, true) << "dir: " << dir << "\n" + << Indent(level) << "'" << night << "' - '" << day << "': " << data.priceDayA << "/" << data.priceDayB << "\n" + << Indent(level) << "'" << day << "' - '" << night << "': " << data.priceNightA << "/" << data.priceNightB << "\n" + << Indent(level) << "threshold: " << data.threshold << "\n" + << Indent(level) << "single price: " << (data.singlePrice ? "yes" : "no") << "\n" + << Indent(level) << "discount: " << (data.noDiscount ? "no" : "yes") << "\n"; // Attention! +} + +void PrintTariffConf(const STG::TariffConf & conf, size_t level) +{ +std::cout << Indent(level, true) << "name: " << conf.name << "\n" + << Indent(level) << "fee: " << conf.fee << "\n" + << Indent(level) << "free mb: " << conf.free << "\n" + << Indent(level) << "passive cost: " << conf.passiveCost << "\n" + << Indent(level) << "traff type: " << TraffTypeToString(conf.traffType) << "\n" + << Indent(level) << "period: " << PeriodToString(conf.period) << "\n" + << Indent(level) << "change policy: " << ChangePolicyToString(conf.changePolicy) << "\n" + << Indent(level) << "change policy timeout: " << formatTime(conf.changePolicyTimeout) << "\n"; +} + +void PrintTariff(const STG::GetTariff::Info & info, size_t level = 0) +{ +PrintTariffConf(info.tariffConf, level); +std::cout << Indent(level) << "dir prices:\n"; +for (size_t i = 0; i < info.dirPrice.size(); ++i) + PrintDirPriceData(i, info.dirPrice[i], level + 1); +} + +std::vector GetTariffParams() +{ +std::vector params; +params.push_back(SGCONF::API_ACTION::PARAM("fee", "", "\t\ttariff fee")); +params.push_back(SGCONF::API_ACTION::PARAM("free", "", "\tprepaid traffic")); +params.push_back(SGCONF::API_ACTION::PARAM("passive-cost", "", "\tpassive cost")); +params.push_back(SGCONF::API_ACTION::PARAM("traff-type", "", "\ttraffic type (up, down, up+down, max)")); +params.push_back(SGCONF::API_ACTION::PARAM("period", "", "\ttarification period (daily, monthly)")); +params.push_back(SGCONF::API_ACTION::PARAM("change-policy", "", "tariff change policy (allow, to_cheap, to_expensive, deny)")); +params.push_back(SGCONF::API_ACTION::PARAM("change-policy-timeout", "", "tariff change policy timeout")); +params.push_back(SGCONF::API_ACTION::PARAM("times", "", "coma-separated day time-spans for each direction")); +params.push_back(SGCONF::API_ACTION::PARAM("day-prices", "", "coma-separated day prices for each direction")); +params.push_back(SGCONF::API_ACTION::PARAM("night-prices", "", "coma-separated night prices for each direction")); +params.push_back(SGCONF::API_ACTION::PARAM("thresholds", "", "coma-separated thresholds for each direction")); +return params; +} + +void SimpleCallback(bool result, + const std::string & reason, + void * /*data*/) +{ +if (!result) + { + std::cerr << "Operation failed. Reason: '" << reason << "'." << std::endl; + return; + } +std::cout << "Success.\n"; +} + +void GetTariffsCallback(bool result, + const std::string & reason, + const std::vector & info, + void * /*data*/) +{ +if (!result) + { + std::cerr << "Failed to get tariff list. Reason: '" << reason << "'." << std::endl; + return; + } +std::cout << "Tariffs:\n"; +for (size_t i = 0; i < info.size(); ++i) + PrintTariff(info[i], 1); +} + +void GetTariffCallback(bool result, + const std::string & reason, + const std::vector & info, + void * data) +{ +assert(data != NULL && "Expecting pointer to std::string with the tariff's name."); +const std::string & name = *static_cast(data); +if (!result) + { + std::cerr << "Failed to get tariff. Reason: '" << reason << "'." << std::endl; + return; + } +for (size_t i = 0; i < info.size(); ++i) + if (info[i].tariffConf.name == name) + PrintTariff(info[i]); +} + +bool GetTariffsFunction(const SGCONF::CONFIG & config, + const std::string & /*arg*/, + const std::map & /*options*/) +{ +STG::ServConf proto(config.server.data(), + config.port.data(), + config.localAddress.data(), + config.localPort.data(), + config.userName.data(), + config.userPass.data()); +return proto.GetTariffs(GetTariffsCallback, NULL) == STG::st_ok; +} + +bool GetTariffFunction(const SGCONF::CONFIG & config, + const std::string & arg, + const std::map & /*options*/) +{ +STG::ServConf proto(config.server.data(), + config.port.data(), + config.localAddress.data(), + config.localPort.data(), + config.userName.data(), + config.userPass.data()); +// STG currently doesn't support . +// So get a list of tariffs and filter it. 'data' param holds a pointer to 'name'. +std::string name(arg); +return proto.GetTariffs(GetTariffCallback, &name) == STG::st_ok; +} + +bool DelTariffFunction(const SGCONF::CONFIG & config, + const std::string & arg, + const std::map & /*options*/) +{ +STG::ServConf proto(config.server.data(), + config.port.data(), + config.localAddress.data(), + config.localPort.data(), + config.userName.data(), + config.userPass.data()); +return proto.DelTariff(arg, SimpleCallback, NULL) == STG::st_ok; +} + +bool AddTariffFunction(const SGCONF::CONFIG & config, + const std::string & arg, + const std::map & options) +{ +STG::TariffDataOpt conf; +conf.tariffConf.name = arg; +SGCONF::MaybeSet(options, "fee", conf.tariffConf.fee); +SGCONF::MaybeSet(options, "free", conf.tariffConf.free); +SGCONF::MaybeSet(options, "passive-cost", conf.tariffConf.passiveCost); +SGCONF::MaybeSet(options, "traff-type", conf.tariffConf.traffType, ConvTraffType); +SGCONF::MaybeSet(options, "period", conf.tariffConf.period, ConvPeriod); +SGCONF::MaybeSet(options, "change-policy", conf.tariffConf.changePolicy, ConvChangePolicy); +SGCONF::MaybeSet(options, "change-policy-timeout", conf.tariffConf.changePolicyTimeout, ConvChangePolicyTimeout); +SGCONF::MaybeSet(options, "times", conf.dirPrice, ConvTimes); +SGCONF::MaybeSet(options, "day-prices", conf.dirPrice, ConvDayPrices); +SGCONF::MaybeSet(options, "night-prices", conf.dirPrice, ConvNightPrices); +SGCONF::MaybeSet(options, "thresholds", conf.dirPrice, ConvThresholds); +for (size_t i = 0; i < conf.dirPrice.size(); ++i) + { + if (!conf.dirPrice[i].priceDayA.empty() && + !conf.dirPrice[i].priceNightA.empty() && + !conf.dirPrice[i].priceDayB.empty() && + !conf.dirPrice[i].priceNightB.empty()) + conf.dirPrice[i].singlePrice = conf.dirPrice[i].priceDayA.data() == conf.dirPrice[i].priceNightA.data() && + conf.dirPrice[i].priceDayB.data() == conf.dirPrice[i].priceNightB.data(); + } +STG::ServConf proto(config.server.data(), + config.port.data(), + config.localAddress.data(), + config.localPort.data(), + config.userName.data(), + config.userPass.data()); +return proto.AddTariff(arg, conf, SimpleCallback, NULL) == STG::st_ok; +} + +bool ChgTariffFunction(const SGCONF::CONFIG & config, + const std::string & arg, + const std::map & options) +{ +STG::TariffDataOpt conf; +conf.tariffConf.name = arg; +SGCONF::MaybeSet(options, "fee", conf.tariffConf.fee); +SGCONF::MaybeSet(options, "free", conf.tariffConf.free); +SGCONF::MaybeSet(options, "passive-cost", conf.tariffConf.passiveCost); +SGCONF::MaybeSet(options, "traff-type", conf.tariffConf.traffType, ConvTraffType); +SGCONF::MaybeSet(options, "period", conf.tariffConf.period, ConvPeriod); +SGCONF::MaybeSet(options, "change-policy", conf.tariffConf.changePolicy, ConvChangePolicy); +SGCONF::MaybeSet(options, "change-policy-timeout", conf.tariffConf.changePolicyTimeout, ConvChangePolicyTimeout); +SGCONF::MaybeSet(options, "times", conf.dirPrice, ConvTimes); +SGCONF::MaybeSet(options, "day-prices", conf.dirPrice, ConvDayPrices); +SGCONF::MaybeSet(options, "night-prices", conf.dirPrice, ConvNightPrices); +SGCONF::MaybeSet(options, "thresholds", conf.dirPrice, ConvThresholds); +for (size_t i = 0; i < conf.dirPrice.size(); ++i) + { + if (!conf.dirPrice[i].priceDayA.empty() && + !conf.dirPrice[i].priceNightA.empty() && + !conf.dirPrice[i].priceDayB.empty() && + !conf.dirPrice[i].priceNightB.empty()) + conf.dirPrice[i].singlePrice = conf.dirPrice[i].priceDayA.data() == conf.dirPrice[i].priceNightA.data() && + conf.dirPrice[i].priceDayB.data() == conf.dirPrice[i].priceNightB.data(); + } +STG::ServConf proto(config.server.data(), + config.port.data(), + config.localAddress.data(), + config.localPort.data(), + config.userName.data(), + config.userPass.data()); +return proto.ChgTariff(conf, SimpleCallback, NULL) == STG::st_ok; +} + +} // namespace anonymous + +void SGCONF::AppendTariffsOptionBlock(COMMANDS & commands, OPTION_BLOCKS & blocks) +{ +std::vector params(GetTariffParams()); +blocks.Add("Tariff management options") + .Add("get-tariffs", SGCONF::MakeAPIAction(commands, GetTariffsFunction), "\tget tariff list") + .Add("get-tariff", SGCONF::MakeAPIAction(commands, "", GetTariffFunction), "get tariff") + .Add("add-tariff", SGCONF::MakeAPIAction(commands, "", params, AddTariffFunction), "add tariff") + .Add("del-tariff", SGCONF::MakeAPIAction(commands, "", DelTariffFunction), "delete tariff") + .Add("chg-tariff", SGCONF::MakeAPIAction(commands, "", params, ChgTariffFunction), "change tariff"); +}