X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;ds=sidebyside;f=src%2Fopt%2Fnetstory%2Fnetstory.py;fp=src%2Fopt%2Fnetstory%2Fnetstory.py;h=30c755455153eeabd9c7c7b57e5015f1ba007871;hb=1dbf7c39cb22d1fbda9fa10d73827e5a62b0f473;hp=0000000000000000000000000000000000000000;hpb=a8b29bf15bf53997624e96244746a5a40a5f17d9;p=netstory diff --git a/src/opt/netstory/netstory.py b/src/opt/netstory/netstory.py new file mode 100644 index 0000000..30c7554 --- /dev/null +++ b/src/opt/netstory/netstory.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python + +# This file is part of NetStory. +# Author: Jere Malinen + + +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