888 lines
31 KiB
Python
888 lines
31 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
| This file is part of the web2py Web Framework
|
|
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
|
|
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
|
|
|
GUI widget and services start function
|
|
--------------------------------------
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
from gluon._compat import thread, xrange, PY2
|
|
import time
|
|
import threading
|
|
import os
|
|
import socket
|
|
import signal
|
|
import math
|
|
import logging
|
|
import getpass
|
|
|
|
from gluon import main, newcron
|
|
from gluon.fileutils import read_file, create_welcome_w2p
|
|
from gluon.console import console
|
|
from gluon.settings import global_settings
|
|
from gluon.shell import die, run, test
|
|
from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
|
|
|
|
|
|
ProgramName = 'web2py Web Framework'
|
|
ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-' + str(
|
|
time.localtime().tm_year)
|
|
ProgramVersion = read_file('VERSION').rstrip()
|
|
|
|
if sys.version_info < (2, 7) or (3, 0) < sys.version_info < (3, 5):
|
|
from platform import python_version
|
|
sys.stderr.write("Warning: web2py requires at least Python 2.7/3.5"
|
|
" but you are running %s\n" % python_version())
|
|
|
|
|
|
def run_system_tests(options):
|
|
"""
|
|
Runs unittests for gluon.tests
|
|
"""
|
|
# see "python -m unittest -h" for unittest options help
|
|
# NOTE: someone might be interested either in using the
|
|
# -f (--failfast) option to stop testing on first failure, or
|
|
# in customizing the test selection, for example to run only
|
|
# 'gluon.tests.<module>', 'gluon.tests.<module>.<class>' (this
|
|
# could be shortened as 'gluon.tests.<class>'), or even
|
|
# 'gluon.tests.<module>.<class>.<method>' (or
|
|
# the shorter 'gluon.tests.<class>.<method>')
|
|
call_args = ['-m', 'unittest', '-c', 'gluon.tests']
|
|
if options.verbose:
|
|
call_args.insert(-1, '-v')
|
|
if options.with_coverage:
|
|
try:
|
|
import coverage
|
|
except:
|
|
die('Coverage not installed')
|
|
if not PY2:
|
|
sys.stderr.write('Experimental ')
|
|
sys.stderr.write("Python %s\n" % sys.version)
|
|
if options.with_coverage:
|
|
coverage_exec = 'coverage2' if PY2 else 'coverage3'
|
|
coverage_config_file = os.path.join('gluon', 'tests', 'coverage.ini')
|
|
coverage_config = os.environ.setdefault("COVERAGE_PROCESS_START",
|
|
coverage_config_file)
|
|
run_args = [coverage_exec, 'run', '--rcfile=%s' % coverage_config]
|
|
# replace the current process
|
|
os.execvpe(run_args[0], run_args + call_args, os.environ)
|
|
else:
|
|
run_args = [sys.executable]
|
|
# replace the current process
|
|
os.execv(run_args[0], run_args + call_args)
|
|
|
|
|
|
def get_url(host, path='/', proto='http', port=80):
|
|
if ':' in host:
|
|
host = '[%s]' % host
|
|
elif host == '0.0.0.0':
|
|
host = '127.0.0.1'
|
|
if path.startswith('/'):
|
|
path = path[1:]
|
|
if proto.endswith(':'):
|
|
proto = proto[:-1]
|
|
if not port or port == 80:
|
|
port = ''
|
|
else:
|
|
port = ':%s' % port
|
|
return '%s://%s%s/%s' % (proto, host, port, path)
|
|
|
|
|
|
def start_browser(url, startup=False):
|
|
if startup:
|
|
print('please visit:')
|
|
print('\t' + url)
|
|
print('starting browser...')
|
|
try:
|
|
import webbrowser
|
|
webbrowser.open(url)
|
|
except:
|
|
print('warning: unable to detect your browser')
|
|
|
|
|
|
class web2pyDialog(object):
|
|
""" Main window dialog """
|
|
|
|
def __init__(self, root, options):
|
|
""" web2pyDialog constructor """
|
|
|
|
if PY2:
|
|
import Tkinter as tkinter
|
|
import tkMessageBox as messagebox
|
|
else:
|
|
import tkinter
|
|
from tkinter import messagebox
|
|
|
|
|
|
bg_color = 'white'
|
|
root.withdraw()
|
|
|
|
self.root = tkinter.Toplevel(root, bg=bg_color)
|
|
self.root.resizable(0, 0)
|
|
self.root.title(ProgramName)
|
|
|
|
self.options = options
|
|
self.scheduler_processes = {}
|
|
self.menu = tkinter.Menu(self.root)
|
|
servermenu = tkinter.Menu(self.menu, tearoff=0)
|
|
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
|
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
|
if os.path.exists(iconphoto):
|
|
img = tkinter.PhotoImage(file=iconphoto)
|
|
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
|
|
# Building the Menu
|
|
item = lambda: start_browser(httplog)
|
|
servermenu.add_command(label='View httpserver.log',
|
|
command=item)
|
|
|
|
servermenu.add_command(label='Quit (pid:%i)' % os.getpid(),
|
|
command=self.quit)
|
|
|
|
self.menu.add_cascade(label='Server', menu=servermenu)
|
|
|
|
self.pagesmenu = tkinter.Menu(self.menu, tearoff=0)
|
|
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
|
|
|
|
#scheduler menu
|
|
self.schedmenu = tkinter.Menu(self.menu, tearoff=0)
|
|
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
|
|
#start and register schedulers from options
|
|
self.update_schedulers(start=True)
|
|
|
|
helpmenu = tkinter.Menu(self.menu, tearoff=0)
|
|
|
|
# Home Page
|
|
item = lambda: start_browser('http://www.web2py.com/')
|
|
helpmenu.add_command(label='Home Page',
|
|
command=item)
|
|
|
|
# About
|
|
ProgramInfo = """%s
|
|
%s
|
|
%s""" % (ProgramName, ProgramAuthor, ProgramVersion)
|
|
item = lambda: messagebox.showinfo('About web2py', ProgramInfo)
|
|
helpmenu.add_command(label='About',
|
|
command=item)
|
|
|
|
self.menu.add_cascade(label='Info', menu=helpmenu)
|
|
|
|
self.root.config(menu=self.menu)
|
|
|
|
if options.taskbar:
|
|
self.root.protocol('WM_DELETE_WINDOW',
|
|
lambda: self.quit(True))
|
|
else:
|
|
self.root.protocol('WM_DELETE_WINDOW', self.quit)
|
|
|
|
sticky = tkinter.NW
|
|
|
|
# Prepare the logo area
|
|
self.logoarea = tkinter.Canvas(self.root,
|
|
background=bg_color,
|
|
width=300,
|
|
height=300)
|
|
self.logoarea.grid(row=0, column=0, columnspan=4, sticky=sticky)
|
|
self.logoarea.after(1000, self.update_canvas)
|
|
|
|
logo = os.path.join('extras', 'icons', 'splashlogo.gif')
|
|
if os.path.exists(logo):
|
|
img = tkinter.PhotoImage(file=logo)
|
|
pnl = tkinter.Label(self.logoarea, image=img, background=bg_color, bd=0)
|
|
pnl.pack(side='top', fill='both', expand='yes')
|
|
# Prevent garbage collection of img
|
|
pnl.image = img
|
|
|
|
# Prepare the banner area
|
|
self.bannerarea = tkinter.Canvas(self.root,
|
|
bg=bg_color,
|
|
width=300,
|
|
height=300)
|
|
self.bannerarea.grid(row=1, column=1, columnspan=2, sticky=sticky)
|
|
|
|
tkinter.Label(self.bannerarea, anchor=tkinter.N,
|
|
text=str(ProgramVersion + "\n" + ProgramAuthor),
|
|
font=('Helvetica', 11), justify=tkinter.CENTER,
|
|
foreground='#195866', background=bg_color,
|
|
height=3).pack(side='top',
|
|
fill='both',
|
|
expand='yes')
|
|
|
|
self.bannerarea.after(1000, self.update_canvas)
|
|
|
|
# IP
|
|
# retrieves the list of server IP addresses
|
|
try:
|
|
if_ips = list(set( # no duplicates
|
|
[addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn())
|
|
if not is_loopback_ip_address(addrinfo=addrinfo)]))
|
|
except socket.gaierror:
|
|
if_ips = []
|
|
|
|
tkinter.Label(self.root,
|
|
text='Server IP:', bg=bg_color,
|
|
justify=tkinter.RIGHT).grid(row=4,
|
|
column=1,
|
|
sticky=sticky)
|
|
self.ips = {}
|
|
self.selected_ip = tkinter.StringVar()
|
|
row = 4
|
|
ips = [('127.0.0.1', 'Local (IPv4)')] + \
|
|
([('::1', 'Local (IPv6)')] if socket.has_ipv6 else []) + \
|
|
[(ip, 'Public') for ip in if_ips] + \
|
|
[('0.0.0.0', 'Public')]
|
|
for ip, legend in ips:
|
|
self.ips[ip] = tkinter.Radiobutton(
|
|
self.root, bg=bg_color, highlightthickness=0,
|
|
selectcolor='light grey', width=30,
|
|
anchor=tkinter.W, text='%s (%s)' % (legend, ip),
|
|
justify=tkinter.LEFT,
|
|
variable=self.selected_ip, value=ip)
|
|
self.ips[ip].grid(row=row, column=2, sticky=sticky)
|
|
if row == 4:
|
|
self.ips[ip].select()
|
|
row += 1
|
|
shift = row
|
|
|
|
# Port
|
|
tkinter.Label(self.root,
|
|
text='Server Port:', bg=bg_color,
|
|
justify=tkinter.RIGHT).grid(row=shift,
|
|
column=1, pady=10,
|
|
sticky=sticky)
|
|
|
|
self.port_number = tkinter.Entry(self.root)
|
|
self.port_number.insert(tkinter.END, self.options.port)
|
|
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
|
|
|
|
# Password
|
|
tkinter.Label(self.root,
|
|
text='Choose Password:', bg=bg_color,
|
|
justify=tkinter.RIGHT).grid(row=shift + 1,
|
|
column=1,
|
|
sticky=sticky)
|
|
|
|
self.password = tkinter.Entry(self.root, show='*')
|
|
self.password.bind('<Return>', lambda e: self.start())
|
|
self.password.focus_force()
|
|
self.password.grid(row=shift + 1, column=2, sticky=sticky)
|
|
|
|
# Prepare the canvas
|
|
self.canvas = tkinter.Canvas(self.root,
|
|
width=400,
|
|
height=100,
|
|
bg='black')
|
|
self.canvas.grid(row=shift + 2, column=1, columnspan=2, pady=5,
|
|
sticky=sticky)
|
|
self.canvas.after(1000, self.update_canvas)
|
|
|
|
# Prepare the frame
|
|
frame = tkinter.Frame(self.root)
|
|
frame.grid(row=shift + 3, column=1, columnspan=2, pady=5,
|
|
sticky=sticky)
|
|
|
|
# Start button
|
|
self.button_start = tkinter.Button(frame,
|
|
text='start server',
|
|
command=self.start)
|
|
|
|
self.button_start.grid(row=0, column=0, sticky=sticky)
|
|
|
|
# Stop button
|
|
self.button_stop = tkinter.Button(frame,
|
|
text='stop server',
|
|
command=self.stop)
|
|
|
|
self.button_stop.grid(row=0, column=1, sticky=sticky)
|
|
self.button_stop.configure(state='disabled')
|
|
|
|
if options.taskbar:
|
|
import gluon.contrib.taskbar_widget
|
|
self.tb = gluon.contrib.taskbar_widget.TaskBarIcon()
|
|
self.checkTaskBar()
|
|
|
|
if options.password != '<ask>':
|
|
self.password.insert(0, options.password)
|
|
self.start()
|
|
self.root.withdraw()
|
|
else:
|
|
self.tb = None
|
|
|
|
def update_schedulers(self, start=False):
|
|
if start and self.options.with_scheduler and self.options.schedulers:
|
|
# the widget takes care of starting the schedulers
|
|
apps = [ag.split(':', 1)[0] for ag in self.options.schedulers]
|
|
else:
|
|
apps = []
|
|
for app in apps:
|
|
self.try_start_scheduler(app)
|
|
|
|
# reset the menu
|
|
applications_folder = os.path.join(self.options.folder, 'applications')
|
|
available_apps = [
|
|
arq for arq in os.listdir(applications_folder)
|
|
if os.path.isdir(os.path.join(applications_folder, arq))
|
|
]
|
|
self.schedmenu.delete(0, len(available_apps))
|
|
|
|
for arq in available_apps:
|
|
if arq not in self.scheduler_processes:
|
|
item = lambda u = arq: self.try_start_scheduler(u)
|
|
self.schedmenu.add_command(label="start %s" % arq,
|
|
command=item)
|
|
if arq in self.scheduler_processes:
|
|
item = lambda u = arq: self.try_stop_scheduler(u)
|
|
self.schedmenu.add_command(label="stop %s" % arq,
|
|
command=item)
|
|
|
|
def start_schedulers(self, app):
|
|
try:
|
|
from multiprocessing import Process
|
|
except:
|
|
sys.stderr.write('Sorry, -K only supported for Python 2.6+\n')
|
|
return
|
|
code = "from gluon.globals import current;current._scheduler.loop()"
|
|
print('starting scheduler from widget for "%s"...' % app)
|
|
args = (app, True, True, None, False, code, False, True)
|
|
logging.getLogger().setLevel(self.options.log_level)
|
|
p = Process(target=run, args=args)
|
|
self.scheduler_processes[app] = p
|
|
self.update_schedulers()
|
|
print("Currently running %s scheduler processes" % (
|
|
len(self.scheduler_processes)))
|
|
p.start()
|
|
print("Processes started")
|
|
|
|
def try_stop_scheduler(self, app):
|
|
if app in self.scheduler_processes:
|
|
p = self.scheduler_processes[app]
|
|
del self.scheduler_processes[app]
|
|
p.terminate()
|
|
p.join()
|
|
self.update_schedulers()
|
|
|
|
def try_start_scheduler(self, app):
|
|
if app not in self.scheduler_processes:
|
|
t = threading.Thread(target=self.start_schedulers, args=(app,))
|
|
t.start()
|
|
|
|
def checkTaskBar(self):
|
|
""" Checks taskbar status """
|
|
|
|
if self.tb.status:
|
|
if self.tb.status[0] == self.tb.EnumStatus.QUIT:
|
|
self.quit()
|
|
elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE:
|
|
if self.root.state() == 'withdrawn':
|
|
self.root.deiconify()
|
|
else:
|
|
self.root.withdraw()
|
|
elif self.tb.status[0] == self.tb.EnumStatus.STOP:
|
|
self.stop()
|
|
elif self.tb.status[0] == self.tb.EnumStatus.START:
|
|
self.start()
|
|
elif self.tb.status[0] == self.tb.EnumStatus.RESTART:
|
|
self.stop()
|
|
self.start()
|
|
del self.tb.status[0]
|
|
|
|
self.root.after(1000, self.checkTaskBar)
|
|
|
|
def update(self, text):
|
|
""" Updates app text """
|
|
|
|
try:
|
|
self.text.configure(state='normal')
|
|
self.text.insert('end', text)
|
|
self.text.configure(state='disabled')
|
|
except:
|
|
pass # ## this should only happen in case app is destroyed
|
|
|
|
def connect_pages(self):
|
|
""" Connects pages """
|
|
# reset the menu
|
|
applications_folder = os.path.join(self.options.folder, 'applications')
|
|
available_apps = [
|
|
arq for arq in os.listdir(applications_folder)
|
|
if os.path.exists(os.path.join(applications_folder, arq, '__init__.py'))
|
|
]
|
|
self.pagesmenu.delete(0, len(available_apps))
|
|
for arq in available_apps:
|
|
url = self.url + arq
|
|
self.pagesmenu.add_command(
|
|
label=url, command=lambda u=url: start_browser(u))
|
|
|
|
def quit(self, justHide=False):
|
|
""" Finishes the program execution """
|
|
if justHide:
|
|
self.root.withdraw()
|
|
else:
|
|
try:
|
|
scheds = self.scheduler_processes.keys()
|
|
for t in scheds:
|
|
self.try_stop_scheduler(t)
|
|
except:
|
|
pass
|
|
try:
|
|
newcron.stopcron()
|
|
except:
|
|
pass
|
|
try:
|
|
self.server.stop()
|
|
except:
|
|
pass
|
|
try:
|
|
self.tb.Destroy()
|
|
except:
|
|
pass
|
|
|
|
self.root.destroy()
|
|
sys.exit(0)
|
|
|
|
def error(self, message):
|
|
""" Shows error message """
|
|
if PY2:
|
|
import tkMessageBox as messagebox
|
|
else:
|
|
from tkinter import messagebox
|
|
|
|
messagebox.showerror('web2py start server', message)
|
|
|
|
def start(self):
|
|
""" Starts web2py server """
|
|
|
|
password = self.password.get()
|
|
|
|
if not password:
|
|
self.error('no password, no web admin interface')
|
|
|
|
ip = self.selected_ip.get()
|
|
|
|
if not is_valid_ip_address(ip):
|
|
return self.error('invalid host ip address')
|
|
|
|
try:
|
|
port = int(self.port_number.get())
|
|
except:
|
|
return self.error('invalid port number')
|
|
|
|
if self.options.server_key and self.options.server_cert:
|
|
proto = 'https'
|
|
else:
|
|
proto = 'http'
|
|
|
|
self.url = get_url(ip, proto=proto, port=port)
|
|
self.connect_pages()
|
|
self.button_start.configure(state='disabled')
|
|
|
|
try:
|
|
options = self.options
|
|
req_queue_size = options.request_queue_size
|
|
self.server = main.HttpServer(
|
|
ip,
|
|
port,
|
|
password,
|
|
pid_filename=options.pid_filename,
|
|
log_filename=options.log_filename,
|
|
profiler_dir=options.profiler_dir,
|
|
ssl_certificate=options.server_cert,
|
|
ssl_private_key=options.server_key,
|
|
ssl_ca_certificate=options.ca_cert,
|
|
min_threads=options.min_threads,
|
|
max_threads=options.max_threads,
|
|
server_name=options.server_name,
|
|
request_queue_size=req_queue_size,
|
|
timeout=options.timeout,
|
|
shutdown_timeout=options.shutdown_timeout,
|
|
path=options.folder,
|
|
interfaces=options.interfaces)
|
|
|
|
thread.start_new_thread(self.server.start, ())
|
|
except Exception as e:
|
|
self.button_start.configure(state='normal')
|
|
return self.error(str(e))
|
|
|
|
if not self.server_ready():
|
|
self.button_start.configure(state='normal')
|
|
return
|
|
|
|
self.button_stop.configure(state='normal')
|
|
|
|
if not options.taskbar:
|
|
thread.start_new_thread(
|
|
start_browser, (get_url(ip, proto=proto, port=port), True))
|
|
|
|
self.password.configure(state='readonly')
|
|
[ip.configure(state='disabled') for ip in self.ips.values()]
|
|
self.port_number.configure(state='readonly')
|
|
|
|
if self.tb:
|
|
self.tb.SetServerRunning()
|
|
|
|
def server_ready(self):
|
|
for listener in self.server.server.listeners:
|
|
if listener.ready:
|
|
return True
|
|
|
|
return False
|
|
|
|
def stop(self):
|
|
""" Stops web2py server """
|
|
|
|
self.button_start.configure(state='normal')
|
|
self.button_stop.configure(state='disabled')
|
|
self.password.configure(state='normal')
|
|
[ip.configure(state='normal') for ip in self.ips.values()]
|
|
self.port_number.configure(state='normal')
|
|
self.server.stop()
|
|
|
|
if self.tb:
|
|
self.tb.SetServerStopped()
|
|
|
|
def update_canvas(self):
|
|
""" Updates canvas """
|
|
|
|
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
|
try:
|
|
t1 = os.path.getsize(httplog)
|
|
except:
|
|
self.canvas.after(1000, self.update_canvas)
|
|
return
|
|
|
|
try:
|
|
fp = open(httplog, 'r')
|
|
fp.seek(self.t0)
|
|
data = fp.read(t1 - self.t0)
|
|
fp.close()
|
|
value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))]
|
|
self.p0 = value
|
|
|
|
for i in xrange(len(self.p0) - 1):
|
|
c = self.canvas.coords(self.q0[i])
|
|
self.canvas.coords(self.q0[i],
|
|
(c[0],
|
|
self.p0[i],
|
|
c[2],
|
|
self.p0[i + 1]))
|
|
self.t0 = t1
|
|
except BaseException:
|
|
self.t0 = time.time()
|
|
self.t0 = t1
|
|
self.p0 = [100] * 400
|
|
self.q0 = [self.canvas.create_line(i, 100, i + 1, 100,
|
|
fill='green') for i in xrange(len(self.p0) - 1)]
|
|
|
|
self.canvas.after(1000, self.update_canvas)
|
|
|
|
|
|
def check_existent_app(options, appname):
|
|
if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
|
|
return True
|
|
|
|
|
|
def get_code_for_scheduler(app_groups, options):
|
|
app = app_groups[0]
|
|
if not check_existent_app(options, app):
|
|
print("Application '%s' doesn't exist, skipping" % app)
|
|
return None, None
|
|
code = 'from gluon.globals import current;'
|
|
if len(app_groups) > 1:
|
|
code += "current._scheduler.group_names=['%s'];" % "','".join(
|
|
app_groups[1:])
|
|
code += "current._scheduler.loop()"
|
|
return app, code
|
|
|
|
|
|
def start_schedulers(options):
|
|
try:
|
|
from multiprocessing import Process
|
|
except:
|
|
sys.stderr.write('Sorry, -K only supported for Python 2.6+\n')
|
|
return
|
|
logging.getLogger().setLevel(options.log_level)
|
|
|
|
apps = [ag.split(':') for ag in options.schedulers]
|
|
if not options.with_scheduler and len(apps) == 1:
|
|
app, code = get_code_for_scheduler(apps[0], options)
|
|
if not app:
|
|
return
|
|
print('starting single-scheduler for "%s"...' % app)
|
|
run(app, True, True, None, False, code, False, True)
|
|
return
|
|
|
|
# Work around OS X problem: http://bugs.python.org/issue9405
|
|
if PY2:
|
|
import urllib
|
|
else:
|
|
import urllib.request as urllib
|
|
urllib.getproxies()
|
|
|
|
processes = []
|
|
for app_groups in apps:
|
|
app, code = get_code_for_scheduler(app_groups, options)
|
|
if not app:
|
|
continue
|
|
print('starting scheduler for "%s"...' % app)
|
|
args = (app, True, True, None, False, code, False, True)
|
|
p = Process(target=run, args=args)
|
|
processes.append(p)
|
|
print("Currently running %s scheduler processes" % (len(processes)))
|
|
p.start()
|
|
##to avoid bashing the db at the same time
|
|
time.sleep(0.7)
|
|
print("Processes started")
|
|
for p in processes:
|
|
try:
|
|
p.join()
|
|
except (KeyboardInterrupt, SystemExit):
|
|
print("Processes stopped")
|
|
except:
|
|
p.terminate()
|
|
p.join()
|
|
|
|
|
|
def start():
|
|
""" Starts server and other services """
|
|
|
|
# get command line arguments
|
|
options = console(version=ProgramVersion)
|
|
|
|
if options.gae:
|
|
# write app.yaml, gaehandler.py, and exit
|
|
if not os.path.exists('app.yaml'):
|
|
name = options.gae
|
|
# for backward compatibility
|
|
if name == 'configure':
|
|
if PY2: input = raw_input
|
|
name = input("Your GAE app name: ")
|
|
content = open(os.path.join('examples', 'app.example.yaml'), 'rb').read()
|
|
open('app.yaml', 'wb').write(content.replace("yourappname", name))
|
|
else:
|
|
print("app.yaml alreday exists in the web2py folder")
|
|
if not os.path.exists('gaehandler.py'):
|
|
content = open(os.path.join('handlers', 'gaehandler.py'), 'rb').read()
|
|
open('gaehandler.py', 'wb').write(content)
|
|
else:
|
|
print("gaehandler.py alreday exists in the web2py folder")
|
|
return
|
|
|
|
logger = logging.getLogger("web2py")
|
|
logger.setLevel(options.log_level)
|
|
|
|
# on new installation build the scaffolding app
|
|
create_welcome_w2p()
|
|
|
|
if options.run_system_tests:
|
|
# run system test and exit
|
|
run_system_tests(options)
|
|
|
|
if options.quiet:
|
|
# to prevent writes on stdout set a null stream
|
|
class NullFile(object):
|
|
def write(self, x):
|
|
pass
|
|
sys.stdout = NullFile()
|
|
# but still has to mute existing loggers, to do that iterate
|
|
# over all existing loggers (root logger included) and remove
|
|
# all attached logging.StreamHandler instances currently
|
|
# streaming on sys.stdout or sys.stderr
|
|
loggers = [logging.getLogger()]
|
|
loggers.extend(logging.Logger.manager.loggerDict.values())
|
|
for l in loggers:
|
|
if isinstance(l, logging.PlaceHolder): continue
|
|
for h in l.handlers[:]:
|
|
if isinstance(h, logging.StreamHandler) and \
|
|
h.stream in (sys.stdout, sys.stderr):
|
|
l.removeHandler(h)
|
|
# NOTE: stderr.write() is still working
|
|
|
|
if not options.no_banner:
|
|
# banner
|
|
print(ProgramName)
|
|
print(ProgramAuthor)
|
|
print(ProgramVersion)
|
|
from pydal.drivers import DRIVERS
|
|
print('Database drivers available: %s' % ', '.join(DRIVERS))
|
|
|
|
if options.run_doctests:
|
|
# run doctests and exit
|
|
test(options.run_doctests, verbose=options.verbose)
|
|
return
|
|
|
|
if options.shell:
|
|
# run interactive shell and exit
|
|
sys.argv = [options.run or ''] + options.args
|
|
run(options.shell, plain=options.plain, bpython=options.bpython,
|
|
import_models=options.import_models, startfile=options.run,
|
|
cron_job=options.cron_job)
|
|
return
|
|
|
|
if options.cron_run:
|
|
# run cron (extcron) and exit
|
|
logger.debug('Starting extcron...')
|
|
global_settings.web2py_crontype = 'external'
|
|
extcron = newcron.extcron(options.folder, apps=options.crontabs)
|
|
extcron.start()
|
|
extcron.join()
|
|
return
|
|
|
|
if not options.with_scheduler and options.schedulers:
|
|
# run schedulers and exit
|
|
try:
|
|
start_schedulers(options)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
return
|
|
|
|
if options.with_cron:
|
|
if options.soft_cron:
|
|
print('Using cron software emulation (but this is not very efficient)')
|
|
global_settings.web2py_crontype = 'soft'
|
|
else:
|
|
# start hardcron thread
|
|
logger.debug('Starting hardcron...')
|
|
global_settings.web2py_crontype = 'hard'
|
|
newcron.hardcron(options.folder, apps=options.crontabs).start()
|
|
|
|
# if no password provided and have Tk library start GUI (when not
|
|
# explicitly disabled), we also need a GUI to put in taskbar (system tray)
|
|
# when requested
|
|
root = None
|
|
|
|
if (not options.no_gui and options.password == '<ask>') or options.taskbar:
|
|
try:
|
|
if PY2:
|
|
import Tkinter as tkinter
|
|
else:
|
|
import tkinter
|
|
root = tkinter.Tk()
|
|
except (ImportError, OSError):
|
|
logger.warn(
|
|
'GUI not available because Tk library is not installed')
|
|
options.no_gui = True
|
|
except:
|
|
logger.exception('cannot get Tk root window, GUI disabled')
|
|
options.no_gui = True
|
|
|
|
if root:
|
|
# run GUI and exit
|
|
root.focus_force()
|
|
|
|
# Mac OS X - make the GUI window rise to the top
|
|
if os.path.exists("/usr/bin/osascript"):
|
|
applescript = """
|
|
tell application "System Events"
|
|
set proc to first process whose unix id is %d
|
|
set frontmost of proc to true
|
|
end tell
|
|
""" % (os.getpid())
|
|
os.system("/usr/bin/osascript -e '%s'" % applescript)
|
|
|
|
# web2pyDialog takes care of schedulers
|
|
master = web2pyDialog(root, options)
|
|
signal.signal(signal.SIGTERM, lambda a, b: master.quit())
|
|
|
|
try:
|
|
root.mainloop()
|
|
except:
|
|
master.quit()
|
|
|
|
sys.exit()
|
|
|
|
spt = None
|
|
|
|
if options.with_scheduler and options.schedulers:
|
|
# start schedulers in a separate thread
|
|
spt = threading.Thread(target=start_schedulers, args=(options,))
|
|
spt.start()
|
|
|
|
# start server
|
|
|
|
if options.password == '<ask>':
|
|
options.password = getpass.getpass('choose a password:')
|
|
|
|
if not options.password and not options.no_banner:
|
|
print('no password, disable admin interface')
|
|
|
|
# Use first interface IP and port if interfaces specified, since the
|
|
# interfaces option overrides the IP (and related) options.
|
|
if not options.interfaces:
|
|
ip = options.ip
|
|
port = options.port
|
|
else:
|
|
first_if = options.interfaces[0]
|
|
ip = first_if[0]
|
|
port = first_if[1]
|
|
|
|
if options.server_key and options.server_cert:
|
|
proto = 'https'
|
|
else:
|
|
proto = 'http'
|
|
|
|
url = get_url(ip, proto=proto, port=port)
|
|
|
|
if not options.no_banner:
|
|
message = '\nplease visit:\n\t%s\n'
|
|
if sys.platform.startswith('win'):
|
|
message += 'use "taskkill /f /pid %i" to shutdown the web2py server\n\n'
|
|
else:
|
|
message += 'use "kill -SIGTERM %i" to shutdown the web2py server\n\n'
|
|
print(message % (url, os.getpid()))
|
|
|
|
# enhance linecache.getline (used by debugger) to look at the source file
|
|
# if the line was not found (under py2exe & when file was modified)
|
|
import linecache
|
|
py2exe_getline = linecache.getline
|
|
|
|
def getline(filename, lineno, *args, **kwargs):
|
|
line = py2exe_getline(filename, lineno, *args, **kwargs)
|
|
if not line:
|
|
try:
|
|
with open(filename, "rb") as f:
|
|
for i, line in enumerate(f):
|
|
line = line.decode('utf-8')
|
|
if lineno == i + 1:
|
|
break
|
|
else:
|
|
line = ''
|
|
except (IOError, OSError):
|
|
line = ''
|
|
return line
|
|
linecache.getline = getline
|
|
|
|
server = main.HttpServer(ip=ip,
|
|
port=port,
|
|
password=options.password,
|
|
pid_filename=options.pid_filename,
|
|
log_filename=options.log_filename,
|
|
profiler_dir=options.profiler_dir,
|
|
ssl_certificate=options.server_cert,
|
|
ssl_private_key=options.server_key,
|
|
ssl_ca_certificate=options.ca_cert,
|
|
min_threads=options.min_threads,
|
|
max_threads=options.max_threads,
|
|
server_name=options.server_name,
|
|
request_queue_size=options.request_queue_size,
|
|
timeout=options.timeout,
|
|
socket_timeout=options.socket_timeout,
|
|
shutdown_timeout=options.shutdown_timeout,
|
|
path=options.folder,
|
|
interfaces=options.interfaces)
|
|
|
|
try:
|
|
server.start()
|
|
except KeyboardInterrupt:
|
|
server.stop()
|
|
if spt is not None:
|
|
try:
|
|
spt.join()
|
|
except:
|
|
logger.exception('error terminating schedulers')
|
|
pass
|
|
logging.shutdown()
|