source: src/mlx/gui/cef.py@ 893:147633a04fda

Last change on this file since 893:147633a04fda was 805:41c3bffe8a25, checked in by István Váradi <ivaradi@…>, 8 years ago

Simplified SimBrief handling by using the CEF API directly (re #310)

File size: 15.0 KB
Line 
1from common import *
2
3from mlx.util import secondaryInstallation
4
5from cefpython3 import cefpython
6
7import platform
8import json
9import time
10import os
11import re
12import thread
13import threading
14import tempfile
15import traceback
16import urllib2
17from lxml import etree
18from StringIO import StringIO
19import lxml.html
20
21#------------------------------------------------------------------------------
22
23## @package mlx.gui.cef
24#
25# Some helper stuff related to the Chrome Embedded Framework
26
27#------------------------------------------------------------------------------
28
29# Indicate if we should quit
30_toQuit = False
31
32# The SimBrief handler
33_simBriefHandler = None
34
35#------------------------------------------------------------------------------
36
37SIMBRIEF_PROGRESS_SEARCHING_BROWSER = 1
38SIMBRIEF_PROGRESS_LOADING_FORM = 2
39SIMBRIEF_PROGRESS_FILLING_FORM = 3
40SIMBRIEF_PROGRESS_WAITING_LOGIN = 4
41SIMBRIEF_PROGRESS_LOGGING_IN = 5
42SIMBRIEF_PROGRESS_WAITING_RESULT = 6
43
44SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING = 7
45SIMBRIEF_PROGRESS_DONE = 1000
46
47SIMBRIEF_RESULT_NONE = 0
48SIMBRIEF_RESULT_OK = 1
49SIMBRIEF_RESULT_ERROR_OTHER = 2
50SIMBRIEF_RESULT_ERROR_NO_FORM = 11
51SIMBRIEF_RESULT_ERROR_NO_POPUP = 12
52SIMBRIEF_RESULT_ERROR_LOGIN_FAILED = 13
53
54#------------------------------------------------------------------------------
55
56class SimBriefHandler(object):
57 """An object to store the state of a SimBrief query."""
58 _formURL = "http://flare.privatedns.org/mava_simbrief/simbrief_form.html"
59
60 _querySettings = {
61 'navlog': True,
62 'etops': True,
63 'stepclimbs': True,
64 'tlr': True,
65 'notams': True,
66 'firnot': True,
67 'maps': 'Simple',
68 };
69
70
71 def __init__(self):
72 """Construct the handler."""
73 self._browser = None
74 self._plan = None
75 self._getCredentials = None
76 self._getCredentialsCount = 0
77 self._updateProgressFn = None
78 self._htmlFilePath = None
79 self._lastProgress = SIMBRIEF_PROGRESS_SEARCHING_BROWSER
80 self._timeoutID = None
81
82 def initialize(self):
83 """Create and initialize the browser used for Simbrief."""
84 windowInfo = cefpython.WindowInfo()
85 windowInfo.SetAsOffscreen(int(0))
86
87 self._browser = \
88 cefpython.CreateBrowserSync(windowInfo, browserSettings = {},
89 navigateUrl = SimBriefHandler._formURL)
90 self._browser.SetClientHandler(OffscreenRenderHandler())
91 self._browser.SetFocus(True)
92 self._browser.SetClientCallback("OnLoadEnd", self._onLoadEnd)
93
94 bindings = cefpython.JavascriptBindings(bindToFrames=True,
95 bindToPopups=True)
96 bindings.SetFunction("briefingData", self._briefingDataAvailable)
97 bindings.SetFunction("formFilled", self._formFilled)
98 self._browser.SetJavascriptBindings(bindings)
99
100 def call(self, plan, getCredentials, updateProgress, htmlFilePath):
101 """Call SimBrief with the given plan."""
102 self._timeoutID = gobject.timeout_add(120*1000, self._timedOut)
103
104 self._plan = plan
105 self._getCredentials = getCredentials
106 self._getCredentialsCount = 0
107 self._updateProgressFn = updateProgress
108 self._htmlFilePath = htmlFilePath
109
110 self._browser.LoadUrl(SimBriefHandler._formURL)
111 self._updateProgress(SIMBRIEF_PROGRESS_LOADING_FORM,
112 SIMBRIEF_RESULT_NONE, None)
113
114 def _onLoadEnd(self, browser, frame, httpCode):
115 """Called when a page has been loaded in the SimBrief browser."""
116 url = frame.GetUrl()
117 print "gui.cef.SimBriefHandler._onLoadEnd", httpCode, url
118 if httpCode>=300:
119 self._updateProgress(self._lastProgress,
120 SIMBRIEF_RESULT_ERROR_OTHER, None)
121 elif url.startswith("http://flare.privatedns.org/mava_simbrief/simbrief_form.html"):
122 if self._plan is None:
123 return
124
125 self._updateProgress(SIMBRIEF_PROGRESS_FILLING_FORM,
126 SIMBRIEF_RESULT_NONE, None)
127
128 js = "form=document.getElementById(\"sbapiform\");"
129 for (name, value) in self._plan.iteritems():
130 js += "form." + name + ".value=\"" + value + "\";"
131 for (name, value) in SimBriefHandler._querySettings.iteritems():
132 if isinstance(value, bool):
133 js += "form." + name + ".checked=" + \
134 ("true" if value else "false") + ";"
135 elif isinstance(value, str):
136 js += "form." + name + ".value=\"" + value + "\";"
137
138 js += "form.submitform.click();"
139 js += "window.formFilled();"
140
141 frame.ExecuteJavascript(js)
142 elif url.startswith("http://www.simbrief.com/system/login.api.php"):
143 (user, password) = self._getCredentials(self._getCredentialsCount)
144 if user is None or password is None:
145 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
146 SIMBRIEF_RESULT_ERROR_LOGIN_FAILED, None)
147 return
148
149 self._getCredentialsCount += 1
150 js = "form=document.forms[0];"
151 js += "form.user.value=\"" + user + "\";"
152 js += "form.pass.value=\"" + password + "\";"
153 js += "form.submit();"
154 frame.ExecuteJavascript(js)
155 elif url.startswith("http://www.simbrief.com/ofp/ofp.loader.api.php"):
156 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_RESULT,
157 SIMBRIEF_RESULT_NONE, None)
158 elif url.startswith("http://flare.privatedns.org/mava_simbrief/simbrief_briefing.php"):
159 js = "form=document.getElementById(\"hiddenform\");"
160 js += "window.briefingData(form.hidden_is_briefing_available.value, form.hidden_link.value);";
161 frame.ExecuteJavascript(js)
162
163 def _formFilled(self):
164 """Called when the form has been filled and submitted."""
165 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
166 SIMBRIEF_RESULT_NONE, None)
167
168 def _briefingDataAvailable(self, available, link):
169 """Called when the briefing data is available."""
170 if available:
171 link ="http://www.simbrief.com/ofp/flightplans/xml/" + link + ".xml"
172
173 self._updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
174 SIMBRIEF_RESULT_NONE, None)
175
176 thread = threading.Thread(target = self._getResults, args = (link,))
177 thread.daemon = True
178 thread.start()
179 else:
180 self._updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
181 SIMBRIEF_RESULT_ERROR_OTHER, None)
182
183 def _resultsAvailable(self, flightInfo):
184 """Called from the result retrieval thread when the result is
185 available.
186
187 It checks for the plan being not None, as we may time out."""
188 if self._plan is not None:
189 self._updateProgress(SIMBRIEF_PROGRESS_DONE,
190 SIMBRIEF_RESULT_OK, flightInfo)
191
192 def _updateProgress(self, progress, results, flightInfo):
193 """Update the progress."""
194 self._lastProgress = progress
195 if results!=SIMBRIEF_RESULT_NONE:
196 gobject.source_remove(self._timeoutID)
197 self._plan = None
198
199 self._updateProgressFn(progress, results, flightInfo)
200
201 def _timedOut(self):
202 """Called when the timeout occurs."""
203 if self._lastProgress==SIMBRIEF_PROGRESS_LOADING_FORM:
204 result = SIMBRIEF_RESULT_ERROR_NO_FORM
205 elif self._lastProgress==SIMBRIEF_PROGRESS_WAITING_LOGIN:
206 result = SIMBRIEF_RESULT_ERROR_NO_POPUP
207 else:
208 result = SIMBRIEF_RESULT_ERROR_OTHER
209
210 self._updateProgress(self._lastProgress, result, None)
211
212 return False
213
214 def _getResults(self, link):
215 """Get the result from the given link."""
216 availableInfo = {}
217 ## Holds analysis data to be used
218 flightInfo = {}
219
220 # Obtaining the xml
221 response = urllib2.urlopen(link)
222 xmlContent = response.read()
223 # Processing xml
224 content = etree.iterparse(StringIO(xmlContent))
225
226 for (action, element) in content:
227 # Processing tags that occur multiple times
228 if element.tag == "weather":
229 weatherElementList = list(element)
230 for weatherElement in weatherElementList:
231 flightInfo[weatherElement.tag] = weatherElement.text
232 else:
233 availableInfo[element.tag] = element.text
234
235 # Processing plan_html
236 ## Obtaining chart links
237 imageLinks = []
238 for imageLinkElement in lxml.html.find_class(availableInfo["plan_html"],
239 "ofpmaplink"):
240 for imageLink in imageLinkElement.iterlinks():
241 if imageLink[1] == 'src':
242 imageLinks.append(imageLink[2])
243 flightInfo["image_links"] = imageLinks
244 print(sorted(availableInfo.keys()))
245 htmlFilePath = "simbrief_plan.html" if self._htmlFilePath is None \
246 else self._htmlFilePath
247 with open(htmlFilePath, 'w') as f:
248 f.write(availableInfo["plan_html"])
249
250 gobject.idle_add(self._resultsAvailable, flightInfo)
251
252#------------------------------------------------------------------------------
253
254def initialize(initializedCallback):
255 """Initialize the Chrome Embedded Framework."""
256 global _toQuit, _simBriefHandler
257 _toQuit = False
258
259 gobject.threads_init()
260
261 _simBriefHandler = SimBriefHandler()
262 _initializeCEF([], initializedCallback)
263
264#------------------------------------------------------------------------------
265
266def _initializeCEF(args, initializedCallback):
267 """Perform the actual initialization of CEF using the given arguments."""
268 print "Initializing CEF with args:", args
269
270 settings = {
271 "debug": True, # cefpython debug messages in console and in log_file
272 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
273 "log_file": "", # Set to "" to disable
274 "release_dcheck_enabled": True, # Enable only when debugging
275 # This directories must be set on Linux
276 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
277 "resources_dir_path": cefpython.GetModuleDirectory(),
278 "browser_subprocess_path": "%s/%s" % \
279 (cefpython.GetModuleDirectory(), "subprocess"),
280 }
281
282 switches={}
283 for arg in args:
284 if arg.startswith("--"):
285 if arg != "--enable-logging":
286 assignIndex = arg.find("=")
287 if assignIndex<0:
288 switches[arg[2:]] = ""
289 else:
290 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
291 else:
292 print "Unhandled switch", arg
293
294 cefpython.Initialize(settings, switches)
295
296 gobject.timeout_add(10, _handleTimeout)
297
298 print "Initialized, executing callback..."
299 initializedCallback()
300
301#------------------------------------------------------------------------------
302
303def getContainer():
304 """Get a container object suitable for running a browser instance
305 within."""
306 if os.name=="nt":
307 container = gtk.DrawingArea()
308 container.set_property("can-focus", True)
309 container.connect("size-allocate", _handleSizeAllocate)
310 else:
311 container = gtk.VBox(True, 0)
312
313 container.show()
314
315 return container
316
317#------------------------------------------------------------------------------
318
319def startInContainer(container, url, browserSettings = {}):
320 """Start a browser instance in the given container with the given URL."""
321 if os.name=="nt":
322 window = container.get_window()
323 if window is None:
324 print "mlx.gui.cef.startInContainer: no window found!"
325 windowID = None
326 else:
327 windowID = window.handle
328 else:
329 m = re.search("GtkVBox at 0x(\w+)", str(container))
330 hexID = m.group(1)
331 windowID = int(hexID, 16)
332
333 windowInfo = cefpython.WindowInfo()
334 if windowID is not None:
335 windowInfo.SetAsChild(windowID)
336
337 return cefpython.CreateBrowserSync(windowInfo,
338 browserSettings = browserSettings,
339 navigateUrl = url)
340
341#------------------------------------------------------------------------------
342
343class OffscreenRenderHandler(object):
344 def GetRootScreenRect(self, browser, rect):
345 #print "GetRootScreenRect"
346 rect += [0, 0, 800, 600]
347 return True
348
349 def GetViewRect(self, browser, rect):
350 #print "GetViewRect"
351 rect += [0, 0, 800, 600]
352 return True
353
354 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
355 #print "GetScreenPoint", viewX, viewY
356 rect += [viewX, viewY]
357 return True
358
359 def GetScreenInfo(self, browser, screenInfo):
360 #print "GetScreenInfo"
361 pass
362
363 def OnPopupShow(self, browser, show):
364 #print "OnPopupShow", show
365 pass
366
367 def OnPopupSize(self, browser, rect):
368 #print "OnPopupSize", rect
369 pass
370
371 def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
372 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
373 pass
374
375 def OnCursorChange(self, browser, cursor):
376 #print "OnCursorChange", cursor
377 pass
378
379 def OnScrollOffsetChanged(self, browser):
380 #print "OnScrollOffsetChange"
381 pass
382
383 def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
384 popupFeatures, windowInfo, client, browserSettings,
385 noJavascriptAccess):
386 wInfo = cefpython.WindowInfo()
387 wInfo.SetAsOffscreen(int(0))
388
389 windowInfo.append(wInfo)
390
391 return False
392
393#------------------------------------------------------------------------------
394
395def initializeSimBrief():
396 """Initialize the (hidden) browser window for SimBrief."""
397 _simBriefHandler.initialize()
398
399#------------------------------------------------------------------------------
400
401def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
402 """Call SimBrief with the given plan.
403
404 The callbacks will be called in the main GUI thread."""
405 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
406
407#------------------------------------------------------------------------------
408
409def finalize():
410 """Finalize the Chrome Embedded Framework."""
411 global _toQuit
412 _toQuit = True
413 cefpython.Shutdown()
414
415#------------------------------------------------------------------------------
416
417def _handleTimeout():
418 """Handle the timeout by running the CEF message loop."""
419 if _toQuit:
420 return False
421 else:
422 cefpython.MessageLoopWork()
423 return True
424
425#------------------------------------------------------------------------------
426
427def _handleSizeAllocate(widget, sizeAlloc):
428 """Handle the size-allocate event."""
429 window = widget.get_window()
430 if widget is not None:
431 cefpython.WindowUtils.OnSize(window.handle, 0, 0, 0)
Note: See TracBrowser for help on using the repository browser.