Initial release netstory-0.1.0
authorJere Malinen <jeremmalinen@gmail.com>
Sun, 13 Jun 2010 07:31:36 +0000 (10:31 +0300)
committerJere Malinen <jeremmalinen@gmail.com>
Sun, 13 Jun 2010 07:31:36 +0000 (10:31 +0300)
buid_netstory.py [new file with mode: 0644]
src/etc/event.d/netstoryd [new file with mode: 0644]
src/opt/netstory/netstory.py [new file with mode: 0644]
src/opt/netstory/netstory_ui.py [new file with mode: 0644]
src/opt/netstory/netstoryd.py [new file with mode: 0644]
src/opt/netstory/settings.py [new file with mode: 0644]
src/usr/share/applications/hildon/netstory.desktop [new file with mode: 0644]
src/usr/share/icons/hicolor/48x48/hildon/netstory.png [new file with mode: 0644]
welcome [deleted file]

diff --git a/buid_netstory.py b/buid_netstory.py
new file mode 100644 (file)
index 0000000..7120979
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/python2.5\r
+# -*- coding: utf-8 -*-\r
+## This program is free software; you can redistribute it and/or modify\r
+## it under the terms of the GNU General Public License as published\r
+## by the Free Software Foundation; version 2 only.\r
+##\r
+## This program is distributed in the hope that it will be useful,\r
+## but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+## GNU General Public License for more details.\r
+##\r
+import py2deb\r
+import os\r
+if __name__ == "__main__":\r
+    try:\r
+        os.chdir(os.path.dirname(sys.argv[0]))\r
+    except:\r
+        pass\r
+    print\r
+    p=py2deb.Py2deb("netstory")   #This is the package name and MUST be in lowercase! (using e.g. "mClock" fails miserably...)\r
+    p.description="This utility consists of two parts: "\\r
+                  "a daemon that records network data counters in background and "\\r
+                  "an GUI application to view hourly, daily, weekly and monthly net traffics."\r
+    p.author="Jere Malinen"\r
+    p.mail="jeremmalinen@gmail.com"\r
+    p.depends = "python2.5, python2.5-qt4-core, python2.5-qt4-gui"\r
+\r
+    p.section="user/network"\r
+\r
+    p.icon = "/home/user/MyDocs/py2deb/netstory/src/usr/share/icons/hicolor/48x48/hildon/netstory.png"\r
+    p.arch="all"             #should be all for python, any for all arch\r
+    p.urgency="low"           #not used in maemo onl for deb os\r
+    p.distribution="fremantle"\r
+    p.repository="extras-devel"\r
+    p.xsbc_bugtracker="http://bugs.maemo.org"\r
+    p.postinstall="""#!/bin/sh\r
+        chmod +x /opt/netstory/netstory.py /opt/netstory/netstoryd.py  || echo "Warning: chmod failed. The package might not work properly"\r
+        start netstoryd || echo "Warning: Starting the netstory daemon failed. The package might not work properly."\r
+        """ #Set here your post install script\r
+    #  p.postremove="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post remove script\r
+    #  p.preinstall="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre install script\r
+    p.preremove="""#!/bin/sh\r
+        stop netstoryd || echo "Warning: Stopping the netstoryd daemon failed."\r
+        rm -f /opt/netstory/*.pyc /opt/netstory/*.pyo\r
+        """ #Set here your pre remove script\r
+    version = "0.1.0"         #Version of your software, e.g. "1.2.0" or "0.8.2"\r
+    build = "1"              #Build number, e.g. "1" for the first build of this version of your software. Increment for later re-builds of the same version of your software.\r
+                           #Text with changelog information to be displayed in the package "Details" tab of the Maemo Application Manager\r
+    changeloginformation = "Initial release." \r
+    dir_name = "src"          #Name of the subfolder containing your package source files (e.g. usr\share\icons\hicolor\scalable\myappicon.svg, usr\lib\myapp\somelib.py). We suggest to leave it named src in all projects and will refer to that in the wiki article on maemo.org\r
+    #Thanks to DareTheHair from talk.maemo.org for this snippet that recursively builds the file list \r
+    for root, dirs, files in os.walk(dir_name):\r
+        real_dir = root[len(dir_name):]\r
+        fake_file = []\r
+        for f in files:\r
+           fake_file.append(root + os.sep + f + "|" + f)\r
+        if len(fake_file) > 0:\r
+           p[real_dir] = fake_file\r
+    print p\r
+    r = p.generate(version,build,changelog=changeloginformation,tar=True,dsc=True,changes=True,build=False,src=True)
\ No newline at end of file
diff --git a/src/etc/event.d/netstoryd b/src/etc/event.d/netstoryd
new file mode 100644 (file)
index 0000000..80d4519
--- /dev/null
@@ -0,0 +1,10 @@
+description    "NetStory data recorder daemon"
+author         "Jere Malinen"
+
+start on started hildon-desktop
+stop on starting shutdown
+
+service
+console none
+
+exec su user -c "exec /opt/netstory/netstoryd.py"
\ No newline at end of file
diff --git a/src/opt/netstory/netstory.py b/src/opt/netstory/netstory.py
new file mode 100644 (file)
index 0000000..30c7554
--- /dev/null
@@ -0,0 +1,445 @@
+#!/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
diff --git a/src/opt/netstory/netstory_ui.py b/src/opt/netstory/netstory_ui.py
new file mode 100644 (file)
index 0000000..ad43da5
--- /dev/null
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-\r
+\r
+# Form implementation generated from reading ui file 'netstory_ui.ui'\r
+#\r
+# Created: Sat Jun 12 20:54:00 2010\r
+#      by: PyQt4 UI code generator 4.7.3\r
+#\r
+# WARNING! All changes made in this file will be lost!\r
+\r
+from PyQt4 import QtCore, QtGui\r
+\r
+class Ui_MainWindow(object):\r
+    def setupUi(self, MainWindow):\r
+        MainWindow.setObjectName("MainWindow")\r
+        MainWindow.resize(800, 440)\r
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)\r
+        sizePolicy.setHorizontalStretch(0)\r
+        sizePolicy.setVerticalStretch(0)\r
+        sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())\r
+        MainWindow.setSizePolicy(sizePolicy)\r
+        MainWindow.setStyleSheet("None")\r
+        MainWindow.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))\r
+        self.centralwidget = QtGui.QWidget(MainWindow)\r
+        self.centralwidget.setObjectName("centralwidget")\r
+        self.tabWidget = QtGui.QTabWidget(self.centralwidget)\r
+        self.tabWidget.setGeometry(QtCore.QRect(0, 0, 800, 425))\r
+        self.tabWidget.setStyleSheet("None")\r
+        self.tabWidget.setObjectName("tabWidget")\r
+        self.tab_5 = QtGui.QWidget()\r
+        self.tab_5.setObjectName("tab_5")\r
+        self.button_reload = QtGui.QPushButton(self.tab_5)\r
+        self.button_reload.setGeometry(QtCore.QRect(400, 270, 391, 70))\r
+        self.button_reload.setMinimumSize(QtCore.QSize(0, 0))\r
+        self.button_reload.setObjectName("button_reload")\r
+        self.combo_box_max_rows = QtGui.QComboBox(self.tab_5)\r
+        self.combo_box_max_rows.setGeometry(QtCore.QRect(150, 270, 211, 70))\r
+        self.combo_box_max_rows.setObjectName("combo_box_max_rows")\r
+        self.label = QtGui.QLabel(self.tab_5)\r
+        self.label.setGeometry(QtCore.QRect(10, 280, 121, 50))\r
+        self.label.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))\r
+        self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)\r
+        self.label.setObjectName("label")\r
+        self.table_summary = QtGui.QTableWidget(self.tab_5)\r
+        self.table_summary.setGeometry(QtCore.QRect(0, 0, 794, 251))\r
+        self.table_summary.setStyleSheet("None")\r
+        self.table_summary.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)\r
+        self.table_summary.setAlternatingRowColors(False)\r
+        self.table_summary.setRowCount(4)\r
+        self.table_summary.setColumnCount(4)\r
+        self.table_summary.setObjectName("table_summary")\r
+        self.table_summary.setColumnCount(4)\r
+        self.table_summary.setRowCount(4)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_summary.setHorizontalHeaderItem(0, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_summary.setHorizontalHeaderItem(1, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_summary.setHorizontalHeaderItem(2, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_summary.setHorizontalHeaderItem(3, item)\r
+        self.table_summary.horizontalHeader().setVisible(False)\r
+        self.table_summary.horizontalHeader().setDefaultSectionSize(150)\r
+        self.table_summary.verticalHeader().setVisible(False)\r
+        self.tabWidget.addTab(self.tab_5, "")\r
+        self.tab = QtGui.QWidget()\r
+        self.tab.setObjectName("tab")\r
+        self.table_hourly = QtGui.QTableWidget(self.tab)\r
+        self.table_hourly.setGeometry(QtCore.QRect(0, 0, 794, 345))\r
+        self.table_hourly.setStyleSheet("None")\r
+        self.table_hourly.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)\r
+        self.table_hourly.setAlternatingRowColors(False)\r
+        self.table_hourly.setRowCount(8)\r
+        self.table_hourly.setColumnCount(4)\r
+        self.table_hourly.setObjectName("table_hourly")\r
+        self.table_hourly.setColumnCount(4)\r
+        self.table_hourly.setRowCount(8)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_hourly.setHorizontalHeaderItem(0, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_hourly.setHorizontalHeaderItem(1, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_hourly.setHorizontalHeaderItem(2, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_hourly.setHorizontalHeaderItem(3, item)\r
+        self.table_hourly.horizontalHeader().setVisible(False)\r
+        self.table_hourly.horizontalHeader().setDefaultSectionSize(150)\r
+        self.table_hourly.verticalHeader().setVisible(False)\r
+        self.tabWidget.addTab(self.tab, "")\r
+        self.tab_2 = QtGui.QWidget()\r
+        self.tab_2.setObjectName("tab_2")\r
+        self.table_daily = QtGui.QTableWidget(self.tab_2)\r
+        self.table_daily.setGeometry(QtCore.QRect(0, 0, 794, 345))\r
+        self.table_daily.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)\r
+        self.table_daily.setAlternatingRowColors(False)\r
+        self.table_daily.setRowCount(8)\r
+        self.table_daily.setColumnCount(4)\r
+        self.table_daily.setObjectName("table_daily")\r
+        self.table_daily.setColumnCount(4)\r
+        self.table_daily.setRowCount(8)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_daily.setHorizontalHeaderItem(0, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_daily.setHorizontalHeaderItem(1, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_daily.setHorizontalHeaderItem(2, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_daily.setHorizontalHeaderItem(3, item)\r
+        self.table_daily.horizontalHeader().setVisible(False)\r
+        self.table_daily.horizontalHeader().setDefaultSectionSize(150)\r
+        self.table_daily.verticalHeader().setVisible(False)\r
+        self.tabWidget.addTab(self.tab_2, "")\r
+        self.tab_3 = QtGui.QWidget()\r
+        self.tab_3.setObjectName("tab_3")\r
+        self.table_weekly = QtGui.QTableWidget(self.tab_3)\r
+        self.table_weekly.setGeometry(QtCore.QRect(0, 0, 794, 345))\r
+        self.table_weekly.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)\r
+        self.table_weekly.setAlternatingRowColors(False)\r
+        self.table_weekly.setRowCount(8)\r
+        self.table_weekly.setColumnCount(4)\r
+        self.table_weekly.setObjectName("table_weekly")\r
+        self.table_weekly.setColumnCount(4)\r
+        self.table_weekly.setRowCount(8)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_weekly.setHorizontalHeaderItem(0, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_weekly.setHorizontalHeaderItem(1, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_weekly.setHorizontalHeaderItem(2, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_weekly.setHorizontalHeaderItem(3, item)\r
+        self.table_weekly.horizontalHeader().setVisible(False)\r
+        self.table_weekly.horizontalHeader().setDefaultSectionSize(150)\r
+        self.table_weekly.verticalHeader().setVisible(False)\r
+        self.tabWidget.addTab(self.tab_3, "")\r
+        self.tab_4 = QtGui.QWidget()\r
+        self.tab_4.setObjectName("tab_4")\r
+        self.table_monthly = QtGui.QTableWidget(self.tab_4)\r
+        self.table_monthly.setGeometry(QtCore.QRect(0, 0, 794, 345))\r
+        self.table_monthly.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)\r
+        self.table_monthly.setAlternatingRowColors(False)\r
+        self.table_monthly.setRowCount(8)\r
+        self.table_monthly.setColumnCount(4)\r
+        self.table_monthly.setObjectName("table_monthly")\r
+        self.table_monthly.setColumnCount(4)\r
+        self.table_monthly.setRowCount(8)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_monthly.setHorizontalHeaderItem(0, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_monthly.setHorizontalHeaderItem(1, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_monthly.setHorizontalHeaderItem(2, item)\r
+        item = QtGui.QTableWidgetItem()\r
+        self.table_monthly.setHorizontalHeaderItem(3, item)\r
+        self.table_monthly.horizontalHeader().setVisible(False)\r
+        self.table_monthly.horizontalHeader().setDefaultSectionSize(150)\r
+        self.table_monthly.verticalHeader().setVisible(False)\r
+        self.tabWidget.addTab(self.tab_4, "")\r
+        MainWindow.setCentralWidget(self.centralwidget)\r
+        self.menubar = QtGui.QMenuBar(MainWindow)\r
+        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))\r
+        self.menubar.setObjectName("menubar")\r
+        self.menuAbout = QtGui.QMenu(self.menubar)\r
+        self.menuAbout.setObjectName("menuAbout")\r
+        MainWindow.setMenuBar(self.menubar)\r
+        self.actionAbout = QtGui.QAction(MainWindow)\r
+        self.actionAbout.setObjectName("actionAbout")\r
+        self.actionDatabaseInfo = QtGui.QAction(MainWindow)\r
+        self.actionDatabaseInfo.setObjectName("actionDatabaseInfo")\r
+        self.actionEmptyDatabase = QtGui.QAction(MainWindow)\r
+        self.actionEmptyDatabase.setObjectName("actionEmptyDatabase")\r
+        self.menuAbout.addAction(self.actionDatabaseInfo)\r
+        self.menuAbout.addAction(self.actionEmptyDatabase)\r
+        self.menuAbout.addAction(self.actionAbout)\r
+        self.menubar.addAction(self.menuAbout.menuAction())\r
+\r
+        self.retranslateUi(MainWindow)\r
+        self.tabWidget.setCurrentIndex(0)\r
+        QtCore.QMetaObject.connectSlotsByName(MainWindow)\r
+\r
+    def retranslateUi(self, MainWindow):\r
+        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "NetStory", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.button_reload.setText(QtGui.QApplication.translate("MainWindow", "Refresh tables", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.label.setText(QtGui.QApplication.translate("MainWindow", "Max rows:", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_summary.setSortingEnabled(False)\r
+        self.table_summary.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("MainWindow", "Description", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_summary.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("MainWindow", "Download", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_summary.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("MainWindow", "Upload", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_summary.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("MainWindow", "Total", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), QtGui.QApplication.translate("MainWindow", "Main", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_hourly.setSortingEnabled(True)\r
+        self.table_hourly.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("MainWindow", "Hour", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_hourly.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("MainWindow", "Download", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_hourly.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("MainWindow", "Upload", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_hourly.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("MainWindow", "Total", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QtGui.QApplication.translate("MainWindow", "Hourly", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_daily.setSortingEnabled(True)\r
+        self.table_daily.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("MainWindow", "Date", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_daily.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("MainWindow", "Download", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_daily.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("MainWindow", "Upload", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_daily.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("MainWindow", "Total", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QtGui.QApplication.translate("MainWindow", "Daily", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_weekly.setSortingEnabled(True)\r
+        self.table_weekly.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("MainWindow", "Week", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_weekly.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("MainWindow", "Download", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_weekly.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("MainWindow", "Upload", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_weekly.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("MainWindow", "Total", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), QtGui.QApplication.translate("MainWindow", "Weekly", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_monthly.setSortingEnabled(True)\r
+        self.table_monthly.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("MainWindow", "Month", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_monthly.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("MainWindow", "Download", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_monthly.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("MainWindow", "Upload", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.table_monthly.horizontalHeaderItem(3).setText(QtGui.QApplication.translate("MainWindow", "Total", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), QtGui.QApplication.translate("MainWindow", "Monthly", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "Menu", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.actionDatabaseInfo.setText(QtGui.QApplication.translate("MainWindow", "Database info", None, QtGui.QApplication.UnicodeUTF8))\r
+        self.actionEmptyDatabase.setText(QtGui.QApplication.translate("MainWindow", "Empty database", None, QtGui.QApplication.UnicodeUTF8))\r
+\r
diff --git a/src/opt/netstory/netstoryd.py b/src/opt/netstory/netstoryd.py
new file mode 100644 (file)
index 0000000..7a600cd
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+# This file is part of NetStory.
+# Author: Jere Malinen <jeremmalinen@gmail.com>
+  
+
+from datetime import datetime
+import os
+import time
+
+import gconf
+
+import settings
+
+
+SLEEP_TIME = 3600
+# prevents from writing e.g. 22:49:59 and 23:00:00 
+# (case when time has changed during sleep)
+NEXT_SLEEP_ATLEAST = 1800  
+# prevents from writing first line e.g. 22:59:30 and next would be 23:00:00
+# (case starting daemon hh:59:00-hh:59:59)
+FIRST_SLEEP_ATLEAST = 60   
+
+
+def log_loop():
+    try:
+        f = open(settings.DATA, 'a')
+        print 'Writing to: ' + settings.DATA
+        first = True
+        while 1:
+            next_sleep = SLEEP_TIME - time.time() % SLEEP_TIME
+            if next_sleep > NEXT_SLEEP_ATLEAST or \
+               (first and next_sleep > FIRST_SLEEP_ATLEAST):
+                download, upload = read_counters()
+                write_data(f, download, upload)
+                first = False
+            time.sleep(next_sleep) #sleep until next interval
+    except KeyboardInterrupt:
+        print 'Stopped'
+
+def read_counters():
+    client = gconf.client_get_default()
+    download = client.get_string(settings.GPRS_HOME_DOWNLOAD)
+    upload = client.get_string(settings.GPRS_HOME_UPLOAD)
+    return (download, upload)
+    
+def write_data(f, download, upload):
+    if check(download) and check(upload):
+        f.write('%s,%s,%s\n' % 
+                (datetime.now().strftime(settings.DATA_TIME_FORMAT), 
+                download, upload))
+        f.flush()
+        os.fsync(f.fileno())
+        
+def check(value='1'):
+    try:
+        int(value)
+        return True
+    except TypeError, e:
+        print e
+    except ValueError, e:
+        print e
+    return False
+
+if __name__ == "__main__":
+    if not os.path.exists(settings.DIR):
+        os.mkdir(settings.DIR)
+    log_loop()
diff --git a/src/opt/netstory/settings.py b/src/opt/netstory/settings.py
new file mode 100644 (file)
index 0000000..55e3758
--- /dev/null
@@ -0,0 +1,13 @@
+# This file is part of NetStory.
+# Author: Jere Malinen <jeremmalinen@gmail.com>
+
+
+import os
+
+
+HOME = os.getenv('HOME')
+DIR = HOME + '/.netstory/'
+DATA = DIR + 'data.csv'
+DATA_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
+GPRS_HOME_DOWNLOAD = '/system/osso/connectivity/network_type/GPRS/gprs_home_rx_bytes'
+GPRS_HOME_UPLOAD = '/system/osso/connectivity/network_type/GPRS/gprs_home_tx_bytes'
\ No newline at end of file
diff --git a/src/usr/share/applications/hildon/netstory.desktop b/src/usr/share/applications/hildon/netstory.desktop
new file mode 100644 (file)
index 0000000..40ac4d3
--- /dev/null
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Version=1.0.0
+Encoding=UTF-8
+Name=NetStory
+Comment=Hourly, daily, weekly and monthly network traffic tables
+Exec=/opt/netstory/netstory.py
+Icon=netstory
+X-Icon-path=/usr/share/icons
+X-Window-Icon=netstory
+Type=Application
+StartupWMClass=NetStory
\ No newline at end of file
diff --git a/src/usr/share/icons/hicolor/48x48/hildon/netstory.png b/src/usr/share/icons/hicolor/48x48/hildon/netstory.png
new file mode 100644 (file)
index 0000000..252c5c6
Binary files /dev/null and b/src/usr/share/icons/hicolor/48x48/hildon/netstory.png differ
diff --git a/welcome b/welcome
deleted file mode 100644 (file)
index e69de29..0000000