1/* -*- C++ -*-
2 * kPPP: A pppd front end for the KDE project
3 *
4 * $Id$
5 *
6 * Copyright (C) 1997 Bernd Johannes Wuebben
7 * wuebben@math.cornell.edu
8 *
9 * This file was contributed by Mario Weilguni <mweilguni@sime.com>
10 * Thanks Mario !
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with this program; if not, write to the Free
24 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26
27#include <stdlib.h>
28#include <stdio.h>
29#include <math.h>
30#include "ruleset.h"
31
32#include <qregexp.h>
33#include <qfile.h>
34
35#include <klocale.h>
36#include <kglobal.h>
37#include <kdebug.h>
38
39RuleSet::RuleSet() {
40 default_costs = -1;
41 default_len = -1;
42 _currency_symbol = "$";
43 _currency_digits = 2;
44 _minimum_costs = 0;
45 flat_init_costs = 0.0;
46 flat_init_duration = 0;
47 have_flat_init_costs = false;
48
49 pcf = 0.0;
50}
51
52// this function is shamelessly stolen from pppcosts 0.5 :-)
53/* calculates the easter sunday in day_of_year style */
54QDate RuleSet::get_easter(int year) {
55 /* not optimized, I took the original names */
56 signed int a,b,m,q,w,p,n,tt,mm;
57
58 /* calculating easter is really funny */
59 /* this is O'Beirne's algorithm, only valid 1900-2099 */
60 n = year - 1900;
61 a = n % 19;
62 b = (int)((7*a+1)/19);
63 m = (11*a+4-b) % 29;
64 q = (int)(n/4);
65 w = (n+q+31-m) % 7;
66 p = 25-m-w;
67 if (p>0)
68 {tt=p;
69 mm=4;}
70 else
71 {tt=p+31;
72 mm=3;}
73
74 return QDate(year, mm, tt);
75}
76
77int RuleSet::dayNameToInt(const char *s) {
78 const char *const name[] = {"monday", "tuesday", "wednesday",
79 "thursday", "friday", "saturday",
80 "sunday", NULL};
81
82 for(int i = 0; name[i] != NULL; i++)
83 if(qstricmp(name[i], s) == 0)
84 return i;
85
86 return -1;
87}
88
89int RuleSet::load(const QString &filename) {
90
91 flat_init_costs = 0.0;
92 flat_init_duration = 0;
93 have_flat_init_costs = false;
94
95 QFile f(filename);
96
97 // delete old rules
98 rules.resize(0);
99 _name = "";
100
101 // ignore "No Accounting"
102 if(filename.isEmpty())
103 return 0;
104
105 if(!f.exists())
106 return -1;
107
108 if(!f.open(QIODevice::ReadOnly))
109 return -1;
110
111 char buffer[2048]; // safe
112 int lineno=0;
113
114 while(!f.atEnd()) {
115 // read continued lines
116 QString line;
117 bool backslashed;
118 do {
119 int br = f.readLine(buffer, sizeof(buffer));
120 if (br < 0) break;
121 if((br > 0) && (buffer[br-1] == '\n'))
122 buffer[br-1] = 0;
123 else
124 buffer[br] = 0;
125 lineno++;
126 line.append(QString::fromUtf8(buffer));
127 backslashed = (line.right(1) == "\\");
128 } while(!f.atEnd() && backslashed);
129
130 // strip whitespace
131 line = line.replace(QRegExp("[ \t\r]"), "");
132 // skip comment lines
133 if((line.left(1) == "#") || line.isEmpty())
134 continue;
135
136 // parse line
137 if(!parseLine(line)) {
138 f.close();
139 kError(5002) << "ERROR IN LINE " << lineno << endl;
140 return lineno;
141 }
142 }
143
144 f.close();
145
146 if(_name.length() > 0)
147 return 0;
148 else {
149 kError(5002) << "NO NAME DEFINED" << endl;
150 return -1;
151 }
152}
153
154void RuleSet::addRule(RULE r) {
155 // check for a default rule
156 if((r.type == 2) &&
157 (r.weekday.from == 0) && (r.weekday.until == 6) &&
158 (r.from == midnight()) &&
159 (r.until == beforeMidnight()))
160 {
161 default_costs = r.costs;
162 default_len = r.len;
163 return;
164 }
165
166 // if from < until (i.e on (monday..friday)
167 // from (21:00..05:00) use (0.2,16)
168 // split it into two rules
169 // ... between (21:00..23:59) use ...
170 // ... between (00:00..05:00) use ...
171 if(r.from > r.until) {
172 RULE r1, r2;
173 r1 = r;
174 r2 = r;
175 r1.until = beforeMidnight();
176 r2.from = midnight();
177 rules.resize(rules.size()+2);
178 rules[rules.size()-2] = r1;
179 rules[rules.size()-1] = r2;
180 } else {
181 rules.resize(rules.size()+1);
182 rules[rules.size()-1] = r;
183 }
184}
185
186bool RuleSet::parseEntry(RULE &ret, QString s, int year) {
187 if(s.contains(QRegExp("^[0-9]+/[0-9]+$"))) {
188 int d, m;
189 sscanf(s.toAscii(), "%d/%d", &m, &d);
190 ret.type = 1;
191 ret.date.from = QDate(year, m, d);
192 ret.date.until = QDate(year, m, d);
193 return true;
194 }
195
196 if(s.contains(QRegExp("^[0-9]+\\.[0-9]+$"))) {
197 int d, m;
198 sscanf(s.toAscii(), "%d.%d", &d, &m);
199 ret.type = 1;
200 ret.date.from = QDate(year, m, d);
201 ret.date.until = QDate(year, m, d);
202 return true;
203 }
204
205 if(s.right(3) == "day") {
206 int d = dayNameToInt(s.toAscii());
207 if(d != -1) {
208 ret.type = 2;
209 ret.weekday.from = d;
210 ret.weekday.until = d;
211 return true;
212 }
213 }
214
215 if(s.left(6) == "easter") {
216 QDate d = get_easter(year);
217 int off;
218 bool ok = true;
219 QString val = s.mid(6, 1000);
220 if(val.isEmpty())
221 off = 0;
222 else
223 off = val.toInt(&ok);
224
225 if(ok) {
226 d = d.addDays(off);
227 ret.type = 1;
228 ret.date.from = d;
229 ret.date.until = d;
230 return true;
231 }
232 }
233
234 ret.type = 0;
235 return false;
236}
237
238
239
240bool RuleSet::parseEntries(QString s, int year,
241 QTime t1, QTime t2,
242 double costs, double len, double after)
243{
244 // special rule: on() is the same as on(monday..sunday)
245 if(s.isEmpty())
246 s = "monday..sunday";
247
248 while(s.length()) {
249 int pos = s.indexOf(',');
250 QString token;
251 if(pos == -1) {
252 token = s;
253 s = "";
254 } else {
255 token = s.left(pos);
256 s = s.right(s.length()-pos-1);
257 }
258
259 // we've a token, now check if it defines a
260 // range
261 RULE r;
262 if(token.contains("..")) {
263 QString left, right;
264 left = token.left(token.indexOf(".."));
265 right = token.right(token.length()-2-left.length());
266 RULE lr, rr;
267 if(parseEntry(lr, left, year) && parseEntry(rr, right, year)) {
268 if(lr.type == rr.type) {
269 r.type = lr.type;
270 switch(lr.type) {
271 case 1:
272 r.date.from = lr.date.from;
273 r.date.until = rr.date.from;
274 break;
275 case 2:
276 r.weekday.from = lr.weekday.from;
277 r.weekday.until = rr.weekday.from;
278 }
279 } else
280 return false;
281 }
282 } else
283 if(!parseEntry(r, token, year))
284 return false;
285
286 r.costs = costs;
287 r.len = len;
288 r.after = after;
289 r.from = t1;
290 r.until = t2;
291 addRule(r);
292 }
293
294 return true;
295}
296
297bool RuleSet::parseTime(QTime &t1, QTime &t2, QString s) {
298 if(s.isEmpty()) {
299 t1 = midnight();
300 t2 = beforeMidnight();
301 return true;
302 } else {
303 int t1m, t1h, t2m, t2h;
304 if(sscanf(s.toAscii(), "%d:%d..%d:%d", &t1h, &t1m, &t2h, &t2m) == 4) {
305 t1.setHMS(t1h, t1m, 0);
306 t2.setHMS(t2h, t2m, 0);
307 return true;
308 } else
309 return false;
310 }
311}
312
313bool RuleSet::parseRate(double &costs, double &len, double &after, QString s) {
314 after = 0;
315 int fields = sscanf(s.toAscii(), "%lf,%lf,%lf", &costs, &len, &after);
316 return (fields == 2) || (fields == 3);
317}
318
319bool RuleSet::parseLine(const QString &s) {
320
321 // ### use QRegExp::cap() instead of mid() and find()
322
323 // for our french friends -- Bernd
324 if(s.contains(QRegExp("flat_init_costs=\\(.*"))) {
325 // parse the time fields
326 QString token = s.mid(s.indexOf("flat_init_costs=(") + 17,
327 s.indexOf(")")-s.indexOf("flat_init_costs=(") - 17);
328 // printf("TOKEN=%s\n",token.ascii());
329
330 double after;
331 if(!parseRate(flat_init_costs, flat_init_duration, after, token))
332 return false;
333
334 //printf("COST %f DURATION %f\n",flat_init_costs,flat_init_duration);
335
336 if(! (flat_init_costs >= 0.0) )
337 return false;
338 if(! (flat_init_duration >= 0.0))
339 return false;
340
341 have_flat_init_costs = true;
342 return true;
343 }
344
345
346 if(s.contains(QRegExp("on\\(.*\\)between\\(.*\\)use\\(.*\\)"))) {
347 // parse the time fields
348 QString token = s.mid(s.indexOf("between(") + 8,
349 s.indexOf(")use")-s.indexOf("between(") - 8);
350 QTime t1, t2;
351 if(!parseTime(t1, t2, token))
352 return false;
353
354 // parse the rate fields
355 token = s.mid(s.indexOf("use(") + 4,
356 s.lastIndexOf(")")-s.indexOf("use(") - 4);
357 double costs;
358 double len;
359 double after;
360 if(!parseRate(costs, len, after, token))
361 return false;
362
363 // parse the days
364 token = s.mid(s.indexOf("on(") + 3,
365 s.indexOf(")betw")-s.indexOf("on(") - 3);
366 if(!parseEntries(token, QDate::currentDate().year(),
367 t1, t2, costs, len, after))
368 return false;
369
370 return true;
371 }
372
373 // check for the name
374 if(s.contains(QRegExp("name=.*"))) {
375 _name = s.right(s.length()-5);
376 return !_name.isEmpty();
377 }
378
379
380 // check default entry
381 if(s.contains(QRegExp("default=\\(.*\\)"))) {
382 QString token = s.mid(9, s.length() - 10);
383 double after;
384 if(parseRate(default_costs, default_len, after, token))
385 return true;
386 }
387
388 // check for "minimum costs"
389 if(s.contains(QRegExp("minimum_costs=.*"))) {
390 QString token = s.right(s.length() - strlen("minimum_costs="));
391 bool ok;
392 _minimum_costs = token.toDouble(&ok);
393 return ok;
394 }
395
396 // check currency settings
397 if(s.startsWith("currency_symbol=")) {
398 _currency_symbol = s.mid(16);
399 return true;
400 }
401
402 if(s.contains(QRegExp("currency_digits=.*"))) {
403 QString token = s.mid(16, s.length() - 16);
404 bool ok;
405 _currency_digits = token.toInt(&ok);
406 return ok && (_currency_digits >= 0);
407 }
408
409 // "currency_position" is deprecated so we'll simply ignore it
410 if(s.contains(QRegExp("currency_position=.*")))
411 return true;
412
413 // check per connection fee
414 if(s.contains(QRegExp("per_connection="))) {
415 QString token = s.mid(15, s.length()-15);
416 bool ok;
417 pcf = token.toDouble(&ok);
418 return ok;
419 }
420
421 return false;
422}
423
424void RuleSet::setStartTime(const QDateTime &dt){
425
426 starttime = dt;
427
428}
429
430void RuleSet::getActiveRule(const QDateTime &dt, double connect_time, double &costs, double &len) {
431 // use default costs first
432 costs = default_costs;
433 len = default_len;
434
435 //printf("In getActiveRule\n");
436 if(have_flat_init_costs){
437
438 costs = flat_init_costs;
439 len = flat_init_duration;
440 have_flat_init_costs = false;
441 //printf("getActiveRule FLATINITCOSTS\n");
442 return;
443 }
444
445 // check every rule
446 for(int i = 0; i < (int)rules.size(); i++) {
447 RULE r = rules[i];
448
449 switch(r.type) {
450 case 1: // a date
451 {
452 // since rules do not have a year's entry, use the one
453 // from dt
454 QDate from = r.date.from;
455 QDate until = r.date.until;
456 from.setYMD(dt.date().year(), from.month(), from.day());
457 until.setYMD(dt.date().year(), until.month(), until.day());
458 if((from <= dt.date()) && (dt.date() <= until)) {
459 // check time
460 if((r.from <= dt.time()) && (dt.time() <= r.until) && (connect_time >= r.after)) {
461 costs = r.costs;
462 len = r.len;
463 }
464 }
465 }
466 break;
467
468 case 2: // one or more weekdays
469 // check if the range overlaps sunday.
470 // (i.e. "on(saturday..monday)")
471 if(r.weekday.from <= r.weekday.until) {
472 if((r.weekday.from <= dt.date().dayOfWeek() - 1) &&
473 (r.weekday.until >= dt.date().dayOfWeek() - 1))
474 {
475 // check time
476 if((r.from <= dt.time()) && (dt.time() <= r.until) && (connect_time >= r.after)) {
477 costs = r.costs;
478 len = r.len;
479 }
480 }
481 } else { // yes, they overlap sunday
482 if((r.weekday.from >= dt.date().dayOfWeek() - 1) &&
483 (dt.date().dayOfWeek() - 1 <= r.weekday.until))
484 {
485 // check time
486 if((r.from <= dt.time()) && (dt.time() <= r.until) && (connect_time >= r.after)) {
487 costs = r.costs;
488 len = r.len;
489 }
490 }
491 }
492 }
493 }
494}
495
496
497#if 0
498static double round(double d, int digits) {
499 d *= pow(10, digits);
500 d = rint(d);
501 d /= pow(10, digits);
502 return d;
503}
504#endif
505
506QString RuleSet::currencySymbol() const {
507 return _currency_symbol;
508}
509
510QString RuleSet::currencyString(double f) const {
511 return KGlobal::locale()->formatMoney(f, _currency_symbol, _currency_digits);
512}
513
514
515double RuleSet::perConnectionCosts() const {
516 return pcf;
517}
518
519
520QString RuleSet::name() const {
521 return _name;
522}
523
524
525double RuleSet::minimumCosts() const {
526 return _minimum_costs;
527}
528
529QTime RuleSet::midnight() const {
530 return QTime(0, 0, 0, 0);
531}
532
533QTime RuleSet::beforeMidnight() const {
534 return QTime(23,59,59,999);
535}
536
537int RuleSet::checkRuleFile(const QString &rulefile) {
538 if(rulefile.isEmpty()) {
539 fputs(i18n("kppp: no rulefile specified\n").toLocal8Bit(), stderr);
540 return 1;
541 }
542
543 QFile fl(rulefile);
544 if(!fl.exists()) {
545 fprintf(stderr, i18n("kppp: rulefile \"%s\" not found\n").toLocal8Bit(), rulefile.toLocal8Bit().data());
546 return 1;
547 }
548
549 if(rulefile.right(4) != ".rst") {
550 fputs(i18n("kppp: rulefiles must have the extension \".rst\"\n").toLocal8Bit(), stderr);
551 return 1;
552 }
553
554 RuleSet r;
555 int err = r.load(rulefile);
556 fl.close();
557
558 if(err == -1) {
559 fputs(i18n("kppp: error parsing the ruleset\n").toLocal8Bit(), stderr);
560 return 1;
561 }
562
563 if(err > 0) {
564 fprintf(stderr, i18n("kppp: parse error in line %d\n").toLocal8Bit(), err);
565 return 1;
566 }
567
568 // check for the existence of a default rule
569 if((r.default_costs < 0) || (r.default_len < 0)) {
570 fputs(i18n("kppp: rulefile does not contain a default rule\n").toLocal8Bit(), stderr);
571 return 1;
572 }
573
574 if(r.name().length() == 0) {
575 fputs(i18n("kppp: rulefile does not contain a \"name=...\" line\n").toLocal8Bit(), stderr);
576 return 1;
577 }
578
579 fputs(i18n("kppp: rulefile is ok\n").toLocal8Bit(), stderr);
580 return 0;
581}
582
583