Fixed unresolved usage of formencode.Invalid (one time with typo).
[philipp/winterrodeln/wrpylib.git] / wrpylib / wrmwcache.py
1 #!/usr/bin/python2.7
2 # -*- coding: iso-8859-15 -*-
3 # $Id$
4 # $HeadURL$
5 """Contains functions that maintain/update the cache tables."""
6 from sqlalchemy import schema
7 from sqlalchemy.sql import select
8 from sqlalchemy.sql.expression import func as sqlfunc
9 from osgeo import ogr
10 import formencode
11 from wrpylib import mwdb, wrmwdb, mwmarkup, wrmwmarkup
12
13
14 class UpdateCacheError(RuntimeError):
15     pass
16
17
18 def update_wrsledruncache(connection):
19     """Updates the wrsledruncache table from the wiki. If convert errors occur, an UpdateCacheError exception
20     is raised. No other exception type should be raised under normal circumstances.
21     
22     >>> from sqlalchemy.engine import create_engine
23     >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1')
24     >>> update_wrsledruncache(engine.connect())
25     """
26     metadata = schema.MetaData()
27     wrsledruncache = wrmwdb.wrsledruncache_table(metadata)
28     page = mwdb.page_table(metadata)
29     categorylinks = mwdb.categorylinks_table(metadata)
30     revision = mwdb.revision_table(metadata)
31     text = mwdb.text_table(metadata)
32
33     transaction = connection.begin()
34
35     # Query all sled runs
36     q = select([page, categorylinks, revision, text], (page.c.page_latest==revision.c.rev_id) & (text.c.old_id==revision.c.rev_text_id) & (categorylinks.c.cl_from==page.c.page_id) & (categorylinks.c.cl_to==u'Rodelbahn'))
37     sledrun_pages = connection.execute(q)
38     # Original SQL:
39     # sql = u"select page_id, rev_id, old_id, page_title, old_text, 'In_Arbeit' in (select cl_to from categorylinks where cl_from=page_id) as under_construction from page, revision, text, categorylinks where page_latest=rev_id and old_id=rev_text_id and cl_from=page_id and cl_to='Rodelbahn' order by page_title"
40     
41     # Delete all existing entries in wrsledruncache
42     # We rely on transactions MySQL InnoDB
43     connection.execute(wrsledruncache.delete())
44     
45     # Refill wrsledruncache table
46     for sledrun_page in sledrun_pages:
47         try: 
48             start, end, sledrun = wrmwmarkup.rodelbahnbox_to_sledrun(sledrun_page.old_text)
49             sledrun.page_id = sledrun_page.page_id
50             sledrun.page_title = sledrun_page.page_title
51             sledrun.under_construction = connection.execute(select([categorylinks], (categorylinks.c.cl_from==sledrun_page.page_id) & (categorylinks.c.cl_to == u'In_Arbeit')).alias('x').count()).fetchone()[0] > 0 # It would be better to do this in the query above
52             connection.execute(wrsledruncache.insert(sledrun.__dict__))
53         except (RuntimeError, formencode.Invalid) as e:
54             transaction.rollback()
55             error_msg = u"Error at sled run '{0}': {1}".format(sledrun_page.page_title, unicode(e))
56             raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
57     transaction.commit()
58
59
60 def update_wrinncache(connection):
61     """Updates the wrinncache table from the wiki. If convert errors occur, an UpdateCacheError exception
62     is raised. No other exception type should be raised under normal circumstances.
63     
64     >>> from sqlalchemy.engine import create_engine
65     >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1')
66     >>> update_wrinncache(engine.connect())
67     """
68     metadata = schema.MetaData()
69     wrinncache = wrmwdb.wrinncache_table(metadata)
70     page = mwdb.page_table(metadata)
71     categorylinks = mwdb.categorylinks_table(metadata)
72     revision = mwdb.revision_table(metadata)
73     text = mwdb.text_table(metadata)
74
75     transaction = connection.begin()
76
77     # Query all inns
78     q = select([page, categorylinks, revision, text], (page.c.page_latest==revision.c.rev_id) & (text.c.old_id==revision.c.rev_text_id) & (categorylinks.c.cl_from==page.c.page_id) & (categorylinks.c.cl_to==u'Gasthaus'))
79     inn_pages = connection.execute(q)
80         
81     # Delete all existing entries in wrinncache
82     # We rely on transactions MySQL InnoDB
83     connection.execute(wrinncache.delete())
84         
85     # Refill wrinncache table
86     for inn_page in inn_pages:
87         try: 
88             start, end, inn = wrmwmarkup.gasthausbox_to_inn(inn_page.old_text)
89             inn.page_id = inn_page.page_id
90             inn.page_title = inn_page.page_title
91             inn.under_construction = connection.execute(select([categorylinks], (categorylinks.c.cl_from==inn_page.page_id) & (categorylinks.c.cl_to == u'In_Arbeit')).alias('x').count()).fetchone()[0] > 0 # It would be better to do this in the query above
92             connection.execute(wrinncache.insert(inn.__dict__))
93         except (RuntimeError, formencode.Invalid) as e:
94             transaction.rollback()
95             error_msg = u"Error as inn '{0}': {1}".format(inn_page.page_title, unicode(e))
96             raise UpdateCacheError(error_msg, inn_page.page_title, e)
97     transaction.commit()
98
99
100 def update_wrreportcache(connection, page_id=None):
101     """Updates the wrreportcache table.
102     :param connection: sqlalchemy connection
103     :param page_id: Updates only the reportcache table for the sledrun described on the Winterrodeln wiki page
104         with the specified page_id. Use None for this parameter to update the whole table.
105
106     >>> from sqlalchemy.engine import create_engine
107     >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1')
108     >>> update_wrreportcache(engine.connect())
109     """
110     metadata = schema.MetaData()
111     wrreport = wrmwdb.wrreport_table(metadata)
112     wrreportcache = wrmwdb.wrreportcache_table(metadata)
113     transaction = connection.begin()
114
115     # Delete the datasets we are going to update
116     sql_del = wrreportcache.delete()
117     if not page_id is None: sql_del = sql_del.where(wrreportcache.c.page_id == page_id)
118     connection.execute(sql_del)
119
120     def insert_row(connection, rowlist):
121         if len(rowlist) == 0: return
122         # Insert the report
123         row = dict(rowlist[0])
124         connection.execute(wrreportcache.insert(values=row))
125
126     # Select the rows to update
127     sql = 'select page_id, page_title, wrreport.id as report_id, date_report, `condition`, description, author_name, if(author_userid is null, null, author_username) as author_username from wrreport where {0}`condition` is not null and date_invalid > now() and delete_date is null order by page_id, date_report desc, date_entry desc'.format('' if page_id is None else 'page_id={0} and '.format(page_id))
128     cursor = connection.execute(sql)
129     page_id = None
130     rowlist = []
131     for row in cursor:
132         if row.page_id != page_id:
133             insert_row(connection, rowlist)
134             page_id = row.page_id
135             rowlist = []
136         rowlist.append(row)
137     insert_row(connection, rowlist)
138     transaction.commit()
139
140
141 def update_wrmapcache(connection):
142     """Updates the wrmappointcache and wrmappathcache tables from the wiki. If convert errors occur, an UpdateCacheError exception
143     is raised. No other exception type should be raised under normal circumstances.
144     
145     >>> from sqlalchemy.engine import create_engine
146     >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1')
147     >>> # or: engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1&passwd=XXXXX')
148     >>> update_wrmapcache(engine.connect())
149     """
150     metadata = schema.MetaData()
151     page = mwdb.page_table(metadata)
152     categorylinks = mwdb.categorylinks_table(metadata)
153     revision = mwdb.revision_table(metadata)
154     text = mwdb.text_table(metadata)
155
156     transaction = connection.begin()
157
158     # Query all sledruns
159     q = select([page, categorylinks, revision, text], (page.c.page_latest==revision.c.rev_id) & (text.c.old_id==revision.c.rev_text_id) & (categorylinks.c.cl_from==page.c.page_id) & (categorylinks.c.cl_to==u'Rodelbahn'))
160     sledrun_pages = connection.execute(q)
161     # Original SQL:
162     # sql = u"select page_id, rev_id, old_id, page_title, old_text, 'In_Arbeit' in (select cl_to from categorylinks where cl_from=page_id) as under_construction from page, revision, text, categorylinks where page_latest=rev_id and old_id=rev_text_id and cl_from=page_id and cl_to='Rodelbahn' order by page_title"
163     
164     # Delete all existing entries in wrmappointcache
165     # We rely on transactions MySQL InnoDB
166     connection.execute('delete from wrmappointcache')
167     connection.execute('delete from wrmappathcache')
168     
169     # Refill wrmappointcache and wrmappathcache tables
170     for sledrun_page in sledrun_pages:
171         try: 
172             start, content, endtag, end = mwmarkup.find_tag(sledrun_page.old_text, 'wrmap')
173             if content is None:
174                 continue
175             geojson = wrmwmarkup.parse_wrmap(sledrun_page.old_text[start:end])
176
177             for feature in geojson['features']:
178                 properties = feature['properties']
179                 coordinates = feature['geometry']['coordinates']
180
181                 # Points
182                 if properties['type'] in wrmwmarkup.WRMAP_POINT_TYPES:
183                     lon, lat = coordinates
184                     label = properties.get('name')
185                     point_types = {u'gasthaus': u'hut', u'haltestelle': u'busstop', u'parkplatz': u'carpark', u'achtung': u'warning', u'foto': u'photo', u'verleih': u'rental', u'punkt': u'point'}
186                     point_type = point_types[properties['type']]
187                     sql = u'insert into wrmappointcache (page_id, type, point, label) values (%s, %s, POINT(%s, %s), %s)'
188                     connection.execute(sql, (sledrun_page.page_id, point_type, lon, lat, label))
189
190                 # Paths
191                 elif properties['type'] in wrmwmarkup.WRMAP_LINE_TYPES:
192                     path_types = {u'rodelbahn': u'sledrun', u'gehweg': u'walkup', u'alternative': u'alternative', u'lift': u'lift', u'anfahrt': u'recommendedcarroute', u'linie': u'line'}
193                     path_type = path_types[properties['type']]
194                     path = u", ".join(["{0} {1}".format(lon, lat) for lon, lat in coordinates])
195                     path = u'LineString({0})'.format(path)
196                     if path_type == u'recommendedcarroute': continue
197                     sql = u'insert into wrmappathcache (path, page_id, type) values (GeomFromText(%s), %s, %s)'
198                     connection.execute(sql, (path, sledrun_page.page_id, path_type))
199
200                 else:
201                     raise RuntimeError(u'Unknown feature type {0}'.format(properties['type']))
202         except RuntimeError as e:
203             error_msg = u"Error at sledrun '{0}': {1}".format(sledrun_page.page_title, unicode(e))
204             transaction.rollback()
205             raise UpdateCacheError(error_msg, sledrun_page.page_title, e)
206     transaction.commit()
207
208
209 def update_wrregioncache(connection):
210     """Updates the wrregioncache table from the wiki.
211     It relays on the table wrsledruncache to be up-to-date.
212     No exceptions should be raised under normal circumstances.
213     
214     >>> from sqlalchemy.engine import create_engine
215     >>> engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1')
216     >>> # or: engine = create_engine('mysql://philipp@localhost:3306/philipp_winterrodeln_wiki?charset=utf8&use_unicode=1&passwd=XXXXX')
217     >>> update_wrregioncache(engine.connect())
218     """
219     metadata = schema.MetaData()
220     wrregion = wrmwdb.wrregion_table(metadata)
221     wrsledruncache = wrmwdb.wrsledruncache_table(metadata)
222     wrregioncache = wrmwdb.wrregioncache_table(metadata)
223
224     transaction = connection.begin()
225
226     # Delete all existing entries in wrregioncache
227     # We rely on transactions MySQL InnoDB
228     connection.execute(wrregioncache.delete())
229     
230     # Query all combinations of sledruns and regions
231     sel = select([wrregion.c.id.label('region_id'), sqlfunc.AsWKB(wrregion.c.border).label('border'), wrsledruncache.c.page_id, wrsledruncache.c.position_longitude, wrsledruncache.c.position_latitude], sqlfunc.contains(wrregion.c.border, sqlfunc.point(wrsledruncache.c.position_longitude, wrsledruncache.c.position_latitude)))
232     ins = wrregioncache.insert()
233
234     # Refill wrregioncache
235     point = ogr.Geometry(ogr.wkbPoint)
236     result = connection.execute(sel)
237     for row in result:
238         point.SetPoint(0, row.position_longitude, row.position_latitude)
239         if point.Within(ogr.CreateGeometryFromWkb(row.border)):
240             connection.execute(ins.values(region_id=row.region_id, page_id=row.page_id))
241
242     # commit
243     transaction.commit()
244