[685] | 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 |
|
---|
[692] | 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 |
|
---|
[685] | 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:
|
---|
[692] | 158 | update_progress(MavaSimbriefIntegrator.PROGRESS_SEARCHING_BROWSER,
|
---|
| 159 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
[685] | 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!"
|
---|
[692] | 164 | update_progress(MavaSimbriefIntegrator.PROGRESS_SEARCHING_BROWSER,
|
---|
| 165 | MavaSimbriefIntegrator.RESULT_ERROR_OTHER, None)
|
---|
[685] | 166 | return None
|
---|
| 167 |
|
---|
| 168 | # normal operation with a real xml file
|
---|
[692] | 169 | update_progress(MavaSimbriefIntegrator.PROGRESS_LOADING_FORM,
|
---|
| 170 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
[685] | 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!"
|
---|
[692] | 181 | update_progress(MavaSimbriefIntegrator.PROGRESS_LOADING_FORM,
|
---|
| 182 | MavaSimbriefIntegrator.RESULT_ERROR_NO_FORM, None)
|
---|
[685] | 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
|
---|
[692] | 190 | update_progress(MavaSimbriefIntegrator.PROGRESS_FILLING_FORM,
|
---|
| 191 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
[685] | 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 |
|
---|
[692] | 200 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN,
|
---|
| 201 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
[685] | 202 | popup_handle = self._find_popup(handles)
|
---|
| 203 | if popup_handle is None:
|
---|
[692] | 204 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN,
|
---|
| 205 | MavaSimbriefIntegrator.RESULT_ERROR_NO_POPUP, None)
|
---|
[685] | 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:
|
---|
[692] | 217 | update_progress(MavaSimbriefIntegrator.PROGRESS_LOGGING_IN,
|
---|
| 218 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
[685] | 219 | (userName, password) = get_credentials(login_count)
|
---|
[692] | 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 |
|
---|
[685] | 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 |
|
---|
[692] | 237 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_RESULT,
|
---|
| 238 | MavaSimbriefIntegrator.RESULT_NONE, None)
|
---|
[685] | 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:
|
---|
[694] | 255 | update_progress(MavaSimbriefIntegrator.PROGRESS_WAITING_RESULT,
|
---|
| 256 | MavaSimbriefIntegrator.RESULT_ERROR_OTHER, None)
|
---|
[685] | 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 |
|
---|