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