3 # This file is part of NetStory.
4 # Author: Jere Malinen <jeremmalinen@gmail.com>
9 from datetime import datetime, timedelta
11 from PyQt4 import QtCore, QtGui
13 from netstory_ui import Ui_MainWindow
17 except ImportError, e:
18 print 'Windows testing: %s' % str(e)
21 class DataForm(QtGui.QMainWindow):
22 def __init__(self, parent=None):
23 QtGui.QWidget.__init__(self, parent)
24 self.ui = Ui_MainWindow()
28 self.ui.combo_box_max_rows.addItems(['100', '1000', '10000',
31 QtCore.QObject.connect(self.ui.combo_box_max_rows,
32 QtCore.SIGNAL('currentIndexChanged(QString)'),
34 QtCore.QObject.connect(self.ui.button_reload,
35 QtCore.SIGNAL('clicked()'), self.generate_traffic_tables)
36 QtCore.QObject.connect(self.ui.actionDatabaseInfo,
37 QtCore.SIGNAL('triggered()'), self.show_db_info)
38 QtCore.QObject.connect(self.ui.actionEmptyDatabase,
39 QtCore.SIGNAL('triggered()'), self.empty_db)
40 QtCore.QObject.connect(self.ui.actionAbout,
41 QtCore.SIGNAL('triggered()'), self.show_about)
43 self.progress = QtGui.QProgressDialog('Please wait...',
45 self.progress.setWindowTitle('Generating tables')
47 # This is gives time for UI to show up before updating tables
48 self.timer = QtCore.QBasicTimer()
49 self.timer.start(100, self)
51 def timerEvent(self, event):
53 self.generate_traffic_tables()
55 def change_max_rows(self):
57 self.max_rows = int(self.ui.combo_box_max_rows.currentText())
59 self.max_rows = 999999999 # should be as good as unlimited
62 QtGui.QMessageBox.about(self, 'About', 'NetStory consists of two '\
63 'parts: a daemon that records network data counters in '\
64 'background and this GUI application to view hourly, daily, '\
65 'weekly and monthly net traffics.\n\n'\
66 'Currently NetStory records '\
67 'only "Home network data counter".\n\nNote that some numbers '\
68 'might be inaccurate and probably will be if you change date '\
69 'or time or clear data counter.')
71 def show_db_info(self):
73 db_size = os.path.getsize(self.file)
75 QtGui.QMessageBox.about(self, 'Error', str(e))
78 size = str(db_size / 1000) + ' kB'
80 size = str(db_size) + ' B'
81 QtGui.QMessageBox.about(self, 'Database info',
82 'Records: %d\nSize: %s' % (len(self.datas) - 1, size))
85 reply = QtGui.QMessageBox.question(self, 'Confirmation',
86 "Are you absolutely sure that you want to empty database?",
87 QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
88 if reply == QtGui.QMessageBox.Yes:
90 f = open(self.file, 'w')
92 download, upload = netstoryd.read_counters()
93 netstoryd.write_data(f, download, upload)
96 QtGui.QMessageBox.about(self, 'Error', str(e))
98 self.generate_traffic_tables()
100 def generate_traffic_tables(self):
101 self.file = settings.DATA
103 for i, value in [(1, 5), (2, 33), (3, 60), (4, 90), (5, 100)]:
105 print str(datetime.now()) + ' self.read_data()'
106 if not self.read_data():
108 print str(datetime.now()) + ' ohi'
109 self._append_latest_traffic_status()
110 if len(self.datas) < 2:
111 self._cancel_and_show_message('Try again later',
112 "Unfortunately there isn't enough data in the "\
113 "database yet. Try again after few minutes.")
116 print str(datetime.now()) + ' self._generate_hourly()'
117 self._generate_hourly()
119 print str(datetime.now()) + ' self._generate_daily()'
120 self._generate_daily()
122 print str(datetime.now()) + ' self._generate_weekly()'
123 self._generate_weekly()
124 print str(datetime.now()) + ' self._generate_monthly()'
125 self._generate_monthly()
126 print str(datetime.now()) + ' self._generate_summary()'
127 self._generate_summary()
128 print str(datetime.now()) + ' ohi'
130 if self.progress.wasCanceled():
132 self.progress.setValue(value)
133 QtCore.QCoreApplication.processEvents()
135 self.progress.setValue(100)
136 self.progress.reset()
141 f = open(self.file, 'r')
143 QtCore.QCoreApplication.processEvents()
144 if self._if_canceled():
147 parts = line.split(',')
149 self.datas.append(TrafficLogLine(parts[0], parts[1],
152 print 'Error in: %s (%s)' % (self.file, str(e))
153 except ValueError, e:
154 print 'Error in: %s (%s)' % (self.file, str(e))
156 self._cancel_and_show_message('Error', str(e))
160 def _cancel_and_show_message(self, title, message):
161 self.progress.cancel()
162 QtGui.QMessageBox.about(self, title, message)
163 QtCore.QCoreApplication.processEvents()
165 def _if_canceled(self):
166 """Checks cheaply from long loop if Cancel was pushed."""
168 if self.loop % 500 == 0:
169 QtCore.QCoreApplication.processEvents()
170 if self.progress.wasCanceled():
174 def _append_latest_traffic_status(self):
176 download, upload = netstoryd.read_counters()
177 if netstoryd.check(download) and netstoryd.check(upload):
178 now = datetime.now().strftime(settings.DATA_TIME_FORMAT)
179 self.datas.append(TrafficLogLine(now, download, upload))
181 QtGui.QMessageBox.about(self, 'Problem', "Your N900 " \
182 "isn't currently probably compatible with NetStory " \
183 "(only PR1.2 is tested)")
185 print 'Windows testing: %s' % str(e)
187 def _generate_hourly(self):
189 for i, data in enumerate(self.datas[1:]):
190 if self._if_canceled():
192 traffic_row = TrafficRow()
193 traffic_row.calculate_between_log_lines(self.datas[i], data)
194 self.hourly.append(traffic_row)
196 table = self.ui.table_hourly
197 self._init_table(table, len(self.hourly))
199 for i, hour in enumerate(reversed(self.hourly[-self.max_rows:])):
200 if self._if_canceled():
202 if hour.start_time.day != hour.end_time.day and \
203 hour.end_time.hour != 0:
204 # Phone has been off or there is some other reason why
205 # end time date is different. Anyhow show end time with date.
206 end_time = hour.end_time.strftime('%H:%M (%d.%m.%Y)')
208 end_time = hour.end_time.strftime('%H:%M')
209 hour.set_description_cell('%s - %s' %
210 (hour.start_time.strftime('%d.%m.%Y %H:%M'),
212 # This is expensive operation if there are thousands of lines
213 self._set_table_row(table, i, hour)
215 def _generate_daily(self):
217 for hour in self.hourly:
218 if self._if_canceled():
220 key = hour.start_time.isocalendar()
221 self.daily[key] = self.daily.get(key, TrafficRow())
222 self.daily[key].add(hour)
224 table = self.ui.table_daily
225 self._init_table(table, len(self.daily))
227 keys = self.daily.keys()
230 for i, key in enumerate(reversed(keys[-self.max_rows:])):
231 if self._if_canceled():
233 day = self.daily[key]
235 day.set_representation()
236 day.set_description_cell(\
237 day.start_time.strftime('%d.%m.%Y'), i)
238 self._set_table_row(table, i, day)
240 def _generate_weekly(self):
242 for day in self.daily.itervalues():
243 # Following works beatifully,
244 # because: datetime(2011, 1, 1).isocalendar()[0] == 2010
245 key = '%d / %02d' % (day.start_time.isocalendar()[0],
246 day.start_time.isocalendar()[1])
247 self.weekly[key] = self.weekly.get(key, TrafficRow())
248 self.weekly[key].add(day)
250 table = self.ui.table_weekly
251 self._init_table(table, len(self.weekly))
253 keys = self.weekly.keys()
256 for i, key in enumerate(reversed(keys[-self.max_rows:])):
257 week = self.weekly[key]
259 week.set_representation()
260 if week.end_time.isocalendar()[1] != \
261 week.start_time.isocalendar()[1]:
262 # it's probably following situation:
263 # e.g. start time is 7.6.2010 0:00 (week 23)
264 # and end time is 14.6.2010 0:00 (week 24)
265 week.end_time -= timedelta(days=1)
266 week.set_description_cell('%d (%s - %s)' %
267 (week.start_time.isocalendar()[1],
268 week.start_time.strftime('%d.%m'),
269 week.end_time.strftime('%d.%m.%Y')), i)
270 self._set_table_row(table, i, week)
272 def _generate_monthly(self):
274 for day in self.daily.itervalues():
275 key = day.start_time.strftime('%Y %m')
276 self.monthly[key] = self.monthly.get(key, TrafficRow())
277 self.monthly[key].add(day)
279 table = self.ui.table_monthly
280 self._init_table(table, len(self.monthly))
282 keys = self.monthly.keys()
285 for i, key in enumerate(reversed(keys[-self.max_rows:])):
286 month = self.monthly[key]
288 month.set_representation()
289 month.set_description_cell(month.start_time.strftime('%Y: %B'), i)
290 self._set_table_row(table, i, month)
292 def _generate_summary(self):
293 table = self.ui.table_summary
294 self._init_table(table, 5)
296 for i, string, traffic_rows in [(0, 'Hourly average', self.hourly),
297 (1, 'Daily average', self.daily.itervalues()),
298 (2, 'Weekly average', self.weekly.itervalues()),
299 (3, 'Monthly average', self.monthly.itervalues())]:
300 averages = self.calculate_averages(traffic_rows)
301 average = TrafficRow()
302 average.download_bytes = averages['download']
303 average.upload_bytes = averages['upload']
304 average.total_bytes = averages['total']
305 average.set_representation()
306 average.set_description_cell(string, i)
307 self._set_table_row(table, i, average)
309 totals = self.calculate_total(self.monthly.itervalues())
311 total.download_bytes = sum(totals['download'])
312 total.upload_bytes = sum(totals['upload'])
313 total.total_bytes = sum(totals['total'])
314 total.set_representation()
315 total.set_description_cell(\
316 self.datas[0].time.strftime('Total since %d.%m.%Y %H:%M'), 0)
317 self._set_table_row(table, 4, total)
319 def _init_table(self, table, rows):
320 table.clearContents()
322 if rows < self.max_rows:
323 table.setRowCount(rows)
325 table.setRowCount(self.max_rows)
326 table.horizontalHeader().resizeSection(0, 300)
327 table.horizontalHeader().setVisible(True)
329 def _set_table_row(self, table, row_number, traffic_row):
330 table.setItem(row_number, 0,
331 SortTableWidgetItem(traffic_row.description,
332 traffic_row.sort_key))
333 table.setItem(row_number, 1,
334 SortTableWidgetItem(traffic_row.download_string,
335 traffic_row.download_bytes))
336 table.setItem(row_number, 2,
337 SortTableWidgetItem(traffic_row.upload_string,
338 traffic_row.upload_bytes))
339 table.setItem(row_number, 3,
340 SortTableWidgetItem(traffic_row.total_string,
341 traffic_row.total_bytes))
343 def calculate_averages(self, traffic_rows=[]):
344 total = self.calculate_total(traffic_rows)
346 for key, l in total.items():
347 averages[key] = sum(l) / len(l)
350 def calculate_total(self, traffic_rows=[]):
351 total = {'download': [], 'upload': [], 'total': []}
352 for traffic_row in traffic_rows:
353 total['download'].append(traffic_row.download_bytes)
354 total['upload'].append(traffic_row.upload_bytes)
355 total['total'].append(traffic_row.total_bytes)
359 class TrafficLogLine:
360 def __init__(self, time='', download='', upload=''):
361 #self.time = datetime.strptime(time.strip(), settings.DATA_TIME_FORMAT)
362 # this is about 4 times faster than above
363 self.time = datetime(int(time[0:4]), int(time[5:7]), int(time[8:10]),
364 int(time[11:13]), int(time[14:16]), int(time[17:19]))
365 self.download = int(download.strip())
366 self.upload = int(upload.strip())
371 self.download_bytes = 0
372 self.upload_bytes = 0
374 self.start_time = None
377 def calculate_between_log_lines(self, start_data, end_data):
378 self.start_time = start_data.time
379 self.end_time = end_data.time
380 self.download_bytes = self.traffic_difference(start_data.download, \
382 self.upload_bytes = self.traffic_difference(start_data.upload, \
385 self.set_representation()
387 def traffic_difference(self, start, end):
391 return end #This value is probably inaccurate compared to reality
394 self.total_bytes = self.download_bytes + self.upload_bytes
396 def set_representation(self):
397 self.download_string = self.bytes_representation(self.download_bytes)
398 self.upload_string = self.bytes_representation(self.upload_bytes)
399 self.total_string = self.bytes_representation(self.total_bytes)
401 def bytes_representation(self, number):
404 s = '%s.%s MB' % (s[0:-6], s[-5])
406 s = '%s kB' % (s[0:-3])
411 def add(self, other):
413 Adds traffic values from other row into self
414 and also sets start and end times properly.
416 self.download_bytes += other.download_bytes
417 self.upload_bytes += other.upload_bytes
418 if not self.start_time or other.start_time < self.start_time:
419 self.start_time = other.start_time
420 if not self.end_time or other.end_time > self.end_time:
421 self.end_time = other.end_time
423 def set_description_cell(self, description, sort_key):
424 self.description = description
425 self.sort_key = sort_key
428 class SortTableWidgetItem(QtGui.QTableWidgetItem):
429 def __init__(self, text, sort_key):
430 # call custom constructor with UserType item type
431 QtGui.QTableWidgetItem.__init__(self, text, \
432 QtGui.QTableWidgetItem.UserType)
433 self.sort_key = sort_key
435 # Qt uses a simple < check for sorting items,
436 # override this to use the sort_key
437 def __lt__(self, other):
438 return self.sort_key < other.sort_key
441 if __name__ == "__main__":
442 app = QtGui.QApplication(sys.argv)
443 dataform = DataForm()
445 sys.exit(app.exec_())