--- /dev/null
+#!/usr/bin/env python
+
+# This file is part of NetStory.
+# Author: Jere Malinen <jeremmalinen@gmail.com>
+
+
+import sys
+import os
+from datetime import datetime, timedelta
+
+from PyQt4 import QtCore, QtGui
+
+from netstory_ui import Ui_MainWindow
+import settings
+try:
+ import netstoryd
+except ImportError, e:
+ print 'Windows testing: %s' % str(e)
+
+
+class DataForm(QtGui.QMainWindow):
+ def __init__(self, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+
+ self.max_rows = 100
+ self.ui.combo_box_max_rows.addItems(['100', '1000', '10000',
+ 'unlimited'])
+
+ QtCore.QObject.connect(self.ui.combo_box_max_rows,
+ QtCore.SIGNAL('currentIndexChanged(QString)'),
+ self.change_max_rows)
+ QtCore.QObject.connect(self.ui.button_reload,
+ QtCore.SIGNAL('clicked()'), self.generate_traffic_tables)
+ QtCore.QObject.connect(self.ui.actionDatabaseInfo,
+ QtCore.SIGNAL('triggered()'), self.show_db_info)
+ QtCore.QObject.connect(self.ui.actionEmptyDatabase,
+ QtCore.SIGNAL('triggered()'), self.empty_db)
+ QtCore.QObject.connect(self.ui.actionAbout,
+ QtCore.SIGNAL('triggered()'), self.show_about)
+
+ self.progress = QtGui.QProgressDialog('Please wait...',
+ 'Stop', 0, 100, self)
+ self.progress.setWindowTitle('Generating tables')
+
+ # This is gives time for UI to show up before updating tables
+ self.timer = QtCore.QBasicTimer()
+ self.timer.start(100, self)
+
+ def timerEvent(self, event):
+ self.timer.stop()
+ self.generate_traffic_tables()
+
+ def change_max_rows(self):
+ try:
+ self.max_rows = int(self.ui.combo_box_max_rows.currentText())
+ except ValueError:
+ self.max_rows = 999999999 # should be as good as unlimited
+
+ def show_about(self):
+ QtGui.QMessageBox.about(self, 'About', 'NetStory consists of two '\
+ 'parts: a daemon that records network data counters in '\
+ 'background and this GUI application to view hourly, daily, '\
+ 'weekly and monthly net traffics.\n\n'\
+ 'Currently NetStory records '\
+ 'only "Home network data counter".\n\nNote that some numbers '\
+ 'might be inaccurate and probably will be if you change date '\
+ 'or time or clear data counter.')
+
+ def show_db_info(self):
+ try:
+ db_size = os.path.getsize(self.file)
+ except OSError, e:
+ QtGui.QMessageBox.about(self, 'Error', str(e))
+ return
+ if db_size > 1000:
+ size = str(db_size / 1000) + ' kB'
+ else:
+ size = str(db_size) + ' B'
+ QtGui.QMessageBox.about(self, 'Database info',
+ 'Records: %d\nSize: %s' % (len(self.datas) - 1, size))
+
+ def empty_db(self):
+ reply = QtGui.QMessageBox.question(self, 'Confirmation',
+ "Are you absolutely sure that you want to empty database?",
+ QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ try:
+ f = open(self.file, 'w')
+ f.write('')
+ download, upload = netstoryd.read_counters()
+ netstoryd.write_data(f, download, upload)
+ f.close()
+ except IOError, e:
+ QtGui.QMessageBox.about(self, 'Error', str(e))
+ return
+ self.generate_traffic_tables()
+
+ def generate_traffic_tables(self):
+ self.file = settings.DATA
+ self.loop = 0
+ for i, value in [(1, 5), (2, 33), (3, 60), (4, 90), (5, 100)]:
+ if i == 2:
+ print str(datetime.now()) + ' self.read_data()'
+ if not self.read_data():
+ break
+ print str(datetime.now()) + ' ohi'
+ self._append_latest_traffic_status()
+ if len(self.datas) < 2:
+ self._cancel_and_show_message('Try again later',
+ "Unfortunately there isn't enough data in the "\
+ "database yet. Try again after few minutes.")
+ break
+ elif i == 3:
+ print str(datetime.now()) + ' self._generate_hourly()'
+ self._generate_hourly()
+ elif i == 4:
+ print str(datetime.now()) + ' self._generate_daily()'
+ self._generate_daily()
+ elif i == 5:
+ print str(datetime.now()) + ' self._generate_weekly()'
+ self._generate_weekly()
+ print str(datetime.now()) + ' self._generate_monthly()'
+ self._generate_monthly()
+ print str(datetime.now()) + ' self._generate_summary()'
+ self._generate_summary()
+ print str(datetime.now()) + ' ohi'
+
+ if self.progress.wasCanceled():
+ break
+ self.progress.setValue(value)
+ QtCore.QCoreApplication.processEvents()
+
+ self.progress.setValue(100)
+ self.progress.reset()
+
+ def read_data(self):
+ self.datas = []
+ try:
+ f = open(self.file, 'r')
+ for line in f:
+ QtCore.QCoreApplication.processEvents()
+ if self._if_canceled():
+ return False
+ if len(line) > 5:
+ parts = line.split(',')
+ try:
+ self.datas.append(TrafficLogLine(parts[0], parts[1],
+ parts[2]))
+ except TypeError, e:
+ print 'Error in: %s (%s)' % (self.file, str(e))
+ except ValueError, e:
+ print 'Error in: %s (%s)' % (self.file, str(e))
+ except IOError, e:
+ self._cancel_and_show_message('Error', str(e))
+ return False
+ return True
+
+ def _cancel_and_show_message(self, title, message):
+ self.progress.cancel()
+ QtGui.QMessageBox.about(self, title, message)
+ QtCore.QCoreApplication.processEvents()
+
+ def _if_canceled(self):
+ """Checks cheaply from long loop if Cancel was pushed."""
+ self.loop += 1
+ if self.loop % 500 == 0:
+ QtCore.QCoreApplication.processEvents()
+ if self.progress.wasCanceled():
+ return True
+ return False
+
+ def _append_latest_traffic_status(self):
+ try:
+ download, upload = netstoryd.read_counters()
+ if netstoryd.check(download) and netstoryd.check(upload):
+ now = datetime.now().strftime(settings.DATA_TIME_FORMAT)
+ self.datas.append(TrafficLogLine(now, download, upload))
+ else:
+ QtGui.QMessageBox.about(self, 'Problem', "Your N900 " \
+ "isn't currently probably compatible with NetStory " \
+ "(only PR1.2 is tested)")
+ except NameError, e:
+ print 'Windows testing: %s' % str(e)
+
+ def _generate_hourly(self):
+ self.hourly = []
+ for i, data in enumerate(self.datas[1:]):
+ if self._if_canceled():
+ return
+ traffic_row = TrafficRow()
+ traffic_row.calculate_between_log_lines(self.datas[i], data)
+ self.hourly.append(traffic_row)
+
+ table = self.ui.table_hourly
+ self._init_table(table, len(self.hourly))
+
+ for i, hour in enumerate(reversed(self.hourly[-self.max_rows:])):
+ if self._if_canceled():
+ return
+ if hour.start_time.day != hour.end_time.day and \
+ hour.end_time.hour != 0:
+ # Phone has been off or there is some other reason why
+ # end time date is different. Anyhow show end time with date.
+ end_time = hour.end_time.strftime('%H:%M (%d.%m.%Y)')
+ else:
+ end_time = hour.end_time.strftime('%H:%M')
+ hour.set_description_cell('%s - %s' %
+ (hour.start_time.strftime('%d.%m.%Y %H:%M'),
+ end_time), i)
+ # This is expensive operation if there are thousands of lines
+ self._set_table_row(table, i, hour)
+
+ def _generate_daily(self):
+ self.daily = {}
+ for hour in self.hourly:
+ if self._if_canceled():
+ return
+ key = hour.start_time.isocalendar()
+ self.daily[key] = self.daily.get(key, TrafficRow())
+ self.daily[key].add(hour)
+
+ table = self.ui.table_daily
+ self._init_table(table, len(self.daily))
+
+ keys = self.daily.keys()
+ keys.sort()
+
+ for i, key in enumerate(reversed(keys[-self.max_rows:])):
+ if self._if_canceled():
+ return
+ day = self.daily[key]
+ day.set_total()
+ day.set_representation()
+ day.set_description_cell(\
+ day.start_time.strftime('%d.%m.%Y'), i)
+ self._set_table_row(table, i, day)
+
+ def _generate_weekly(self):
+ self.weekly = {}
+ for day in self.daily.itervalues():
+ # Following works beatifully,
+ # because: datetime(2011, 1, 1).isocalendar()[0] == 2010
+ key = '%d / %02d' % (day.start_time.isocalendar()[0],
+ day.start_time.isocalendar()[1])
+ self.weekly[key] = self.weekly.get(key, TrafficRow())
+ self.weekly[key].add(day)
+
+ table = self.ui.table_weekly
+ self._init_table(table, len(self.weekly))
+
+ keys = self.weekly.keys()
+ keys.sort()
+
+ for i, key in enumerate(reversed(keys[-self.max_rows:])):
+ week = self.weekly[key]
+ week.set_total()
+ week.set_representation()
+ if week.end_time.isocalendar()[1] != \
+ week.start_time.isocalendar()[1]:
+ # it's probably following situation:
+ # e.g. start time is 7.6.2010 0:00 (week 23)
+ # and end time is 14.6.2010 0:00 (week 24)
+ week.end_time -= timedelta(days=1)
+ week.set_description_cell('%d (%s - %s)' %
+ (week.start_time.isocalendar()[1],
+ week.start_time.strftime('%d.%m'),
+ week.end_time.strftime('%d.%m.%Y')), i)
+ self._set_table_row(table, i, week)
+
+ def _generate_monthly(self):
+ self.monthly = {}
+ for day in self.daily.itervalues():
+ key = day.start_time.strftime('%Y %m')
+ self.monthly[key] = self.monthly.get(key, TrafficRow())
+ self.monthly[key].add(day)
+
+ table = self.ui.table_monthly
+ self._init_table(table, len(self.monthly))
+
+ keys = self.monthly.keys()
+ keys.sort()
+
+ for i, key in enumerate(reversed(keys[-self.max_rows:])):
+ month = self.monthly[key]
+ month.set_total()
+ month.set_representation()
+ month.set_description_cell(month.start_time.strftime('%Y: %B'), i)
+ self._set_table_row(table, i, month)
+
+ def _generate_summary(self):
+ table = self.ui.table_summary
+ self._init_table(table, 5)
+
+ for i, string, traffic_rows in [(0, 'Hourly average', self.hourly),
+ (1, 'Daily average', self.daily.itervalues()),
+ (2, 'Weekly average', self.weekly.itervalues()),
+ (3, 'Monthly average', self.monthly.itervalues())]:
+ averages = self.calculate_averages(traffic_rows)
+ average = TrafficRow()
+ average.download_bytes = averages['download']
+ average.upload_bytes = averages['upload']
+ average.total_bytes = averages['total']
+ average.set_representation()
+ average.set_description_cell(string, i)
+ self._set_table_row(table, i, average)
+
+ totals = self.calculate_total(self.monthly.itervalues())
+ total = TrafficRow()
+ total.download_bytes = sum(totals['download'])
+ total.upload_bytes = sum(totals['upload'])
+ total.total_bytes = sum(totals['total'])
+ total.set_representation()
+ total.set_description_cell(\
+ self.datas[0].time.strftime('Total since %d.%m.%Y %H:%M'), 0)
+ self._set_table_row(table, 4, total)
+
+ def _init_table(self, table, rows):
+ table.clearContents()
+ table.sortItems(0)
+ if rows < self.max_rows:
+ table.setRowCount(rows)
+ else:
+ table.setRowCount(self.max_rows)
+ table.horizontalHeader().resizeSection(0, 300)
+ table.horizontalHeader().setVisible(True)
+
+ def _set_table_row(self, table, row_number, traffic_row):
+ table.setItem(row_number, 0,
+ SortTableWidgetItem(traffic_row.description,
+ traffic_row.sort_key))
+ table.setItem(row_number, 1,
+ SortTableWidgetItem(traffic_row.download_string,
+ traffic_row.download_bytes))
+ table.setItem(row_number, 2,
+ SortTableWidgetItem(traffic_row.upload_string,
+ traffic_row.upload_bytes))
+ table.setItem(row_number, 3,
+ SortTableWidgetItem(traffic_row.total_string,
+ traffic_row.total_bytes))
+
+ def calculate_averages(self, traffic_rows=[]):
+ total = self.calculate_total(traffic_rows)
+ averages = {}
+ for key, l in total.items():
+ averages[key] = sum(l) / len(l)
+ return averages
+
+ def calculate_total(self, traffic_rows=[]):
+ total = {'download': [], 'upload': [], 'total': []}
+ for traffic_row in traffic_rows:
+ total['download'].append(traffic_row.download_bytes)
+ total['upload'].append(traffic_row.upload_bytes)
+ total['total'].append(traffic_row.total_bytes)
+ return total
+
+
+class TrafficLogLine:
+ def __init__(self, time='', download='', upload=''):
+ #self.time = datetime.strptime(time.strip(), settings.DATA_TIME_FORMAT)
+ # this is about 4 times faster than above
+ self.time = datetime(int(time[0:4]), int(time[5:7]), int(time[8:10]),
+ int(time[11:13]), int(time[14:16]), int(time[17:19]))
+ self.download = int(download.strip())
+ self.upload = int(upload.strip())
+
+
+class TrafficRow:
+ def __init__(self):
+ self.download_bytes = 0
+ self.upload_bytes = 0
+ self.total_bytes = 0
+ self.start_time = None
+ self.end_time = None
+
+ def calculate_between_log_lines(self, start_data, end_data):
+ self.start_time = start_data.time
+ self.end_time = end_data.time
+ self.download_bytes = self.traffic_difference(start_data.download, \
+ end_data.download)
+ self.upload_bytes = self.traffic_difference(start_data.upload, \
+ end_data.upload)
+ self.set_total()
+ self.set_representation()
+
+ def traffic_difference(self, start, end):
+ if end >= start:
+ return end - start
+ else:
+ return end #This value is probably inaccurate compared to reality
+
+ def set_total(self):
+ self.total_bytes = self.download_bytes + self.upload_bytes
+
+ def set_representation(self):
+ self.download_string = self.bytes_representation(self.download_bytes)
+ self.upload_string = self.bytes_representation(self.upload_bytes)
+ self.total_string = self.bytes_representation(self.total_bytes)
+
+ def bytes_representation(self, number):
+ s = str(number)
+ if len(s) > 6:
+ s = '%s.%s MB' % (s[0:-6], s[-5])
+ elif len(s) > 3:
+ s = '%s kB' % (s[0:-3])
+ else:
+ s = '%s B' % (s)
+ return s
+
+ def add(self, other):
+ """
+ Adds traffic values from other row into self
+ and also sets start and end times properly.
+ """
+ self.download_bytes += other.download_bytes
+ self.upload_bytes += other.upload_bytes
+ if not self.start_time or other.start_time < self.start_time:
+ self.start_time = other.start_time
+ if not self.end_time or other.end_time > self.end_time:
+ self.end_time = other.end_time
+
+ def set_description_cell(self, description, sort_key):
+ self.description = description
+ self.sort_key = sort_key
+
+
+class SortTableWidgetItem(QtGui.QTableWidgetItem):
+ def __init__(self, text, sort_key):
+ # call custom constructor with UserType item type
+ QtGui.QTableWidgetItem.__init__(self, text, \
+ QtGui.QTableWidgetItem.UserType)
+ self.sort_key = sort_key
+
+ # Qt uses a simple < check for sorting items,
+ # override this to use the sort_key
+ def __lt__(self, other):
+ return self.sort_key < other.sort_key
+
+
+if __name__ == "__main__":
+ app = QtGui.QApplication(sys.argv)
+ dataform = DataForm()
+ dataform.show()
+ sys.exit(app.exec_())
\ No newline at end of file