1 | # needed for os specific separators
|
---|
2 | import os
|
---|
3 | # selenium is needed for browser manipulation
|
---|
4 | from selenium import webdriver
|
---|
5 | from selenium.common.exceptions import NoSuchElementException
|
---|
6 | from selenium.common.exceptions import NoSuchWindowException
|
---|
7 | from selenium.webdriver.common.by import By
|
---|
8 | from selenium.webdriver.support.ui import WebDriverWait, Select
|
---|
9 | from selenium.webdriver.support import expected_conditions as EC
|
---|
10 | from selenium.webdriver.common.keys import Keys
|
---|
11 | # urllib is needed to obtain the xml
|
---|
12 | import urllib2
|
---|
13 | # xmlparser
|
---|
14 | from lxml import etree
|
---|
15 | from StringIO import StringIO
|
---|
16 | import lxml.html
|
---|
17 | import time
|
---|
18 |
|
---|
19 |
|
---|
20 | class MavaSimbriefIntegrator():
|
---|
21 | """Implements the integration with the excellent Simbrief pilot briefing
|
---|
22 | system for MALEV Virtual."""
|
---|
23 |
|
---|
24 | # Progress stage: searching the suitable browser window
|
---|
25 | PROGRESS_SEARCHING_BROWSER = 1
|
---|
26 |
|
---|
27 | # Progress stage: retrieving the form from the server
|
---|
28 | PROGRESS_LOADING_FORM = 2
|
---|
29 |
|
---|
30 | # Progress stage: filling the form
|
---|
31 | PROGRESS_FILLING_FORM = 3
|
---|
32 |
|
---|
33 | # Progress stage: waiting for the login
|
---|
34 | PROGRESS_WAITING_LOGIN = 4
|
---|
35 |
|
---|
36 | # Progress stage: logging in
|
---|
37 | PROGRESS_LOGGING_IN = 5
|
---|
38 |
|
---|
39 | # Progress stage: waiting for result
|
---|
40 | PROGRESS_WAITING_RESULT = 6
|
---|
41 |
|
---|
42 | # The maximal reserved progress stage
|
---|
43 | PROGRESS_MAX = 16
|
---|
44 |
|
---|
45 | # Result code: none (i.e. the SimBrief query is in progress).
|
---|
46 | RESULT_NONE = 0
|
---|
47 |
|
---|
48 | # Result code: success
|
---|
49 | RESULT_OK = 1
|
---|
50 |
|
---|
51 | # Result code: other error
|
---|
52 | RESULT_ERROR_OTHER = 2
|
---|
53 |
|
---|
54 | # Result code: form could not be loaded
|
---|
55 | RESULT_ERROR_NO_FORM = 11
|
---|
56 |
|
---|
57 | # Result code: no popup (i.e. login) window found
|
---|
58 | RESULT_ERROR_NO_POPUP = 12
|
---|
59 |
|
---|
60 | # Result code: login failed
|
---|
61 | RESULT_ERROR_LOGIN_FAILED = 13
|
---|
62 |
|
---|
63 | # The maximal reserved result code
|
---|
64 | RESULT_MAX = 32
|
---|
65 |
|
---|
66 | def __init__(self,
|
---|
67 | plan,
|
---|
68 | driver=None,
|
---|
69 | simbrief_query_settings=None,
|
---|
70 | mava_simbrief_url=None,
|
---|
71 | xml_link_fix_part=None):
|
---|
72 | """Init the integrator with settings that are typical of our use.
|
---|
73 | @param: plan - flightplan dictionary
|
---|
74 | @param: webdriver - a selenium webdriver
|
---|
75 | @param: simbrief_query_settings - a dictionary of query settings
|
---|
76 | @param: mava_simbrief_url - url to the form that is sent to simbrief
|
---|
77 | on the mava server
|
---|
78 | @param: xml_link_fix_part = url to the simbrief website under which the
|
---|
79 | xml is to be found"""
|
---|
80 | self.plan = plan
|
---|
81 | if simbrief_query_settings is None:
|
---|
82 | self.simbrief_query_settings = {
|
---|
83 | 'navlog': True,
|
---|
84 | 'etops': True,
|
---|
85 | 'stepclimbs': True,
|
---|
86 | 'tlr': True,
|
---|
87 | 'notams': True,
|
---|
88 | 'firnot': True,
|
---|
89 | 'maps': 'Simple',
|
---|
90 | }
|
---|
91 | else:
|
---|
92 | self.simbrief_query_settings = simbrief_query_settings
|
---|
93 |
|
---|
94 | if driver is None:
|
---|
95 | self.driver = webdriver.Firefox()
|
---|
96 | else:
|
---|
97 | self.driver = driver
|
---|
98 |
|
---|
99 | if mava_simbrief_url is None:
|
---|
100 | self.mava_simbrief_url = "http://flare.privatedns.org/" \
|
---|
101 | "mava_simbrief/simbrief_form.html"
|
---|
102 | else:
|
---|
103 | self.mava_simbrief_url = mava_simbrief_url
|
---|
104 |
|
---|
105 | if xml_link_fix_part is None:
|
---|
106 | self.xml_link_fix_part = "http://www.simbrief.com/ofp/" \
|
---|
107 | "flightplans/xml/"
|
---|
108 | else:
|
---|
109 | self.xml_link_fix_part = xml_link_fix_part
|
---|
110 |
|
---|
111 | def fill_form(self,
|
---|
112 | plan,
|
---|
113 | simbrief_query_settings):
|
---|
114 | """Fills the form of the webpage using the paramteres that the class
|
---|
115 | has been initialized with.
|
---|
116 | @param: driver - a selenium webdriver
|
---|
117 | @param: plan - dictionary containing plan details
|
---|
118 | @param: simbrief_query_settings - dictionary containing plan settings"""
|
---|
119 | for plan_input_field in plan.iterkeys():
|
---|
120 | self.driver.find_element_by_name(plan_input_field).send_keys(
|
---|
121 | plan[plan_input_field])
|
---|
122 | for option_checkbox in simbrief_query_settings.iterkeys():
|
---|
123 | if (isinstance(simbrief_query_settings[option_checkbox], bool) and
|
---|
124 | simbrief_query_settings[option_checkbox]):
|
---|
125 | # if setting is a boolean type and true
|
---|
126 | self.driver.find_element_by_name(option_checkbox).click()
|
---|
127 | elif isinstance(simbrief_query_settings[option_checkbox], str):
|
---|
128 | # if setting is a select
|
---|
129 | Select(self.driver.find_element_by_name(option_checkbox)).\
|
---|
130 | select_by_visible_text(simbrief_query_settings[
|
---|
131 | option_checkbox])
|
---|
132 |
|
---|
133 | def get_xml_link(self,
|
---|
134 | get_credentials, update_progress,
|
---|
135 | local_xml_debug=False,
|
---|
136 | local_html_debug=False):
|
---|
137 | """Obtains the link of the xml to be processed.
|
---|
138 | @param get_credentials - a function, which should return a pair of the
|
---|
139 | user name and password to log in to SimBrief. It gets an integer which
|
---|
140 | is the number of times it has been called so far.
|
---|
141 | @param local_xml_debug - if True then not the real location will be
|
---|
142 | checked but the current working dir will be searched for 'xml.xml'
|
---|
143 | @param local_html_debug - if True then not real mava_simbrief_url will
|
---|
144 | be used but the current working dir will be searched for
|
---|
145 | 'mava_simbrief.html'
|
---|
146 | @returns: the string of the xml link if it could be obtained and None
|
---|
147 | otherwise"""
|
---|
148 | # Finding xml_link
|
---|
149 | is_briefing_available = False
|
---|
150 | if local_xml_debug:
|
---|
151 | # set link for locally debugging an existing xml file
|
---|
152 | xml_link = ("file://"
|
---|
153 | + os.getcwd()
|
---|
154 | + os.sep
|
---|
155 | + "xml.xml")
|
---|
156 | is_briefing_available = True
|
---|
157 | else:
|
---|
158 | update_progress(MavaSimbriefIntegrator.PROGRESS_SEARCHING_BROWSER,
|
---|
159 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
160 | # There must be window whose title is 'SimBrief' so that we
|
---|
161 | # could find our one among several
|
---|
162 | if self._find_window_by_title("SimBrief") is None:
|
---|
163 | print "No SimBrief window was found!"
|
---|
164 | update_progress(MavaSimbriefIntegrator.PROGRESS_SEARCHING_BROWSER,
|
---|
165 | MavaSimbriefIntegrator.RESULT_ERROR_OTHER, None)
|
---|
166 | return None
|
---|
167 |
|
---|
168 | # normal operation with a real xml file
|
---|
169 | update_progress(MavaSimbriefIntegrator.PROGRESS_LOADING_FORM,
|
---|
170 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
171 | if local_html_debug:
|
---|
172 | self.driver.get(
|
---|
173 | "file://" + os.getcwd() + os.sep + "simbrief_form.html")
|
---|
174 | is_briefing_available = True
|
---|
175 | else:
|
---|
176 | self.driver.get(self.mava_simbrief_url)
|
---|
177 |
|
---|
178 | main_handle = self._find_window_by_title("Malev Virtual Simbrief Integration System")
|
---|
179 | if main_handle is None:
|
---|
180 | print "No SimBrief Integration window was found!"
|
---|
181 | update_progress(MavaSimbriefIntegrator.PROGRESS_LOADING_FORM,
|
---|
182 | MavaSimbriefIntegrator.RESULT_ERROR_NO_FORM, None)
|
---|
183 | return None
|
---|
184 |
|
---|
185 | # Make a copy of the window handles before submitting the form,
|
---|
186 | # so that we could find the popup
|
---|
187 | handles = self.driver.window_handles[:]
|
---|
188 |
|
---|
189 | # Entering form data
|
---|
190 | update_progress(MavaSimbriefIntegrator.PROGRESS_FILLING_FORM,
|
---|
191 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
192 | self.driver.switch_to_window(main_handle)
|
---|
193 | self.fill_form(self.plan,
|
---|
194 | self.simbrief_query_settings)
|
---|
195 |
|
---|
196 | # Loading page
|
---|
197 | button = self.driver.find_element_by_name("submitform")
|
---|
198 | button.send_keys(Keys.RETURN)
|
---|
199 |
|
---|
200 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN,
|
---|
201 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
202 | popup_handle = self._find_popup(handles)
|
---|
203 | if popup_handle is None:
|
---|
204 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN,
|
---|
205 | MavaSimbriefIntegrator.RESULT_ERROR_NO_POPUP, None)
|
---|
206 | return None
|
---|
207 |
|
---|
208 | login_count = 0
|
---|
209 | end_time = time.time() + 120.0
|
---|
210 | while not is_briefing_available and end_time > time.time():
|
---|
211 | try:
|
---|
212 | self.driver.switch_to.window(popup_handle)
|
---|
213 |
|
---|
214 | userElement = self.driver.find_element_by_name("user")
|
---|
215 |
|
---|
216 | if userElement is not None:
|
---|
217 | update_progress(MavaSimbriefIntegrator.PROGRESS_LOGGING_IN,
|
---|
218 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
219 | (userName, password) = get_credentials(login_count)
|
---|
220 | if userName is None or password is None:
|
---|
221 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN,
|
---|
222 | MavaSimbriefIntegrator.RESULT_ERROR_LOGIN_FAILED, None)
|
---|
223 | return None
|
---|
224 |
|
---|
225 | userElement.send_keys(userName)
|
---|
226 | self.driver.find_element_by_name("pass").send_keys(password)
|
---|
227 | self.driver.find_element_by_name("staylogged").click()
|
---|
228 |
|
---|
229 | self.driver.find_element(By.XPATH,
|
---|
230 | "//input[@value='Login']").send_keys(Keys.RETURN)
|
---|
231 | login_count += 1
|
---|
232 | except NoSuchElementException:
|
---|
233 | pass
|
---|
234 | except NoSuchWindowException:
|
---|
235 | pass
|
---|
236 |
|
---|
237 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_RESULT,
|
---|
238 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
239 | self.driver.switch_to.window(main_handle)
|
---|
240 | try:
|
---|
241 | if self.driver.find_element_by_name("hidden_is_briefing_available") is not None:
|
---|
242 | is_briefing_available = True
|
---|
243 | xml_link_element = self.driver.find_element_by_name(
|
---|
244 | 'hidden_link')
|
---|
245 | xml_link_generated_part = xml_link_element.get_attribute(
|
---|
246 | 'value')
|
---|
247 | xml_link = self.xml_link_fix_part + xml_link_generated_part + '.xml'
|
---|
248 | print(xml_link)
|
---|
249 | except NoSuchElementException:
|
---|
250 | pass
|
---|
251 |
|
---|
252 | if is_briefing_available:
|
---|
253 | return xml_link
|
---|
254 | else:
|
---|
255 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_RESULT,
|
---|
256 | MavaSimbriefIntegrator.RESULT_ERROR_OTHER, None)
|
---|
257 | return None
|
---|
258 |
|
---|
259 | def get_results(self,
|
---|
260 | xml_link,
|
---|
261 | html_file_path = None):
|
---|
262 | """Parses the xml for information.
|
---|
263 | @param xml_link - a path to the xml file
|
---|
264 | @return a dictionary of the found information"""
|
---|
265 | # Setup variables
|
---|
266 | ## Holds analysis data not used
|
---|
267 | available_info = {}
|
---|
268 | ## Holds analysis data to be used
|
---|
269 | flight_info = {}
|
---|
270 | ## Holds notams
|
---|
271 | notams_list = []
|
---|
272 | ## Notam counter
|
---|
273 | i = 0
|
---|
274 | # Obtaining the xml
|
---|
275 | response = urllib2.urlopen(xml_link)
|
---|
276 | xml_content = response.read()
|
---|
277 | # Processing xml
|
---|
278 | tree = etree.parse(StringIO(xml_content))
|
---|
279 | context = etree.iterparse(StringIO(xml_content))
|
---|
280 | for action, element in context:
|
---|
281 | # Processing tags that occur multiple times
|
---|
282 | ## NOTAMS
|
---|
283 | if element.tag == 'notamdrec':
|
---|
284 | notams_element_list = list(element)
|
---|
285 | notam_dict = {}
|
---|
286 | for notam in notams_element_list:
|
---|
287 | notam_dict[notam.tag] = notam.text
|
---|
288 | notams_list.append(notam_dict)
|
---|
289 | i += 1
|
---|
290 | ## WEATHER
|
---|
291 | elif element.tag == 'weather':
|
---|
292 | weather_element_list = list(element)
|
---|
293 | for weather in weather_element_list:
|
---|
294 | flight_info[weather.tag] = weather.text
|
---|
295 | else:
|
---|
296 | available_info[element.tag] = element.text
|
---|
297 | # Processing plan_html
|
---|
298 | ## Obtaining chart links
|
---|
299 | image_links = []
|
---|
300 | for image_link_a_element in lxml.html.find_class(
|
---|
301 | available_info['plan_html'], 'ofpmaplink'):
|
---|
302 | for image_link_tuple in image_link_a_element.iterlinks():
|
---|
303 | if image_link_tuple[1] == 'src':
|
---|
304 | image_links.append(image_link_tuple[2])
|
---|
305 | flight_info['image_links'] = image_links
|
---|
306 | print(sorted(available_info.keys()))
|
---|
307 | if html_file_path is None:
|
---|
308 | html_file_path = 'simbrief_plan.html'
|
---|
309 | with open(html_file_path, 'w') as f:
|
---|
310 | f.write(available_info['plan_html'])
|
---|
311 | return flight_info
|
---|
312 |
|
---|
313 | def _find_window_by_title(self, title, timeout = 10.0):
|
---|
314 | """Find the window with the given title.
|
---|
315 |
|
---|
316 | Switch to that window and return its handle."""
|
---|
317 | def predicate(handle):
|
---|
318 | self.driver.switch_to.window(handle)
|
---|
319 | return self.driver.title == title
|
---|
320 |
|
---|
321 | return self._find_window(predicate, timeout = timeout)
|
---|
322 |
|
---|
323 | def _find_popup(self, handles, timeout = 10.0):
|
---|
324 | """Find a popup, i.e. a new window being created.
|
---|
325 |
|
---|
326 | handles is a list of window handles that existed before the popup was
|
---|
327 | expected to be created. If a new handle is found, that is assumed to be
|
---|
328 | the popup window."""
|
---|
329 | return self._find_window(lambda handle: handle not in handles,
|
---|
330 | timeout = timeout)
|
---|
331 |
|
---|
332 | def _find_window(self, predicate, timeout = 10.0):
|
---|
333 | """Find a window that fulfills the given predicate."""
|
---|
334 | window_handle = None
|
---|
335 | end_time = time.time() + timeout
|
---|
336 | while window_handle is None and end_time > time.time():
|
---|
337 | for handle in self.driver.window_handles:
|
---|
338 | if predicate(handle):
|
---|
339 | window_handle = handle
|
---|
340 | break
|
---|
341 |
|
---|
342 | return window_handle
|
---|
343 |
|
---|
344 | if __name__ == "__main__":
|
---|
345 | mava_simbrief_url = "http://flare.privatedns.org/" \
|
---|
346 | "mava_simbrief/simbrief_form.html"
|
---|
347 | xml_link_fix_part = "http://www.simbrief.com/ofp/" \
|
---|
348 | "flightplans/xml/"
|
---|
349 | plan = {
|
---|
350 | 'airline': 'MAH',
|
---|
351 | 'fltnum': '764',
|
---|
352 | 'type': 'B738',
|
---|
353 | 'orig': 'LHBP',
|
---|
354 | 'dest': 'LSZH',
|
---|
355 | 'date': '25FEB15',
|
---|
356 | 'deph': '20',
|
---|
357 | 'depm': '00',
|
---|
358 | 'route': 'GILEP DCT ARSIN UL851 SITNI UL856 NEGRA',
|
---|
359 | 'steh': '22',
|
---|
360 | 'stem': '05',
|
---|
361 | 'reg': 'HA-LOC',
|
---|
362 | 'fin': 'LOC',
|
---|
363 | 'selcal': 'XXXX',
|
---|
364 | 'pax': '100',
|
---|
365 | 'altn': 'LSGG',
|
---|
366 | 'fl': '36000',
|
---|
367 | 'cpt': 'BALINT SZEBENYI',
|
---|
368 | 'pid': 'P008',
|
---|
369 | 'fuelfactor': 'P000',
|
---|
370 | 'manualzfw': '42.7',
|
---|
371 | 'addedfuel': '2.5',
|
---|
372 | 'contpct': '0.05',
|
---|
373 | 'resvrule': '45',
|
---|
374 | 'taxiout': '10',
|
---|
375 | 'taxiin': '4',
|
---|
376 | 'cargo': '5.0',
|
---|
377 | 'origrwy': '31L',
|
---|
378 | 'destrwy': '34',
|
---|
379 | 'climb': '250/300/78',
|
---|
380 | 'descent': '80/280/250',
|
---|
381 | 'cruise': 'LRC',
|
---|
382 | 'civalue': 'AUTO',
|
---|
383 | }
|
---|
384 | integrator = MavaSimbriefIntegrator(plan=plan)
|
---|
385 | link = integrator.get_xml_link(local_xml_debug=False,
|
---|
386 | local_html_debug=False)
|
---|
387 | flight_info = integrator.get_results(link)
|
---|
388 | print(flight_info)
|
---|
389 |
|
---|