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')
46 self.generate_traffic_tables()
48 def change_max_rows(self):
50 self.max_rows = int(self.ui.combo_box_max_rows.currentText())
52 self.max_rows = 999999999 # should be as good as unlimited
55 QtGui.QMessageBox.about(self, 'About', 'NetStory consists of two '\
56 'parts: a daemon that records network data counters in '\
57 'background and this GUI application to view hourly, daily, '\
58 'weekly and monthly net traffics.\n\n'\
59 'Currently NetStory records '\
60 'only "Home network data counter".\n\nNote that some numbers '\
61 'might be inaccurate and probably will be if you change date '\
62 'or time or clear data counter.')
64 def show_db_info(self):
66 db_size = os.path.getsize(self.file)
68 QtGui.QMessageBox.about(self, 'Error', str(e))
71 size = str(db_size / 1000) + ' kB'
73 size = str(db_size) + ' B'
74 QtGui.QMessageBox.about(self, 'Database info',
75 'Records: %d\nSize: %s' % (len(self.datas) - 1, size))
78 reply = QtGui.QMessageBox.question(self, 'Confirmation',
79 "Are you absolutely sure that you want to empty database?",
80 QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
81 if reply == QtGui.QMessageBox.Yes:
83 f = open(self.file, 'w')
85 download, upload = netstoryd.read_counters()
86 netstoryd.write_data(f, download, upload)
89 QtGui.QMessageBox.about(self, 'Error', str(e))
91 self.generate_traffic_tables()
93 def generate_traffic_tables(self):
94 self.file = settings.DATA
96 for i, value in [(1, 5), (2, 33), (3, 60), (4, 90), (5, 100)]:
98 if not self.read_data():
100 self._append_latest_traffic_status()
101 if len(self.datas) < 2:
102 self._cancel_and_show_message('Try again later',
103 "Unfortunately there isn't enough data in the "\
104 "database yet. Try again after few minutes.")
107 self._generate_hourly()
109 self._generate_daily()
111 self._generate_weekly()
112 self._generate_monthly()
113 self._generate_summary()
115 if self.progress.wasCanceled():
117 self.progress.setValue(value)
118 QtCore.QCoreApplication.processEvents()
120 self.progress.setValue(100)
121 self.progress.reset()
126 f = open(self.file, 'r')
128 QtCore.QCoreApplication.processEvents()
129 if self._if_canceled():
132 parts = line.split(',')
134 self.datas.append(TrafficLogLine(parts[0], parts[1],
137 print 'Error in: %s (%s)' % (self.file, str(e))
138 except ValueError, e:
139 print 'Error in: %s (%s)' % (self.file, str(e))
141 self._cancel_and_show_message('Error', str(e))
145 def _cancel_and_show_message(self, title, message):
146 self.progress.cancel()
147 QtGui.QMessageBox.about(self, title, message)
148 QtCore.QCoreApplication.processEvents()
150 def _if_canceled(self):
151 """Checks cheaply from long loop if Cancel was pushed."""
153 if self.loop % 500 == 0:
154 QtCore.QCoreApplication.processEvents()
155 if self.progress.wasCanceled():
159 def _append_latest_traffic_status(self):
161 download, upload = netstoryd.read_counters()
162 if netstoryd.check(download) and netstoryd.check(upload):
163 now = datetime.now().strftime(settings.DATA_TIME_FORMAT)
164 self.datas.append(TrafficLogLine(now, download, upload))
166 QtGui.QMessageBox.about(self, 'Problem', "Your N900 " \
167 "isn't currently probably compatible with NetStory " \
168 "(only PR1.2 is tested)")
170 print 'Windows testing: %s' % str(e)
172 def _generate_hourly(self):
174 for i, data in enumerate(self.datas[1:]):
175 if self._if_canceled():
177 traffic_row = TrafficRow()
178 traffic_row.calculate_between_log_lines(self.datas[i], data)
179 self.hourly.append(traffic_row)
181 table = self.ui.table_hourly
182 self._init_table(table, len(self.hourly))
184 for i, hour in enumerate(reversed(self.hourly[-self.max_rows:])):
185 if self._if_canceled():
187 if hour.start_time.day != hour.end_time.day and \
188 hour.end_time.hour != 0:
189 # Phone has been off or there is some other reason why
190 # end time date is different. Anyhow show end time with date.
191 end_time = hour.end_time.strftime('%H:%M (%d.%m.%Y)')
193 end_time = hour.end_time.strftime('%H:%M')
194 hour.set_description_cell('%s - %s' %
195 (hour.start_time.strftime('%d.%m.%Y %H:%M'),
197 # This is expensive operation if there are thousands of lines
198 self._set_table_row(table, i, hour)
200 def _generate_daily(self):
202 for hour in self.hourly:
203 if self._if_canceled():
205 key = hour.start_time.isocalendar()
206 self.daily[key] = self.daily.get(key, TrafficRow())
207 self.daily[key].add(hour)
209 table = self.ui.table_daily
210 self._init_table(table, len(self.daily))
212 keys = self.daily.keys()
215 for i, key in enumerate(reversed(keys[-self.max_rows:])):
216 if self._if_canceled():
218 day = self.daily[key]
220 day.set_representation()
221 day.set_description_cell(\
222 day.start_time.strftime('%d.%m.%Y'), i)
223 self._set_table_row(table, i, day)
225 def _generate_weekly(self):
227 for day in self.daily.itervalues():
228 # Following works beatifully,
229 # because: datetime(2011, 1, 1).isocalendar()[0] == 2010
230 key = '%d / %02d' % (day.start_time.isocalendar()[0],
231 day.start_time.isocalendar()[1])
232 self.weekly[key] = self.weekly.get(key, TrafficRow())
233 self.weekly[key].add(day)
235 table = self.ui.table_weekly
236 self._init_table(table, len(self.weekly))
238 keys = self.weekly.keys()
241 for i, key in enumerate(reversed(keys[-self.max_rows:])):
242 week = self.weekly[key]
244 week.set_representation()
245 if week.end_time.isocalendar()[1] != \
246 week.start_time.isocalendar()[1]:
247 # it's probably following situation:
248 # e.g. start time is 7.6.2010 0:00 (week 23)
249 # and end time is 14.6.2010 0:00 (week 24)
250 week.end_time -= timedelta(days=1)
251 week.set_description_cell('%d (%s - %s)' %
252 (week.start_time.isocalendar()[1],
253 week.start_time.strftime('%d.%m'),
254 week.end_time.strftime('%d.%m.%Y')), i)
255 self._set_table_row(table, i, week)
257 def _generate_monthly(self):
259 for day in self.daily.itervalues():
260 key = day.start_time.strftime('%Y %m')
261 self.monthly[key] = self.monthly.get(key, TrafficRow())
262 self.monthly[key].add(day)
264 table = self.ui.table_monthly
265 self._init_table(table, len(self.monthly))
267 keys = self.monthly.keys()
270 for i, key in enumerate(reversed(keys[-self.max_rows:])):
271 month = self.monthly[key]
273 month.set_representation()
274 month.set_description_cell(month.start_time.strftime('%Y: %B'), i)
275 self._set_table_row(table, i, month)
277 def _generate_summary(self):
278 table = self.ui.table_summary
279 self._init_table(table, 5)
281 for i, string, traffic_rows in [(0, 'Hourly average', self.hourly),
282 (1, 'Daily average', self.daily.itervalues()),
283 (2, 'Weekly average', self.weekly.itervalues()),
284 (3, 'Monthly average', self.monthly.itervalues())]:
285 averages = self.calculate_averages(traffic_rows)
286 average = TrafficRow()
287 average.download_bytes = averages['download']
288 average.upload_bytes = averages['upload']
289 average.total_bytes = averages['total']
290 average.set_representation()
291 average.set_description_cell(string, i)
292 self._set_table_row(table, i, average)
294 totals = self.calculate_total(self.monthly.itervalues())
296 total.download_bytes = sum(totals['download'])
297 total.upload_bytes = sum(totals['upload'])
298 total.total_bytes = sum(totals['total'])
299 total.set_representation()
300 total.set_description_cell(\
301 self.datas[0].time.strftime('Total since %d.%m.%Y %H:%M'), 0)
302 self._set_table_row(table, 4, total)
304 def _init_table(self, table, rows):
305 table.clearContents()
307 if rows < self.max_rows:
308 table.setRowCount(rows)
310 table.setRowCount(self.max_rows)
311 table.horizontalHeader().resizeSection(0, 315)
312 table.horizontalHeader().setVisible(True)
314 def _set_table_row(self, table, row_number, traffic_row):
315 table.setItem(row_number, 0,
316 SortTableWidgetItem(traffic_row.description,
317 traffic_row.sort_key))
318 table.setItem(row_number, 1,
319 SortTableWidgetItem(traffic_row.download_string,
320 traffic_row.download_bytes))
321 table.setItem(row_number, 2,
322 SortTableWidgetItem(traffic_row.upload_string,
323 traffic_row.upload_bytes))
324 table.setItem(row_number, 3,
325 SortTableWidgetItem(traffic_row.total_string,
326 traffic_row.total_bytes))
328 def calculate_averages(self, traffic_rows=[]):
329 total = self.calculate_total(traffic_rows)
331 for key, l in total.items():
332 averages[key] = sum(l) / len(l)
335 def calculate_total(self, traffic_rows=[]):
336 total = {'download': [], 'upload': [], 'total': []}
337 for traffic_row in traffic_rows:
338 total['download'].append(traffic_row.download_bytes)
339 total['upload'].append(traffic_row.upload_bytes)
340 total['total'].append(traffic_row.total_bytes)
344 class TrafficLogLine:
345 def __init__(self, time='', download='', upload=''):
346 #self.time = datetime.strptime(time.strip(), settings.DATA_TIME_FORMAT)
347 # this is about 4 times faster than above
348 self.time = datetime(int(time[0:4]), int(time[5:7]), int(time[8:10]),
349 int(time[11:13]), int(time[14:16]), int(time[17:19]))
350 self.download = int(download.strip())
351 self.upload = int(upload.strip())
356 self.download_bytes = 0
357 self.upload_bytes = 0
359 self.start_time = None
362 def calculate_between_log_lines(self, start_data, end_data):
363 self.start_time = start_data.time
364 self.end_time = end_data.time
365 self.download_bytes = self.traffic_difference(start_data.download, \
367 self.upload_bytes = self.traffic_difference(start_data.upload, \
370 self.set_representation()
372 def traffic_difference(self, start, end):
376 return end #This value is probably inaccurate compared to reality
379 self.total_bytes = self.download_bytes + self.upload_bytes
381 def set_representation(self):
382 self.download_string = self.bytes_representation(self.download_bytes)
383 self.upload_string = self.bytes_representation(self.upload_bytes)
384 self.total_string = self.bytes_representation(self.total_bytes)
386 def bytes_representation(self, number):
388 s = '%.1f MB' % round(number / 1000000.0, 1)
390 s = '%d kB' % round(number / 1000.0, 0)
392 s = '%d B' % (number)
395 def add(self, other):
397 Adds traffic values from other row into self
398 and also sets start and end times properly.
400 self.download_bytes += other.download_bytes
401 self.upload_bytes += other.upload_bytes
402 if not self.start_time or other.start_time < self.start_time:
403 self.start_time = other.start_time
404 if not self.end_time or other.end_time > self.end_time:
405 self.end_time = other.end_time
407 def set_description_cell(self, description, sort_key):
408 self.description = description
409 self.sort_key = sort_key
412 class SortTableWidgetItem(QtGui.QTableWidgetItem):
413 def __init__(self, text, sort_key):
414 # call custom constructor with UserType item type
415 QtGui.QTableWidgetItem.__init__(self, text, \
416 QtGui.QTableWidgetItem.UserType)
417 self.sort_key = sort_key
419 # Qt uses a simple < check for sorting items,
420 # override this to use the sort_key
421 def __lt__(self, other):
422 return self.sort_key < other.sort_key
425 if __name__ == "__main__":
426 app = QtGui.QApplication(sys.argv)
427 dataform = DataForm()
429 sys.exit(app.exec_())