Package gluon :: Module widget
[hide private]
[frames] | no frames]

Source Code for Module gluon.widget

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3   
   4  """ 
   5  This file is part of the web2py Web Framework 
   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
   8   
   9  The widget is called from web2py. 
  10  """ 
  11   
  12  import datetime 
  13  import sys 
  14  import cStringIO 
  15  import time 
  16  import thread 
  17  import threading 
  18  import os 
  19  import socket 
  20  import signal 
  21  import math 
  22  import logging 
  23  import newcron 
  24  import getpass 
  25  import gluon.main as main 
  26   
  27  from gluon.fileutils import read_file, write_file, create_welcome_w2p 
  28  from gluon.settings import global_settings 
  29  from gluon.shell import run, test 
  30  from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo 
  31   
  32   
  33  ProgramName = 'web2py Web Framework' 
  34  ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-' + str( 
  35      datetime.datetime.now().year) 
  36  ProgramVersion = read_file('VERSION').strip() 
  37   
  38  ProgramInfo = '''%s 
  39                   %s 
  40                   %s''' % (ProgramName, ProgramAuthor, ProgramVersion) 
  41   
  42  if not sys.version[:3] in ['2.5', '2.6', '2.7']: 
  43      msg = 'Warning: web2py requires Python 2.5, 2.6 or 2.7 but you are running:\n%s' 
  44      msg = msg % sys.version 
  45      sys.stderr.write(msg) 
  46   
  47  logger = logging.getLogger("web2py") 
  48   
  49   
