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 | |
39 | RuleSet::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 */ |
54 | QDate 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 | |
77 | int 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 | |
89 | int 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 | |
154 | void 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 | |
186 | bool 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 | |
240 | bool 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 | |
297 | bool 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 | |
313 | bool 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 | |
319 | bool 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 | |
424 | void RuleSet::setStartTime(const QDateTime &dt){ |
425 | |
426 | starttime = dt; |
427 | |
428 | } |
429 | |
430 | void 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 |
498 | static 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 | |
506 | QString RuleSet::currencySymbol() const { |
507 | return _currency_symbol; |
508 | } |
509 | |
510 | QString RuleSet::currencyString(double f) const { |
511 | return KGlobal::locale()->formatMoney(f, _currency_symbol, _currency_digits); |
512 | } |
513 | |
514 | |
515 | double RuleSet::perConnectionCosts() const { |
516 | return pcf; |
517 | } |
518 | |
519 | |
520 | QString RuleSet::name() const { |
521 | return _name; |
522 | } |
523 | |
524 | |
525 | double RuleSet::minimumCosts() const { |
526 | return _minimum_costs; |
527 | } |
528 | |
529 | QTime RuleSet::midnight() const { |
530 | return QTime(0, 0, 0, 0); |
531 | } |
532 | |
533 | QTime RuleSet::beforeMidnight() const { |
534 | return QTime(23,59,59,999); |
535 | } |
536 | |
537 | int 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 | |