Departure object changes, tests, small fix
authorFlorian Schweikert <kelvan@logic.at>
Wed, 15 Feb 2012 02:18:14 +0000 (03:18 +0100)
committerFlorian Schweikert <kelvan@logic.at>
Wed, 15 Feb 2012 02:18:14 +0000 (03:18 +0100)
save deltatime for all input types, calculate and save departure
datetime
use assert-is_instance in tests
set gstation at last value in qml, should activate refresh

gotovienna-qml
gotovienna/realtime.py
gotovienna/tests/departures.py
gotovienna/tests/realtime.py
gotovienna/tests/update.py [new file with mode: 0644]
gotovienna/utils.py
qml/MainPage.qml
qml/MapView.qml [new file with mode: 0644]
qml/ResultRealtime.qml
qml/Settings.qml

index 2ba243e..d2dc8fa 100755 (executable)
@@ -151,14 +151,37 @@ class Gui(QObject):
         threading.Thread(target=load_async).start()
 
     def map_departure(self, dep):
+        """ prepare departure list for qml gui
+        """
         dep['lowfloor'] = 1 if dep['lowfloor'] else 0
-        if type(dep['time']) == time:
-            dep['time'] = dep['time'].strftime('%H:%M')
+        dep['realtime'] = 1 if dep['realtime'] else 0
+        dep['time'] = dep['ftime']
         return dep
 
     departuresLoaded = Signal()
 
     @Slot(str)
