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