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

Source Code for Module x2go.utils

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