Package x2go :: Module utils
[frames] | no frames]

Source Code for Module x2go.utils

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2014 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2Go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU Affero General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2Go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU Affero General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Affero General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 19   
 20  """\ 
 21  Python X2Go helper functions, constants etc. 
 22   
 23  """ 
 24  __NAME__ = 'x2goutils-pylib' 
 25   
 26  import sys 
 27  import os 
 28  import locale 
 29  import re 
 30  import types 
 31  import copy 
 32  import socket 
 33  import gevent 
 34  import string 
 35  import subprocess 
 36  import distutils.version 
 37   
 38  # Python X2Go modules 
 39  from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS 
 40  from defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS 
 41  from defaults import X2GO_MIMEBOX_ACTIONS as _X2GO_MIMEBOX_ACTIONS 
 42  from defaults import pack_methods_nx3 
 43   
 44  if _X2GOCLIENT_OS != 'Windows': 
 45      import Xlib 
 46      from defaults import X_DISPLAY as _X_DISPLAY 
 47   
 48  if _X2GOCLIENT_OS == 'Windows': 
 49      import win32gui 
 50      import win32print 
 51      import win32con 
 52   
53 -def is_in_nx3packmethods(method):
54 55 """\ 56 Test if a given compression method is valid for NX3 Proxy. 57 58 @return: C{True} if C{method} is in the hard-coded list of NX3 compression methods. 59 @rtype: C{bool} 60 61 """ 62 return method in pack_methods_nx3
63 64
65 -def find_session_line_in_x2golistsessions(session_name, stdout):
66 """\ 67 Return the X2Go session meta information as returned by the 68 C{x2golistsessions} server command for session C{session_name}. 69 70 @param session_name: name of a session 71 @type session_name: C{str} 72 @param stdout: raw output from the ,,x2golistsessions'' command, as list of strings 73 @type stdout: C{list} 74 75 @return: the output line that contains C{<session_name>} 76 @rtype: C{str} or C{None} 77 78 """ 79 sessions = stdout.read().split("\n") 80 for line in sessions: 81 # skip empty lines 82 if not line: 83 continue 84 if session_name == line.split("|")[1]: 85 return line 86 return None
87 88
89 -def slugify(value):
90 """\ 91 Normalizes string, converts to lowercase, removes non-alpha characters, 92 converts spaces to hyphens and replaces round brackets by pointed brackets. 93 94 @param value: a string that shall be sluggified 95 @type value: C{str} 96 97 @return: the sluggified string 98 @rtype: C{str} 99 100 """ 101 import unicodedata 102 value = unicodedata.normalize('NFKD', unicode(value)).encode('ascii', 'ignore') 103 value = re.sub('[^\w\s-]', '', value).strip().lower() 104 value = re.sub('[(]', '<', value).strip().lower() 105 value = re.sub('[)]', '>', value).strip().lower() 106 return value
107
108 -def _genSessionProfileId():
109 """\ 110 Generate a session profile ID as used in x2goclient's sessions config file. 111 112 @return: profile ID 113 @rtype: C{str} 114 115 """ 116 import datetime 117 return datetime.datetime.utcnow().strftime('%Y%m%d%H%m%S%f')
118 119
120 -def _checkIniFileDefaults(data_structure):
121 """\ 122 Check an ini file data structure passed on by a user app or class. 123 124 @param data_structure: an ini file date structure 125 @type data_structure: C{dict} of C{dict}s 126 127 @return: C{True} if C{data_structure} matches that of an ini file data structure 128 @rtype: C{bool} 129 130 """ 131 if data_structure is None: 132 return False 133 if type(data_structure) is not types.DictType: 134 return False 135 for sub_dict in data_structure.values(): 136 if type(sub_dict) is not types.DictType: 137 return False 138 return True
139 140
141 -def _checkSessionProfileDefaults(data_structure):
142 """\ 143 Check the data structure of a default session profile passed by a user app. 144 145 @param data_structure: an ini file date structure 146 @type data_structure: C{dict} of C{dict}s 147 148 @return: C{True} if C{data_structure} matches that of an ini file data structure 149 @rtype: C{bool} 150 151 """ 152 if data_structure is None: 153 return False 154 if type(data_structure) is not types.DictType: 155 return False 156 return True
157 158
159 -def _convert_SessionProfileOptions_2_SessionParams(options):
160 """\ 161 Convert session profile options as used in x2goclient's sessions file to 162 Python X2Go session parameters. 163 164 @param options: a dictionary of options, parameter names as in the X2Go ,,sessions'' file 165 @type options: C{dict} 166 167 @return: session options as used in C{X2GoSession} instances 168 @rtype: C{dict} 169 170 """ 171 _params = copy.deepcopy(options) 172 173 # get rid of unknown session profile options 174 _known_options = _X2GO_SESSIONPROFILE_DEFAULTS.keys() 175 for p in _params.keys(): 176 if p not in _known_options: 177 del _params[p] 178 179 _rename_dict = { 180 'host': 'server', 181 'user': 'username', 182 'soundsystem': 'snd_system', 183 'sndport': 'snd_port', 184 'type': 'kbtype', 185 'layout': 'kblayout', 186 'variant': 'kbvariant', 187 'speed': 'link', 188 'sshport': 'port', 189 'useexports': 'allow_share_local_folders', 190 'restoreexports': 'restore_shared_local_folders', 191 'usemimebox': 'allow_mimebox', 192 'mimeboxextensions': 'mimebox_extensions', 193 'mimeboxaction': 'mimebox_action', 194 'print': 'printing', 195 'name': 'profile_name', 196 'key': 'key_filename', 197 'command': 'cmd', 198 'rdpserver': 'rdp_server', 199 'rdpoptions': 'rdp_options', 200 'xdmcpserver': 'xdmcp_server', 201 'useiconv': 'convert_encoding', 202 'iconvto': 'server_encoding', 203 'iconvfrom': 'client_encoding', 204 'usesshproxy': 'use_sshproxy', 205 'sshproxyhost': 'sshproxy_host', 206 'sshproxyport': 'sshproxy_port', 207 'sshproxyuser': 'sshproxy_user', 208 'sshproxykeyfile': 'sshproxy_key_filename', 209 'sessiontitle': 'session_title', 210 'setsessiontitle': 'set_session_title', 211 'published': 'published_applications', 212 'autostart': 'auto_start_or_resume', 213 'autoconnect': 'auto_connect', 214 'forwardsshagent': 'forward_sshagent', 215 'autologin': 'look_for_keys', 216 'sshproxyautologin': 'sshproxy_look_for_keys', 217 'uniquehostkeyaliases': 'unique_hostkey_aliases', 218 } 219 _speed_dict = { 220 '0': 'modem', 221 '1': 'isdn', 222 '2': 'adsl', 223 '3': 'wan', 224 '4': 'lan', 225 } 226 227 for opt, val in options.iteritems(): 228 229 # rename options if necessary 230 if opt in _rename_dict.keys(): 231 del _params[opt] 232 opt = _rename_dict[opt] 233 if opt in _known_options: 234 _type = type(_known_options[opt]) 235 _params[opt] = _type(val) 236 else: 237 _params[opt] = val 238 239 # translate integer values for connection speed to readable strings 240 if opt == 'link': 241 val = str(val).lower() 242 if val in _speed_dict.keys(): 243 val = _speed_dict[val] 244 val = val.lower() 245 _params['link'] = val 246 247 # share_local_folders is a list 248 if opt in ('share_local_folders', 'mimebox_extensions'): 249 if type(val) is types.StringType: 250 if val: 251 _params[opt] = val.split(',') 252 else: 253 _params[opt] = [] 254 255 if _params['cmd'] == "XFCE4": _params['cmd'] = "XFCE" 256 if _params['look_for_keys']: 257 _params['allow_agent'] = True 258 if _params['sshproxy_look_for_keys']: 259 _params['sshproxy_allow_agent'] = True 260 261 # append value for quality to value for pack method 262 if _params['quality']: 263 _params['pack'] = '%s-%s' % (_params['pack'], _params['quality']) 264 # delete quality in any case... 265 del _params['quality'] 266 267 del _params['fstunnel'] 268 269 if _params.has_key('export'): 270 271 _export = _params['export'] 272 del _params['export'] 273 # fix for wrong export field usage in PyHoca-GUI/CLI and python-x2go before 20110923 274 _export = _export.replace(",", ";") 275 276 _export = _export.strip().strip('"').strip().strip(';').strip() 277 _export_list = [ f for f in _export.split(';') if f ] 278 279 _params['share_local_folders'] = [] 280 for _shared_folder in _export_list: 281 # fix for wrong export field usage in PyHoca-GUI/CLI and python-x2go before 20110923 282 if not ":" in _shared_folder: _shared_folder = "%s:1" % _shared_folder 283 if _shared_folder.split(":")[-1] == "1": 284 _params['share_local_folders'].append(":".join(_shared_folder.split(":")[:-1])) 285 286 if options['fullscreen']: 287 _params['geometry'] = 'fullscreen' 288 elif options['maxdim']: 289 _params['geometry'] = 'maximize' 290 else: 291 _params['geometry'] = '%sx%s' % (options['width'], options['height']) 292 del _params['width'] 293 del _params['height'] 294 del _params['fullscreen'] 295 del _params['maxdim'] 296 297 if not options['sound']: 298 _params['snd_system'] = 'none' 299 del _params['sound'] 300 301 if not options['rootless']: 302 _params['session_type'] = 'desktop' 303 else: 304 _params['session_type'] = 'application' 305 del _params['rootless'] 306 307 if _params['mimebox_action'] not in _X2GO_MIMEBOX_ACTIONS.keys(): 308 _params['mimebox_action'] = 'OPEN' 309 310 if not options['usekbd']: 311 _params['kbtype'] = 'null/null' 312 _params['kblayout'] = 'null' 313 _params['kbvariant'] = 'null' 314 del _params['usekbd'] 315 316 if not _params['kbtype'].strip(): _params['kbtype'] = 'null/null' 317 if not _params['kblayout'].strip(): _params['kblayout'] = 'null' 318 if not _params['kbvariant'].strip(): _params['kbvariant'] = 'null' 319 320 if not options['setdpi']: 321 del _params['dpi'] 322 del _params['setdpi'] 323 324 if options['sshproxysameuser']: 325 _params['sshproxy_user'] = _params['username'] 326 del _params['sshproxysameuser'] 327 if options['sshproxysamepass']: 328 _params['sshproxy_reuse_authinfo'] = True 329 _params['sshproxy_key_filename'] = _params['key_filename'] 330 del _params['sshproxysamepass'] 331 332 if _params['use_sshproxy']: 333 334 # compat code for Python X2Go 0.2.1.0 -> 0.2.2.0 335 if options.has_key('sshproxytunnel'): 336 if not options['sshproxytunnel'].startswith('DEPRECATED'): 337 _params['server'] = options['sshproxytunnel'].split(":")[-2] 338 _params['port'] = options['sshproxytunnel'].split(":")[-1] 339 try: del _params['sshproxytunnel'] 340 except KeyError: pass 341 342 _params['sshproxy_tunnel'] = 'localhost:44444:%s:%s' % (_params['server'], _params['port']) 343 344 345 # currently known but ignored in Python X2Go 346 _ignored_options = [ 347 'startsoundsystem', 348 'soundtunnel', 349 'defsndport', 350 'icon', 351 'xinerama', 352 'multidisp', 353 'display', 354 'krblogin', 355 'directrdp', 356 'directrdpsettings', 357 'rdpclient', 358 'rdpport', 359 'sshproxytype', 360 ] 361 for i in _ignored_options: 362 del _params[i] 363 364 return _params
365 366
367 -def session_names_by_timestamp(session_infos):
368 """\ 369 Sorts session profile names by their timestamp (as used in the file format's section name). 370 371 @param session_infos: a dictionary of session infos as reported by L{X2GoClient.list_sessions()} 372 @type session_infos: C{dict} 373 374 @return: a timestamp-sorted list of session names found in C{session_infos} 375 @rtype: C{list} 376 377 """ 378 session_names = session_infos.keys() 379 sortable_session_names = [ '%s|%s' % (session_name.split('-')[-1].split('_')[0], session_name) for session_name in session_names ] 380 sortable_session_names.sort() 381 return [ session_name.split('|')[1] for session_name in sortable_session_names ]
382 383
384 -def touch_file(filename, mode='a'):
385 """\ 386 Imitates the behaviour of the GNU/touch command. 387 388 @param filename: name of the file to touch 389 @type filename: C{str} 390 @param mode: the file mode (as used for Python file objects) 391 @type mode: C{str} 392 393 """ 394 if not os.path.isdir(os.path.dirname(filename)): 395 os.makedirs(os.path.dirname(filename), mode=00700) 396 f = open(filename, mode=mode) 397 f.close()
398 399
400 -def unique(seq):
401 """\ 402 Imitates the behaviour of the GNU/uniq command. 403 404 @param seq: a list/sequence containing consecutive duplicates. 405 @type seq: C{list} 406 407 @return: list that has been clean up from the consecutive duplicates 408 @rtype: C{list} 409 410 """ 411 # order preserving 412 noDupes = [] 413 [noDupes.append(i) for i in seq if not noDupes.count(i)] 414 return noDupes
415 416
417 -def known_encodings():
418 """\ 419 Render a list of all-known-to-Python character encodings (including 420 all known aliases) 421 422 """ 423 from encodings.aliases import aliases 424 _raw_encname_list = [] 425 _raw_encname_list.extend(aliases.keys()) 426 _raw_encname_list.extend(aliases.values()) 427 _raw_encname_list.sort() 428 _encname_list = [] 429 for _raw_encname in _raw_encname_list: 430 _encname = _raw_encname.upper() 431 _encname = _encname.replace('_', '-') 432 _encname_list.append(_encname) 433 _encname_list.sort() 434 _encname_list = unique(_encname_list) 435 return _encname_list
436 437
438 -def patiently_remove_file(dirname, filename):
439 """\ 440 Try to remove a file, wait for unlocking, remove it once removing is possible... 441 442 @param dirname: directory name the file is in 443 @type dirname: C{str} 444 @param filename: name of the file to be removed 445 @type filename: C{str} 446 447 """ 448 _not_removed = True 449 while _not_removed: 450 try: 451 os.remove(os.path.join(dirname, filename)) 452 _not_removed = False 453 except: 454 # file is probably locked 455 gevent.sleep(5)
456 457
458 -def detect_unused_port(bind_address='127.0.0.1', preferred_port=None):
459 """\ 460 Detect an unused IP socket. 461 462 @param bind_address: IP address to bind to 463 @type bind_address: C{str} 464 @param preferred_port: IP socket port that shall be tried first for availability 465 @type preferred_port: C{str} 466 467 @return: free local IP socket port that can be used for binding 468 @rtype: C{str} 469 470 """ 471 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 472 try: 473 if preferred_port: 474 sock.bind((bind_address, preferred_port)) 475 ipaddr, port = sock.getsockname() 476 else: 477 raise 478 except: 479 sock.bind(('', 0)) 480 ipaddr, port = sock.getsockname() 481 return port
482 483
484 -def get_encoding():
485 """\ 486 Detect systems default character encoding. 487 488 @return: The system's local character encoding. 489 @rtype: C{str} 490 491 """ 492 try: 493 encoding = locale.getdefaultlocale()[1] 494 if encoding is None: 495 raise BaseException 496 except: 497 try: 498 encoding = sys.getdefaultencoding() 499 except: 500 encoding = 'ascii' 501 return encoding
502 503
504 -def is_abs_path(path):
505 """\ 506 Test if a given path is an absolute path name. 507 508 @param path: test this path for absolutism... 509 @type path: C{str} 510 511 @return: Returns C{True} if path is an absolute path name 512 @rtype: C{bool} 513 514 """ 515 return bool((path.startswith('/') or re.match('^[%s]\:\\\\' % string.ascii_letters, path)))
516 517
518 -def xkb_rules_names():
519 """\ 520 Wrapper for: xprop -root _XKB_RULES_NAMES 521 522 @return: A Python dictionary that contains the current X11 keyboard rules. 523 @rtype: C{dict} 524 525 """ 526 p = subprocess.Popen(['xprop', '-root', '_XKB_RULES_NAMES',], stdout=subprocess.PIPE, ) 527 _rn_list = p.stdout.read().split('"') 528 _rn_dict = { 529 'rules': _rn_list[1], 530 'model': _rn_list[3], 531 'layout': _rn_list[5], 532 'variant': _rn_list[7], 533 'options': _rn_list[9], 534 } 535 return _rn_dict
536
537 -def local_color_depth():
538 """\ 539 Detect the current local screen's color depth. 540 541 @return: the local color depth in bits 542 @rtype: C{int} 543 544 """ 545 if _X2GOCLIENT_OS != 'Windows': 546 try: 547 p = subprocess.Popen(['xwininfo', '-root',], stdout=subprocess.PIPE, ) 548 _depth_line = [ _info.strip() for _info in p.stdout.read().split('\n') if 'Depth:' in _info ][0] 549 _depth = _depth_line.split(' ')[1] 550 return int(_depth) 551 except IndexError: 552 # a sensible default value 553 return 24 554 except OSError: 555 # for building your package... 556 return 24 557 558 else: 559 # This gets the color depth of the primary monitor. All monitors need not have the same color depth. 560 dc = win32gui.GetDC(None) 561 _depth = win32print.GetDeviceCaps(dc, win32con.BITSPIXEL) * win32print.GetDeviceCaps(dc, win32con.PLANES) 562 win32gui.ReleaseDC(None, dc)
563
564 -def is_color_depth_ok(depth_session, depth_local):
565 """\ 566 Test if color depth of this session is compatible with the 567 local screen's color depth. 568 569 @param depth_session: color depth of the session 570 @type depth_session: C{int} 571 @param depth_local: color depth of local screen 572 @type depth_local: C{int} 573 574 @return: Does the session color depth work with the local display? 575 @rtype: C{bool} 576 577 """ 578 if depth_session == 0: 579 return True 580 if depth_session == depth_local: 581 return True 582 if ( ( depth_session == 24 or depth_session == 32 ) and ( depth_local == 24 or depth_local == 32 ) ): 583 return True; 584 if ( ( depth_session == 16 or depth_session == 17 ) and ( depth_local == 16 or depth_local == 17 ) ): 585 return True; 586 return False
587 588
589 -def find_session_window(session_name):
590 """\ 591 Find a session window by its X2GO session ID. 592 593 @param session_name: session name/ID of an X2Go session window 594 @type session_name: C{str} 595 596 @return: the window object (or ID) of the searched for session window 597 @rtype: C{obj} on Unix, C{int} on Windows 598 599 """ 600 if _X2GOCLIENT_OS != 'Windows': 601 # establish connection to the win API in use... 602 display = _X_DISPLAY 603 root = display.screen().root 604 605 success = False 606 windowIDs_obj = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'), Xlib.X.AnyPropertyType) 607 608 if windowIDs_obj is None: 609 # works with i3 session manager... 610 windowIDs_obj = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST_STACKING'), Xlib.X.AnyPropertyType) 611 612 if windowIDs_obj is not None: 613 windowIDs = windowIDs_obj.value 614 615 for windowID in windowIDs: 616 window = display.create_resource_object('window', windowID) 617 try: 618 name = window.get_wm_name() 619 except Xlib.error.BadWindow: 620 continue 621 if name is not None and session_name in name: 622 success = True 623 break 624 625 if success: 626 return window 627 628 else: 629 630 windows = [] 631 window = None 632 633 def _callback(hwnd, extra): 634 if win32gui.GetWindowText(hwnd) == "X2GO-%s" % session_name: 635 windows.append(hwnd)
636 637 win32gui.EnumWindows(_callback, None) 638 if len(windows): window = windows[0] 639 640 return window 641 642
643 -def get_desktop_geometry():
644 """\ 645 Get the geometry of the current screen's desktop by 646 wrapping around:: 647 648 xprop -root '_NET_DESKTOP_GEOMETRY' 649 650 @return: a (<width>, <height>) tuple will be returned 651 @rtype: C{tuple} 652 653 """ 654 if _X2GOCLIENT_OS != 'Windows': 655 p = subprocess.Popen(['xprop', '-root', '_NET_DESKTOP_GEOMETRY',], stdout=subprocess.PIPE, ) 656 _paramval = p.stdout.read().split("=") 657 if len(_paramval) == 2: 658 _list = _paramval[1].rstrip('\n').split(',') 659 if len(_list) == 2: 660 return (_list[0].strip(), _list[1].strip()) 661 662 return None
663
664 -def get_workarea_geometry():
665 """\ 666 Get the geometry of the current screen's work area by 667 wrapping around:: 668 669 xprop -root '_NET_WORKAREA' 670 671 @return: a (<width>, <height>) tuple will be returned 672 @rtype: C{tuple} 673 674 """ 675 if _X2GOCLIENT_OS != 'Windows': 676 p = subprocess.Popen(['xprop', '-root', '_NET_WORKAREA',], stdout=subprocess.PIPE, ) 677 _list = p.stdout.read().rstrip('\n').split(',') 678 if len(_list) == 4: 679 return (_list[2].strip(), _list[3].strip()) 680 else: 681 return None 682 else: 683 684 return None
685 686
687 -def set_session_window_title(session_window, session_title):
688 """\ 689 Set title of session window. 690 691 @param session_window: session window instance 692 @type session_window: C{obj} 693 @param session_title: session title to be set for C{session_window} 694 @type session_title: C{str} 695 696 """ 697 if _X2GOCLIENT_OS != 'Windows': 698 try: 699 session_window.set_wm_name(str(session_title)) 700 session_window.set_wm_icon_name(str(session_title)) 701 _X_DISPLAY.sync() 702 except Xlib.error.BadWindow: 703 pass 704 705 else: 706 win32gui.SetWindowText(session_window, session_title)
707 708
709 -def raise_session_window(session_window):
710 """\ 711 Raise session window. Not functional for Unix-like operating systems. 712 713 @param session_window: session window instance 714 @type session_window: C{obj} 715 716 """ 717 if _X2GOCLIENT_OS != 'Windows': 718 pass 719 else: 720 if session_window is not None: 721 win32gui.SetForegroundWindow(session_window)
722 723
724 -def merge_ordered_lists(l1, l2):
725 """\ 726 Merge sort two sorted lists 727 728 @param l1: first sorted list 729 @type l1: C{list} 730 @param l2: second sorted list 731 @type l2: C{list} 732 733 @return: the merge result of both sorted lists 734 @rtype: C{list} 735 736 """ 737 ordered_list = [] 738 739 # Copy both the args to make sure the original lists are not 740 # modified 741 l1 = l1[:] 742 l2 = l2[:] 743 744 while (l1 and l2): 745 if l1[0] not in l2: 746 item = l1.pop(0) 747 elif l2[0] not in l1: 748 item = l2.pop(0) 749 elif l1[0] in l2: 750 item = l1.pop(0) 751 l2.remove(item) 752 if item not in ordered_list: 753 ordered_list.append(item) 754 755 # Add the remaining of the lists 756 ordered_list.extend(l1 if l1 else l2) 757 758 return ordered_list
759
760 -def compare_versions(version_a, op, version_b):
761 """\ 762 Compare <version_a> with <version_b> using operator <op>. 763 In the background C{distutils.version.LooseVersion} is 764 used for the comparison operation. 765 766 @param version_a: a version string 767 @type version_a: C{str} 768 @param op: an operator provide as string (e.g. '<', '>', '==', '>=' etc.) 769 @type op: C{str} 770 @param version_b: another version string that is to be compared with <version_a> 771 @type version_b: C{str} 772 773 """ 774 775 ### FIXME: this comparison is not reliable with beta et al. version strings 776 777 ver_a = distutils.version.LooseVersion(version_a) 778 ver_b = distutils.version.LooseVersion(version_b) 779 780 return eval("ver_a %s ver_b" % op)
781
782 -class ProgressStatus(object):
783 """\ 784 A simple progress status iterator class. 785 786 """
787 - def __init__(self, progress_event, progress_func=range(0, 100, 10)):
788 """\ 789 @param progress_event: a threading.Event() object that gets notified on progress 790 @type progress_event: C{obj} 791 @param progress_func: a function that delivers a value between 0 and 100 (progress percentage value) 792 @type progress_func: C{func} 793 794 """ 795 self.ev = progress_event 796 self.progress_func = progress_func
797
798 - def __iter__(self):
799 """\ 800 Intialize the L{ProgressStatus} iterator object. 801 802 """ 803 self.status = self.progress_func() 804 return self
805
806 - def next(self):
807 """\ 808 On each iteration wait for the progress event to get triggered from an outside 809 part of the application. 810 811 Once the event fires read the progress status from the progress retrieval function 812 and clear the event afterwards (so we wait for the next firing of the event). 813 814 """ 815 if self.status < 100 and self.status != -1: 816 self.ev.wait() 817 self.status = self.progress_func() 818 self.ev.clear() 819 return self.status 820 else: 821 raise StopIteration
822