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