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 | |
51 | extern PPPData gpppdata; |
52 | |
53 | ///////////////////////////////////////////////////////////////////////////// |
54 | // |
55 | // Helper functions |
56 | // |
57 | ///////////////////////////////////////////////////////////////////////////// |
58 | static 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 | ///////////////////////////////////////////////////////////////////////////// |
73 | AccountingBase::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 | |
89 | AccountingBase::~AccountingBase() { |
90 | if(running()) |
91 | slotStop(); |
92 | } |
93 | |
94 | |
95 | double AccountingBase::total() const { |
96 | return _total + _session; |
97 | } |
98 | |
99 | |
100 | |
101 | double AccountingBase::session() const { |
102 | return _session; |
103 | } |
104 | |
105 | |
106 | // set costs back to zero ( typically once per month) |
107 | void 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 | |
117 | void 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 | |
127 | void 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 | |
156 | QString 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 | |
171 | bool 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 | |
185 | bool 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 | |
201 | QString 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 | ///////////////////////////////////////////////////////////////////////////// |
218 | Accounting::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 | |
227 | bool Accounting::running() const { |
228 | return (bool)(acct_timer_id != 0); |
229 | } |
230 | |
231 | |
232 | void 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 | |
280 | void 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 | |
304 | void 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 | |
326 | bool 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 | |
346 | double Accounting::total() const { |
347 | if(rules.minimumCosts() <= _session) |
348 | return _total + _session; |
349 | else |
350 | return _total + rules.minimumCosts(); |
351 | } |
352 | |
353 | |
354 | |
355 | double Accounting::session() const { |
356 | if(rules.minimumCosts() <= _session) |
357 | return _session; |
358 | else |
359 | return rules.minimumCosts(); |
360 | } |
361 | |
362 | |
363 | |
364 | |
365 | ExecutableAccounting::ExecutableAccounting(PPPStats *st, QObject *parent) : |
366 | AccountingBase(parent), |
367 | proc(0), |
368 | stats(st) |
369 | { |
370 | } |
371 | |
372 | |
373 | bool ExecutableAccounting::running() const |
374 | { |
375 | return proc && proc->isRunning(); |
376 | } |
377 | |
378 | |
379 | bool ExecutableAccounting::loadRuleSet(const QString &) { |
380 | QString s = AccountingBase::getAccountingFile(gpppdata.accountingFile()); |
381 | return (access(QFile::encodeName(s), X_OK) == 0); |
382 | } |
383 | |
384 | |
385 | void 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 | |
436 | void 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 | |
462 | void 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 | |