class TARIFF {
public:
+ enum CHANGE_POLICY { ALLOW = 0, TO_CHEAP, TO_EXPENSIVE, DENY };
+
enum PERIOD { DAY = 0, MONTH };
enum TRAFF_TYPE { TRAFF_UP = 0, TRAFF_DOWN, TRAFF_UP_DOWN, TRAFF_MAX };
+ static std::string ChangePolicyToString(CHANGE_POLICY changePolicy);
+ static CHANGE_POLICY StringToChangePolicy(const std::string& value);
+
static std::string PeriodToString(PERIOD period);
static PERIOD StringToPeriod(const std::string& value);
virtual double GetFee() const = 0;
virtual double GetFree() const = 0;
virtual PERIOD GetPeriod() const = 0;
+ virtual CHANGE_POLICY GetChangePolicy() const = 0;
+ virtual time_t GetChangePolicyTimeout() const = 0;
virtual const std::string & GetName() const = 0;
virtual void SetName(const std::string & name) = 0;
virtual int64_t GetTraffByType(uint64_t up, uint64_t down) const = 0;
virtual int GetThreshold(int dir) const = 0;
virtual const TARIFF_DATA & GetTariffData() const = 0;
+ virtual std::string TariffChangeIsAllowed(const TARIFF & to, time_t currentTime) const = 0;
};
+inline
+std::string TARIFF::ChangePolicyToString(TARIFF::CHANGE_POLICY changePolicy)
+{
+switch (changePolicy)
+ {
+ case ALLOW: return "allow";
+ case TO_CHEAP: return "to_cheap";
+ case TO_EXPENSIVE: return "to_expensive";
+ case DENY: return "deny";
+ }
+return "allow"; // Classic behaviour.
+}
+
+inline
+TARIFF::CHANGE_POLICY TARIFF::StringToChangePolicy(const std::string& value)
+{
+if (strcasecmp(value.c_str(), "to_cheap") == 0)
+ return TO_CHEAP;
+if (strcasecmp(value.c_str(), "to_expensive") == 0)
+ return TO_EXPENSIVE;
+if (strcasecmp(value.c_str(), "deny") == 0)
+ return DENY;
+return ALLOW; // Classic behaviour.
+}
+
inline
std::string TARIFF::PeriodToString(TARIFF::PERIOD period)
{
double passiveCost;
std::string name;
TARIFF::PERIOD period;
+ TARIFF::CHANGE_POLICY changePolicy;
+ time_t changePolicyTimeout;
TARIFF_CONF()
: fee(0),
traffType(TARIFF::TRAFF_UP_DOWN),
passiveCost(0),
name(),
- period(TARIFF::MONTH)
+ period(TARIFF::MONTH),
+ changePolicy(TARIFF::ALLOW),
+ changePolicyTimeout(0)
{}
TARIFF_CONF(const std::string & n)
traffType(TARIFF::TRAFF_UP_DOWN),
passiveCost(0),
name(n),
- period(TARIFF::MONTH)
+ period(TARIFF::MONTH),
+ changePolicy(TARIFF::ALLOW),
+ changePolicyTimeout(0)
{}
};
//-----------------------------------------------------------------------------
traffType(),
passiveCost(),
name(),
- period()
+ period(),
+ changePolicy(),
+ changePolicyTimeout()
{}
TARIFF_CONF_RES & operator=(const TARIFF_CONF & tc)
passiveCost = tc.passiveCost;
name = tc.name;
period = tc.period;
+ changePolicy = tc.changePolicy;
+ changePolicyTimeout = tc.changePolicyTimeout;
return *this;
}
passiveCost.maybeSet(tc.passiveCost);
traffType.maybeSet(tc.traffType);
period.maybeSet(tc.period);
+ changePolicy.maybeSet(tc.changePolicy);
+ changePolicyTimeout.maybeSet(tc.changePolicyTimeout);
return tc;
}
RESETABLE<double> passiveCost;
RESETABLE<std::string> name;
RESETABLE<TARIFF::PERIOD> period;
+ RESETABLE<TARIFF::CHANGE_POLICY> changePolicy;
+ RESETABLE<time_t> changePolicyTimeout;
};
//-----------------------------------------------------------------------------
struct TARIFF_DATA
dirPrice(DIR_NUM)
{}
+ TARIFF_DATA_RES & operator=(const TARIFF_DATA & td)
+ {
+ tariffConf = td.tariffConf;
+ for (size_t i = 0; i < DIR_NUM; ++i)
+ dirPrice[i] = td.dirPrice[i];
+ return *this;
+ }
+
TARIFF_DATA GetData() const
{
TARIFF_DATA td;
return dash ? std::string(level * 4 - 2, ' ') + "- " : std::string(level * 4, ' ');
}
+std::string ChangePolicyToString(TARIFF::CHANGE_POLICY changePolicy)
+{
+switch (changePolicy)
+ {
+ case TARIFF::ALLOW: return "allow";
+ case TARIFF::TO_CHEAP: return "to_cheap";
+ case TARIFF::TO_EXPENSIVE: return "to_expensive";
+ case TARIFF::DENY: return "deny";
+ }
+return "unknown";
+}
+
std::string PeriodToString(TARIFF::PERIOD period)
{
switch (period)
throw SGCONF::ACTION::ERROR("Period should be 'daily' or 'monthly'. Got: '" + value + "'");
}
+void ConvChangePolicy(const std::string & value, RESETABLE<TARIFF::CHANGE_POLICY> & res)
+{
+std::string lowered = ToLower(value);
+if (lowered == "allow")
+ res = TARIFF::ALLOW;
+else if (lowered == "to_cheap")
+ res = TARIFF::TO_CHEAP;
+else if (lowered == "to_expensive")
+ res = TARIFF::TO_EXPENSIVE;
+else if (lowered == "deny")
+ res = 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, RESETABLE<time_t> & 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, RESETABLE<TARIFF::TRAFF_TYPE> & res)
{
std::string lowered = ToLower(value);
<< 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) << "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::GET_TARIFF::INFO & info, size_t level = 0)
params.push_back(SGCONF::API_ACTION::PARAM("passive-cost", "<cost>", "\tpassive cost"));
params.push_back(SGCONF::API_ACTION::PARAM("traff-type", "<type>", "\ttraffic type (up, down, up+down, max)"));
params.push_back(SGCONF::API_ACTION::PARAM("period", "<period>", "\ttarification period (daily, monthly)"));
+params.push_back(SGCONF::API_ACTION::PARAM("change-policy", "<policy>", "tariff change policy (allow, to_cheap, to_expensive, deny)"));
+params.push_back(SGCONF::API_ACTION::PARAM("change-policy-timeout", "<yyyy-mm-dd hh:mm:ss>", "tariff change policy timeout"));
params.push_back(SGCONF::API_ACTION::PARAM("times", "<hh:mm-hh:mm, ...>", "coma-separated day time-spans for each direction"));
params.push_back(SGCONF::API_ACTION::PARAM("day-prices", "<price/price, ...>", "coma-separated day prices for each direction"));
params.push_back(SGCONF::API_ACTION::PARAM("night-prices", "<price/price, ...>", "coma-separated night prices for each direction"));
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, "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);
--- /dev/null
+/*
+ * DB migration from v02 to v03 (postgres)
+ */
+BEGIN;
+
+CREATE DOMAIN DM_TARIFF_CHANGE_POLICY AS TEXT NOT NULL
+ CONSTRAINT valid_value CHECK (VALUE IN ('allow', 'to_cheap', 'to_expensive', 'deny'));
+
+ALTER TABLE tb_tariffs ADD change_policy DM_TARIFF_CHANGE_POLICY DEFAULT 'allow';
+ALTER TABLE tb_tariffs ADD change_policy_timeout TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00';
+ALTER TABLE tb_tariffs ALTER COLUMN change_policy DROP DEFAULT;
+ALTER TABLE tb_tariffs ALTER COLUMN change_policy_timeout DROP DEFAULT;
+
+UPDATE tb_info SET version = 8;
+
+COMMIT;
--- /dev/null
+/*
+ * DB migration from v02 to v03 (firebird)
+ */
+
+CREATE DOMAIN DM_TARIFF_CHANGE_POLICY AS VARCHAR(32) NOT NULL
+ CHECK (VALUE IN ('allow', 'to_cheap', 'to_expensive', 'deny'));
+
+ALTER TABLE tb_tariffs ADD change_policy DM_TARIFF_CHANGE_POLICY DEFAULT 'allow';
+ALTER TABLE tb_tariffs ADD change_policy_timeout DM_MOMENT DEFAULT '1970-01-01 00:00:00';
+
+UPDATE tb_tariffs SET change_policy = 'allow', change_policy_timeout = '1970-01-01 00:00:00';
+
+ALTER TABLE tb_tariffs ALTER COLUMN change_policy DROP DEFAULT;
+ALTER TABLE tb_tariffs ALTER COLUMN change_policy_timeout DROP DEFAULT;
+
+UPDATE tb_info SET version = 2;
#include <ostream> // xmlrpc-c devs have missed something :)
#include "tariff_helper.h"
+#include "stg/common.h"
void TARIFF_HELPER::GetTariffInfo(xmlrpc_c::value * info) const
{
structVal["passivecost"] = xmlrpc_c::value_double(data.tariffConf.passiveCost);
structVal["traffType"] = xmlrpc_c::value_int(data.tariffConf.traffType);
structVal["period"] = xmlrpc_c::value_string(TARIFF::PeriodToString(data.tariffConf.period));
+structVal["changePolicy"] = xmlrpc_c::value_string(TARIFF::ChangePolicyToString(data.tariffConf.changePolicy));
+structVal["changePolicyTimeout"] = xmlrpc_c::value_string(formatTime(data.tariffConf.changePolicyTimeout));
std::vector<xmlrpc_c::value> prices(DIR_NUM);
data.tariffConf.period = TARIFF::StringToPeriod(xmlrpc_c::value_string(it->second));
}
+if ((it = structVal.find("changePolicy")) != structVal.end())
+ {
+ data.tariffConf.changePolicy = TARIFF::StringToChangePolicy(xmlrpc_c::value_string(it->second));
+ }
+
+if ((it = structVal.find("changePolicyTimeout")) != structVal.end())
+ {
+ data.tariffConf.changePolicyTimeout = readTime(xmlrpc_c::value_string(it->second));
+ }
+
if ((it = structVal.find("dirprices")) != structVal.end())
{
std::vector<xmlrpc_c::value> prices(
tariff = tariff.substr(0, pos);
}
- if (tariffs->FindByName(tariff))
- if (ptr->GetProperty().tariffName.Get() != tariff)
- if (!ptr->GetProperty().tariffName.Set(tariff,
- admin,
- login,
- &store))
- return true;
+ const TARIFF * newTariff = tariffs->FindByName(tariff);
+ if (newTariff)
+ {
+ const TARIFF * currentTariff = ptr->GetTariff();
+ std::string message = currentTariff->TariffChangeIsAllowed(*newTariff, stgTime);
+ if (message.empty())
+ {
+ if (ptr->GetProperty().tariffName.Get() != tariff)
+ {
+ if (!ptr->GetProperty().tariffName.Set(tariff,
+ admin,
+ login,
+ &store))
+ return true;
+ }
+ }
+ else
+ {
+ GetStgLogger()("Tariff change is prohibited for user %s. %s", ptr->GetLogin().c_str(), message.c_str());
+ }
+ }
if (nextTariff != "" &&
tariffs->FindByName(nextTariff))
}
else
{
- if (u->GetProperty().tariffName.Set(tariff,
+ const TARIFF * newTariff = tariffs->FindByName(tariff);
+ if (newTariff)
+ {
+ const TARIFF * currentTariff = u->GetTariff();
+ std::string message = currentTariff->TariffChangeIsAllowed(*newTariff, stgTime);
+ if (message.empty())
+ {
+ if (u->GetProperty().tariffName.Set(tariff,
admin,
login,
store,
comment))
- {
- u->ResetNextTariff();
- u->WriteConf();
- *retvalPtr = xmlrpc_c::value_boolean(true);
- return;
+ {
+ u->ResetNextTariff();
+ u->WriteConf();
+ *retvalPtr = xmlrpc_c::value_boolean(true);
+ return;
+ }
+ }
+ else
+ {
+ GetStgLogger()("Tariff change is prohibited for user %s. %s", u->GetLogin().c_str(), message.c_str());
+ }
}
}
}
"<Free value=\"" + x2str(it->tariffConf.free) + "\"/>" +
"<TraffType value=\"" + TARIFF::TraffTypeToString(it->tariffConf.traffType) + "\"/>" +
"<Period value=\"" + TARIFF::PeriodToString(it->tariffConf.period) + "\"/>" +
+ "<ChangePolicy value=\"" + TARIFF::ChangePolicyToString(it->tariffConf.changePolicy) + "\"/>" +
+ "<ChangePolicyTimeout value=\"" + x2str(it->tariffConf.changePolicyTimeout) + "\"/>" +
"</tariff>";
}
{
if (strcasecmp(el, m_tag.c_str()) == 0)
{
- td.tariffConf.name = attr[1];
+ const TARIFF * tariff = m_tariffs.FindByName(attr[1]);
+ if (tariff != NULL)
+ td = tariff->GetTariffData();
+ else
+ return -1;
return 0;
}
}
td.tariffConf.period = TARIFF::StringToPeriod(attr[1]);
return 0;
}
+
+ if (strcasecmp(el, "ChangePolicy") == 0)
+ {
+ td.tariffConf.changePolicy = TARIFF::StringToChangePolicy(attr[1]);
+ return 0;
+ }
+
+ if (strcasecmp(el, "ChangePolicyTimeout") == 0)
+ {
+ int64_t policyTime = 0;
+ if (str2x(attr[1], policyTime) == 0)
+ td.tariffConf.changePolicyTimeout = (time_t)policyTime;
+ return 0;
+ }
}
return -1;
}
if (!m_ucr.tariffName.empty())
{
- if (m_tariffs.FindByName(m_ucr.tariffName.const_data()))
+ const TARIFF * newTariff = m_tariffs.FindByName(m_ucr.tariffName.const_data());
+ if (newTariff)
{
- if (!u->GetProperty().tariffName.Set(m_ucr.tariffName.const_data(), &m_currAdmin, m_login, &m_store))
- return -1;
- u->ResetNextTariff();
+ const TARIFF * tariff = u->GetTariff();
+ std::string message = tariff->TariffChangeIsAllowed(*newTariff, stgTime);
+ if (message.empty())
+ {
+ if (!u->GetProperty().tariffName.Set(m_ucr.tariffName.const_data(), &m_currAdmin, m_login, &m_store))
+ return -1;
+ u->ResetNextTariff();
+ }
+ else
+ {
+ GetStgLogger()("Tariff change is prohibited for user %s. %s", u->GetLogin().c_str(), message.c_str());
+ }
}
else
{
td->tariffConf.period = TARIFF::MONTH;
else
td->tariffConf.period = TARIFF::StringToPeriod(str);
+
+if (conf.ReadString("ChangePolicy", &str, "allow") < 0)
+ td->tariffConf.changePolicy = TARIFF::ALLOW;
+else
+ td->tariffConf.changePolicy = TARIFF::StringToChangePolicy(str);
+
+if (conf.ReadTime("ChangePolicyTimeout", &td->tariffConf.changePolicyTimeout, 0) < 0)
+ {
+ STG_LOCKER lock(&mutex);
+ errorStr = "Cannot read tariff " + tariffName + ". Parameter ChangePolicyTimeout";
+ printfd(__FILE__, "FILES_STORE::RestoreTariff - changepolicytimeout read failed for tariff '%s'\n", tariffName.c_str());
+ return -1;
+ }
return 0;
}
//-----------------------------------------------------------------------------
cf.WriteDouble("Free", td.tariffConf.free);
cf.WriteString("TraffType", TARIFF::TraffTypeToString(td.tariffConf.traffType));
cf.WriteString("Period", TARIFF::PeriodToString(td.tariffConf.period));
+ cf.WriteString("ChangePolicy", TARIFF::ChangePolicyToString(td.tariffConf.changePolicy));
+ cf.WriteTime("ChangePolicyTimeout", td.tariffConf.changePolicyTimeout);
}
return 0;
int32_t id;
st->Get(1, id);
st->Close();
+
+ std::string query = "update tb_tariffs set \
+ fee = ?, \
+ free = ?, \
+ passive_cost = ?, \
+ traff_type = ?";
+
+ if (schemaVersion > 0)
+ query += ", period = ?";
+ if (schemaVersion > 1)
+ query += ", change_policy = ?, \
+ change_policy_timeout = ?";
+
+ query += " where pk_tariff = ?";
+
+ unsigned num = 5;
+ st->Prepare(query);
+ st->Set(1, td.tariffConf.fee);
+ st->Set(2, td.tariffConf.free);
+ st->Set(3, td.tariffConf.passiveCost);
+ st->Set(4, td.tariffConf.traffType);
+
if (schemaVersion > 0)
{
- st->Prepare("update tb_tariffs set \
- fee = ?, \
- free = ?, \
- passive_cost = ?, \
- traff_type = ?, \
- period = ? \
- where pk_tariff = ?");
- st->Set(1, td.tariffConf.fee);
- st->Set(2, td.tariffConf.free);
- st->Set(3, td.tariffConf.passiveCost);
- st->Set(4, td.tariffConf.traffType);
st->Set(5, TARIFF::PeriodToString(td.tariffConf.period));
- st->Set(6, id);
+ ++num;
}
- else
+
+ if (schemaVersion > 1)
{
- st->Prepare("update tb_tariffs set \
- fee = ?, \
- free = ?, \
- passive_cost = ?, \
- traff_type = ? \
- where pk_tariff = ?");
- st->Set(1, td.tariffConf.fee);
- st->Set(2, td.tariffConf.free);
- st->Set(3, td.tariffConf.passiveCost);
- st->Set(4, td.tariffConf.traffType);
- st->Set(5, id);
+ st->Set(6, TARIFF::ChangePolicyToString(td.tariffConf.changePolicy));
+ IBPP::Timestamp policyTimeout;
+ time_t2ts(td.tariffConf.changePolicyTimeout, &policyTimeout);
+ st->Set(7, policyTimeout);
+ num += 2;
}
+
+ st->Set(num, id);
st->Execute();
st->Close();
td->tariffConf.traffType = TARIFF::IntToTraffType(Get<int>(st, 6));
if (schemaVersion > 0)
td->tariffConf.period = TARIFF::StringToPeriod(Get<std::string>(st, 7));
+ if (schemaVersion > 1)
+ {
+ td->tariffConf.changePolicy = TARIFF::StringToChangePolicy(Get<std::string>(st, 8));
+ td->tariffConf.changePolicyTimeout = ts2time_t(Get<IBPP::Timestamp>(st, 9));
+ }
st->Close();
st->Prepare("select * from tb_tariffs_params where fk_tariff = ?");
st->Set(1, id);
#include <mysql.h>
#include <errmsg.h>
+#include "stg/common.h"
#include "stg/user_ips.h"
#include "stg/user_conf.h"
#include "stg/user_stat.h"
res += "PassiveCost DOUBLE DEFAULT 0.0, Fee DOUBLE DEFAULT 0.0,"
"Free DOUBLE DEFAULT 0.0, TraffType VARCHAR(10) DEFAULT '',"
- "period VARCHAR(32) NOT NULL DEFAULT 'month')";
+ "period VARCHAR(32) NOT NULL DEFAULT 'month',"
+ "change_policy VARCHAR(32) NOT NULL DEFAULT 'allow',"
+ "change_policy_timeout TIMESTAMP NOT NULL DEFAULT 0)";
if(MysqlQuery(res.c_str(),sock))
{
res += "PassiveCost=0.0, Fee=10.0, Free=0,"\
"SinglePrice0=1, SinglePrice1=1,PriceDayA1=0.75,PriceDayB1=0.75,"\
- "PriceNightA0=1.0,PriceNightB0=1.0,TraffType='up+down',period='month'";
+ "PriceNightA0=1.0,PriceNightB0=1.0,TraffType='up+down',period='month',"\
+ "change_policy='allow', change_policy_timeout=0";
if(MysqlQuery(res.c_str(),sock))
{
mysql_close(sock);
return -1;
}
- schemaVersion = 1;
+ schemaVersion = 2;
}
//users-----------------------------------------------------------------------
schemaVersion = 1;
logger("MYSQL_STORE: Updated DB schema to version %d", schemaVersion);
}
+
+if (schemaVersion < 2)
+ {
+ if (MysqlQuery("ALTER TABLE tariffs ADD change_policy VARCHAR(32) NOT NULL DEFAULT 'allow'", sock) ||
+ MysqlQuery("ALTER TABLE tariffs ADD change_policy_timeout TIMESTAMP NOT NULL DEFAULT 0", sock))
+ {
+ errorStr = "Couldn't update tariffs table to version 2. With error:\n";
+ errorStr += mysql_error(sock);
+ mysql_close(sock);
+ return -1;
+ }
+ if (MysqlQuery("UPDATE info SET version = 2", sock))
+ {
+ errorStr = "Couldn't update DB schema version to 2. With error:\n";
+ errorStr += mysql_error(sock);
+ mysql_close(sock);
+ return -1;
+ }
+ schemaVersion = 2;
+ logger("MYSQL_STORE: Updated DB schema to version %d", schemaVersion);
+ }
return 0;
}
//-----------------------------------------------------------------------------
td->tariffConf.period = TARIFF::MONTH;
}
+if (schemaVersion > 1)
+ {
+ str = row[6+8*DIR_NUM];
+ param = "ChangePolicy";
+
+ if (str.length() == 0)
+ {
+ mysql_free_result(res);
+ errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param;
+ mysql_close(sock);
+ return -1;
+ }
+
+ td->tariffConf.changePolicy = TARIFF::StringToChangePolicy(str);
+
+ str = row[7+8*DIR_NUM];
+ param = "ChangePolicyTimeout";
+
+ if (str.length() == 0)
+ {
+ mysql_free_result(res);
+ errorStr = "Cannot read tariff " + tariffName + ". Parameter " + param;
+ mysql_close(sock);
+ return -1;
+ }
+
+ td->tariffConf.changePolicyTimeout = readTime(str);
+ }
+else
+ {
+ td->tariffConf.changePolicy = TARIFF::ALLOW;
+ td->tariffConf.changePolicyTimeout = 0;
+ }
+
mysql_free_result(res);
mysql_close(sock);
return 0;
if (schemaVersion > 0)
res += ", Period='" + TARIFF::PeriodToString(td.tariffConf.period) + "'";
+if (schemaVersion > 1)
+ res += ", change_policy='" + TARIFF::ChangePolicyToString(td.tariffConf.changePolicy) + "'"\
+ ", change_policy_timeout='" + formatTime(td.tariffConf.changePolicy) + "'";
+
strprintf(¶m, " WHERE name='%s' LIMIT 1", tariffName.c_str());
res += param;
int EscapeString(std::string & value) const;
- std::string Int2TS(time_t value) const;
- time_t TS2Int(const std::string & value) const;
-
int SaveStat(const USER_STAT & stat, const std::string & login, int year = 0, int month = 0) const;
int SaveUserServices(uint32_t uid, const std::vector<std::string> & services) const;
#include <libpq-fe.h>
+#include "stg/common.h"
#include "postgresql_store.h"
#include "stg/locker.h"
#include "stg/message.h"
<< "'" << elogin << "', "
<< "CAST(1 AS SMALLINT), " // Here need to be a version, but, it's uninitiated actually
<< "CAST(" << msg->header.type << " AS SMALLINT), "
- << "CAST('" << Int2TS(msg->header.lastSendTime) << "' AS TIMESTAMP), "
- << "CAST('" << Int2TS(msg->header.creationTime) << "' AS TIMESTAMP), "
+ << "CAST('" << formatTime(msg->header.lastSendTime) << "' AS TIMESTAMP), "
+ << "CAST('" << formatTime(msg->header.creationTime) << "' AS TIMESTAMP), "
<< msg->header.showTime << ", "
<< "CAST(" << msg->header.repeat << " AS SMALLINT), "
<< msg->header.repeatPeriod << ", "
<< "fk_user = (SELECT pk_user FROM tb_users WHERE name = '" << elogin << "'), "
<< "ver = " << msg.header.ver << ", "
<< "msg_type = " << msg.header.type << ", "
- << "last_send_time = CAST('" << Int2TS(msg.header.lastSendTime) << "' AS TIMESTAMP), "
- << "creation_time = CAST('" << Int2TS(msg.header.creationTime) << "' AS TIMESTAMP), "
+ << "last_send_time = CAST('" << formatTime(msg.header.lastSendTime) << "' AS TIMESTAMP), "
+ << "creation_time = CAST('" << formatTime(msg.header.creationTime) << "' AS TIMESTAMP), "
<< "show_time = " << msg.header.showTime << ", "
<< "repeat = " << msg.header.repeat << ", "
<< "repeat_period = " << msg.header.repeatPeriod << ", "
str2x(PQgetvalue(result, 0, 0), msg->header.ver);
str2x(PQgetvalue(result, 0, 1), msg->header.type);
-msg->header.lastSendTime = static_cast<unsigned int>(TS2Int(PQgetvalue(result, 0, 2)));
-msg->header.creationTime = static_cast<unsigned int>(TS2Int(PQgetvalue(result, 0, 3)));
+msg->header.lastSendTime = static_cast<unsigned int>(readTime(PQgetvalue(result, 0, 2)));
+msg->header.creationTime = static_cast<unsigned int>(readTime(PQgetvalue(result, 0, 3)));
str2x(PQgetvalue(result, 0, 4), msg->header.showTime);
str2x(PQgetvalue(result, 0, 5), msg->header.repeat);
str2x(PQgetvalue(result, 0, 6), msg->header.repeatPeriod);
tuple << PQgetvalue(result, i, 0) << " ";
tuple << PQgetvalue(result, i, 1) << " ";
tuple << PQgetvalue(result, i, 2) << " ";
- header.lastSendTime = static_cast<unsigned int>(TS2Int(PQgetvalue(result, i, 3)));
- header.creationTime = static_cast<unsigned int>(TS2Int(PQgetvalue(result, i, 4)));
+ header.lastSendTime = static_cast<unsigned int>(readTime(PQgetvalue(result, i, 3)));
+ header.creationTime = static_cast<unsigned int>(readTime(PQgetvalue(result, i, 4)));
tuple << PQgetvalue(result, i, 5) << " ";
tuple << PQgetvalue(result, i, 6) << " ";
tuple << PQgetvalue(result, i, 7) << " ";
#include <libpq-fe.h>
+#include "stg/common.h"
#include "postgresql_store.h"
#include "stg/locker.h"
if (version > 6)
query << ", period = '" << TARIFF::PeriodToString(td.tariffConf.period) << "'";
+ if (version > 7)
+ query << ", change_policy = '" << TARIFF::ChangePolicyToString(td.tariffConf.changePolicy) << "', \
+ change_policy_timeout = CAST('" << formatTime(td.tariffConf.changePolicyTimeout) << "' AS TIMESTAMP)";
+
query << " WHERE pk_tariff = " << id;
result = PQexec(connection, query.str().c_str());
if (version > 6)
query << ", period";
+if (version > 7)
+ query << ", change_policy \
+ , change_policy_timeout";
+
query << " FROM tb_tariffs WHERE name = '" << ename << "'";
result = PQexec(connection, query.str().c_str());
if (version > 6)
td->tariffConf.period = TARIFF::StringToPeriod(PQgetvalue(result, 0, 5));
+if (version > 7)
+ {
+ td->tariffConf.changePolicy = TARIFF::StringToChangePolicy(PQgetvalue(result, 0, 6));
+ td->tariffConf.changePolicyTimeout = readTime(PQgetvalue(result, 0, 7));
+ }
+
PQclear(result);
query.str("");
#include <libpq-fe.h>
+#include "stg/common.h"
#include "stg/const.h"
#include "stg/locker.h"
#include "../../../stg_timer.h"
query << "UPDATE tb_users SET "
"cash = " << stat.cash << ", "
"free_mb = " << stat.freeMb << ", "
- "last_activity_time = CAST('" << Int2TS(stat.lastActivityTime) << "' AS TIMESTAMP), "
+ "last_activity_time = CAST('" << formatTime(stat.lastActivityTime) << "' AS TIMESTAMP), "
"last_cash_add = " << stat.lastCashAdd << ", "
- "last_cash_add_time = CAST('" << Int2TS(stat.lastCashAddTime) << "' AS TIMESTAMP), "
+ "last_cash_add_time = CAST('" << formatTime(stat.lastCashAddTime) << "' AS TIMESTAMP), "
"passive_time = " << stat.passiveTime << " "
"WHERE name = '" << elogin << "'";
"address = '" << eaddress << "', "
"always_online = " << (conf.alwaysOnline ? "'t'" : "'f'") << ", "
"credit = " << conf.credit << ", "
- "credit_expire = CAST('" << Int2TS(conf.creditExpire) << "' AS TIMESTAMP), "
+ "credit_expire = CAST('" << formatTime(conf.creditExpire) << "' AS TIMESTAMP), "
"disabled = " << (conf.disabled ? "'t'" : "'f'") << ", "
"disabled_detail_stat = " << (conf.disabledDetailStat ? "'t'" : "'f'") << ", "
"email = '" << eemail << "', "
std::stringstream tuple;
tuple << PQgetvalue(result, 0, 0) << " ";
tuple << PQgetvalue(result, 0, 1) << " ";
- stat->lastActivityTime = TS2Int(PQgetvalue(result, 0, 2));
+ stat->lastActivityTime = readTime(PQgetvalue(result, 0, 2));
tuple << PQgetvalue(result, 0, 3) << " ";
- stat->lastCashAddTime = TS2Int(PQgetvalue(result, 0, 4));
+ stat->lastCashAddTime = readTime(PQgetvalue(result, 0, 4));
tuple << PQgetvalue(result, 0, 5) << " ";
PQclear(result);
query << "SELECT dir_num, upload, download "
"FROM tb_stats_traffic "
"WHERE fk_user IN (SELECT pk_user FROM tb_users WHERE name = '" << elogin << "') AND "
- "DATE_TRUNC('month', stats_date) = DATE_TRUNC('month', CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP))";
+ "DATE_TRUNC('month', stats_date) = DATE_TRUNC('month', CAST('" << formatTime(stgTime) << "' AS TIMESTAMP))";
result = PQexec(connection, query.str().c_str());
}
conf->address = PQgetvalue(result, 0, 1); // address
conf->alwaysOnline = !strncmp(PQgetvalue(result, 0, 2), "t", 1);
tuple << PQgetvalue(result, 0, 3) << " "; // credit
- conf->creditExpire = TS2Int(PQgetvalue(result, 0, 4)); // creditExpire
+ conf->creditExpire = readTime(PQgetvalue(result, 0, 4)); // creditExpire
conf->disabled = !strncmp(PQgetvalue(result, 0, 5), "t", 1);
conf->disabledDetailStat = !strncmp(PQgetvalue(result, 0, 6), "t", 1);
conf->email = PQgetvalue(result, 0, 7); // email
"'" << eadminLogin << "', CAST('"
<< inet_ntostring(admIP) << "/32' AS INET), "
"'" << eparam << "', "
- "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(stgTime) << "' AS TIMESTAMP), "
"'" << eold << "', "
"'" << enew << "', "
"'" << emessage << "')";
{
query << "SELECT sp_add_session_log_entry("
"'" << elogin << "', "
- "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(stgTime) << "' AS TIMESTAMP), "
"'c', CAST('"
<< inet_ntostring(ip) << "/32' AS INET), 0)";
}
{
query << "SELECT sp_add_session_log_entry("
"'" << elogin << "', "
- "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(stgTime) << "' AS TIMESTAMP), "
"'c', CAST('"
<< inet_ntostring(ip) << "/32' AS INET), 0, 0, '')";
}
// Old database version - no freeMb logging support
query << "SELECT sp_add_session_log_entry("
"'" << elogin << "', "
- "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(stgTime) << "' AS TIMESTAMP), "
"'d', CAST('0.0.0.0/0' AS INET), "
<< cash << ")";
}
{
query << "SELECT sp_add_session_log_entry("
"'" << elogin << "', "
- "CAST('" << Int2TS(stgTime) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(stgTime) << "' AS TIMESTAMP), "
"'d', CAST('0.0.0.0/0' AS INET), "
<< cash << ", " << freeMb << ", '" << ereason << "')";
}
"(till_time, from_time, fk_user, "
"dir_num, ip, download, upload, cost) "
"VALUES ("
- "CAST('" << Int2TS(currTime) << "' AS TIMESTAMP), "
- "CAST('" << Int2TS(lastStat) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(currTime) << "' AS TIMESTAMP), "
+ "CAST('" << formatTime(lastStat) << "' AS TIMESTAMP), "
"(SELECT pk_user FROM tb_users WHERE name = '" << elogin << "'), "
<< it->first.dir << ", "
<< "CAST('" << inet_ntostring(it->first.ip) << "' AS INET), "
return 0;
}
-std::string POSTGRESQL_STORE::Int2TS(time_t ts) const
-{
-struct tm brokenTime;
-
-brokenTime.tm_wday = 0;
-brokenTime.tm_yday = 0;
-brokenTime.tm_isdst = 0;
-
-gmtime_r(&ts, &brokenTime);
-
-char buf[32];
-strftime(buf, 32, "%Y-%m-%d %H:%M:%S", &brokenTime);
-
-return buf;
-}
-
-time_t POSTGRESQL_STORE::TS2Int(const std::string & ts) const
-{
-struct tm brokenTime;
-
-brokenTime.tm_wday = 0;
-brokenTime.tm_yday = 0;
-brokenTime.tm_isdst = 0;
-
-stg_strptime(ts.c_str(), "%Y-%m-%d %H:%M:%S", &brokenTime);
-
-return stg_timegm(&brokenTime);
-}
-
void POSTGRESQL_STORE::MakeDate(std::string & date, int year, int month) const
{
struct tm brokenTime;
return tariffData.dirPrice[dir].priceDayA;
}
//-----------------------------------------------------------------------------
+std::string TARIFF_IMPL::TariffChangeIsAllowed(const TARIFF & to, time_t currentTime) const
+{
+time_t timeout = GetChangePolicyTimeout();
+if ((currentTime > timeout) && (timeout != 0))
+ return "";
+switch (GetChangePolicy())
+ {
+ case TARIFF::ALLOW:
+ return "";
+ case TARIFF::TO_CHEAP:
+ if (to.GetFee() < GetFee())
+ return "";
+ else
+ return "New tariff '" + to.GetName() + "' is more expensive than current tariff '" + GetName() + "'. The policy is '" + TARIFF::ChangePolicyToString(GetChangePolicy()) + "'.";
+ case TARIFF::TO_EXPENSIVE:
+ if (to.GetFee() >= GetFee())
+ return "";
+ else
+ return "New tariff '" + to.GetName() + "' is more cheap than current tariff '" + GetName() + "'. The policy is '" + TARIFF::ChangePolicyToString(GetChangePolicy()) + "'.";
+ case TARIFF::DENY:
+ return "Current tariff '" + GetName() + "', new tariff '" + to.GetName() + "'. The policy is '" + TARIFF::ChangePolicyToString(GetChangePolicy()) + "'.";
+ }
+}
+//-----------------------------------------------------------------------------
double GetFee() const { return tariffData.tariffConf.fee; }
double GetFree() const { return tariffData.tariffConf.free; }
PERIOD GetPeriod() const { return tariffData.tariffConf.period; }
+ CHANGE_POLICY GetChangePolicy() const { return tariffData.tariffConf.changePolicy; }
+ time_t GetChangePolicyTimeout() const { return tariffData.tariffConf.changePolicyTimeout; }
void Print() const;
TARIFF_IMPL & operator=(const TARIFF_IMPL & t);
bool operator==(const TARIFF_IMPL & rhs) const { return GetName() == rhs.GetName(); }
bool operator!=(const TARIFF_IMPL & rhs) const { return GetName() != rhs.GetName(); }
+ std::string TariffChangeIsAllowed(const TARIFF & to, time_t currentTime) const;
private:
TARIFF_DATA tariffData;
{
const TARIFF * nt = tariffs->FindByName(nextTariff);
if (nt == NULL)
+ {
WriteServLog("Cannot change tariff for user %s. Tariff %s not exist.",
login.c_str(), property.tariffName.Get().c_str());
+ }
else
- property.tariffName.Set(nextTariff, sysAdmin, login, store);
+ {
+ std::string message = tariff->TariffChangeIsAllowed(*nt, stgTime);
+ if (message.empty())
+ {
+ property.tariffName.Set(nextTariff, sysAdmin, login, store);
+ }
+ else
+ {
+ WriteServLog("Tariff change is prohibited for user %s. %s",
+ login.c_str(),
+ message.c_str());
+ }
+ }
ResetNextTariff();
WriteConf();
}
value = temp;
}
//---------------------------------------------------------------------------
+std::string formatTime(time_t ts)
+{
+char buf[32];
+struct tm brokenTime;
+
+brokenTime.tm_wday = 0;
+brokenTime.tm_yday = 0;
+brokenTime.tm_isdst = 0;
+
+gmtime_r(&ts, &brokenTime);
+
+strftime(buf, 32, "%Y-%m-%d %H:%M:%S", &brokenTime);
+
+return buf;
+}
+//---------------------------------------------------------------------------
+time_t readTime(const std::string & ts)
+{
+if (ts == "0000-00-00 00:00:00")
+ return 0;
+
+struct tm brokenTime;
+
+brokenTime.tm_wday = 0;
+brokenTime.tm_yday = 0;
+brokenTime.tm_isdst = 0;
+
+stg_strptime(ts.c_str(), "%Y-%m-%d %H:%M:%S", &brokenTime);
+
+return stg_timegm(&brokenTime);
+}
+//---------------------------------------------------------------------------
int str2x(const std::string & str, int32_t & x)
{
x = static_cast<int32_t>(strtol(str.c_str(), NULL, 10));
std::string ToPrintable(const std::string & src);
+std::string formatTime(time_t value);
+time_t readTime(const std::string & value);
//-----------------------------------------------------------------------------
int str2x(const std::string & str, int32_t & x);
int str2x(const std::string & str, uint32_t & x);
#include <cerrno> // E*
#include <cstring>
+#include <sstream>
#include <cstdlib>
#include <cstdio>
changed = true;
}
//---------------------------------------------------------------------------
+void CONFIGFILE::WriteTime(const std::string & param, time_t val)
+{
+std::stringstream ss;
+ss<<val;
+param_val[param] = ss.str();
+changed = true;
+}
+//---------------------------------------------------------------------------
int CONFIGFILE::ReadDouble(const std::string & param, double * val, double defaultVal) const
{
const std::map<std::string, std::string, StringCaseCmp_t>::const_iterator it(param_val.find(param));
void WriteString(const std::string & param, const std::string& val);
void WriteInt(const std::string & param, int64_t val);
void WriteDouble(const std::string & param, double val);
+ void WriteTime(const std::string & param, time_t val);
int Error() const;
int Flush() const;
case TARIFF::MONTH: stream << "<period value=\"month\"/>"; break;
}
+if (!data.tariffConf.changePolicy.empty())
+ switch (data.tariffConf.changePolicy.data())
+ {
+ case TARIFF::ALLOW: stream << "<changePolicy value=\"allow\"/>"; break;
+ case TARIFF::TO_CHEAP: stream << "<changePolicy value=\"to_cheap\"/>"; break;
+ case TARIFF::TO_EXPENSIVE: stream << "<changePolicy value=\"to_expensive\"/>"; break;
+ case TARIFF::DENY: stream << "<changePolicy value=\"deny\"/>"; break;
+ }
+
+appendResetableTag(stream, "changePolicyTimeout", data.tariffConf.changePolicyTimeout);
for (size_t i = 0; i < DIR_NUM; ++i)
if (!data.dirPrice[i].hDay.empty() &&
!data.dirPrice[i].mDay.empty() &&
return true;
}
+template <typename T>
+bool GetChangePolicy(const char ** attr, T & value, const std::string & attrName)
+{
+if (!CheckValue(attr, attrName))
+ return false;
+std::string type(attr[1]);
+if (type == "allow")
+ value = TARIFF::ALLOW;
+else if (type == "to_cheap")
+ value = TARIFF::TO_CHEAP;
+else if (type == "to_expensive")
+ value = TARIFF::TO_EXPENSIVE;
+else if (type == "deny")
+ value = TARIFF::DENY;
+else
+ return false;
+return true;
+}
+
+template <typename T>
+bool GetChangePolicyTimeout(const char ** attr, T & value, const std::string & attrName)
+{
+if (!CheckValue(attr, attrName))
+ return false;
+value = readTime(attr[1]);
+return true;
+}
+
template <typename A, typename T>
bool GetSlashedValue(const char ** attr, A & array, T A::value_type:: * field)
{
AddParser(propertyParsers, "free", info.tariffConf.free);
AddParser(propertyParsers, "traffType", info.tariffConf.traffType, GetTraffType);
AddParser(propertyParsers, "period", info.tariffConf.period, GetPeriod);
+ AddParser(propertyParsers, "changePolicy", info.tariffConf.changePolicy, GetChangePolicy);
+ AddParser(propertyParsers, "changePolicyTimeout", info.tariffConf.changePolicyTimeout, GetChangePolicyTimeout);
for (size_t i = 0; i < DIR_NUM; ++i)
AddParser(propertyParsers, "time" + unsigned2str(i), info.dirPrice[i], GetTimeSpan);
AddAOSParser(propertyParsers, "priceDayA", info.dirPrice, &DIRPRICE_DATA::priceDayA, GetSlashedValue);
ensure_equals("1101 == 0", tariff.GetPriceWithTraffType(0, 6 * 1024 * 1024, 0, 1286461245), 0); // Near 17:30, 6 > 4 DA (ignore night)
ensure_equals("1110 == 0", tariff.GetPriceWithTraffType(0, 0 * 1024 * 1024, 0, 1286479245), 0); // Near 22:30, 0 < 4 DA (ignore night)
ensure_equals("1111 == 0", tariff.GetPriceWithTraffType(0, 6 * 1024 * 1024, 0, 1286479245), 0); // Near 22:30, 6 > 4 DA (ignore night)
+ }
+
+ template<>
+ template<>
+ void testobject::test<7>()
+ {
+ set_test_name("Check changePolicy - ALLOW");
+
+ TARIFF_DATA td("test");
+ td.tariffConf.changePolicy = TARIFF::ALLOW;
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL tariff(td);
+
+ td.tariffConf.fee = 50;
+ TARIFF_IMPL cheaper(td);
+
+ ensure_equals("Allow cheaper", tariff.TariffChangeIsAllowed(cheaper).empty(), true);
+
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL equal(td);
+
+ ensure_equals("Allow equal", tariff.TariffChangeIsAllowed(equal).empty(), true);
+
+ td.tariffConf.fee = 150;
+ TARIFF_IMPL expensive(td);
+
+ ensure_equals("Allow expensive", tariff.TariffChangeIsAllowed(expensive).empty(), true);
+ }
+
+ template<>
+ template<>
+ void testobject::test<8>()
+ {
+ set_test_name("Check changePolicy - TO_CHEAP");
+
+ TARIFF_DATA td("test");
+ td.tariffConf.changePolicy = TARIFF::TO_CHEAP;
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL tariff(td);
+
+ td.tariffConf.fee = 50;
+ TARIFF_IMPL cheaper(td);
+
+ ensure_equals("Allow cheaper", tariff.TariffChangeIsAllowed(cheaper).empty(), true);
+
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL equal(td);
+
+ ensure_equals("Prohibit equal", tariff.TariffChangeIsAllowed(equal).empty(), false);
+
+ td.tariffConf.fee = 150;
+ TARIFF_IMPL expensive(td);
+
+ ensure_equals("Prohibit expensive", tariff.TariffChangeIsAllowed(expensive).empty(), false);
+ }
+
+ template<>
+ template<>
+ void testobject::test<9>()
+ {
+ set_test_name("Check changePolicy - TO_EXPENSIVE");
+
+ TARIFF_DATA td("test");
+ td.tariffConf.changePolicy = TARIFF::TO_EXPENSIVE;
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL tariff(td);
+
+ td.tariffConf.fee = 50;
+ TARIFF_IMPL cheaper(td);
+
+ ensure_equals("Prohibit cheaper", tariff.TariffChangeIsAllowed(cheaper).empty(), false);
+
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL equal(td);
+
+ ensure_equals("Allow equal", tariff.TariffChangeIsAllowed(equal).empty(), true);
+
+ td.tariffConf.fee = 150;
+ TARIFF_IMPL expensive(td);
+
+ ensure_equals("Allow expensive", tariff.TariffChangeIsAllowed(expensive).empty(), true);
+ }
+
+ template<>
+ template<>
+ void testobject::test<10>()
+ {
+ set_test_name("Check changePolicy - DENY");
+
+ TARIFF_DATA td("test");
+ td.tariffConf.changePolicy = TARIFF::DENY;
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL tariff(td);
+
+ td.tariffConf.fee = 50;
+ TARIFF_IMPL cheaper(td);
+
+ ensure_equals("Prohibit cheaper", tariff.TariffChangeIsAllowed(cheaper).empty(), false);
+
+ td.tariffConf.fee = 100;
+ TARIFF_IMPL equal(td);
+
+ ensure_equals("Prohibit equal", tariff.TariffChangeIsAllowed(equal).empty(), false);
+
+ td.tariffConf.fee = 150;
+ TARIFF_IMPL expensive(td);
+
+ ensure_equals("Prohibit expensive", tariff.TariffChangeIsAllowed(expensive).empty(), false);
}
}