1
2
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
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
87 """ """
88
90 """ """
91
92 self.buffer = cStringIO.StringIO()
93
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
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
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
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
187 """ Main window dialog """
188
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
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
220 self.schedmenu = Tkinter.Menu(self.menu, tearoff=0)
221 self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
222
223 self.update_schedulers(start=True)
224
225 helpmenu = Tkinter.Menu(self.menu, tearoff=0)
226
227
228 item = lambda: start_browser('http://www.web2py.com/')
229 helpmenu.add_command(label='Home Page',
230 command=item)
231
232
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
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
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
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
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
303 frame = Tkinter.Frame(self.root)
304 frame.grid(row=shift + 3, column=0, columnspan=2)
305
306
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
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
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
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
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
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
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
387 if app not in self.scheduler_processes:
388 t = threading.Thread(target=self.start_schedulers, args=(app,))
389 t.start()
390
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
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
422
423 - def connect_pages(self):
424 """ Connect pages """
425
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
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
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
542 for listener in self.server.server.listeners:
543 if listener.ready:
544 return True
545
546 return False
547
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
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
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(
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
963 options.plain = True
964 options.nobanner = True
965 options.nogui = True
966
967 options.folder = os.path.abspath(options.folder)
968
969
970
971
972 if isinstance(options.interfaces, str):
973 interfaces = options.interfaces.split(';')
974 options.interfaces = []
975 for interface in interfaces:
976 if interface.startswith('['):
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])
981 options.interfaces.append(tuple([ip] + if_remainder))
982 else:
983 interface = interface.split(':')
984 interface[1] = int(interface[1])
985 options.interfaces.append(tuple(interface))
986
987
988
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
1000
1001 create_welcome_w2p()
1002
1003 if not options.cronjob:
1004
1005 if not os.path.exists('applications/__init__.py'):
1006 write_file('applications/__init__.py', '')
1007
1008 return options, args
1009
1010
1014
1015
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
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
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
1073 """ Start server """
1074
1075
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
1089 if options.config:
1090 try:
1091 options2 = __import__(options.config, {}, {}, '')
1092 except Exception:
1093 try:
1094
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
1111 if hasattr(options, 'test') and options.test:
1112 test(options.test, verbose=options.verbose)
1113 return
1114
1115
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
1125
1126 if options.extcron:
1127 logger.debug('Starting extcron...')
1128 global_settings.web2py_crontype = 'external'
1129 if options.scheduler:
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
1140 if options.scheduler and not options.with_scheduler:
1141 try:
1142 start_schedulers(options)
1143 except KeyboardInterrupt:
1144 pass
1145 return
1146
1147
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
1165
1166
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
1176
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
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
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
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
1244
1245
1246
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
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
1267
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