50 -def run_system_tests(options):
51 """ 52 Runs unittests for gluon.tests 53 """ 54 import subprocess 55 major_version = sys.version_info[0] 56 minor_version = sys.version_info[1] 57 if major_version == 2: 58 if minor_version in (5, 6): 59 sys.stderr.write("Python 2.5 or 2.6\n") 60 ret = subprocess.call(['unit2', '-v', 'gluon.tests']) 61 elif minor_version in (7,): 62 call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests'] 63 if options.with_coverage: 64 try: 65 import coverage 66 coverage_config = os.environ.get( 67 "COVERAGE_PROCESS_START", 68 os.path.join('gluon', 'tests', 'coverage.ini')) 69 70 call_args = ['coverage', 'run', '--rcfile=%s' % 71 coverage_config, 72 '-m', 'unittest', '-v', 'gluon.tests'] 73 except: 74 sys.stderr.write('Coverage was not installed, skipping\n') 75 sys.stderr.write("Python 2.7\n") 76 ret = subprocess.call(call_args) 77 else: 78 sys.stderr.write("unknown python 2.x version\n") 79 ret = 256 80 else: 81 sys.stderr.write("Only Python 2.x supported.\n") 82 ret = 256 83 sys.exit(ret and 1)
84 85
86 -class IO(object):
87 """ """ 88
89 - def __init__(self):
90 """ """ 91 92 self.buffer = cStringIO.StringIO()
93
94 - def write(self, data):
95 """ """ 96 97 sys.__stdout__.write(data) 98 if hasattr(self, 'callback'): 99 self.callback(data) 100 else: 101 self.buffer.write(data)
102 103
104 -def get_url(host, path='/', proto='http', port=80):
105 if ':' in host: 106 host = '[%s]' % host 107 else: 108 host = host.replace('0.0.0.0', '127.0.0.1') 109 if path.startswith('/'): 110 path = path[1:] 111 if proto.endswith(':'): 112 proto = proto[:-1] 113 if not port or port == 80: 114 port = '' 115 else: 116 port = ':%s' % port 117 return '%s://%s%s/%s' % (proto, host, port, path)
118 119
120 -def start_browser(url, startup=False):
121 if startup: 122 print 'please visit:' 123 print '\t', url 124 print 'starting browser...' 125 try: 126 import webbrowser 127 webbrowser.open(url) 128 except: 129 print 'warning: unable to detect your browser'
130 131
132 -def presentation(root):
133 """ Draw the splash screen """ 134 import Tkinter 135 136 root.withdraw() 137 138 dx = root.winfo_screenwidth() 139 dy = root.winfo_screenheight() 140 141 dialog = Tkinter.Toplevel(root, bg='white') 142 dialog.geometry('%ix%i+%i+%i' % (500, 300, dx / 2 - 200, dy / 2 - 150)) 143 144 dialog.overrideredirect(1) 145 dialog.focus_force() 146 147 canvas = Tkinter.Canvas(dialog, 148 background='white', 149 width=500, 150 height=300) 151 canvas.pack() 152 root.update() 153 154 logo = os.path.join('extras','icons','splashlogo.gif') 155 if os.path.exists(logo): 156 img = Tkinter.PhotoImage(file=logo) 157 pnl = Tkinter.Label(canvas, image=img, background='white', bd=0) 158 pnl.pack(side='top', fill='both', expand='yes') 159 # Prevent garbage collection of img 160 pnl.image = img 161 162 def add_label(text='Change Me', font_size=12, 163 foreground='#195866', height=1): 164 return Tkinter.Label( 165 master=canvas, 166 width=250, 167 height=height, 168 text=text, 169 font=('Helvetica', font_size), 170 anchor=Tkinter.CENTER, 171 foreground=foreground, 172 background='white' 173 )
174 175 add_label('Welcome to...').pack(side='top') 176 add_label(ProgramName, 18, '#FF5C1F', 2).pack() 177 add_label(ProgramAuthor).pack() 178 add_label(ProgramVersion).pack() 179 180 root.update() 181 time.sleep(5) 182 dialog.destroy() 183 return 184 185
186 -class web2pyDialog(object):
187 """ Main window dialog """ 188
189 - def __init__(self, root, options):
190 """ web2pyDialog constructor """ 191 192 import Tkinter 193 import tkMessageBox 194 195 root.title('web2py server') 196 self.root = Tkinter.Toplevel(root) 197 self.options = options 198 self.scheduler_processes = {} 199 self.menu = Tkinter.Menu(self.root) 200 servermenu = Tkinter.Menu(self.menu, tearoff=0) 201 httplog = os.path.join(self.options.folder, 'httpserver.log') 202 iconphoto = os.path.join('extras','icons','web2py.gif') 203 if os.path.exists(iconphoto): 204 img = Tkinter.PhotoImage(file=iconphoto) 205 self.root.tk.call('wm', 'iconphoto', self.root._w, img) 206 # Building the Menu 207 item = lambda: start_browser(httplog) 208 servermenu.add_command(label='View httpserver.log', 209 command=item) 210 211 servermenu.add_command(label='Quit (pid:%i)' % os.getpid(), 212 command=self.quit) 213 214 self.menu.add_cascade(label='Server', menu=servermenu) 215 216 self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0) 217 self.menu.add_cascade(label='Pages', menu=self.pagesmenu) 218 219 #scheduler menu 220 self.schedmenu = Tkinter.Menu(self.menu, tearoff=0) 221 self.menu.add_cascade(label='Scheduler', menu=self.schedmenu) 222 #start and register schedulers from options 223 self.update_schedulers(start=True) 224 225 helpmenu = Tkinter.Menu(self.menu, tearoff=0) 226 227 # Home Page 228 item = lambda: start_browser('http://www.web2py.com/') 229 helpmenu.add_command(label='Home Page', 230 command=item) 231 232 # About 233 item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo) 234 helpmenu.add_command(label='About', 235 command=item) 236 237 self.menu.add_cascade(label='Info', menu=helpmenu) 238 239 self.root.config(menu=self.menu) 240 241 if options.taskbar: 242 self.root.protocol('WM_DELETE_WINDOW', 243 lambda: self.quit(True)) 244 else: 245 self.root.protocol('WM_DELETE_WINDOW', self.quit) 246 247 sticky = Tkinter.NW 248 249 # IP 250 Tkinter.Label(self.root, 251 text='Server IP:', 252 justify=Tkinter.LEFT).grid(row=0, 253 column=0, 254 sticky=sticky) 255 self.ips = {} 256 self.selected_ip = Tkinter.StringVar() 257 row = 0 258 ips = [('127.0.0.1', 'Local (IPv4)')] + \ 259 ([('::1', 'Local (IPv6)')] if socket.has_ipv6 else []) + \ 260 [(ip, 'Public') for ip in options.ips] + \ 261 [('0.0.0.0', 'Public')] 262 for ip, legend in ips: 263 self.ips[ip] = Tkinter.Radiobutton( 264 self.root, text='%s (%s)' % (legend, ip), 265 variable=self.selected_ip, value=ip) 266 self.ips[ip].grid(row=row, column=1, sticky=sticky) 267 if row == 0: 268 self.ips[ip].select() 269 row += 1 270 shift = row 271 # Port 272 Tkinter.Label(self.root, 273 text='Server Port:', 274 justify=Tkinter.LEFT).grid(row=shift, 275 column=0, 276 sticky=sticky) 277 278 self.port_number = Tkinter.Entry(self.root) 279 self.port_number.insert(Tkinter.END, self.options.port) 280 self.port_number.grid(row=shift, column=1, sticky=sticky) 281 282 # Password 283 Tkinter.Label(self.root, 284 text='Choose Password:', 285 justify=Tkinter.LEFT).grid(row=shift + 1, 286 column=0, 287 sticky=sticky) 288 289 self.password = Tkinter.Entry(self.root, show='*') 290 self.password.bind('<Return>', lambda e: self.start()) 291 self.password.focus_force() 292 self.password.grid(row=shift + 1, column=1, sticky=sticky) 293 294 # Prepare the canvas 295 self.canvas = Tkinter.Canvas(self.root, 296 width=300, 297 height=100, 298 bg='black') 299 self.canvas.grid(row=shift + 2, column=0, columnspan=2) 300 self.canvas.after(1000, self.update_canvas) 301 302 # Prepare the frame 303 frame = Tkinter.Frame(self.root) 304 frame.grid(row=shift + 3, column=0, columnspan=2) 305 306 # Start button 307 self.button_start = Tkinter.Button(frame, 308 text='start server', 309 command=self.start) 310 311 self.button_start.grid(row=0, column=0) 312 313 # Stop button 314 self.button_stop = Tkinter.Button(frame, 315 text='stop server', 316 command=self.stop) 317 318 self.button_stop.grid(row=0, column=1) 319 self.button_stop.configure(state='disabled') 320 321 if options.taskbar: 322 import gluon.contrib.taskbar_widget 323 self.tb = gluon.contrib.taskbar_widget.TaskBarIcon() 324 self.checkTaskBar() 325 326 if options.password != '<ask>': 327 self.password.insert(0, options.password) 328 self.start() 329 self.root.withdraw() 330 else: 331 self.tb = None
332
333 - def update_schedulers(self, start=False):
334 apps = [] 335 available_apps = [arq for arq in os.listdir('applications/')] 336 available_apps = [arq for arq in available_apps 337 if os.path.exists( 338 'applications/%s/models/scheduler.py' % arq)] 339 if start: 340 #the widget takes care of starting the scheduler 341 if self.options.scheduler and self.options.with_scheduler: 342 apps = [app.strip() for app 343 in self.options.scheduler.split(',') 344 if app in available_apps] 345 for app in apps: 346 self.try_start_scheduler(app) 347 348 #reset the menu 349 self.schedmenu.delete(0, len(available_apps)) 350 for arq in available_apps: 351 if arq not in self.scheduler_processes: 352 item = lambda u = arq: self.try_start_scheduler(u) 353 self.schedmenu.add_command(label="start %s" % arq, 354 command=item) 355 if arq in self.scheduler_processes: 356 item = lambda u = arq: self.try_stop_scheduler(u) 357 self.schedmenu.add_command(label="stop %s" % arq, 358 command=item)
359
360 - def start_schedulers(self, app):
361 try: 362 from multiprocessing import Process 363 except: 364 sys.stderr.write('Sorry, -K only supported for python 2.6-2.7\n') 365 return 366 code = "from gluon import current;current._scheduler.loop()" 367 print 'starting scheduler from widget for "%s"...' % app 368 args = (app, True, True, None, False, code) 369 logging.getLogger().setLevel(self.options.debuglevel) 370 p = Process(target=run, args=args) 371 self.scheduler_processes[app] = p 372 self.update_schedulers() 373 print "Currently running %s scheduler processes" % ( 374 len(self.scheduler_processes)) 375 p.start() 376 print "Processes started"
377
378 - def try_stop_scheduler(self, app):
379 if app in self.scheduler_processes: 380 p = self.scheduler_processes[app] 381 del self.scheduler_processes[app] 382 p.terminate() 383 p.join() 384 self.update_schedulers()
385
386 - def try_start_scheduler(self, app):
387 if app not in self.scheduler_processes: 388 t = threading.Thread(target=self.start_schedulers, args=(app,)) 389 t.start()
390
391 - def checkTaskBar(self):
392 """ Check taskbar status """ 393 394 if self.tb.status: 395 if self.tb.status[0] == self.tb.EnumStatus.QUIT: 396 self.quit() 397 elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE: 398 if self.root.state() == 'withdrawn': 399 self.root.deiconify() 400 else: 401 self.root.withdraw() 402 elif self.tb.status[0] == self.tb.EnumStatus.STOP: 403 self.stop() 404 elif self.tb.status[0] == self.tb.EnumStatus.START: 405 self.start() 406 elif self.tb.status[0] == self.tb.EnumStatus.RESTART: 407 self.stop() 408 self.start() 409 del self.tb.status[0] 410 411 self.root.after(1000, self.checkTaskBar)
412
413 - def update(self, text):
414 """ Update app text """ 415 416 try: 417 self.text.configure(state='normal') 418 self.text.insert('end', text) 419 self.text.configure(state='disabled') 420 except: 421 pass # ## this should only happen in case app is destroyed
422
423 - def connect_pages(self):
424 """ Connect pages """ 425 #reset the menu 426 available_apps = [arq for arq in os.listdir('applications/') 427 if os.path.exists( 428 'applications/%s/__init__.py' % arq)] 429 self.pagesmenu.delete(0, len(available_apps)) 430 for arq in available_apps: 431 url = self.url + arq 432 self.pagesmenu.add_command( 433 label=url, command=lambda u=url: start_browser(u))
434
435 - def quit(self, justHide=False):
436 """ Finish the program execution """ 437 if justHide: 438 self.root.withdraw() 439 else: 440 try: 441 scheds = self.scheduler_processes.keys() 442 for t in scheds: 443 self.try_stop_scheduler(t) 444 except: 445 pass 446 try: 447 newcron.stopcron() 448 except: 449 pass 450 try: 451 self.server.stop() 452 except: 453 pass 454 try: 455 self.tb.Destroy() 456 except: 457 pass 458 459 self.root.destroy() 460 sys.exit(0)
461
462 - def error(self, message):
463 """ Show error message """ 464 465 import tkMessageBox 466 tkMessageBox.showerror('web2py start server', message)
467
468 - def start(self):
469 """ Start web2py server """ 470 471 password = self.password.get() 472 473 if not password: 474 self.error('no password, no web admin interface') 475 476 ip = self.selected_ip.get() 477 478 if not is_valid_ip_address(ip): 479 return self.error('invalid host ip address') 480 481 try: 482 port = int(self.port_number.get()) 483 except: 484 return self.error('invalid port number') 485 486 # Check for non default value for ssl inputs 487 if (len(self.options.ssl_certificate) > 0 or 488 len(self.options.ssl_private_key) > 0): 489 proto = 'https' 490 else: 491 proto = 'http' 492 493 self.url = get_url(ip, proto=proto, port=port) 494 self.connect_pages() 495 self.button_start.configure(state='disabled') 496 497 try: 498 options = self.options 499 req_queue_size = options.request_queue_size 500 self.server = main.HttpServer( 501 ip, 502 port, 503 password, 504 pid_filename=options.pid_filename, 505 log_filename=options.log_filename, 506 profiler_dir=options.profiler_dir, 507 ssl_certificate=options.ssl_certificate, 508 ssl_private_key=options.ssl_private_key, 509 ssl_ca_certificate=options.ssl_ca_certificate, 510 min_threads=options.minthreads, 511 max_threads=options.maxthreads, 512 server_name=options.server_name, 513 request_queue_size=req_queue_size, 514 timeout=options.timeout, 515 shutdown_timeout=options.shutdown_timeout, 516 path=options.folder, 517 interfaces=options.interfaces) 518 519 thread.start_new_thread(self.server.start, ()) 520 except Exception, e: 521 self.button_start.configure(state='normal') 522 return self.error(str(e)) 523 524 if not self.server_ready(): 525 self.button_start.configure(state='normal') 526 return 527 528 self.button_stop.configure(state='normal') 529 530 if not options.taskbar: 531 thread.start_new_thread( 532 start_browser, (get_url(ip, proto=proto, port=port), True)) 533 534 self.password.configure(state='readonly') 535 [ip.configure(state='disabled') for ip in self.ips.values()] 536 self.port_number.configure(state='readonly') 537 538 if self.tb: 539 self.tb.SetServerRunning()
540
541 - def server_ready(self):
542 for listener in self.server.server.listeners: 543 if listener.ready: 544 return True 545 546 return False
547
548 - def stop(self):
549 """ Stop web2py server """ 550 551 self.button_start.configure(state='normal') 552 self.button_stop.configure(state='disabled') 553 self.password.configure(state='normal') 554 [ip.configure(state='normal') for ip in self.ips.values()] 555 self.port_number.configure(state='normal') 556 self.server.stop() 557 558 if self.tb: 559 self.tb.SetServerStopped()
560
561 - def update_canvas(self):
562 """ Update canvas """ 563 564 try: 565 t1 = os.path.getsize('httpserver.log') 566 except: 567 self.canvas.after(1000, self.update_canvas) 568 return 569 570 try: 571 fp = open('httpserver.log', 'r') 572 fp.seek(self.t0) 573 data = fp.read(t1 - self.t0) 574 fp.close() 575 value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))] 576 self.p0 = value 577 578 for i in xrange(len(self.p0) - 1): 579 c = self.canvas.coords(self.q0[i]) 580 self.canvas.coords(self.q0[i], 581 (c[0], 582 self.p0[i], 583 c[2], 584 self.p0[i + 1])) 585 self.t0 = t1 586 except BaseException: 587 self.t0 = time.time() 588 self.t0 = t1 589 self.p0 = [100] * 300 590 self.q0 = [self.canvas.create_line(i, 100, i + 1, 100, 591 fill='green') for i in xrange(len(self.p0) - 1)] 592 593 self.canvas.after(1000, self.update_canvas)
594 595
596 -def console():
597 """ Defines the behavior of the console web2py execution """ 598 import optparse 599 import textwrap 600 601 usage = "python web2py.py" 602 603 description = """\ 604 web2py Web Framework startup script. 605 ATTENTION: unless a password is specified (-a 'passwd') web2py will 606 attempt to run a GUI. In this case command line options are ignored.""" 607 608 description = textwrap.dedent(description) 609 610 parser = optparse.OptionParser( 611 usage, None, optparse.Option, ProgramVersion) 612 613 parser.description = description 614 615 msg = ('IP address of the server (e.g., 127.0.0.1 or ::1); ' 616 'Note: This value is ignored when using the \'interfaces\' option.') 617 parser.add_option('-i', 618 '--ip', 619 default='127.0.0.1', 620 dest='ip', 621 help=msg) 622 623 parser.add_option('-p', 624 '--port', 625 default='8000', 626 dest='port', 627 type='int', 628 help='port of server (8000)') 629 630 msg = ('password to be used for administration ' 631 '(use -a "<recycle>" to reuse the last password))') 632 parser.add_option('-a', 633 '--password', 634 default='<ask>', 635 dest='password', 636 help=msg) 637 638 parser.add_option('-c', 639 '--ssl_certificate', 640 default='', 641 dest='ssl_certificate', 642 help='file that contains ssl certificate') 643 644 parser.add_option('-k', 645 '--ssl_private_key', 646 default='', 647 dest='ssl_private_key', 648 help='file that contains ssl private key') 649 650 msg = ('Use this file containing the CA certificate to validate X509 ' 651 'certificates from clients') 652 parser.add_option('--ca-cert', 653 action='store', 654 dest='ssl_ca_certificate', 655 default=None, 656 help=msg) 657 658 parser.add_option('-d', 659 '--pid_filename', 660 default='httpserver.pid', 661 dest='pid_filename', 662 help='file to store the pid of the server') 663 664 parser.add_option('-l', 665 '--log_filename', 666 default='httpserver.log', 667 dest='log_filename', 668 help='file to log connections') 669 670 parser.add_option('-n', 671 '--numthreads', 672 default=None, 673 type='int', 674 dest='numthreads', 675 help='number of threads (deprecated)') 676 677 parser.add_option('--minthreads', 678 default=None, 679 type='int', 680 dest='minthreads', 681 help='minimum number of server threads') 682 683 parser.add_option('--maxthreads', 684 default=None, 685 type='int', 686 dest='maxthreads', 687 help='maximum number of server threads') 688 689 parser.add_option('-s', 690 '--server_name', 691 default=socket.gethostname(), 692 dest='server_name', 693 help='server name for the web server') 694 695 msg = 'max number of queued requests when server unavailable' 696 parser.add_option('-q', 697 '--request_queue_size', 698 default='5', 699 type='int', 700 dest='request_queue_size', 701 help=msg) 702 703 parser.add_option('-o', 704 '--timeout', 705 default='10', 706 type='int', 707 dest='timeout', 708 help='timeout for individual request (10 seconds)') 709 710 parser.add_option('-z', 711 '--shutdown_timeout', 712 default='5', 713 type='int', 714 dest='shutdown_timeout', 715 help='timeout on shutdown of server (5 seconds)') 716 717 parser.add_option('--socket-timeout', 718 default=5, 719 type='int', 720 dest='socket_timeout', 721 help='timeout for socket (5 second)') 722 723 parser.add_option('-f', 724 '--folder', 725 default=os.getcwd(), 726 dest='folder', 727 help='folder from which to run web2py') 728 729 parser.add_option('-v', 730 '--verbose', 731 action='store_true', 732 dest='verbose', 733 default=False, 734 help='increase --test verbosity') 735 736 parser.add_option('-Q', 737 '--quiet', 738 action='store_true', 739 dest='quiet', 740 default=False, 741 help='disable all output') 742 743 msg = ('set debug output level (0-100, 0 means all, 100 means none; ' 744 'default is 30)') 745 parser.add_option('-D', 746 '--debug', 747 dest='debuglevel', 748 default=30, 749 type='int', 750 help=msg) 751 752 msg = ('run web2py in interactive shell or IPython (if installed) with ' 753 'specified appname (if app does not exist it will be created). ' 754 'APPNAME like a/c/f (c,f optional)') 755 parser.add_option('-S', 756 '--shell', 757 dest='shell', 758 metavar='APPNAME', 759 help=msg) 760 761 msg = ('run web2py in interactive shell or bpython (if installed) with ' 762 'specified appname (if app does not exist it will be created).\n' 763 'Use combined with --shell') 764 parser.add_option('-B', 765 '--bpython', 766 action='store_true', 767 default=False, 768 dest='bpython', 769 help=msg) 770 771 msg = 'only use plain python shell; should be used with --shell option' 772 parser.add_option('-P', 773 '--plain', 774 action='store_true', 775 default=False, 776 dest='plain', 777 help=msg) 778 779 msg = ('auto import model files; default is False; should be used ' 780 'with --shell option') 781 parser.add_option('-M', 782 '--import_models', 783 action='store_true', 784 default=False, 785 dest='import_models', 786 help=msg) 787 788 msg = ('run PYTHON_FILE in web2py environment; ' 789 'should be used with --shell option') 790 parser.add_option('-R', 791 '--run', 792 dest='run', 793 metavar='PYTHON_FILE', 794 default='', 795 help=msg) 796 797 msg = ('run scheduled tasks for the specified apps: expects a list of ' 798 'app names as -K app1,app2,app3 ' 799 'or a list of app:groups as -K app1:group1:group2,app2:group1 ' 800 'to override specific group_names. (only strings, no spaces ' 801 'allowed. Requires a scheduler defined in the models') 802 parser.add_option('-K', 803 '--scheduler', 804 dest='scheduler', 805 default=None, 806 help=msg) 807 808 msg = 'run schedulers alongside webserver, needs -K app1 and -a too' 809 parser.add_option('-X', 810 '--with-scheduler', 811 action='store_true', 812 default=False, 813 dest='with_scheduler', 814 help=msg) 815 816 msg = ('run doctests in web2py environment; ' 817 'TEST_PATH like a/c/f (c,f optional)') 818 parser.add_option('-T', 819 '--test', 820 dest='test', 821 metavar='TEST_PATH', 822 default=None, 823 help=msg) 824 825 parser.add_option('-W', 826 '--winservice', 827 dest='winservice', 828 default='', 829 help='-W install|start|stop as Windows service') 830 831 msg = 'trigger a cron run manually; usually invoked from a system crontab' 832 parser.add_option('-C', 833 '--cron', 834 action='store_true', 835 dest='extcron', 836 default=False, 837 help=msg) 838 839 msg = 'triggers the use of softcron' 840 parser.add_option('--softcron', 841 action='store_true', 842 dest='softcron', 843 default=False, 844 help=msg) 845 846 parser.add_option('-Y', 847 '--run-cron', 848 action='store_true', 849 dest='runcron', 850 default=False, 851 help='start the background cron process') 852 853 parser.add_option('-J', 854 '--cronjob', 855 action='store_true', 856 dest='cronjob', 857 default=False, 858 help='identify cron-initiated command') 859 860 parser.add_option('-L', 861 '--config', 862 dest='config', 863 default='', 864 help='config file') 865 866 parser.add_option('-F', 867 '--profiler', 868 dest='profiler_dir', 869 default=None, 870 help='profiler dir') 871 872 parser.add_option('-t', 873 '--taskbar', 874 action='store_true', 875 dest='taskbar', 876 default=False, 877 help='use web2py gui and run in taskbar (system tray)') 878 879 parser.add_option('', 880 '--nogui', 881 action='store_true', 882 default=False, 883 dest='nogui', 884 help='text-only, no GUI') 885 886 msg = ('should be followed by a list of arguments to be passed to script, ' 887 'to be used with -S, -A must be the last option') 888 parser.add_option('-A', 889 '--args', 890 action='store', 891 dest='args', 892 default=None, 893 help=msg) 894 895 parser.add_option('--no-banner', 896 action='store_true', 897 default=False, 898 dest='nobanner', 899 help='Do not print header banner') 900 901 msg = ('listen on multiple addresses: ' 902 '"ip1:port1:key1:cert1:ca_cert1;ip2:port2:key2:cert2:ca_cert2;..." ' 903 '(:key:cert:ca_cert optional; no spaces; IPv6 addresses must be in ' 904 'square [] brackets)') 905 parser.add_option('--interfaces', 906 action='store', 907 dest='interfaces', 908 default=None, 909 help=msg) 910 911 msg = 'runs web2py tests' 912 parser.add_option('--run_system_tests', 913 action='store_true', 914 dest='run_system_tests', 915 default=False, 916 help=msg) 917 918 msg = ('adds coverage reporting (needs --run_system_tests), ' 919 'python 2.7 and the coverage module installed. ' 920 'You can alter the default path setting the environmental ' 921 'var "COVERAGE_PROCESS_START". ' 922 'By default it takes gluon/tests/coverage.ini') 923 parser.add_option('--with_coverage', 924 action='store_true', 925 dest='with_coverage', 926 default=False, 927 help=msg) 928 929 if '-A' in sys.argv: 930 k = sys.argv.index('-A') 931 elif '--args' in sys.argv: 932 k = sys.argv.index('--args') 933 else: 934 k = len(sys.argv) 935 sys.argv, other_args = sys.argv[:k], sys.argv[k + 1:] 936 (options, args) = parser.parse_args() 937 options.args = [options.run] + other_args 938 global_settings.cmd_options = options 939 global_settings.cmd_args = args 940 941 try: 942 options.ips = list(set( # no duplicates 943 [addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn()) 944 if not is_loopback_ip_address(addrinfo=addrinfo)])) 945 except socket.gaierror: 946 options.ips = [] 947 948 if options.run_system_tests: 949 run_system_tests(options) 950 951 if options.quiet: 952 capture = cStringIO.StringIO() 953 sys.stdout = capture 954 logger.setLevel(logging.CRITICAL + 1) 955 else: 956 logger.setLevel(options.debuglevel) 957 958 if options.config[-3:] == '.py': 959 options.config = options.config[:-3] 960 961 if options.cronjob: 962 global_settings.cronjob = True # tell the world 963 options.plain = True # cronjobs use a plain shell 964 options.nobanner = True 965 options.nogui = True 966 967 options.folder = os.path.abspath(options.folder) 968 969 # accept --interfaces in the form 970 # "ip1:port1:key1:cert1:ca_cert1;[ip2]:port2;ip3:port3:key3:cert3" 971 # (no spaces; optional key:cert indicate SSL) 972 if isinstance(options.interfaces, str): 973 interfaces = options.interfaces.split(';') 974 options.interfaces = [] 975 for interface in interfaces: 976 if interface.startswith('['): # IPv6 977 ip, if_remainder = interface.split(']', 1) 978 ip = ip[1:] 979 if_remainder = if_remainder[1:].split(':') 980 if_remainder[0] = int(if_remainder[0]) # numeric port 981 options.interfaces.append(tuple([ip] + if_remainder)) 982 else: # IPv4 983 interface = interface.split(':') 984 interface[1] = int(interface[1]) # numeric port 985 options.interfaces.append(tuple(interface)) 986 987 # accepts --scheduler in the form 988 # "app:group1,group2,app2:group1" 989 scheduler = [] 990 options.scheduler_groups = None 991 if isinstance(options.scheduler, str): 992 if ':' in options.scheduler: 993 for opt in options.scheduler.split(','): 994 scheduler.append(opt.split(':')) 995 options.scheduler = ','.join([app[0] for app in scheduler]) 996 options.scheduler_groups = scheduler 997 998 if options.numthreads is not None and options.minthreads is None: 999 options.minthreads = options.numthreads # legacy 1000 1001 create_welcome_w2p() 1002 1003 if not options.cronjob: 1004 # If we have the applications package or if we should upgrade 1005 if not os.path.exists('applications/__init__.py'): 1006 write_file('applications/__init__.py', '') 1007 1008 return options, args
1009 1010
1011 -def check_existent_app(options, appname):
1012 if os.path.isdir(os.path.join(options.folder, 'applications', appname)): 1013 return True
1014 1015
1016 -def get_code_for_scheduler(app, options):
1017 if len(app) == 1 or app[1] is None: 1018 code = "from gluon import current;current._scheduler.loop()" 1019 else: 1020 code = "from gluon import current;current._scheduler.group_names = ['%s'];" 1021 code += "current._scheduler.loop()" 1022 code = code % ("','".join(app[1:])) 1023 app_ = app[0] 1024 if not check_existent_app(options, app_): 1025 print "Application '%s' doesn't exist, skipping" % app_ 1026 return None, None 1027 return app_, code
1028 1029
1030 -def start_schedulers(options):
1031 try: 1032 from multiprocessing import Process 1033 except: 1034 sys.stderr.write('Sorry, -K only supported for python 2.6-2.7\n') 1035 return 1036 processes = [] 1037 apps = [(app.strip(), None) for app in options.scheduler.split(',')] 1038 if options.scheduler_groups: 1039 apps = options.scheduler_groups 1040 code = "from gluon import current;current._scheduler.loop()" 1041 logging.getLogger().setLevel(options.debuglevel) 1042 if len(apps) == 1 and not options.with_scheduler: 1043 app_, code = get_code_for_scheduler(apps[0], options) 1044 if not app_: 1045 return 1046 print 'starting single-scheduler for "%s"...' % app_ 1047 run(app_, True, True, None, False, code) 1048 return 1049 for app in apps: 1050 app_, code = get_code_for_scheduler(app, options) 1051 if not app_: 1052 continue 1053 print 'starting scheduler for "%s"...' % app_ 1054 args = (app_, True, True, None, False, code) 1055 p = Process(target=run, args=args) 1056 processes.append(p) 1057 print "Currently running %s scheduler processes" % (len(processes)) 1058 p.start() 1059 ##to avoid bashing the db at the same time 1060 time.sleep(0.7) 1061 print "Processes started" 1062 for p in processes: 1063 try: 1064 p.join() 1065 except (KeyboardInterrupt, SystemExit): 1066 print "Processes stopped" 1067 except: 1068 p.terminate() 1069 p.join()
1070 1071
1072 -def start(cron=True):
1073 """ Start server """ 1074 1075 # ## get command line arguments 1076 1077 (options, args) = console() 1078 1079 if not options.nobanner: 1080 print ProgramName 1081 print ProgramAuthor 1082 print ProgramVersion 1083 1084 from dal import DRIVERS 1085 if not options.nobanner: 1086 print 'Database drivers available: %s' % ', '.join(DRIVERS) 1087 1088 # ## if -L load options from options.config file 1089 if options.config: 1090 try: 1091 options2 = __import__(options.config, {}, {}, '') 1092 except Exception: 1093 try: 1094 # Jython doesn't like the extra stuff 1095 options2 = __import__(options.config) 1096 except Exception: 1097 print 'Cannot import config file [%s]' % options.config 1098 sys.exit(1) 1099 for key in dir(options2): 1100 if hasattr(options, key): 1101 setattr(options, key, getattr(options2, key)) 1102 1103 logfile0 = os.path.join('extras','examples','logging.example.conf') 1104 if not os.path.exists('logging.conf') and os.path.exists(logfile0): 1105 import shutil 1106 sys.stdout.write("Copying logging.conf.example to logging.conf ... ") 1107 shutil.copyfile('logging.example.conf', logfile0) 1108 sys.stdout.write("OK\n") 1109 1110 # ## if -T run doctests (no cron) 1111 if hasattr(options, 'test') and options.test: 1112 test(options.test, verbose=options.verbose) 1113 return 1114 1115 # ## if -S start interactive shell (also no cron) 1116 if options.shell: 1117 if not options.args is None: 1118 sys.argv[:] = options.args 1119 run(options.shell, plain=options.plain, bpython=options.bpython, 1120 import_models=options.import_models, startfile=options.run, 1121 cronjob=options.cronjob) 1122 return 1123 1124 # ## if -C start cron run (extcron) and exit 1125 # ## -K specifies optional apps list (overloading scheduler) 1126 if options.extcron: 1127 logger.debug('Starting extcron...') 1128 global_settings.web2py_crontype = 'external' 1129 if options.scheduler: # -K 1130 apps = [app.strip() for app in options.scheduler.split( 1131 ',') if check_existent_app(options, app.strip())] 1132 else: 1133 apps = None 1134 extcron = newcron.extcron(options.folder, apps=apps) 1135 extcron.start() 1136 extcron.join() 1137 return 1138 1139 # ## if -K 1140 if options.scheduler and not options.with_scheduler: 1141 try: 1142 start_schedulers(options) 1143 except KeyboardInterrupt: 1144 pass 1145 return 1146 1147 # ## if -W install/start/stop web2py as service 1148 if options.winservice: 1149 if os.name == 'nt': 1150 try: 1151 from winservice import register_service_handler, Web2pyService 1152 register_service_handler( 1153 argv=['', options.winservice], 1154 opt_file=options.config, 1155 cls=Web2pyService) 1156 except ImportError: 1157 print 'Error: Missing python module winservice' 1158 sys.exit(1) 1159 else: 1160 print 'Error: Windows services not supported on this platform' 1161 sys.exit(1) 1162 return 1163 1164 # ## if -H cron is enabled in this *process* 1165 # ## if --softcron use softcron 1166 # ## use hardcron in all other cases 1167 if cron and options.runcron and options.softcron: 1168 print 'Using softcron (but this is not very efficient)' 1169 global_settings.web2py_crontype = 'soft' 1170 elif cron and options.runcron: 1171 logger.debug('Starting hardcron...') 1172 global_settings.web2py_crontype = 'hard' 1173 newcron.hardcron(options.folder).start() 1174 1175 # ## if no password provided and havetk start Tk interface 1176 # ## or start interface if we want to put in taskbar (system tray) 1177 1178 try: 1179 options.taskbar 1180 except: 1181 options.taskbar = False 1182 1183 if options.taskbar and os.name != 'nt': 1184 print 'Error: taskbar not supported on this platform' 1185 sys.exit(1) 1186 1187 root = None 1188 1189 if not options.nogui: 1190 try: 1191 import Tkinter 1192 havetk = True 1193 except ImportError: 1194 logger.warn( 1195 'GUI not available because Tk library is not installed') 1196 havetk = False 1197 options.nogui = True 1198 1199 if options.password == '<ask>' and havetk or options.taskbar and havetk: 1200 try: 1201 root = Tkinter.Tk() 1202 except: 1203 pass 1204 1205 if root: 1206 root.focus_force() 1207 1208 # Mac OS X - make the GUI window rise to the top 1209 if os.path.exists("/usr/bin/osascript"): 1210 applescript = """ 1211 tell application "System Events" 1212 set proc to first process whose unix id is %d 1213 set frontmost of proc to true 1214 end tell 1215 """ % (os.getpid()) 1216 os.system("/usr/bin/osascript -e '%s'" % applescript) 1217 1218 if not options.quiet: 1219 presentation(root) 1220 master = web2pyDialog(root, options) 1221 signal.signal(signal.SIGTERM, lambda a, b: master.quit()) 1222 1223 try: 1224 root.mainloop() 1225 except: 1226 master.quit() 1227 1228 sys.exit() 1229 1230 # ## if no tk and no password, ask for a password 1231 1232 if not root and options.password == '<ask>': 1233 options.password = getpass.getpass('choose a password:') 1234 1235 if not options.password and not options.nobanner: 1236 print 'no password, no admin interface' 1237 1238 # ##-X (if no tk, the widget takes care of it himself) 1239 if not root and options.scheduler and options.with_scheduler: 1240 t = threading.Thread(target=start_schedulers, args=(options,)) 1241 t.start() 1242 1243 # ## start server 1244 1245 # Use first interface IP and port if interfaces specified, since the 1246 # interfaces option overrides the IP (and related) options. 1247 if not options.interfaces: 1248 (ip, port) = (options.ip, int(options.port)) 1249 else: 1250 first_if = options.interfaces[0] 1251 (ip, port) = first_if[0], first_if[1] 1252 1253 # Check for non default value for ssl inputs 1254 if (len(options.ssl_certificate) > 0) or (len(options.ssl_private_key) > 0): 1255 proto = 'https' 1256 else: 1257 proto = 'http' 1258 1259 url = get_url(ip, proto=proto, port=port) 1260 1261 if not options.nobanner: 1262 print 'please visit:' 1263 print '\t', url 1264 print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid() 1265 1266 # enhance linecache.getline (used by debugger) to look at the source file 1267 # if the line was not found (under py2exe & when file was modified) 1268 import linecache 1269 py2exe_getline = linecache.getline 1270 def getline(filename, lineno, *args, **kwargs): 1271 line = py2exe_getline(filename, lineno, *args, **kwargs) 1272 if not line: 1273 f = open(filename, "r") 1274 try: 1275 for i, line in enumerate(f): 1276 if lineno == i + 1: 1277 break 1278 else: 1279 line = None 1280 finally: 1281 f.close() 1282 return line
1283 linecache.getline = getline 1284 1285 server = main.HttpServer(ip=ip, 1286 port=port, 1287 password=options.password, 1288 pid_filename=options.pid_filename, 1289 log_filename=options.log_filename, 1290 profiler_dir=options.profiler_dir, 1291 ssl_certificate=options.ssl_certificate, 1292 ssl_private_key=options.ssl_private_key, 1293 ssl_ca_certificate=options.ssl_ca_certificate, 1294 min_threads=options.minthreads, 1295 max_threads=options.maxthreads, 1296 server_name=options.server_name, 1297 request_queue_size=options.request_queue_size, 1298 timeout=options.timeout, 1299 socket_timeout=options.socket_timeout, 1300 shutdown_timeout=options.shutdown_timeout, 1301 path=options.folder, 1302 interfaces=options.interfaces) 1303 1304 try: 1305 server.start() 1306 except KeyboardInterrupt: 1307 server.stop() 1308 try: 1309 t.join() 1310 except: 1311 pass 1312 logging.shutdown() 1313