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 = 'only use plain python shell; should be used with --shell option'
589 parser.add_option('-P',
590 '--plain',
591 action='store_true',
592 default=False,
593 dest='plain',
594 help=msg)
595
596 msg = 'auto import model files; default is False; should be used'
597 msg += ' with --shell option'
598 parser.add_option('-M',
599 '--import_models',
600 action='store_true',
601 default=False,
602 dest='import_models',
603 help=msg)
604
605 msg = 'run PYTHON_FILE in web2py environment;'
606 msg += ' should be used with --shell option'
607 parser.add_option('-R',
608 '--run',
609 dest='run',
610 metavar='PYTHON_FILE',
611 default='',
612 help=msg)
613
614 msg = 'run doctests in web2py environment; ' +\
615 'TEST_PATH like a/c/f (c,f optional)'
616 parser.add_option('-T',
617 '--test',
618 dest='test',
619 metavar='TEST_PATH',
620 default=None,
621 help=msg)
622
623 parser.add_option('-W',
624 '--winservice',
625 dest='winservice',
626 default='',
627 help='-W install|start|stop as Windows service')
628
629 msg = 'trigger a cron run manually; usually invoked from a system crontab'
630 parser.add_option('-C',
631 '--cron',
632 action='store_true',
633 dest='extcron',
634 default=False,
635 help=msg)
636
637 msg = 'triggers the use of softcron'
638 parser.add_option('--softcron',
639 action='store_true',
640 dest='softcron',
641 default=False,
642 help=msg)
643
644 parser.add_option('-N',
645 '--no-cron',
646 action='store_true',
647 dest='nocron',
648 default=False,
649 help='do not start cron automatically')
650
651 parser.add_option('-J',
652 '--cronjob',
653 action='store_true',
654 dest='cronjob',
655 default=False,
656 help='identify cron-initiated command')
657
658 parser.add_option('-L',
659 '--config',
660 dest='config',
661 default='',
662 help='config file')
663
664 parser.add_option('-F',
665 '--profiler',
666 dest='profiler_filename',
667 default=None,
668 help='profiler filename')
669
670 parser.add_option('-t',
671 '--taskbar',
672 action='store_true',
673 dest='taskbar',
674 default=False,
675 help='use web2py gui and run in taskbar (system tray)')
676
677 parser.add_option('',
678 '--nogui',
679 action='store_true',
680 default=False,
681 dest='nogui',
682 help='text-only, no GUI')
683
684 parser.add_option('-A',
685 '--args',
686 action='store',
687 dest='args',
688 default=None,
689 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')
690
691 msg = 'listen on multiple addresses: "ip:port:cert:key;ip2:port2:cert2:key2;..." (:cert:key optional; no spaces)'
692 parser.add_option('--interfaces',
693 action='store',
694 dest='interfaces',
695 default=None,
696 help=msg)
697
698 if '-A' in sys.argv: k = sys.argv.index('-A')
699 elif '--args' in sys.argv: k = sys.argv.index('--args')
700 else: k=len(sys.argv)
701 sys.argv, other_args = sys.argv[:k], sys.argv[k+1:]
702 (options, args) = parser.parse_args()
703 options.args = [options.run] + other_args
704 global_settings.cmd_options = options
705 global_settings.cmd_args = args
706
707 if options.quiet:
708 capture = cStringIO.StringIO()
709 sys.stdout = capture
710 logger.setLevel(logging.CRITICAL + 1)
711 else:
712 logger.setLevel(options.debuglevel)
713
714 if options.config[-3:] == '.py':
715 options.config = options.config[:-3]
716
717 if options.cronjob:
718 global_settings.cronjob = True
719 options.nocron = True
720 options.plain = True
721
722 options.folder = os.path.abspath(options.folder)
723
724
725
726
727 if isinstance(options.interfaces, str):
728 options.interfaces = [interface.split(':') for interface in options.interfaces.split(';')]
729 for interface in options.interfaces:
730 interface[1] = int(interface[1])
731 options.interfaces = [tuple(interface) for interface in options.interfaces]
732
733 if options.numthreads is not None and options.minthreads is None:
734 options.minthreads = options.numthreads
735
736 if not options.cronjob:
737
738 if not os.path.exists('applications/__init__.py'):
739 fp = open('applications/__init__.py', 'w')
740 fp.write('')
741 fp.close()
742
743 if not os.path.exists('welcome.w2p') or os.path.exists('NEWINSTALL'):
744 try:
745 w2p_pack('welcome.w2p','applications/welcome')
746 os.unlink('NEWINSTALL')
747 except:
748 msg = "New installation: unable to create welcome.w2p file"
749 sys.stderr.write(msg)
750
751 return (options, args)
752
753
755 """ Start server """
756
757
758
759 (options, args) = console()
760
761 print ProgramName
762 print ProgramAuthor
763 print ProgramVersion
764
765 from dal import drivers
766 print 'Database drivers available: %s' % ', '.join(drivers)
767
768
769
770 if options.config:
771 try:
772 options2 = __import__(options.config, [], [], '')
773 except Exception:
774 try:
775
776 options = __import__(options.config)
777 except Exception:
778 print 'Cannot import config file [%s]' % options.config
779 sys.exit(1)
780 for key in dir(options2):
781 if hasattr(options,key):
782 setattr(options,key,getattr(options2,key))
783
784
785 if hasattr(options,'test') and options.test:
786 test(options.test, verbose=options.verbose)
787 return
788
789
790 if options.shell:
791 if options.args!=None:
792 sys.argv[:] = options.args
793 run(options.shell, plain=options.plain,
794 import_models=options.import_models, startfile=options.run)
795 return
796
797
798
799
800
801 if options.extcron:
802 print 'Starting extcron...'
803 global_settings.web2py_crontype = 'external'
804 extcron = newcron.extcron(options.folder)
805 extcron.start()
806 extcron.join()
807 return
808 elif cron and not options.nocron and options.softcron:
809 print 'Using softcron (but this is not very efficient)'
810 global_settings.web2py_crontype = 'soft'
811 elif cron and not options.nocron:
812 print 'Starting hardcron...'
813 global_settings.web2py_crontype = 'hard'
814 newcron.hardcron(options.folder).start()
815
816
817 if options.winservice:
818 if os.name == 'nt':
819 web2py_windows_service_handler(['', options.winservice],
820 options.config)
821 else:
822 print 'Error: Windows services not supported on this platform'
823 sys.exit(1)
824 return
825
826
827
828
829 try:
830 options.taskbar
831 except:
832 options.taskbar = False
833
834 if options.taskbar and os.name != 'nt':
835 print 'Error: taskbar not supported on this platform'
836 sys.exit(1)
837
838 root = None
839
840 if not options.nogui:
841 try:
842 import Tkinter
843 havetk = True
844 except ImportError:
845 logger.warn('GUI not available because Tk library is not installed')
846 havetk = False
847
848 if options.password == '<ask>' and havetk or options.taskbar and havetk:
849 try:
850 root = Tkinter.Tk()
851 except:
852 pass
853
854 if root:
855 root.focus_force()
856 if not options.quiet:
857 presentation(root)
858 master = web2pyDialog(root, options)
859 signal.signal(signal.SIGTERM, lambda a, b: master.quit())
860
861 try:
862 root.mainloop()
863 except:
864 master.quit()
865
866 sys.exit()
867
868
869
870 if not root and options.password == '<ask>':
871 options.password = raw_input('choose a password:')
872
873 if not options.password:
874 print 'no password, no admin interface'
875
876
877
878 (ip, port) = (options.ip, int(options.port))
879
880 print 'please visit:'
881 print '\thttp://%s:%s' % (ip, port)
882 print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid()
883
884 server = main.HttpServer(ip=ip,
885 port=port,
886 password=options.password,
887 pid_filename=options.pid_filename,
888 log_filename=options.log_filename,
889 profiler_filename=options.profiler_filename,
890 ssl_certificate=options.ssl_certificate,
891 ssl_private_key=options.ssl_private_key,
892 min_threads=options.minthreads,
893 max_threads=options.maxthreads,
894 server_name=options.server_name,
895 request_queue_size=options.request_queue_size,
896 timeout=options.timeout,
897 shutdown_timeout=options.shutdown_timeout,
898 path=options.folder,
899 interfaces=options.interfaces)
900
901 try:
902 server.start()
903 except KeyboardInterrupt:
904 server.stop()
905 logging.shutdown()
906