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