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 sys
13 import cStringIO
14 import time
15 import thread
16 import re
17 import os
18 import socket
19 import signal
20 import math
21 import logging
22
23 import newcron
24 import main
25
26 from fileutils import w2p_pack
27 from shell import run, test
28 from settings import global_settings
29
30 try:
31 import Tkinter, tkMessageBox
32 import contrib.taskbar_widget
33 from winservice import web2py_windows_service_handler
34 except:
35 pass
36
37
38 try:
39 BaseException
40 except NameError:
41 BaseException = Exception
42
43 ProgramName = 'web2py Enterprise Web Framework'
44 ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-2011'
45 versioninfo = open('VERSION', 'r')
46 ProgramVersion = versioninfo.read().strip()
47 versioninfo.close()
48
49 ProgramInfo = '''%s
50 %s
51 %s''' % (ProgramName, ProgramAuthor, ProgramVersion)
52
53 if not sys.version[:3] in ['2.4', '2.5', '2.6', '2.7']:
54 msg = 'Warning: web2py requires Python 2.4, 2.5 (recommended), 2.6 or 2.7 but you are running:\n%s'
55 msg = msg % sys.version
56 sys.stderr.write(msg)
57
58 logger = logging.getLogger("web2py")
59
61 """ """
62
64 """ """
65
66 self.buffer = cStringIO.StringIO()
67
69 """ """
70
71 sys.__stdout__.write(data)
72 if hasattr(self, 'callback'):
73 self.callback(data)
74 else:
75 self.buffer.write(data)
76
77
79 """ Try to start the default browser """
80
81 try:
82 import webbrowser
83 webbrowser.open(url)
84 except:
85 print 'warning: unable to detect your browser'
86
87
89 """ Starts the default browser """
90 print 'please visit:'
91 print '\thttp://%s:%s' % (ip, port)
92 print 'starting browser...'
93 try_start_browser('http://%s:%s' % (ip, port))
94
95
97 """ Draw the splash screen """
98
99 root.withdraw()
100
101 dx = root.winfo_screenwidth()
102 dy = root.winfo_screenheight()
103
104 dialog = Tkinter.Toplevel(root, bg='white')
105 dialog.geometry('%ix%i+%i+%i' % (500, 300, dx / 2 - 200, dy / 2 - 150))
106
107 dialog.overrideredirect(1)
108 dialog.focus_force()
109
110 canvas = Tkinter.Canvas(dialog,
111 background='white',
112 width=500,
113 height=300)
114 canvas.pack()
115 root.update()
116
117 img = Tkinter.PhotoImage(file='splashlogo.gif')
118 pnl = Tkinter.Label(canvas, image=img, background='white', bd=0)
119 pnl.pack(side='top', fill='both', expand='yes')
120
121 pnl.image=img
122
123 def add_label(text='Change Me', font_size=12, foreground='#195866', height=1):
124 return Tkinter.Label(
125 master=canvas,
126 width=250,
127 height=height,
128 text=text,
129 font=('Helvetica', font_size),
130 anchor=Tkinter.CENTER,
131 foreground=foreground,
132 background='white'
133 )
134
135 add_label('Welcome to...').pack(side='top')
136 add_label(ProgramName, 18, '#FF5C1F', 2).pack()
137 add_label(ProgramAuthor).pack()
138 add_label(ProgramVersion).pack()
139
140 root.update()
141 time.sleep(5)
142 dialog.destroy()
143 return
144
145
147 """ Main window dialog """
148
150 """ web2pyDialog constructor """
151
152 root.title('web2py server')
153 self.root = Tkinter.Toplevel(root)
154 self.options = options
155 self.menu = Tkinter.Menu(self.root)
156 servermenu = Tkinter.Menu(self.menu, tearoff=0)
157 httplog = os.path.join(self.options.folder, 'httpserver.log')
158
159
160 item = lambda: try_start_browser(httplog)
161 servermenu.add_command(label='View httpserver.log',
162 command=item)
163
164 servermenu.add_command(label='Quit (pid:%i)' % os.getpid(),
165 command=self.quit)
166
167 self.menu.add_cascade(label='Server', menu=servermenu)
168
169 self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0)
170 self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
171
172 helpmenu = Tkinter.Menu(self.menu, tearoff=0)
173
174
175 item = lambda: try_start_browser('http://www.web2py.com')
176 helpmenu.add_command(label='Home Page',
177 command=item)
178
179
180 item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo)
181 helpmenu.add_command(label='About',
182 command=item)
183
184 self.menu.add_cascade(label='Info', menu=helpmenu)
185
186 self.root.config(menu=self.menu)
187
188 if options.taskbar:
189 self.root.protocol('WM_DELETE_WINDOW',
190 lambda: self.quit(True))
191 else:
192 self.root.protocol('WM_DELETE_WINDOW', self.quit)
193
194 sticky = Tkinter.NW
195
196
197 Tkinter.Label(self.root,
198 text='Server IP:',
199 justify=Tkinter.LEFT).grid(row=0,
200 column=0,
201 sticky=sticky)
202 self.ip = Tkinter.Entry(self.root)
203 self.ip.insert(Tkinter.END, self.options.ip)
204 self.ip.grid(row=0, column=1, sticky=sticky)
205
206
207 Tkinter.Label(self.root,
208 text='Server Port:',
209 justify=Tkinter.LEFT).grid(row=1,
210 column=0,
211 sticky=sticky)
212
213 self.port_number = Tkinter.Entry(self.root)
214 self.port_number.insert(Tkinter.END, self.options.port)
215 self.port_number.grid(row=1, column=1, sticky=sticky)
216
217
218 Tkinter.Label(self.root,
219 text='Choose Password:',
220 justify=Tkinter.LEFT).grid(row=2,
221 column=0,
222 sticky=sticky)
223
224 self.password = Tkinter.Entry(self.root, show='*')
225 self.password.bind('<Return>', lambda e: self.start())
226 self.password.focus_force()
227 self.password.grid(row=2, column=1, sticky=sticky)
228
229
230 self.canvas = Tkinter.Canvas(self.root,
231 width=300,
232 height=100,
233 bg='black')
234 self.canvas.grid(row=3, column=0, columnspan=2)
235 self.canvas.after(1000, self.update_canvas)
236
237
238 frame = Tkinter.Frame(self.root)
239 frame.grid(row=4, column=0, columnspan=2)
240
241
242 self.button_start = Tkinter.Button(frame,
243 text='start server',
244 command=self.start)
245
246 self.button_start.grid(row=0, column=0)
247
248
249 self.button_stop = Tkinter.Button(frame,
250 text='stop server',
251 command=self.stop)
252
253 self.button_stop.grid(row=0, column=1)
254 self.button_stop.configure(state='disabled')
255
256 if options.taskbar:
257 self.tb = contrib.taskbar_widget.TaskBarIcon()
258 self.checkTaskBar()
259
260 if options.password != '<ask>':
261 self.password.insert(0, options.password)
262 self.start()
263 self.root.withdraw()
264 else:
265 self.tb = None
266
268 """ Check taskbar status """
269
270 if self.tb.status:
271 if self.tb.status[0] == self.tb.EnumStatus.QUIT:
272 self.quit()
273 elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE:
274 if self.root.state() == 'withdrawn':
275 self.root.deiconify()
276 else:
277 self.root.withdraw()
278 elif self.tb.status[0] == self.tb.EnumStatus.STOP:
279 self.stop()
280 elif self.tb.status[0] == self.tb.EnumStatus.START:
281 self.start()
282 elif self.tb.status[0] == self.tb.EnumStatus.RESTART:
283 self.stop()
284 self.start()
285 del self.tb.status[0]
286
287 self.root.after(1000, self.checkTaskBar)
288
290 """ Update app text """
291
292 try:
293 self.text.configure(state='normal')
294 self.text.insert('end', text)
295 self.text.configure(state='disabled')
296 except:
297 pass
298
299 - def connect_pages(self):
300 """ Connect pages """
301
302 for arq in os.listdir('applications/'):
303 if os.path.exists('applications/%s/__init__.py' % arq):
304 url = self.url + '/' + arq
305 start_browser = lambda u = url: try_start_browser(u)
306 self.pagesmenu.add_command(label=url,
307 command=start_browser)
308
309 - def quit(self, justHide=False):
310 """ Finish the program execution """
311
312 if justHide:
313 self.root.withdraw()
314 else:
315 try:
316 self.server.stop()
317 except:
318 pass
319
320 try:
321 self.tb.Destroy()
322 except:
323 pass
324
325 self.root.destroy()
326 sys.exit()
327
328 - def error(self, message):
329 """ Show error message """
330
331 tkMessageBox.showerror('web2py start server', message)
332
334 """ Start web2py server """
335
336 password = self.password.get()
337
338 if not password:
339 self.error('no password, no web admin interface')
340
341 ip = self.ip.get()
342
343 regexp = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
344 if ip and not re.compile(regexp).match(ip):
345 return self.error('invalid host ip address')
346
347 try:
348 port = int(self.port_number.get())
349 except:
350 return self.error('invalid port number')
351
352 self.url = 'http://%s:%s' % (ip, port)
353 self.connect_pages()
354 self.button_start.configure(state='disabled')
355
356 try:
357 options = self.options
358 req_queue_size = options.request_queue_size
359 self.server = main.HttpServer(
360 ip,
361 port,
362 password,
363 pid_filename=options.pid_filename,
364 log_filename=options.log_filename,
365 profiler_filename=options.profiler_filename,
366 ssl_certificate=options.ssl_certificate,
367 ssl_private_key=options.ssl_private_key,
368 min_threads=options.minthreads,
369 max_threads=options.maxthreads,
370 server_name=options.server_name,
371 request_queue_size=req_queue_size,
372 timeout=options.timeout,
373 shutdown_timeout=options.shutdown_timeout,
374 path=options.folder,
375 interfaces=options.interfaces)
376
377 thread.start_new_thread(self.server.start, ())
378 except Exception, e:
379 self.button_start.configure(state='normal')
380 return self.error(str(e))
381
382 self.button_stop.configure(state='normal')
383
384 if not options.taskbar:
385 thread.start_new_thread(start_browser, (ip, port))
386
387 self.password.configure(state='readonly')
388 self.ip.configure(state='readonly')
389 self.port_number.configure(state='readonly')
390
391 if self.tb:
392 self.tb.SetServerRunning()
393
395 """ Stop web2py server """
396
397 self.button_start.configure(state='normal')
398 self.button_stop.configure(state='disabled')
399 self.password.configure(state='normal')
400 self.ip.configure(state='normal')
401 self.port_number.configure(state='normal')
402 self.server.stop()
403
404 if self.tb:
405 self.tb.SetServerStopped()
406
408 """ Update canvas """
409
410 try:
411 t1 = os.path.getsize('httpserver.log')
412 except:
413 self.canvas.after(1000, self.update_canvas)
414 return
415
416 try:
417 fp = open('httpserver.log', 'r')
418 fp.seek(self.t0)
419 data = fp.read(t1 - self.t0)
420 fp.close()
421 value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))]
422 self.p0 = value
423
424 for i in xrange(len(self.p0) - 1):
425 c = self.canvas.coords(self.q0[i])
426 self.canvas.coords(self.q0[i],
427 (c[0],
428 self.p0[i],
429 c[2],
430 self.p0[i + 1]))
431 self.t0 = t1
432 except BaseException:
433 self.t0 = time.time()
434 self.t0 = t1
435 self.p0 = [100] * 300
436 self.q0 = [self.canvas.create_line(i, 100, i + 1, 100,
437 fill='green') for i in xrange(len(self.p0) - 1)]
438
439 self.canvas.after(1000, self.update_canvas)
440
441
443 """ Defines the behavior of the console web2py execution """
444 import optparse
445 import textwrap
446
447 usage = "python web2py.py"
448
449 description = """\
450 web2py Web Framework startup script.
451 ATTENTION: unless a password is specified (-a 'passwd') web2py will
452 attempt to run a GUI. In this case command line options are ignored."""
453
454 description = textwrap.dedent(description)
455
456 parser = optparse.OptionParser(usage, None, optparse.Option, ProgramVersion)
457
458 parser.description = description
459
460 parser.add_option('-i',
461 '--ip',
462 default='127.0.0.1',
463 dest='ip',
464 help='ip address of the server (127.0.0.1)')
465
466 parser.add_option('-p',
467 '--port',
468 default='8000',
469 dest='port',
470 type='int',
471 help='port of server (8000)')
472
473 msg = 'password to be used for administration'
474 msg += ' (use -a "<recycle>" to reuse the last password))'
475 parser.add_option('-a',
476 '--password',
477 default='<ask>',
478 dest='password',
479 help=msg)
480
481 parser.add_option('-c',
482 '--ssl_certificate',
483 default='',
484 dest='ssl_certificate',
485 help='file that contains ssl certificate')
486
487 parser.add_option('-k',
488 '--ssl_private_key',
489 default='',
490 dest='ssl_private_key',
491 help='file that contains ssl private key')
492
493 parser.add_option('-d',
494 '--pid_filename',
495 default='httpserver.pid',
496 dest='pid_filename',
497 help='file to store the pid of the server')
498
499 parser.add_option('-l',
500 '--log_filename',
501 default='httpserver.log',
502 dest='log_filename',
503 help='file to log connections')
504
505 parser.add_option('-n',
506 '--numthreads',
507 default=None,
508 type='int',
509 dest='numthreads',
510 help='number of threads (deprecated)')
511
512 parser.add_option('--minthreads',
513 default=None,
514 type='int',
515 dest='minthreads',
516 help='minimum number of server threads')
517
518 parser.add_option('--maxthreads',
519 default=None,
520 type='int',
521 dest='maxthreads',
522 help='maximum number of server threads')
523
524 parser.add_option('-s',
525 '--server_name',
526 default=socket.gethostname(),
527 dest='server_name',
528 help='server name for the web server')
529
530 msg = 'max number of queued requests when server unavailable'
531 parser.add_option('-q',
532 '--request_queue_size',
533 default='5',
534 type='int',
535 dest='request_queue_size',
536 help=msg)
537
538 parser.add_option('-o',
539 '--timeout',
540 default='10',
541 type='int',
542 dest='timeout',
543 help='timeout for individual request (10 seconds)')
544
545 parser.add_option('-z',
546 '--shutdown_timeout',
547 default='5',
548 type='int',
549 dest='shutdown_timeout',
550 help='timeout on shutdown of server (5 seconds)')
551 parser.add_option('-f',
552 '--folder',
553 default=os.getcwd(),
554 dest='folder',
555 help='folder from which to run web2py')
556
557 parser.add_option('-v',
558 '--verbose',
559 action='store_true',
560 dest='verbose',
561 default=False,
562 help='increase --test verbosity')
563
564 parser.add_option('-Q',
565 '--quiet',
566 action='store_true',
567 dest='quiet',
568 default=False,
569 help='disable all output')
570
571 msg = 'set debug output level (0-100, 0 means all, 100 means none;'
572 msg += ' default is 30)'
573 parser.add_option('-D',
574 '--debug',
575 dest='debuglevel',
576 default=30,
577 type='int',
578 help=msg)
579
580 msg = 'run web2py in interactive shell or IPython (if installed) with'
581 msg += ' specified appname (if app does not exist it will be created).'
582 parser.add_option('-S',
583 '--shell',
584 dest='shell',
585 metavar='APPNAME',
586 help=msg)
587
588 msg = 'run web2py in interactive shell or bpython (if installed) with'
589 msg += ' specified appname (if app does not exist it will be created).'
590 msg += '\n Use combined with --shell'
591 parser.add_option('-B',
592 '--bpython',
593 action='store_true',
594 default=False,
595 dest='bpython',
596 help=msg)
597
598 msg = 'only use plain python shell; should be used with --shell option'
599 parser.add_option('-P',
600 '--plain',
601 action='store_true',
602 default=False,
603 dest='plain',
604 help=msg)
605
606 msg = 'auto import model files; default is False; should be used'
607 msg += ' with --shell option'
608 parser.add_option('-M',
609 '--import_models',
610 action='store_true',
611 default=False,
612 dest='import_models',
613 help=msg)
614
615 msg = 'run PYTHON_FILE in web2py environment;'
616 msg += ' should be used with --shell option'
617 parser.add_option('-R',
618 '--run',
619 dest='run',
620 metavar='PYTHON_FILE',
621 default='',
622 help=msg)
623
624 msg = 'run doctests in web2py environment; ' +\
625 'TEST_PATH like a/c/f (c,f optional)'
626 parser.add_option('-T',
627 '--test',
628 dest='test',
629 metavar='TEST_PATH',
630 default=None,
631 help=msg)
632
633 parser.add_option('-W',
634 '--winservice',
635 dest='winservice',
636 default='',
637 help='-W install|start|stop as Windows service')
638
639 msg = 'trigger a cron run manually; usually invoked from a system crontab'
640 parser.add_option('-C',
641 '--cron',
642 action='store_true',
643 dest='extcron',
644 default=False,
645 help=msg)
646
647 msg = 'triggers the use of softcron'
648 parser.add_option('--softcron',
649 action='store_true',
650 dest='softcron',
651 default=False,
652 help=msg)
653
654 parser.add_option('-N',
655 '--no-cron',
656 action='store_true',
657 dest='nocron',
658 default=False,
659 help='do not start cron automatically')
660
661 parser.add_option('-J',
662 '--cronjob',
663 action='store_true',
664 dest='cronjob',
665 default=False,
666 help='identify cron-initiated command')
667
668 parser.add_option('-L',
669 '--config',
670 dest='config',
671 default='',
672 help='config file')
673
674 parser.add_option('-F',
675 '--profiler',
676 dest='profiler_filename',
677 default=None,
678 help='profiler filename')
679
680 parser.add_option('-t',
681 '--taskbar',
682 action='store_true',
683 dest='taskbar',
684 default=False,
685 help='use web2py gui and run in taskbar (system tray)')
686
687 parser.add_option('',
688 '--nogui',
689 action='store_true',
690 default=False,
691 dest='nogui',
692 help='text-only, no GUI')
693
694 parser.add_option('-A',
695 '--args',
696 action='store',
697 dest='args',
698 default=None,
699 help='should be followed by a list of arguments to be passed to script, to be used with -S, -A must be the last option')
700
701 msg = 'listen on multiple addresses: "ip:port:cert:key;ip2:port2:cert2:key2;..." (:cert:key optional; no spaces)'
702 parser.add_option('--interfaces',
703 action='store',
704 dest='interfaces',
705 default=None,
706 help=msg)
707
708 if '-A' in sys.argv: k = sys.argv.index('-A')
709 elif '--args' in sys.argv: k = sys.argv.index('--args')
710 else: k=len(sys.argv)
711 sys.argv, other_args = sys.argv[:k], sys.argv[k+1:]
712 (options, args) = parser.parse_args()
713 options.args = [options.run] + other_args
714 global_settings.cmd_options = options
715 global_settings.cmd_args = args
716
717 if options.quiet:
718 capture = cStringIO.StringIO()
719 sys.stdout = capture
720 logger.setLevel(logging.CRITICAL + 1)
721 else:
722 logger.setLevel(options.debuglevel)
723
724 if options.config[-3:] == '.py':
725 options.config = options.config[:-3]
726
727 if options.cronjob:
728 global_settings.cronjob = True
729 options.nocron = True
730 options.plain = True
731
732 options.folder = os.path.abspath(options.folder)
733
734
735
736
737 if isinstance(options.interfaces, str):
738 options.interfaces = [interface.split(':') for interface in options.interfaces.split(';')]
739 for interface in options.interfaces:
740 interface[1] = int(interface[1])
741 options.interfaces = [tuple(interface) for interface in options.interfaces]
742
743 if options.numthreads is not None and options.minthreads is None:
744 options.minthreads = options.numthreads
745
746 if not options.cronjob:
747
748 if not os.path.exists('applications/__init__.py'):
749 fp = open('applications/__init__.py', 'w')
750 fp.write('')
751 fp.close()
752
753 if not os.path.exists('welcome.w2p') or os.path.exists('NEWINSTALL'):
754 try:
755 w2p_pack('welcome.w2p','applications/welcome')
756 os.unlink('NEWINSTALL')
757 except:
758 msg = "New installation: unable to create welcome.w2p file"
759 sys.stderr.write(msg)
760
761 return (options, args)
762
763
765 """ Start server """
766
767
768
769 (options, args) = console()
770
771 print ProgramName
772 print ProgramAuthor
773 print ProgramVersion
774
775 from dal import drivers
776 print 'Database drivers available: %s' % ', '.join(drivers)
777
778
779
780 if options.config:
781 try:
782 options2 = __import__(options.config, [], [], '')
783 except Exception:
784 try:
785
786 options = __import__(options.config)
787 except Exception:
788 print 'Cannot import config file [%s]' % options.config
789 sys.exit(1)
790 for key in dir(options2):
791 if hasattr(options,key):
792 setattr(options,key,getattr(options2,key))
793
794
795 if hasattr(options,'test') and options.test:
796 test(options.test, verbose=options.verbose)
797 return
798
799
800 if options.shell:
801 if options.args!=None:
802 sys.argv[:] = options.args
803 run(options.shell, plain=options.plain, bpython=options.bpython,
804 import_models=options.import_models, startfile=options.run)
805 return
806
807
808
809
810
811 if options.extcron:
812 print 'Starting extcron...'
813 global_settings.web2py_crontype = 'external'
814 extcron = newcron.extcron(options.folder)
815 extcron.start()
816 extcron.join()
817 return
818 elif cron and not options.nocron and options.softcron:
819 print 'Using softcron (but this is not very efficient)'
820 global_settings.web2py_crontype = 'soft'
821 elif cron and not options.nocron:
822 print 'Starting hardcron...'
823 global_settings.web2py_crontype = 'hard'
824 newcron.hardcron(options.folder).start()
825
826
827 if options.winservice:
828 if os.name == 'nt':
829 web2py_windows_service_handler(['', options.winservice],
830 options.config)
831 else:
832 print 'Error: Windows services not supported on this platform'
833 sys.exit(1)
834 return
835
836
837
838
839 try:
840 options.taskbar
841 except:
842 options.taskbar = False
843
844 if options.taskbar and os.name != 'nt':
845 print 'Error: taskbar not supported on this platform'
846 sys.exit(1)
847
848 root = None
849
850 if not options.nogui:
851 try:
852 import Tkinter
853 havetk = True
854 except ImportError:
855 logger.warn('GUI not available because Tk library is not installed')
856 havetk = False
857
858 if options.password == '<ask>' and havetk or options.taskbar and havetk:
859 try:
860 root = Tkinter.Tk()
861 except:
862 pass
863
864 if root:
865 root.focus_force()
866 if not options.quiet:
867 presentation(root)
868 master = web2pyDialog(root, options)
869 signal.signal(signal.SIGTERM, lambda a, b: master.quit())
870
871 try:
872 root.mainloop()
873 except:
874 master.quit()
875
876 sys.exit()
877
878
879
880 if not root and options.password == '<ask>':
881 options.password = raw_input('choose a password:')
882
883 if not options.password:
884 print 'no password, no admin interface'
885
886
887
888 (ip, port) = (options.ip, int(options.port))
889
890 print 'please visit:'
891 print '\thttp://%s:%s' % (ip, port)
892 print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid()
893
894 server = main.HttpServer(ip=ip,
895 port=port,
896 password=options.password,
897 pid_filename=options.pid_filename,
898 log_filename=options.log_filename,
899 profiler_filename=options.profiler_filename,
900 ssl_certificate=options.ssl_certificate,
901 ssl_private_key=options.ssl_private_key,
902 min_threads=options.minthreads,
903 max_threads=options.maxthreads,
904 server_name=options.server_name,
905 request_queue_size=options.request_queue_size,
906 timeout=options.timeout,
907 shutdown_timeout=options.shutdown_timeout,
908 path=options.folder,
909 interfaces=options.interfaces)
910
911 try:
912 server.start()
913 except KeyboardInterrupt:
914 server.stop()
915 logging.shutdown()
916