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 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
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
99 """ """
100
102 """ """
103
104 self.buffer = cStringIO.StringIO()
105
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
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
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
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
197 """ Main window dialog """
198
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
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
227 self.schedmenu = Tkinter.Menu(self.menu, tearoff=0)
228 self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
229
230 self.update_schedulers(start=True)
231
232 helpmenu = Tkinter.Menu(self.menu, tearoff=0)
233
234
235 item = lambda: start_browser('http://www.web2py.com/')
236 helpmenu.add_command(label='Home Page',
237 command=item)
238
239
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
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
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
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
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
310 frame = Tkinter.Frame(self.root)
311 frame.grid(row=shift + 3, column=0, columnspan=2)
312
313
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
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
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
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
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
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
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
391 if app not in self.scheduler_processes:
392 t = threading.Thread(target=self.start_schedulers, args=(app,))
393 t.start()
394
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
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
426
427 - def connect_pages(self):
428 """ Connect pages """
429
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
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
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
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_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(
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
1012 if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
1013 return True
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 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
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 return
1122
1123
1124
1125 if options.extcron:
1126 logger.debug('Starting extcron...')
1127 global_settings.web2py_crontype = 'external'
1128 if options.scheduler:
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
1139 if options.scheduler and not options.with_scheduler:
1140 try:
1141 start_schedulers(options)
1142 except KeyboardInterrupt:
1143 pass
1144 return
1145
1146
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
1163
1164
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
1174
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
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
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
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
1241
1242
1243
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
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