Fix syntax for older python/sqlite
[opensuse:susefox.git] / pfs / web / plugin-finder.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2007-2008  Canonical Ltd.
4 # Copyright (C)      2009  Wolfgang Rosenauer
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #
20
21 import httplib
22
23 from cStringIO import StringIO
24 from mod_python import apache
25 from xml.dom.xmlbuilder import *
26 from xml.dom.minidom import *
27 import sqlite
28 import os
29 import time
30 import thread
31 import threading
32 import sys
33 import plugindata
34
35 # TestURL: http://localhost/plugin-finder.py?mimetype=%PLUGIN_MIMETYPE%&appID=%APP_ID%&appVersion=%APP_VERSION%&clientOS=%CLIENT_OS%&chromeLocale=%CHROME_LOCALE%&distributionID=%SUSE_VERSION%
36 # example:
37 # http://localhost/plugin-finder.py?mimetype=application%2Fx-shockwave-flash&appID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}&appVersion=2009061000&clientOS=Linux%20x86_64&chromeLocale=de&appRelease=3.5b99&distributionID=11.1
38
39 counter=0
40 docroot=""
41 CACHE_TIME_SEC=3600
42 DB_FILE="../susefox/pfs/web/pfs.sqlite" # relative to docroot
43 ENABLE_MOZILLA_PFS_RESULTS=True
44
45 arch_map=dict()
46 cache_con_map=dict()
47
48 arch_map['x86_64']="x86_64"
49 arch_map['i686']="i586"
50
51 def connect_db(docroot):
52         db_con = sqlite.connect(docroot + "/" + DB_FILE)
53         return db_con
54
55 def get_db(req):
56         db_con = connect_db(req.document_root())
57         return db_con
58
59 def retrieve_proxied_result(req):
60         builder = DOMBuilder()
61         result_doc = None
62         if ENABLE_MOZILLA_PFS_RESULTS:
63                 hc = httplib.HTTPSConnection("pfs.mozilla.org")
64                 hc.request("GET", "/plugins/PluginFinderService.php?" + req.args)
65                 res = hc.getresponse()
66                 source = DOMInputSource()
67                 source.byteStream = StringIO(res.read())
68                 result_doc = builder.parse(source)
69         else:
70                 result_doc = Document()
71                 root = result_doc.createElement("RDF:RDF")
72                 root.setAttribute("xmlns:RDF", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
73                 root.setAttribute("xmlns:pfs", "http://www.mozilla.org/2004/pfs-rdf#")
74                 result_doc.appendChild(root)
75
76         return result_doc
77
78
79 def parse_http_parameters (req):
80         parameters = dict()
81         for parameterblock in req.args.split("&"):
82                 keyvaluepair = parameterblock.split("=")
83                 if len(keyvaluepair) < 2:
84                         continue
85                 parameters[url_unquote(keyvaluepair[0])] = url_unquote(keyvaluepair[1])
86         return parameters
87
88
89 def retrieve_filehint_name_map(req, parameters):
90         res = ""
91         db_con = get_db(req)
92         
93         distribution = "11.1"
94         if parameters.has_key('distributionID') and parameters['distributionID'] is not None:
95                 distribution = parameters['distributionID']
96         cur = db_con.cursor()
97         cur.execute("SELECT distinct filehint, name FROM package " \
98                         + "WHERE filehint!=\"\" AND distribution=%s", (distribution,)) 
99         for row in cur:
100                 res = res + "filehint: ";
101                 res = res + row[0];
102                 res = res + "\n";
103                 res = res + "name: ";
104                 res = res + row[1];
105                 res = res + "\n";
106         return res
107
108 def retrieve_descriptions(req, parameters):
109         ret = []
110         db_con = get_db(req)
111
112         mimetype = parameters['mimetype']
113         architecture = None
114         if not parameters['clientOS'] is None:
115                 tmp = parameters['clientOS'].split(" ")
116                 architecture = arch_map[tmp[1]]
117                 if architecture is None:
118                         architecture = tmp[1]
119                 if not architecture in ["x86_64", "i586"]:
120                         return ret # we only support x86-64 and i586 for now
121         else:
122                 return ret # nothing to do for no clientOS
123
124         distribution = "11.1"
125         if parameters.has_key('distributionID') and parameters['distributionID'] is not None:
126                 distribution = parameters['distributionID']
127
128         architectures = [architecture]
129         if architecture == "x86_64":
130                 architectures = ["x86_64", "i586"]
131
132         appid = parameters['appID']
133
134         cur = db_con.cursor()
135         cur.execute("SELECT name, mimetype, description, repo, filehint FROM package " \
136                         + "WHERE mimetype=%s AND appid=%s " \
137                         + "AND distribution=%s order by package.weight desc", (mimetype, appid, distribution))
138
139         for row in cur:
140                 desc = plugindata.PluginDescription ( \
141                                 row[0],         \
142                                 row[1],         \
143                                 None,           \
144                                 None,           \
145                                 None,           \
146                                 "yast:" + row[3], \
147                                 None,    \
148                                 None,    \
149                                 None,    \
150                                 "false", \
151                                 row[4],  \
152                                 row[2] )
153                 ret.append(desc)
154
155         cur.close()
156
157         return ret              
158
159 def retrieve_opensuse_results(req, http_parameters):
160         description_results = []
161         description_results = retrieve_descriptions(req, http_parameters)
162         return description_results
163
164 def url_unquote(s):
165         res = s.split('%')
166         for i in xrange(1, len(res)):
167                 item = res[i]
168                 try:
169                         res[i] = unichr(int(item[:2], 16)) + item[2:]
170                 except KeyError:
171                         res[i] = '%' + item
172                 except ValueError:
173                         res[i] = '%' + item
174
175         return "".join(res)
176
177 def get_cache_con():
178         if not cache_con_map.has_key(threading._get_ident()):
179                 cache_con = sqlite.connect(":memory:")
180                 cur = cache_con.cursor()
181                 cur.execute("create table plugin_result_cache(url string, timestamp long, content string)")
182                 cur.close()
183                 cache_con_map[threading._get_ident()] = cache_con
184         con = cache_con_map[threading._get_ident()]
185         return con
186
187 def inject_opensuse_descriptions_in_upstream_result (resxml, opensuse_descriptions, mimetype):
188         result_list = None
189
190         node = resxml.documentElement.firstChild
191         seq_r = None
192         while not node is None:
193                 nextNode = node.nextSibling
194                 if node.nodeType == node.ELEMENT_NODE:
195                         about = node.getAttribute("about")
196                         if not about is None and about.rfind("urn:mozilla:plugin-results:") == 0:
197                                 item = node.getElementsByTagName("RDF:Seq").item(0)
198                                 seq_r = item
199                                 result_list = node
200                                 break
201                 node = nextNode
202
203         if not result_list:
204                 result_list = resxml.createElement("RDF:Description")
205
206         if seq_r is None:
207                 result_list.setAttribute("about", "urn:mozilla:plugin-results:"+mimetype)
208                 pluginsElement = resxml.createElement("pfs:plugins")
209                 result_list.appendChild(pluginsElement)
210                 seq_r = resxml.createElement("RDF:Seq")
211                 pluginsElement.appendChild(seq_r)
212                 resxml.documentElement.appendChild(result_list)
213
214         for description in opensuse_descriptions:
215                 result_list.setAttribute("about", "urn:mozilla:plugin-results:"+description.requestedMimetype)
216                 update_list = resxml.createElement("RDF:Description")
217                 update_list.setAttribute("about", "urn:mozilla:plugin:"+ description.guid)
218                 resxml.documentElement.appendChild(update_list)
219                 updatesElement = resxml.createElement("pfs:updates")
220                 update_list.appendChild(updatesElement)
221                 seq_u = resxml.createElement("RDF:Seq")
222                 updatesElement.appendChild(seq_u)
223
224                 main_element = description.to_element(resxml)
225                 resxml.documentElement.appendChild(main_element);
226                 li_r = resxml.createElement("RDF:li")
227                 li_u = resxml.createElement("RDF:li")
228                 li_r.setAttribute("resource", "urn:mozilla:plugin:" + description.guid)
229                 li_u.setAttribute("resource", description.id)
230                 seq_r.appendChild(li_r)
231                 seq_u.appendChild(li_u)
232
233         node = seq_r.lastChild
234         while not node is None:
235                 nextNode = node.previousSibling
236                 if node.nodeType == node.ELEMENT_NODE and node.getAttribute("resource") == "urn:mozilla:plugin:-1":
237                         seq_r.removeChild(node)
238                 
239                 node = nextNode
240
241
242
243 def get_cached_and_cache(req, http_params):
244         global counter
245         counter= (counter + 1) % 2
246         req.content_type = 'text/xml'
247
248         if counter == 0:
249                 now = int(time.time() * 1000)
250                 low_time = now - CACHE_TIME_SEC * 1000
251                 cur = get_cache_con().cursor()
252                 cur.execute("DELETE FROM plugin_result_cache WHERE timestamp <= %s", (low_time,))
253                 cur.close()
254
255         import urllib
256         key = urllib.unquote_plus(req.args)
257         found = False
258         result = None
259         cur = get_cache_con().cursor()
260         cur.execute("SELECT timestamp, content FROM plugin_result_cache WHERE url = %s", (key,))
261         for row in cur:
262                 found = True
263                 result = row[1]
264
265         cur.close()
266
267         if found:
268                 print "found!"
269                 return result
270
271         resxml = retrieve_proxied_result(req)
272         opensuse_descriptions = retrieve_opensuse_results(req, http_params)
273
274         inject_opensuse_descriptions_in_upstream_result(resxml, opensuse_descriptions, http_params['mimetype'])
275
276         result_string = resxml.toxml()
277
278         now = int(time.time() * 1000)
279         cur = get_cache_con().cursor()
280         cur.execute("INSERT INTO plugin_result_cache (timestamp, url, content) VALUES (%s, %s, %s)", (now, key, result_string))
281         return result_string
282
283 def get_filehint_and_name(req, http_params):
284         req.content_type = 'text/plain'
285         res = retrieve_filehint_name_map( req, http_params)
286         return res
287
288 db_con_lock = thread.allocate_lock()
289
290 def handler(req):
291         http_params = parse_http_parameters(req)
292         op = None
293         if http_params.has_key('op'):
294                 op = http_params['op']
295
296         if op == "filehint2name":
297                 res = get_filehint_and_name (req, http_params)
298         else:   
299                 res = get_cached_and_cache(req, http_params)
300         req.write(res)
301         return apache.OK
302