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