You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kppp/ruleset.cpp

582 lines
13 KiB

/* -*- C++ -*-
* kPPP: A pppd front end for the KDE project
*
* $Id$
*
* Copyright (C) 1997 Bernd Johannes Wuebben
* wuebben@math.cornell.edu
*
* This file was contributed by Mario Weilguni <mweilguni@sime.com>
* Thanks Mario !
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "ruleset.h"
#include <qregexp.h>
#include <qfile.h>
#include <klocale.h>
#include <kglobal.h>
#include <kdebug.h>
RuleSet::RuleSet() {
default_costs = -1;
default_len = -1;
_currency_symbol = "$";
_currency_digits = 2;
_minimum_costs = 0;
flat_init_costs = 0.0;
flat_init_duration = 0;
have_flat_init_costs = false;
pcf = 0.0;
}
// this function is shamelessly stolen from pppcosts 0.5 :-)
/* calculates the easter sunday in day_of_year style */
QDate RuleSet::get_easter(int year) {
/* not optimized, I took the original names */
signed int a,b,m,q,w,p,n,tt,mm;
/* calculating easter is really funny */
/* this is O'Beirne's algorithm, only valid 1900-2099 */
n = year - 1900;
a = n % 19;
b = (int)((7*a+1)/19);
m = (11*a+4-b) % 29;
q = (int)(n/4);
w = (n+q+31-m) % 7;
p = 25-m-w;
if (p>0)
{tt=p;
mm=4;}
else
{tt=p+31;
mm=3;}
return QDate(year, mm, tt);
}
int RuleSet::dayNameToInt(const char *s) {
const char *const name[] = {"monday", "tuesday", "wednesday",
"thursday", "friday", "saturday",
"sunday", NULL};
for(int i = 0; name[i] != NULL; i++)
if(qstricmp(name[i], s) == 0)
return i;
return -1;
}
int RuleSet::load(const QString &filename) {
flat_init_costs = 0.0;
flat_init_duration = 0;
have_flat_init_costs = false;
QFile f(filename);
// delete old rules
rules.resize(0);
_name = "";
// ignore "No Accounting"
if(filename.isEmpty())
return 0;
if(!f.exists())
return -1;
if(!f.open(IO_ReadOnly))
return -1;
char buffer[2048]; // safe
int lineno=0;
while(!f.atEnd()) {
// read continued lines
QString line;
bool backslashed;
do {
int br = f.readLine(buffer, sizeof(buffer));
if((br > 0) && (buffer[br-1] == '\n'))
buffer[br-1] = 0;
else
buffer[br] = 0;
lineno++;
line.append(QString::fromUtf8(buffer));
backslashed = (line.right(1) == "\\");
} while(!f.atEnd() && backslashed);
// strip whitespace
line = line.replace(QRegExp("[ \t\r]"), "");
// skip comment lines
if((line.left(1) == "#") || line.isEmpty())
continue;
// parse line
if(!parseLine(line)) {
f.close();
kdError(5002) << "ERROR IN LINE " << lineno << endl;
return lineno;
}
}
f.close();
if(_name.length() > 0)
return 0;
else {
kdError(5002) << "NO NAME DEFINED" << endl;
return -1;
}
}
void RuleSet::addRule(RULE r) {
// check for a default rule
if((r.type == 2) &&
(r.weekday.from == 0) && (r.weekday.until == 6) &&
(r.from == midnight()) &&
(r.until == beforeMidnight()))
{
default_costs = r.costs;
default_len = r.len;
return;
}
// if from < until (i.e on (monday..friday)
// from (21:00..05:00) use (0.2,16)
// split it into two rules
// ... between (21:00..23:59) use ...
// ... between (00:00..05:00) use ...
if(r.from > r.until) {
RULE r1, r2;
r1 = r;
r2 = r;
r1.until = beforeMidnight();
r2.from = midnight();
rules.resize(rules.size()+2);
rules[rules.size()-2] = r1;
rules[rules.size()-1] = r2;
} else {
rules.resize(rules.size()+1);
rules[rules.size()-1] = r;
}
}
bool RuleSet::parseEntry(RULE &ret, QString s, int year) {
if(s.contains(QRegExp("^[0-9]+/[0-9]+$"))) {
int d, m;
sscanf(s.ascii(), "%d/%d", &m, &d);
ret.type = 1;
ret.date.from = QDate(year, m, d);
ret.date.until = QDate(year, m, d);
return TRUE;
}
if(s.contains(QRegExp("^[0-9]+\\.[0-9]+$"))) {
int d, m;
sscanf(s.ascii(), "%d.%d", &d, &m);
ret.type = 1;
ret.date.from = QDate(year, m, d);
ret.date.until = QDate(year, m, d);
return TRUE;
}
if(s.right(3) == "day") {
int d = dayNameToInt(s.ascii());
if(d != -1) {
ret.type = 2;
ret.weekday.from = d;
ret.weekday.until = d;
return TRUE;
}
}
if(s.left(6) == "easter") {
QDate d = get_easter(year);
int off;
bool ok = TRUE;
QString val = s.mid(6, 1000);
if(val.isEmpty())
off = 0;
else
off = val.toInt(&ok);
if(ok) {
d = d.addDays(off);
ret.type = 1;
ret.date.from = d;
ret.date.until = d;
return TRUE;
}
}
ret.type = 0;
return FALSE;
}
bool RuleSet::parseEntries(QString s, int year,
QTime t1, QTime t2,
double costs, double len, double after)
{
// special rule: on() is the same as on(monday..sunday)
if(s.isEmpty())
s = "monday..sunday";
while(s.length()) {
int pos = s.find(',');
QString token;
if(pos == -1) {
token = s;
s = "";
} else {
token = s.left(pos);
s = s.right(s.length()-pos-1);
}
// we've a token, now check if it defines a
// range
RULE r;
if(token.contains("..")) {
QString left, right;
left = token.left(token.find(".."));
right = token.right(token.length()-2-left.length());
RULE lr, rr;
if(parseEntry(lr, left, year) && parseEntry(rr, right, year)) {
if(lr.type == rr.type) {
r.type = lr.type;
switch(lr.type) {
case 1:
r.date.from = lr.date.from;
r.date.until = rr.date.from;
break;
case 2:
r.weekday.from = lr.weekday.from;
r.weekday.until = rr.weekday.from;
}
} else
return FALSE;
}
} else
if(!parseEntry(r, token, year))
return FALSE;
r.costs = costs;
r.len = len;
r.after = after;
r.from = t1;
r.until = t2;
addRule(r);
}
return TRUE;
}
bool RuleSet::parseTime(QTime &t1, QTime &t2, QString s) {
if(s.isEmpty()) {
t1 = midnight();
t2 = beforeMidnight();
return TRUE;
} else {
int t1m, t1h, t2m, t2h;
if(sscanf(s.ascii(), "%d:%d..%d:%d", &t1h, &t1m, &t2h, &t2m) == 4) {
t1.setHMS(t1h, t1m, 0);
t2.setHMS(t2h, t2m, 0);
return TRUE;
} else
return FALSE;
}
}
bool RuleSet::parseRate(double &costs, double &len, double &after, QString s) {
after = 0;
int fields = sscanf(s.ascii(), "%lf,%lf,%lf", &costs, &len, &after);
return (fields == 2) || (fields == 3);
}
bool RuleSet::parseLine(const QString &s) {
// ### use QRegExp::cap() instead of mid() and find()
// for our french friends -- Bernd
if(s.contains(QRegExp("flat_init_costs=\\(.*"))) {
// parse the time fields
QString token = s.mid(s.find("flat_init_costs=(") + 17,
s.find(")")-s.find("flat_init_costs=(") - 17);
// printf("TOKEN=%s\n",token.ascii());
double after;
if(!parseRate(flat_init_costs, flat_init_duration, after, token))
return FALSE;
//printf("COST %f DURATION %f\n",flat_init_costs,flat_init_duration);
if(! (flat_init_costs >= 0.0) )
return FALSE;
if(! (flat_init_duration >= 0.0))
return FALSE;
have_flat_init_costs = true;
return TRUE;
}
if(s.contains(QRegExp("on\\(.*\\)between\\(.*\\)use\\(.*\\)"))) {
// parse the time fields
QString token = s.mid(s.find("between(") + 8,
s.find(")use")-s.find("between(") - 8);
QTime t1, t2;
if(!parseTime(t1, t2, token))
return FALSE;
// parse the rate fields
token = s.mid(s.find("use(") + 4,
s.findRev(")")-s.find("use(") - 4);
double costs;
double len;
double after;
if(!parseRate(costs, len, after, token))
return FALSE;
// parse the days
token = s.mid(s.find("on(") + 3,
s.find(")betw")-s.find("on(") - 3);
if(!parseEntries(token, QDate::currentDate().year(),
t1, t2, costs, len, after))
return FALSE;
return TRUE;
}
// check for the name
if(s.contains(QRegExp("name=.*"))) {
_name = s.right(s.length()-5);
return !_name.isEmpty();
}
// check default entry
if(s.contains(QRegExp("default=\\(.*\\)"))) {
QString token = s.mid(9, s.length() - 10);
double after;
if(parseRate(default_costs, default_len, after, token))
return TRUE;
}
// check for "minimum costs"
if(s.contains(QRegExp("minimum_costs=.*"))) {
QString token = s.right(s.length() - strlen("minimum_costs="));
bool ok;
_minimum_costs = token.toDouble(&ok);
return ok;
}
// check currency settings
if(s.startsWith("currency_symbol=")) {
_currency_symbol = s.mid(16);
return TRUE;
}
if(s.contains(QRegExp("currency_digits=.*"))) {
QString token = s.mid(16, s.length() - 16);
bool ok;
_currency_digits = token.toInt(&ok);
return ok && (_currency_digits >= 0);
}
// "currency_position" is deprecated so we'll simply ignore it
if(s.contains(QRegExp("currency_position=.*")))
return TRUE;
// check per connection fee
if(s.contains(QRegExp("per_connection="))) {
QString token = s.mid(15, s.length()-15);
bool ok;
pcf = token.toDouble(&ok);
return ok;
}
return FALSE;
}
void RuleSet::setStartTime(QDateTime dt){
starttime = dt;
}
void RuleSet::getActiveRule(QDateTime dt, double connect_time, double &costs, double &len) {
// use default costs first
costs = default_costs;
len = default_len;
//printf("In getActiveRule\n");
if(have_flat_init_costs){
costs = flat_init_costs;
len = flat_init_duration;
have_flat_init_costs = false;
//printf("getActiveRule FLATINITCOSTS\n");
return;
}
// check every rule
for(int i = 0; i < (int)rules.size(); i++) {
RULE r = rules[i];
switch(r.type) {
case 1: // a date
{
// since rules do not have a year's entry, use the one
// from dt
QDate from = r.date.from;
QDate until = r.date.until;
from.setYMD(dt.date().year(), from.month(), from.day());
until.setYMD(dt.date().year(), until.month(), until.day());
if((from <= dt.date()) && (dt.date() <= until)) {
// check time
if((r.from <= dt.time()) && (dt.time() <= r.until) && (connect_time >= r.after)) {
costs = r.costs;
len = r.len;
}
}
}
break;
case 2: // one or more weekdays
// check if the range overlaps sunday.
// (i.e. "on(saturday..monday)")
if(r.weekday.from <= r.weekday.until) {
if((r.weekday.from <= dt.date().dayOfWeek() - 1) &&
(r.weekday.until >= dt.date().dayOfWeek() - 1))
{
// check time
if((r.from <= dt.time()) && (dt.time() <= r.until) && (connect_time >= r.after)) {
costs = r.costs;
len = r.len;
}
}
} else { // yes, they overlap sunday
if((r.weekday.from >= dt.date().dayOfWeek() - 1) &&
(dt.date().dayOfWeek() - 1 <= r.weekday.until))
{
// check time
if((r.from <= dt.time()) && (dt.time() <= r.until) && (connect_time >= r.after)) {
costs = r.costs;
len = r.len;
}
}
}
}
}
}
#if 0
static double round(double d, int digits) {
d *= pow(10, digits);
d = rint(d);
d /= pow(10, digits);
return d;
}
#endif
QString RuleSet::currencySymbol() const {
return _currency_symbol.copy();
}
QString RuleSet::currencyString(double f) const {
return KGlobal::locale()->formatMoney(f, _currency_symbol, _currency_digits);
}
double RuleSet::perConnectionCosts() const {
return pcf;
}
QString RuleSet::name() const {
return _name;
}
double RuleSet::minimumCosts() const {
return _minimum_costs;
}
QTime RuleSet::midnight() const {
return QTime(0, 0, 0, 0);
}
QTime RuleSet::beforeMidnight() const {
return QTime(23,59,59,999);
}
int RuleSet::checkRuleFile(const QString &rulefile) {
if(rulefile == NULL) {
fputs(i18n("kppp: no rulefile specified\n").local8Bit(), stderr);
return 1;
}
QFile fl(rulefile);
if(!fl.exists()) {
fprintf(stderr, i18n("kppp: rulefile \"%s\" not found\n").local8Bit(), rulefile.local8Bit().data());
return 1;
}
if(rulefile.right(4) != ".rst") {
fputs(i18n("kppp: rulefiles must have the extension \".rst\"\n").local8Bit(), stderr);
return 1;
}
RuleSet r;
int err = r.load(rulefile);
fl.close();
if(err == -1) {
fputs(i18n("kppp: error parsing the ruleset\n").local8Bit(), stderr);
return 1;
}
if(err > 0) {
fprintf(stderr, i18n("kppp: parse error in line %d\n").local8Bit(), err);
return 1;
}
// check for the existance of a default rule
if((r.default_costs < 0) || (r.default_len < 0)) {
fputs(i18n("kppp: rulefile does not contain a default rule\n").local8Bit(), stderr);
return 1;
}
if(r.name().length() == 0) {
fputs(i18n("kppp: rulefile does not contain a \"name=...\" line\n").local8Bit(), stderr);
return 1;
}
fputs(i18n("kppp: rulefile is ok\n").local8Bit(), stderr);
return 0;
}