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 main 
  26   
  27  from fileutils import read_file, write_file, create_welcome_w2p 
  28  from settings import global_settings 
  29  from shell import run, test 
  30  from utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo 
  31   
  32  try: 
  33      import Tkinter 
  34      import tkMessageBox 
  35      import contrib.taskbar_widget 
  36      from winservice import register_service_handler, Web2pyService 
  37      have_winservice = True 
  38  except: 
  39      have_winservice = False 
  40   
  41   
  42  try: 
  43      BaseException 
  44  except NameError: 
  45      BaseException = Exception 
  46   
  47  ProgramName = 'web2py Web Framework' 
  48  ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-' + str( 
  49      datetime.datetime.now().year) 
  50  ProgramVersion = read_file('VERSION').strip() 
  51   
  52  ProgramInfo = '''%s 
  53                   %s 
  54                   %s''' % (ProgramName, ProgramAuthor, ProgramVersion) 
  55   
  56  if not sys.version[:3] in ['2.4', '2.5', '2.6', '2.7']: 
  57      msg = 'Warning: web2py requires Python 2.4, 2.5 (recommended), 2.6 or 2.7 but you are running:\n%s' 
  58      msg = msg % sys.version 
  59      sys.stderr.write(msg) 
  60   
  61  logger = logging.getLogger("web2py") 
  62   
  63   