+    def load_departures_test(self, **args):
+        """ valid args combinations
+            station
+            line, station
+        """
+        def load_async():
+            if args.has_key('station'):
+                if args.has_key('line'):
+                    self.current_departures = map(self.map_departure, \
+                                                  self.itip.get_departures(args['line'], args['station']))
+                    #print self.current_departures
+                    self.departuresLoaded.emit()
+                else:
+                    self.current_departures = map(self.map_departure, \
+                                                  sort_departures(self.itip.get_departures_by_station(station)))
+            else:
+                raise KeyError('Missing valid argument combination')
+
+        threading.Thread(target=load_async).start()
+
+    @Slot(str)
     def load_departures(self, url):
         def load_async():
             self.current_departures = map(self.map_departure, \
index 7bb2773..ad43db2 100644 (file)
@@ -3,8 +3,10 @@
 from gotovienna.BeautifulSoup import BeautifulSoup
 #from urllib2 import urlopen
 from urllib import quote_plus
+# Use urlopen proxy for fake user agent
 from UrlOpener import urlopen
 from datetime import time, datetime, timedelta
+import datetime as date
 import re
 import collections
 from errors import LineNotFoundError, StationNotFoundError
@@ -20,45 +22,25 @@ class Departure(dict):
         self['line'] = line
         self['station'] = station
         self['direction'] = direction
-        self['time'] = time
-        self['lowfloor'] = lowfloor
-
-    def __getitem__(self, *args, **kwargs):
-        if args[0] == 'ftime':
-            # string representation of time/minutes
-            return self.ftime
-        elif args[0] == 'deltatime':
-            # minutes
-            return self.departure_deltatime
-        elif args[0] == 'atime':
-            # time object
-            return self.departure_time
-        return dict.__getitem__(self, *args, **kwargs)
-
-    @property
-    def departure_time(self):
-        """ return time object of departure time
-        """
-        if type(self['time']) == time:
-            return self['time']
+        now = datetime.now()
+        if type(time) == date.time:
+            time = make_datetime(now, time)
+        if type(time) == datetime:
+            # FIXME convert in ModelList
+            self['realtime'] = False
+            self['time'] = (time - now).seconds/60
+            self['departure'] = time
+        elif type(time) == int:
+            # FIXME convert in ModelList
+            self['realtime'] = True
+            self['time'] = time
+            self['departure'] = now + timedelta(minutes=self['time'])
         else:
-            return (datetime.now() + timedelta(0, self['time']) * 60).time()
+            raise ValueError('Wrong type: time')
 
-    @property
-    def departure_deltatime(self):
-        """ return int representing minutes until departure
-        """
-        if type(self['time']) == int:
-            return self['time']
-        else:
-            raise NotImplementedError()
-
-    @property
-    def ftime(self):
-        if type(self['time']) == int:
-            return str(self['time'])
-        elif type(self['time']) == time:
-            return self['time'].strftime('%H:%M')
+        # FIXME convert in ModelList
+        self['ftime'] = str(self['time'])
+        self['lowfloor'] = lowfloor
 
 class ITipParser:
     def __init__(self):
@@ -263,7 +245,7 @@ class ITipParser:
                 t = tim.strip('&nbsp;').split(':')
                 if len(t) == 2 and all(map(lambda x: x.isdigit(), t)):
                     t = map(int, t)
-                    d['time'] = time(*t)
+                    d['time'] = make_datetime(datetime.now(), time(*t))
                 else:
                     # Unexpected content
                     #TODO replace with logger
@@ -274,7 +256,7 @@ class ITipParser:
         return dep
 
     def get_departures(self, url):
-        """ Get list of next departures as Departure object
+        """ Get list of next departures as Departure objects
         """
 
         #TODO parse line name and direction for station site parsing
@@ -301,7 +283,11 @@ class ITipParser:
 
             sleep(0.5)
 
-
+    def get_departures_test(self, line, station):
+        """ replacement for get_departure
+            hide url in higher levels :)
+        """
+        raise NotImplementedError
 
 
 UBAHN, TRAM, BUS, NIGHTLINE, OTHER = range(5)
@@ -361,4 +347,17 @@ def categorize_lines(lines):
         lines.sort(key=get_line_sort_key)
 
     return [(LINE_TYPE_NAMES[key], categorized_lines[key])
-            for key in sorted(categorized_lines)]
+        for key in sorted(categorized_lines)]
+
+def make_datetime(date, time):
+    """ Ugly workaround, immutable datetime ftw -.-
+        If 
+    """
+    if date.hour > time.hour:
+        date = date + timedelta(1)
+    return datetime(year=date.year,
+                    month=date.month,
+                    day=date.day,
+                    hour=time.hour,
+                    minute=time.minute,
+                    second=time.second)
index 9da88d1..0047cff 100644 (file)
@@ -1,4 +1,4 @@
-from nose.tools import assert_equal
+from nose.tools import assert_equal, assert_is_instance
 import sys
 import os
 from datetime import time, datetime
@@ -34,12 +34,12 @@ def test_sort():
 
 def test_atime():
     for dep in departures:
-        assert_equal(time, type(dep['atime']))
+        assert_is_instance(dep['departure'], datetime)
 
 def test_ftime():
     for dep in departures:
-        assert_equal(str, type(dep['ftime']))
+        assert_is_instance(dep['ftime'], str)
 
 def test_deltatime():
     for dep in departures:
-        assert_equal(int, type(dep['deltatime']))
+        assert_is_instance(dep['time'], int)
index 0d3b08a..35dd9e0 100644 (file)
@@ -1,13 +1,22 @@
 # -*- coding: utf-8 -*-
 
-from nose.tools import assert_equal, assert_true, assert_false
+from nose.tools import assert_equal, assert_true, assert_false, assert_is_instance
 import sys
 import os
-from datetime import time
+from datetime import datetime
 sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
 DATAPATH = os.path.join(os.path.dirname(__file__), 'data')
 
+# bananas for the monkey
+class datetime_static(datetime):
+    @classmethod
+    def now(cls):
+        return datetime(2000, 1, 1, 11, 50) 
+
+from gotovienna import realtime
+realtime.datetime = datetime_static
 from gotovienna.realtime import *
+# </bananas>
 
 parser = ITipParser()
 
@@ -22,7 +31,7 @@ stations2 = open(os.path.join(DATAPATH, 'stations2.html'), 'r').read()
 parsed_lines = parser.parse_lines(lines)
 
 def test_lines():
-    assert_equal(dict, type(parsed_lines))
+    assert_is_instance(parsed_lines, dict)
     assert_true(parsed_lines)
 
 def test_line_amount():
@@ -65,14 +74,14 @@ def test_departures_by_station_lowfloor():
 
 def test_departures_by_station_datetime():
     dep = parser.parse_departures_by_station(stationbased)
-    assert_equal(int, type(dep[13]['time']))
-    assert_equal(time, type(dep[14]['time']))
+    assert_is_instance(dep[13]['time'], int)
+    assert_is_instance(dep[14]['departure'], datetime)
     assert_equal(4, dep[3]['time'])
     assert_equal(2, dep[4]['time'])
     assert_equal(18, dep[13]['time'])
     assert_equal('59A', dep[-4]['line'])
     assert_equal('WLB', dep[-1]['line'])
-    assert_equal(time(13, 5), dep[14]['time'])
+    assert_equal(datetime(2000, 1, 1, 13, 5), dep[14]['departure'])
 
 def test_departures():
     dep = parser.parse_departures(line_station)
diff --git a/gotovienna/tests/update.py b/gotovienna/tests/update.py
new file mode 100644 (file)
index 0000000..64bead4
--- /dev/null
@@ -0,0 +1,19 @@
+from nose.tools import assert_true, assert_false
+import sys
+from os import path
+
+DATA = path.join(path.dirname(__file__), 'data')
+HASHFILENAME = 'hashtestfile'
+
+with open(path.join(DATA, HASHFILENAME + '.md5')) as f:
+    HASH = f.read()
+
+sys.path.insert(0, path.dirname(path.dirname(__file__)))
+from gotovienna.update import compare_hash
+
+def test_hash_equal():
+    assert_true(compare_hash(HASH, path.join(DATA, HASHFILENAME)))
+
+def test_hash_not_equal():
+    assert_false(compare_hash('GG' + HASH[2:], path.join(DATA, HASHFILENAME)))
+
index 2a15f6f..51940a6 100644 (file)
@@ -10,7 +10,7 @@ def inblue(x):
     return '\033[94m' + x + '\033[0m'
 
 def sort_departures(dep):
-    print 'sorting ...'
-    d = sorted(dep, lambda x, y: cmp(x['atime'], y['atime']))
-    print map(lambda x: x['atime'], d)
+    #print 'sorting ...'
+    d = sorted(dep, lambda x, y: cmp(x['departure'], y['departure']))
+    #print map(lambda x: x['departure'], d)
     return d
index 4d1947b..16090f0 100644 (file)
@@ -27,13 +27,13 @@ Page {
     function showNearby() {
         console.log("show nearby")
 
-        var stations = nearbyStations
-        stationSelectorModel.clear()
+        var stations = nearbyStations;
+        stationSelectorModel.clear();
         for (var idx in stations) {
-            stationSelectorModel.append({'name': stations[idx]})
+            stationSelectorModel.append({'name': stations[idx]});
         }
 
-        stationSelector.open()
+        stationSelector.open();
     }
 
     Text {
@@ -98,11 +98,11 @@ Page {
 
         onAccepted: {
             realtimeResult.isStation = true
-            realtimeResult.gstation = stationSelectorModel.get(selectedIndex).name
             realtimeResult.gline = ''
             realtimeResult.sourceUrl = ''
             gline.text = ''
             gstation.text = stationSelectorModel.get(selectedIndex).name
+            realtimeResult.gstation = stationSelectorModel.get(selectedIndex).name
             console.log('station to get: ' + realtimeResult.gstation)
         }
     }
@@ -185,14 +185,13 @@ Page {
             gstation.text = stationSheet.currentStation
 
             realtimeResult.gline = stationSheet.currentLine
-            realtimeResult.gstation = stationSheet.currentStation
             realtimeResult.gdirection = stationSheet.currentDirection
             realtimeResult.isStation = false
-
             realtimeResult.sourceUrl = itip.get_directions_url(stationSheet.currentLine, stationSheet.currentDirection, stationSheet.currentStation)
-            console.log('url to get: ' + realtimeResult.sourceUrl)
+            realtimeResult.gstation = stationSheet.currentStation
+            
+            console.debug('url to get: ' + realtimeResult.sourceUrl)
             realtimeResult.refresh()
-
         }
     }
 
diff --git a/qml/MapView.qml b/qml/MapView.qml
new file mode 100644 (file)
index 0000000..ad52fdf
--- /dev/null
@@ -0,0 +1,35 @@
+import QtQuick 1.1
+import Qt 4.7
+import QtMobility.location 1.2
+import com.nokia.meego 1.0
+
+Page {
+    tools: mapTools
+
+    ToolBarLayout {
+        id: mapTools
+        x: 0
+        y: 0
+        ToolIcon { iconId: "toolbar-back"; onClicked: { menu.close(); pageStack.pop(null,false); } }
+    }
+
+    Map {
+        id: map
+        plugin : Plugin {
+            name : "nokia"
+        }
+
+        anchors.fill: parent
+        size.width: parent.width
+        size.height: parent.height
+        zoomLevel: 7
+        //center: positionSource.position.coordinate
+        //objects: t_data.mapObjectsList
+
+
+        onZoomLevelChanged: {
+            console.log("Zoom changed")
+        }
+
+    }
+}
index bf8e695..1ec034a 100644 (file)
@@ -17,13 +17,13 @@ Item {
 
     function refresh() {
         busy = true
-        console.log('refreshing')
+        console.debug('refreshing')
 
         if (isStation) {
-            console.log('station based')
+            console.debug('station based')
             itip.load_station_departures(gstation)
         } else {
-            console.log('one line')
+            console.debug('one line')
             itip.load_departures(sourceUrl)
         }
     }
@@ -33,6 +33,7 @@ Item {
     }
 
     onGstationChanged: {
+        console.debug('gstation changed')
         refresh()
     }
 
index 6bf0d49..87f7f4f 100644 (file)
@@ -22,6 +22,10 @@ Page {
         flickableDirection: Flickable.VerticalFlick
 
         Component.onCompleted: {
+            var updateAvailable = config.checkStationsUpdate();
+            if (updateAvailable) {
+                btnUpdate.color = "green"
+            }
         }
 
         Column {
@@ -53,13 +57,18 @@ Page {
 
                     onCheckedChanged: {
                         var gps = config.setGpsEnabled(checked);
-                        if(gps !== '') {
+                        if (gps !== '') {
                             // Unable to set config
                             console.log(gps);
                             checked=!checked;
                         } else {
                             positionSource.active = checked;
                         }
+                        if (checked) {
+                            positionSource.start();
+                        } else {
+                            positionSource.stop();
+                        }
                     }
                 }
             }