1/* -*- C++ -*-
2 *
3 * kPPP: A pppd front end for the KDE project
4 *
5 * $Id$
6 *
7 * Copyright (C) 1997 Bernd Johannes Wuebben
8 * wuebben@math.cornell.edu
9 *
10 * This file contributed by: Mario Weilguni, <mweilguni@sime.com>
11 * Thanks Mario!
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Library General Public
15 * License as published by the Free Software Foundation; either
16 * version 2 of the License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Library General Public License for more details.
22 *
23 * You should have received a copy of the GNU Library General Public
24 * License along with this program; if not, write to the Free
25 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 */
27
28#include <unistd.h>
29#include <sys/stat.h>
30#include <sys/types.h>
31
32#include <qdir.h>
33//Added by qt3to4:
34#include <QTimerEvent>
35#include <Q3CString>
36
37#include <kstandarddirs.h>
38#include <klocale.h>
39#include <kdebug.h>
40#include <time.h>
41
42#include "accounting.h"
43#include "pppdata.h"
44#include "pppstats.h"
45
46// defines the maximum duration until the current costs
47// are saved again (to prevent loss due to "kill")
48// specifying -1 disables the features
49#define UPDATE_TIME (5*60*1000)
50
51extern PPPData gpppdata;
52
53/////////////////////////////////////////////////////////////////////////////
54//
55// Helper functions
56//
57/////////////////////////////////////////////////////////////////////////////
58static QString timet2qstring(time_t t) {
59 QString s;
60
61 s.sprintf("%lu", t);
62 return s;
63}
64
65
66/////////////////////////////////////////////////////////////////////////////
67//
68// The base class for the accounting system provides a base set of useful
69// and common functions, but does not do any accounting by itself. The
70// accounting is accomplished withing it's derived classes
71//
72/////////////////////////////////////////////////////////////////////////////
73AccountingBase::AccountingBase(QObject *parent) :
74 QObject(parent),
75 _total(0),
76 _session(0)
77{
78 QDate dt = QDate::currentDate();
79 LogFileName = QString("%1-%2.log")
80 .arg(QDate::shortMonthName(dt.month()))
81 .arg(dt.year(), 4);
82
83 LogFileName = KGlobal::dirs()->saveLocation("appdata", "Log")
84 + '/' + LogFileName;
85
86 kDebug(5002) << "LogFileName: " << LogFileName;
87}
88
89AccountingBase::~AccountingBase() {
90 if(running())
91 slotStop();
92}
93
94
95double AccountingBase::total() const {
96 return _total + _session;
97}
98
99
100
101double AccountingBase::session() const {
102 return _session;
103}
104
105
106// set costs back to zero ( typically once per month)
107void AccountingBase::resetCosts(const QString & accountname){
108 QString prev_account = gpppdata.accname();
109
110 gpppdata.setAccount(accountname);
111 gpppdata.setTotalCosts("");
112
113 gpppdata.setAccount(prev_account);
114}
115
116
117void AccountingBase::resetVolume(const QString & accountname){
118 QString prev_account = gpppdata.accname();
119
120 gpppdata.setAccount(accountname);
121 gpppdata.setTotalBytes(0);
122
123 gpppdata.setAccount(prev_account);
124}
125
126
127void AccountingBase::logMessage(const QString &s, bool newline) {
128 int old_umask = umask(0077);
129
130 QFile f(LogFileName);
131
132 bool result = f.open(QIODevice::ReadWrite);
133 if(result) {
134 // move to eof, and place \n if necessary
135 if(f.size() > 0) {
136 if(newline) {
137 f.seek(f.size());
138 char c = 0;
139 f.read(&c, 1);
140 if(c != '\n')
141 f.write("\n", 1);
142 } else
143 f.seek(f.size());
144 }
145
146 Q3CString tmp = s.toLocal8Bit();
147 f.write(tmp, tmp.length());
148 f.close();
149 }
150
151 // restore umask
152 umask(old_umask);
153}
154
155
156QString AccountingBase::getCosts(const QString & accountname) {
157 QString prev_account = gpppdata.accname();
158
159 gpppdata.setAccount(accountname);
160 QString val = gpppdata.totalCosts();
161 // ### currency from rule file
162 // QString val = KGlobal::locale()->formatMoney(gpppdata.totalCosts().toDouble(), currency);
163
164 gpppdata.setAccount(prev_account);
165
166 return val;
167}
168
169
170
171bool AccountingBase::saveCosts() {
172 if(!_name.isNull() && _name.length() > 0) {
173 QString val;
174 val.setNum(total());
175
176 gpppdata.setTotalCosts(val);
177 gpppdata.save();
178
179 return true;
180 } else
181 return false;
182}
183
184
185bool AccountingBase::loadCosts() {
186 QString val = gpppdata.totalCosts();
187
188 if(val.isNull()) // QString will segfault if isnull and toDouble called
189 _total = 0.0;
190 else {
191 bool ok;
192 _total = val.toDouble(&ok);
193 if(!ok)
194 _total = 0.0;
195 }
196
197 return true;
198}
199
200
201QString AccountingBase::getAccountingFile(const QString &accountname) {
202 QString f = "kppp/Rules/";
203 f += accountname;
204 QString d = KStandardDirs::locate("data", f);
205
206 if(d.isNull())
207 return "";
208 else
209 return d;
210}
211
212
213/////////////////////////////////////////////////////////////////////////////
214//
215// Accounting class for ruleset files
216//
217/////////////////////////////////////////////////////////////////////////////
218Accounting::Accounting(QObject *parent, PPPStats *st) :
219 AccountingBase(parent),
220 acct_timer_id(0),
221 update_timer_id(0),
222 stats(st)
223{
224}
225
226
227bool Accounting::running() const {
228 return (bool)(acct_timer_id != 0);
229}
230
231
232void Accounting::timerEvent(QTimerEvent *t) {
233 if(t->timerId() == acct_timer_id) {
234
235 double newCosts;
236 double newLen;
237 double connect_time = difftime(time(0), start_time);
238
239 rules.getActiveRule(QDateTime::currentDateTime(), connect_time, newCosts, newLen);
240 if(newLen < 1) { // changed to < 1
241 slotStop();
242 return; // no default rule found
243 }
244
245 // check if we have a new rule. If yes,
246 // kill the timer and restart it with new
247 // duration
248 if((newCosts != _lastcosts) || (newLen != _lastlen)) {
249
250 kDebug(5002) << "SWITCHING RULES, new costs = "
251 << fixed << qSetRealNumberPrecision(2) << newCosts
252 << "new len = " << newLen;
253
254 killTimer(acct_timer_id);
255 if(newLen > 0)
256 acct_timer_id = startTimer((int)(newLen * 1000.0));
257
258 _lastlen = newLen;
259 _lastcosts = newCosts;
260 }
261
262 // emit changed() signal if necessary
263 if(newCosts != 0) {
264 _session += newCosts;
265 emit changed(rules.currencyString(total()),
266 rules.currencyString(session()));
267
268
269 }
270 } // if(t->timerId() == acct_timer_id)...
271
272 if(t->timerId() == update_timer_id) {
273 // just to be sure, save the current costs
274 // every n seconds (see UPDATE_TIME)
275 saveCosts();
276 }
277}
278
279
280void Accounting::slotStart() {
281 if(!running()) {
282 loadCosts();
283 _lastcosts = 0.0;
284 _lastlen = 0.0;
285 _session = rules.perConnectionCosts();
286 rules.setStartTime(QDateTime::currentDateTime());
287 acct_timer_id = startTimer(1);
288 if(UPDATE_TIME > 0)
289 update_timer_id = startTimer(UPDATE_TIME);
290
291 start_time = time(0);
292 QString s;
293 s = timet2qstring(start_time);
294 s += ':';
295 s += gpppdata.accname();
296 s += ':';
297 s += rules.currencySymbol();
298
299 logMessage(s, true);
300 }
301}
302
303
304void Accounting::slotStop() {
305 if(running()) {
306 killTimer(acct_timer_id);
307 if(update_timer_id != 0)
308 killTimer(update_timer_id);
309 acct_timer_id = 0;
310 update_timer_id = 0;
311
312 QString s;
313 s.sprintf(":%s:%0.4e:%0.4e:%u:%u\n",
314 timet2qstring(time(0)).toUtf8().data(),
315 session(),
316 total(),
317 stats->ibytes,
318 stats->obytes);
319
320 logMessage(s, false);
321 saveCosts();
322 }
323}
324
325
326bool Accounting::loadRuleSet(const QString & name) {
327
328 if (name.isEmpty()) {
329 rules.load(""); // delete old rules
330 return true;
331 }
332
333 QString d = AccountingBase::getAccountingFile(name);
334
335 QFileInfo fg(d);
336 if(fg.exists()) {
337 int ret = rules.load(d);
338 _name = rules.name();
339 return (bool)(ret == 0);
340 }
341
342 return false;
343}
344
345
346double Accounting::total() const {
347 if(rules.minimumCosts() <= _session)
348 return _total + _session;
349 else
350 return _total + rules.minimumCosts();
351}
352
353
354
355double Accounting::session() const {
356 if(rules.minimumCosts() <= _session)
357 return _session;
358 else
359 return rules.minimumCosts();
360}
361
362
363
364
365ExecutableAccounting::ExecutableAccounting(PPPStats *st, QObject *parent) :
366 AccountingBase(parent),
367 proc(0),
368 stats(st)
369{
370}
371
372
373bool ExecutableAccounting::running() const
374{
375 return proc && proc->isRunning();
376}
377
378
379bool ExecutableAccounting::loadRuleSet(const QString &) {
380 QString s = AccountingBase::getAccountingFile(gpppdata.accountingFile());
381 return (access(QFile::encodeName(s), X_OK) == 0);
382}
383
384
385void ExecutableAccounting::gotData(K3Process */*proc*/, char *buffer, int /*buflen*/) {
386 QString field[8];
387 int nFields = 0;
388 int pos, last_pos = 0;
389
390 // split string
391 QString b(buffer);
392 pos = b.indexOf(':');
393 while(pos != -1 && nFields < 8) {
394 field[nFields++] = b.mid(last_pos, pos-last_pos);
395 last_pos = pos+1;
396 pos = b.indexOf(':', last_pos);
397 }
398
399 for(int i = 0; i < nFields;i++)
400 fprintf(stderr, "FIELD[%d] = %s\n", i, field[i].toLocal8Bit().data());
401
402 QString __total, __session;
403 QString s(buffer);
404 int del1, del2, del3;
405
406 del1 = s.indexOf(':');
407 del2 = s.indexOf(':', del1+1);
408 del3 = s.indexOf(':', del2+1);
409 if(del1 == -1 || del2 == -1 || del3 == -1) {
410 // TODO: do something useful here
411 return;
412 }
413
414 provider = s.left(del1);
415 currency = s.mid(del1, del2-del1);
416 __total = s.mid(del2, del2-del1);
417 __session = s.mid(del3, s.length()-del3+1);
418
419 bool ok1, ok2;
420 _total = __total.toDouble(&ok1);
421 _session = __session.toDouble(&ok2);
422
423 if(!ok1 || !ok2) {
424 // TODO: do something useful here
425 return;
426 }
427
428 printf("PROVIDER=%s, CURRENCY=%s, TOTAL=%0.3e, SESSION=%0.3e\n",
429 provider.toLocal8Bit().data(),
430 currency.toLocal8Bit().data(),
431 _total,
432 _session);
433}
434
435
436void ExecutableAccounting::slotStart() {
437 if(proc != 0)
438 slotStop(); // just to make sure
439
440 loadCosts();
441 QString s = AccountingBase::getAccountingFile(gpppdata.accountingFile());
442 proc = new K3Process;
443
444 QString s_total;
445 s_total.sprintf("%0.8f", total());
446 *proc << s << s_total;
447 connect(proc, SIGNAL(receivedStdout(K3Process*,char*,int)),
448 this, SLOT(gotData(K3Process*,char*,int)));
449 proc->start();
450
451 time_t start_time = time(0);
452 s = timet2qstring(start_time);
453 s += ':';
454 s += gpppdata.accname();
455 s += ':';
456 s += currency;
457
458 logMessage(s, true);
459}
460
461
462void ExecutableAccounting::slotStop() {
463 if(proc != 0) {
464 proc->kill();
465 delete proc;
466 proc = 0;
467
468 QString s;
469 s.sprintf(":%s:%0.4e:%0.4e:%u:%u\n",
470 timet2qstring(time(0)).toLocal8Bit().data(),
471 session(),
472 total(),
473 stats->ibytes,
474 stats->obytes);
475
476 logMessage(s, false);
477 saveCosts();
478 }
479}
480
481#include "accounting.moc"
482