64 -def run_system_tests(options):
65 """ 66 Runs unittests for gluon.tests 67 """ 68 import subprocess 69 major_version = sys.version_info[0] 70 minor_version = sys.version_info[1] 71 if major_version == 2: 72 if minor_version in (5, 6): 73 sys.stderr.write("Python 2.5 or 2.6\n") 74 ret = subprocess.call(['unit2', '-v', 'gluon.tests']) 75 elif minor_version in (7,): 76 call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests'] 77 if options.with_coverage: 78 try: 79 import coverage 80 coverage_config = os.environ.get("COVERAGE_PROCESS_START", 81 os.path.join('gluon', 'tests', 'coverage.ini') 82 ) 83 call_args = ['coverage', 'run', '--rcfile=%s' % coverage_config, 84 '-m', 'unittest', '-v', 'gluon.tests'] 85 except: 86 sys.stderr.write('Coverage was not installed, skipping\n') 87 sys.stderr.write("Python 2.7\n") 88 ret = subprocess.call(call_args) 89 else: 90 sys.stderr.write("unknown python 2.x version\n") 91 ret = 256 92 else: 93 sys.stderr.write("Only Python 2.x supported.\n") 94 ret = 256 95 sys.exit(ret and 1)
96 97
98 -class IO(object):
99 """ """ 100
101 - def __init__(self):
102 """ """ 103 104 self.buffer = cStringIO.StringIO()
105
106 - def write(self, data):
107 """ """ 108 109 sys.__stdout__.write(data) 110 if hasattr(self, 'callback'): 111 self.callback(data) 112 else: 113 self.buffer.write(data)
114 115
116 -def get_url(host, path='/', proto='http', port=80):
117 if ':' in host: 118 host = '[%s]' % host 119 else: 120 host = host.replace('0.0.0.0', '127.0.0.1') 121 if path.startswith('/'): 122 path = path[1:] 123 if proto.endswith(':'): 124 proto = proto[:-1] 125 if not port or port == 80: 126 port = '' 127 else: 128 port = ':%s' % port 129 return '%s://%s%s/%s' % (proto, host, port, path)
130 131
132 -def start_browser(url, startup=False):
133 if startup: 134 print 'please visit:' 135 print '\t', url 136 print 'starting browser...' 137 try: 138 import webbrowser 139 webbrowser.open(url) 140 except: 141 print 'warning: unable to detect your browser'
142 143
144 -def presentation(root):
145 """ Draw the splash screen """ 146 147 root.withdraw() 148 149 dx = root.winfo_screenwidth() 150 dy = root.winfo_screenheight() 151 152 dialog = Tkinter.Toplevel(root, bg='white') 153 dialog.geometry('%ix%i+%i+%i' % (500, 300, dx / 2 - 200, dy / 2 - 150)) 154 155 dialog.overrideredirect(1) 156 dialog.focus_force() 157 158 canvas = Tkinter.Canvas(dialog, 159 background='white', 160 width=500, 161 height=300) 162 canvas.pack() 163 root.update() 164 165 logo = 'splashlogo.gif' 166 if os.path.exists(logo): 167 img = Tkinter.PhotoImage(file=logo) 168 pnl = Tkinter.Label(canvas, image=img, background='white', bd=0) 169 pnl.pack(side='top', fill='both', expand='yes') 170 # Prevent garbage collection of img 171 pnl.image = img 172 173 def add_label(text='Change Me', font_size=12, foreground='#195866', height=1): 174 return Tkinter.Label( 175 master=canvas, 176 width=250, 177 height=height, 178 text=text, 179 font=('Helvetica', font_size), 180 anchor=Tkinter.CENTER, 181 foreground=foreground, 182 background='white' 183 )
184 185 add_label('Welcome to...').pack(side='top') 186 add_label(ProgramName, 18, '#FF5C1F', 2).pack() 187 add_label(ProgramAuthor).pack() 188 add_label(ProgramVersion).pack() 189 190 root.update() 191 time.sleep(5) 192 dialog.destroy() 193 return 194 195
196 -class web2pyDialog(object):
197 """ Main window dialog """ 198
199 - def __init__(self, root, options):
200 """ web2pyDialog constructor """ 201 202 root.title('web2py server') 203 self.root = Tkinter.Toplevel(root) 204 self.options = options 205 self.scheduler_processes = {} 206 self.menu = Tkinter.Menu(self.root) 207 servermenu = Tkinter.Menu(self.menu, tearoff=0) 208 httplog = os.path.join(self.options.folder, 'httpserver.log') 209 iconphoto = 'web2py.gif' 210 if os.path.exists(iconphoto): 211 img = Tkinter.PhotoImage(file=iconphoto) 212 self.root.tk.call('wm', 'iconphoto', self.root._w, img) 213 # Building the Menu 214 item = lambda: start_browser(httplog) 215 servermenu.add_command(label='View httpserver.log', 216 command=item) 217 218 servermenu.add_command(label='Quit (pid:%i)' % os.getpid(), 219 command=self.quit) 220 221 self.menu.add_cascade(label='Server', menu=servermenu) 222 223 self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0) 224 self.menu.add_cascade(label='Pages', menu=self.pagesmenu) 225 226 #scheduler menu 227 self.schedmenu = Tkinter.Menu(self.menu, tearoff=0) 228 self.menu.add_cascade(label='Scheduler', menu=self.schedmenu) 229 #start and register schedulers from options 230 self.update_schedulers(start=True) 231 232 helpmenu = Tkinter.Menu(self.menu, tearoff=0) 233 234 # Home Page 235 item = lambda: start_browser('http://www.web2py.com/') 236 helpmenu.add_command(label='Home Page', 237 command=item) 238 239 # About 240 item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo) 241 helpmenu.add_command(label='About', 242 command=item) 243 244 self.menu.add_cascade(label='Info', menu=helpmenu) 245 246 self.root.config(menu=self.menu) 247 248 if options.taskbar: 249 self.root.protocol('WM_DELETE_WINDOW', 250 lambda: self.quit(True)) 251 else: 252 self.root.protocol('WM_DELETE_WINDOW', self.quit) 253 254 sticky = Tkinter.NW 255 256 # IP 257 Tkinter.Label(self.root, 258 text='Server IP:', 259 justify=Tkinter.LEFT).grid(row=0, 260 column=0, 261 sticky=sticky) 262 self.ips = {} 263 self.selected_ip = Tkinter.StringVar() 264 row = 0 265 ips = [('127.0.0.1', 'Local (IPv4)')] + \ 266 ([('::1', 'Local (IPv6)')] if socket.has_ipv6 else []) + \ 267 [(ip, 'Public') for ip in options.ips] + \ 268 [('0.0.0.0', 'Public')] 269 for ip, legend in ips: 270 self.ips[ip] = Tkinter.Radiobutton( 271 self.root, text='%s (%s)' % (legend, ip), 272 variable=self.selected_ip, value=ip) 273 self.ips[ip].grid(row=row, column=1, sticky=sticky) 274 if row == 0: 275 self.ips[ip].select() 276 row += 1 277 shift = row 278 # Port 279 Tkinter.Label(self.root, 280 text='Server Port:', 281 justify=Tkinter.LEFT).grid(row=shift, 282 column=0, 283 sticky=sticky) 284 285 self.port_number = Tkinter.Entry(self.root) 286 self.port_number.insert(Tkinter.END, self.options.port) 287 self.port_number.grid(row=shift, column=1, sticky=sticky) 288 289 # Password 290 Tkinter.Label(self.root, 291 text='Choose Password:', 292 justify=Tkinter.LEFT).grid(row=shift + 1, 293 column=0, 294 sticky=sticky) 295 296 self.password = Tkinter.Entry(self.root, show='*') 297 self.password.bind('<Return>', lambda e: self.start()) 298 self.password.focus_force() 299 self.password.grid(row=shift + 1, column=1, sticky=sticky) 300 301 # Prepare the canvas 302 self.canvas = Tkinter.Canvas(self.root, 303 width=300, 304 height=100, 305 bg='black') 306 self.canvas.grid(row=shift + 2, column=0, columnspan=2) 307 self.canvas.after(1000, self.update_canvas) 308 309 # Prepare the frame 310 frame = Tkinter.Frame(self.root) 311 frame.grid(row=shift + 3, column=0, columnspan=2) 312 313 # Start button 314 self.button_start = Tkinter.Button(frame, 315 text='start server', 316 command=self.start) 317 318 self.button_start.grid(row=0, column=0) 319 320 # Stop button 321 self.button_stop = Tkinter.Button(frame, 322 text='stop server', 323 command=self.stop) 324 325 self.button_stop.grid(row=0, column=1) 326 self.button_stop.configure(state='disabled') 327 328 if options.taskbar: 329 self.tb = contrib.taskbar_widget.TaskBarIcon() 330 self.checkTaskBar() 331 332 if options.password != '<ask>': 333 self.password.insert(0, options.password) 334 self.start() 335 self.root.withdraw() 336 else: 337 self.tb = None
338
339 - def update_schedulers(self, start=False):
340 apps = [] 341 available_apps = [arq for arq in os.listdir('applications/')] 342 available_apps = [arq for arq in available_apps 343 if os.path.exists('applications/%s/models/scheduler.py' % arq)] 344 if start: 345 #the widget takes care of starting the scheduler 346 if self.options.scheduler and self.options.with_scheduler: 347 apps = [app.strip() for app in self.options.scheduler.split(',') 348 if app in available_apps] 349 for app in apps: 350 self.try_start_scheduler(app) 351 352 #reset the menu 353 self.schedmenu.delete(0, len(available_apps)) 354 for arq in available_apps: 355 if arq not in self.scheduler_processes: 356 item = lambda u = arq: self.try_start_scheduler(u) 357 self.schedmenu.add_command(label="start %s" % arq, 358 command=item) 359 if arq in self.scheduler_processes: 360 item = lambda u = arq: self.try_stop_scheduler(u) 361 self.schedmenu.add_command(label="stop %s" % arq, 362 command=item)
363
364 - def start_schedulers(self, app):
365 try: 366 from multiprocessing import Process 367 except: 368 sys.stderr.write('Sorry, -K only supported for python 2.6-2.7\n') 369 return 370 code = "from gluon import current;current._scheduler.loop()" 371 print 'starting scheduler from widget for "%s"...' % app 372 args = (app, True, True, None, False, code) 373 logging.getLogger().setLevel(self.options.debuglevel) 374 p = Process(target=run, args=args) 375 self.scheduler_processes[app] = p 376 self.update_schedulers() 377 print "Currently running %s scheduler processes" % ( 378 len(self.scheduler_processes)) 379 p.start() 380 print "Processes started"
381
382 - def try_stop_scheduler(self, app):
383 if app in self.scheduler_processes: 384 p = self.scheduler_processes[app] 385 del self.scheduler_processes[app] 386 p.terminate() 387 p.join() 388 self.update_schedulers()
389
390 - def try_start_scheduler(self, app):
391 if app not in self.scheduler_processes: 392 t = threading.Thread(target=self.start_schedulers, args=(app,)) 393 t.start()
394
395 - def checkTaskBar(self):
396 """ Check taskbar status """ 397 398 if self.tb.status: 399 if self.tb.status[0] == self.tb.EnumStatus.QUIT: 400 self.quit() 401 elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE: 402 if self.root.state() == 'withdrawn': 403 self.root.deiconify() 404 else: 405 self.root.withdraw() 406 elif self.tb.status[0] == self.tb.EnumStatus.STOP: 407 self.stop() 408 elif self.tb.status[0] == self.tb.EnumStatus.START: 409 self.start() 410 elif self.tb.status[0] == self.tb.EnumStatus.RESTART: 411 self.stop() 412 self.start() 413 del self.tb.status[0] 414 415 self.root.after(1000, self.checkTaskBar)
416
417 - def update(self, text):
418 """ Update app text """ 419 420 try: 421 self.text.configure(state='normal') 422 self.text.insert('end', text) 423 self.text.configure(state='disabled') 424 except: 425 pass # ## this should only happen in case app is destroyed
426
427 - def connect_pages(self):
428 """ Connect pages """ 429 #reset the menu 430 available_apps = [arq for arq in os.listdir('applications/') 431 if os.path.exists('applications/%s/__init__.py' % arq)] 432 self.pagesmenu.delete(0, len(available_apps)) 433 for arq in available_apps: 434 url = self.url + arq 435 self.pagesmenu.add_command(label=url, 436 command=lambda u=url: start_browser(u))
437
438 - def quit(self, justHide=False):
439 """ Finish the program execution """ 440 if justHide: 441 self.root.withdraw() 442 else: 443 try: 444 scheds = self.scheduler_processes.keys() 445 for t in scheds: 446 self.try_stop_scheduler(t) 447 except: 448 pass 449 try: 450 newcron.stopcron() 451 except: 452 pass 453 try: 454 self.server.stop() 455 except: 456 pass 457 try: 458 self.tb.Destroy() 459 except: 460 pass 461 462 self.root.destroy() 463 sys.exit(0)
464
465 - def error(self, message):
466 """ Show error message """ 467 468 tkMessageBox.showerror('web2py start server', message)
469
470 - def start(self):
471 """ Start web2py server """ 472 473 password = self.password.get() 474 475 if not password: 476 self.error('no password, no web admin interface') 477 478 ip = self.selected_ip.get() 479 480 if not is_valid_ip_address(ip): 481 return self.error('invalid host ip address') 482 483 try: 484 port = int(self.port_number.get()) 485 except: 486 return self.error('invalid port number') 487 488 # Check for non default value for ssl inputs 489 if (len(self.options.ssl_certificate) > 0) or (len(self.options.ssl_private_key) > 0): 490 proto = 'https' 491 else: 492 proto = 'http' 493 494 self.url = get_url(ip, proto=proto, port=port) 495 self.connect_pages() 496 self.button_start.configure(state='disabled') 497 498 try: 499 options = self.options 500 req_queue_size = options.request_queue_size 501 self.server = main.HttpServer( 502 ip, 503 port, 504 password, 505 pid_filename=options.pid_filename, 506 log_filename=options.log_filename, 507 profiler_filename=options.profiler_filename, 508 ssl_certificate=options.ssl_certificate, 509 ssl_private_key=options.ssl_private_key, 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(start_browser, 532 (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_filename', 869 default=None, 870 help='profiler filename') 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 if False and not os.path.exists('logging.conf') and \ 1104 os.path.exists('logging.example.conf'): 1105 import shutil 1106 sys.stdout.write("Copying logging.conf.example to logging.conf ... ") 1107 shutil.copyfile('logging.example.conf', 'logging.conf') 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 return 1122 1123 # ## if -C start cron run (extcron) and exit 1124 # ## -K specifies optional apps list (overloading scheduler) 1125 if options.extcron: 1126 logger.debug('Starting extcron...') 1127 global_settings.web2py_crontype = 'external' 1128 if options.scheduler: # -K 1129 apps = [app.strip() for app in options.scheduler.split( 1130 ',') if check_existent_app(options, app.strip())] 1131 else: 1132 apps = None 1133 extcron = newcron.extcron(options.folder, apps=apps) 1134 extcron.start() 1135 extcron.join() 1136 return 1137 1138 # ## if -K 1139 if options.scheduler and not options.with_scheduler: 1140 try: 1141 start_schedulers(options) 1142 except KeyboardInterrupt: 1143 pass 1144 return 1145 1146 # ## if -W install/start/stop web2py as service 1147 if options.winservice: 1148 if os.name == 'nt': 1149 if have_winservice: 1150 register_service_handler( 1151 argv=['', options.winservice], 1152 opt_file=options.config, 1153 cls=Web2pyService) 1154 else: 1155 print 'Error: Missing python module winservice' 1156 sys.exit(1) 1157 else: 1158 print 'Error: Windows services not supported on this platform' 1159 sys.exit(1) 1160 return 1161 1162 # ## if -H cron is enabled in this *process* 1163 # ## if --softcron use softcron 1164 # ## use hardcron in all other cases 1165 if cron and options.runcron and options.softcron: 1166 print 'Using softcron (but this is not very efficient)' 1167 global_settings.web2py_crontype = 'soft' 1168 elif cron and options.runcron: 1169 logger.debug('Starting hardcron...') 1170 global_settings.web2py_crontype = 'hard' 1171 newcron.hardcron(options.folder).start() 1172 1173 # ## if no password provided and havetk start Tk interface 1174 # ## or start interface if we want to put in taskbar (system tray) 1175 1176 try: 1177 options.taskbar 1178 except: 1179 options.taskbar = False 1180 1181 if options.taskbar and os.name != 'nt': 1182 print 'Error: taskbar not supported on this platform' 1183 sys.exit(1) 1184 1185 root = None 1186 1187 if not options.nogui: 1188 try: 1189 import Tkinter 1190 havetk = True 1191 except ImportError: 1192 logger.warn( 1193 'GUI not available because Tk library is not installed') 1194 havetk = False 1195 1196 if options.password == '<ask>' and havetk or options.taskbar and havetk: 1197 try: 1198 root = Tkinter.Tk() 1199 except: 1200 pass 1201 1202 if root: 1203 root.focus_force() 1204 1205 # Mac OS X - make the GUI window rise to the top 1206 if os.path.exists("/usr/bin/osascript"): 1207 applescript = """ 1208 tell application "System Events" 1209 set proc to first process whose unix id is %d 1210 set frontmost of proc to true 1211 end tell 1212 """ % (os.getpid()) 1213 os.system("/usr/bin/osascript -e '%s'" % applescript) 1214 1215 if not options.quiet: 1216 presentation(root) 1217 master = web2pyDialog(root, options) 1218 signal.signal(signal.SIGTERM, lambda a, b: master.quit()) 1219 1220 try: 1221 root.mainloop() 1222 except: 1223 master.quit() 1224 1225 sys.exit() 1226 1227 # ## if no tk and no password, ask for a password 1228 1229 if not root and options.password == '<ask>': 1230 options.password = getpass.getpass('choose a password:') 1231 1232 if not options.password and not options.nobanner: 1233 print 'no password, no admin interface' 1234 1235 # ##-X (if no tk, the widget takes care of it himself) 1236 if not root and options.scheduler and options.with_scheduler: 1237 t = threading.Thread(target=start_schedulers, args=(options,)) 1238 t.start() 1239 1240 # ## start server 1241 1242 # Use first interface IP and port if interfaces specified, since the 1243 # interfaces option overrides the IP (and related) options. 1244 if not options.interfaces: 1245 (ip, port) = (options.ip, int(options.port)) 1246 else: 1247 first_if = options.interfaces[0] 1248 (ip, port) = first_if[0], first_if[1] 1249 1250 # Check for non default value for ssl inputs 1251 if (len(options.ssl_certificate) > 0) or (len(options.ssl_private_key) > 0): 1252 proto = 'https' 1253 else: 1254 proto = 'http' 1255 1256 url = get_url(ip, proto=proto, port=port) 1257 1258 if not options.nobanner: 1259 print 'please visit:' 1260 print '\t', url 1261 print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid() 1262 1263 server = main.HttpServer(ip=ip, 1264 port=port, 1265 password=options.password, 1266 pid_filename=options.pid_filename, 1267 log_filename=options.log_filename, 1268 profiler_filename=options.profiler_filename, 1269 ssl_certificate=options.ssl_certificate, 1270 ssl_private_key=options.ssl_private_key, 1271 ssl_ca_certificate=options.ssl_ca_certificate, 1272 min_threads=options.minthreads, 1273 max_threads=options.maxthreads, 1274 server_name=options.server_name, 1275 request_queue_size=options.request_queue_size, 1276 timeout=options.timeout, 1277 socket_timeout=options.socket_timeout, 1278 shutdown_timeout=options.shutdown_timeout, 1279 path=options.folder, 1280 interfaces=options.interfaces) 1281 1282 try: 1283 server.start() 1284 except KeyboardInterrupt: 1285 server.stop() 1286 try: 1287 t.join() 1288 except: 1289 pass 1290 logging.shutdown()
1291