better thread handling in GUI and more resiliant start/stop, thanks P.Pastori
This commit is contained in:
+9
-1
@@ -554,10 +554,14 @@ def wsgibase(environ, responder):
|
|||||||
http_response, request, environ, ticket)
|
http_response, request, environ, ticket)
|
||||||
if not http_response:
|
if not http_response:
|
||||||
return wsgibase(new_environ, responder)
|
return wsgibase(new_environ, responder)
|
||||||
|
|
||||||
if global_settings.web2py_crontype == 'soft':
|
if global_settings.web2py_crontype == 'soft':
|
||||||
|
# FIXME: calling softcron crondance in a new thread at every
|
||||||
|
# request is not a good idea in a long running process
|
||||||
cmd_opts = global_settings.cmd_options
|
cmd_opts = global_settings.cmd_options
|
||||||
newcron.softcron(global_settings.applications_parent,
|
newcron.softcron(global_settings.applications_parent,
|
||||||
apps=cmd_opts and cmd_opts.crontabs).start()
|
apps=cmd_opts and cmd_opts.crontabs).start()
|
||||||
|
|
||||||
return http_response.to(responder, env=env)
|
return http_response.to(responder, env=env)
|
||||||
|
|
||||||
|
|
||||||
@@ -783,7 +787,11 @@ class HttpServer(object):
|
|||||||
"""
|
"""
|
||||||
stop cron and the web server
|
stop cron and the web server
|
||||||
"""
|
"""
|
||||||
newcron.stopcron()
|
if global_settings.web2py_crontype == 'soft':
|
||||||
|
try:
|
||||||
|
newcron.stopcron()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
self.server.stop(stoplogging)
|
self.server.stop(stoplogging)
|
||||||
try:
|
try:
|
||||||
os.unlink(self.pid_filename)
|
os.unlink(self.pid_filename)
|
||||||
|
|||||||
+14
-11
@@ -12,25 +12,30 @@ Cron-style interface
|
|||||||
|
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
from logging import getLogger, DEBUG
|
from logging import getLogger
|
||||||
import time
|
import time
|
||||||
import sched
|
import sched
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
from functools import reduce
|
||||||
|
import datetime
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
import datetime
|
|
||||||
from functools import reduce
|
|
||||||
from gluon.settings import global_settings
|
|
||||||
from gluon import fileutils
|
from gluon import fileutils
|
||||||
from gluon._compat import to_bytes, pickle
|
from gluon._compat import to_bytes, pickle
|
||||||
from pydal.contrib import portalocker
|
from pydal.contrib import portalocker
|
||||||
|
from gluon.settings import global_settings
|
||||||
|
|
||||||
logger_name = 'web2py.cron'
|
logger_name = 'web2py.cron'
|
||||||
|
|
||||||
|
|
||||||
_cron_stopping = False
|
_cron_stopping = False
|
||||||
|
|
||||||
|
def reset():
|
||||||
|
global _cron_stopping
|
||||||
|
_cron_stopping = False
|
||||||
|
|
||||||
|
|
||||||
_cron_subprocs_lock = threading.RLock()
|
_cron_subprocs_lock = threading.RLock()
|
||||||
_cron_subprocs = []
|
_cron_subprocs = []
|
||||||
|
|
||||||
@@ -75,9 +80,8 @@ class extcron(threading.Thread):
|
|||||||
self.apps = apps
|
self.apps = apps
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not _cron_stopping:
|
getLogger(logger_name).debug('external cron invocation')
|
||||||
getLogger(logger_name).debug('external cron invocation')
|
crondance(self.path, 'external', startup=False, apps=self.apps)
|
||||||
crondance(self.path, 'external', startup=False, apps=self.apps)
|
|
||||||
|
|
||||||
|
|
||||||
class hardcron(threading.Thread):
|
class hardcron(threading.Thread):
|
||||||
@@ -119,7 +123,6 @@ class softcron(threading.Thread):
|
|||||||
crondance(self.path, 'soft', startup=False, apps=self.apps)
|
crondance(self.path, 'soft', startup=False, apps=self.apps)
|
||||||
|
|
||||||
|
|
||||||
# TODO: context manager
|
|
||||||
class Token(object):
|
class Token(object):
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
@@ -140,7 +143,7 @@ class Token(object):
|
|||||||
stop == 0 if job started but did not yet complete
|
stop == 0 if job started but did not yet complete
|
||||||
if a cron job started within less than 60 seconds, acquire returns None
|
if a cron job started within less than 60 seconds, acquire returns None
|
||||||
if a cron job started before 60 seconds and did not stop,
|
if a cron job started before 60 seconds and did not stop,
|
||||||
a warning is issue "Stale cron.master detected"
|
a warning is issued ("Stale cron.master detected")
|
||||||
"""
|
"""
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
locktime = 59.5
|
locktime = 59.5
|
||||||
@@ -150,8 +153,8 @@ class Token(object):
|
|||||||
self.logger.warning('cron disabled because no file locking')
|
self.logger.warning('cron disabled because no file locking')
|
||||||
return None
|
return None
|
||||||
self.master = fileutils.open_file(self.path, 'rb+')
|
self.master = fileutils.open_file(self.path, 'rb+')
|
||||||
|
ret = None
|
||||||
try:
|
try:
|
||||||
ret = None
|
|
||||||
portalocker.lock(self.master, portalocker.LOCK_EX)
|
portalocker.lock(self.master, portalocker.LOCK_EX)
|
||||||
try:
|
try:
|
||||||
(start, stop) = pickle.load(self.master)
|
(start, stop) = pickle.load(self.master)
|
||||||
@@ -278,7 +281,7 @@ class cronlauncher(threading.Thread):
|
|||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
logger.warning('%r call returned code %s:\n%s\n%s',
|
logger.warning('%r call returned code %s:\n%s\n%s',
|
||||||
' '.join(self.cmd), proc.returncode, stdoutdata, stderrdata)
|
' '.join(self.cmd), proc.returncode, stdoutdata, stderrdata)
|
||||||
elif logger.isEnabledFor(DEBUG):
|
else:
|
||||||
logger.debug('%r call returned success:\n%s',
|
logger.debug('%r call returned success:\n%s',
|
||||||
' '.join(self.cmd), stdoutdata)
|
' '.join(self.cmd), stdoutdata)
|
||||||
|
|
||||||
|
|||||||
+153
-132
@@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: set ts=4 sw=4 et ai:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
| This file is part of the web2py Web Framework
|
| This file is part of the web2py Web Framework
|
||||||
@@ -11,23 +12,26 @@ GUI widget and services start function
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
|
||||||
from gluon._compat import thread, xrange, PY2
|
|
||||||
import time
|
import time
|
||||||
import threading
|
import sys
|
||||||
import os
|
import os
|
||||||
|
from collections import OrderedDict
|
||||||
import socket
|
import socket
|
||||||
import signal
|
import threading
|
||||||
import math
|
import math
|
||||||
import logging
|
import logging
|
||||||
|
import signal
|
||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
from gluon import main, newcron
|
|
||||||
from gluon.fileutils import read_file, create_welcome_w2p
|
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.shell import die, run, test
|
||||||
from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
|
from gluon._compat import PY2, xrange
|
||||||
|
from gluon.utils import (getipaddrinfo, is_loopback_ip_address,
|
||||||
|
is_valid_ip_address)
|
||||||
|
from gluon.console import is_appdir, console
|
||||||
|
from gluon import newcron
|
||||||
|
from gluon import main
|
||||||
|
from gluon.settings import global_settings
|
||||||
|
|
||||||
|
|
||||||
ProgramName = 'web2py Web Framework'
|
ProgramName = 'web2py Web Framework'
|
||||||
@@ -119,24 +123,27 @@ class web2pyDialog(object):
|
|||||||
import tkinter
|
import tkinter
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
|
||||||
bg_color = 'white'
|
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
|
|
||||||
|
bg_color = 'white'
|
||||||
self.root = tkinter.Toplevel(root, bg=bg_color)
|
self.root = tkinter.Toplevel(root, bg=bg_color)
|
||||||
self.root.resizable(0, 0)
|
self.root.resizable(0, 0)
|
||||||
self.root.title(ProgramName)
|
self.root.title(ProgramName)
|
||||||
|
|
||||||
self.options = options
|
self.options = options
|
||||||
self.scheduler_processes = {}
|
self.scheduler_processes_lock = threading.RLock()
|
||||||
self.menu = tkinter.Menu(self.root)
|
self.scheduler_processes = OrderedDict()
|
||||||
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')
|
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
||||||
if os.path.exists(iconphoto):
|
if os.path.exists(iconphoto):
|
||||||
img = tkinter.PhotoImage(file=iconphoto)
|
img = tkinter.PhotoImage(file=iconphoto)
|
||||||
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
|
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
|
||||||
|
|
||||||
# Building the Menu
|
# Building the Menu
|
||||||
|
self.menu = tkinter.Menu(self.root)
|
||||||
|
servermenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
|
|
||||||
|
httplog = os.path.join(options.folder, options.log_filename)
|
||||||
item = lambda: start_browser(httplog)
|
item = lambda: start_browser(httplog)
|
||||||
servermenu.add_command(label='View httpserver.log',
|
servermenu.add_command(label='View httpserver.log',
|
||||||
command=item)
|
command=item)
|
||||||
@@ -149,10 +156,9 @@ class web2pyDialog(object):
|
|||||||
self.pagesmenu = tkinter.Menu(self.menu, tearoff=0)
|
self.pagesmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
|
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
|
||||||
|
|
||||||
#scheduler menu
|
|
||||||
self.schedmenu = tkinter.Menu(self.menu, tearoff=0)
|
self.schedmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
|
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
|
||||||
#start and register schedulers from options
|
# register and start schedulers
|
||||||
self.update_schedulers(start=True)
|
self.update_schedulers(start=True)
|
||||||
|
|
||||||
helpmenu = tkinter.Menu(self.menu, tearoff=0)
|
helpmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
@@ -257,7 +263,7 @@ class web2pyDialog(object):
|
|||||||
sticky=sticky)
|
sticky=sticky)
|
||||||
|
|
||||||
self.port_number = tkinter.Entry(self.root)
|
self.port_number = tkinter.Entry(self.root)
|
||||||
self.port_number.insert(tkinter.END, self.options.port)
|
self.port_number.insert(tkinter.END, options.port)
|
||||||
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
|
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
|
||||||
|
|
||||||
# Password
|
# Password
|
||||||
@@ -314,6 +320,26 @@ class web2pyDialog(object):
|
|||||||
self.tb = None
|
self.tb = None
|
||||||
|
|
||||||
def update_schedulers(self, start=False):
|
def update_schedulers(self, start=False):
|
||||||
|
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))
|
||||||
|
]
|
||||||
|
with self.scheduler_processes_lock:
|
||||||
|
# reset the menu
|
||||||
|
# since applications can disappear (be disinstalled) must
|
||||||
|
# clear the menu (should use tkinter.END or tkinter.LAST)
|
||||||
|
self.schedmenu.delete(0, 'end')
|
||||||
|
for arq in available_apps:
|
||||||
|
if arq not in self.scheduler_processes:
|
||||||
|
item = lambda a = arq: self.try_start_scheduler(a)
|
||||||
|
self.schedmenu.add_command(label="start %s" % arq,
|
||||||
|
command=item)
|
||||||
|
if arq in self.scheduler_processes:
|
||||||
|
item = lambda a = arq: self.try_stop_scheduler(a)
|
||||||
|
self.schedmenu.add_command(label="stop %s" % arq,
|
||||||
|
command=item)
|
||||||
|
|
||||||
if start and self.options.with_scheduler and self.options.schedulers:
|
if start and self.options.with_scheduler and self.options.schedulers:
|
||||||
# the widget takes care of starting the schedulers
|
# the widget takes care of starting the schedulers
|
||||||
apps = [ag.split(':', 1)[0] for ag in self.options.schedulers]
|
apps = [ag.split(':', 1)[0] for ag in self.options.schedulers]
|
||||||
@@ -322,100 +348,90 @@ class web2pyDialog(object):
|
|||||||
for app in apps:
|
for app in apps:
|
||||||
self.try_start_scheduler(app)
|
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):
|
def start_schedulers(self, app):
|
||||||
try:
|
from multiprocessing import Process
|
||||||
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()"
|
code = "from gluon.globals import current;current._scheduler.loop()"
|
||||||
print('starting scheduler from widget for "%s"...' % app)
|
print('starting scheduler from widget for "%s"...' % app)
|
||||||
args = (app, True, True, None, False, code, False, True)
|
args = (app, True, True, None, False, code, False, True)
|
||||||
logging.getLogger().setLevel(self.options.log_level)
|
|
||||||
p = Process(target=run, args=args)
|
p = Process(target=run, args=args)
|
||||||
self.scheduler_processes[app] = p
|
with self.scheduler_processes_lock:
|
||||||
self.update_schedulers()
|
self.scheduler_processes[app] = p
|
||||||
print("Currently running %s scheduler processes" % (
|
self.update_schedulers()
|
||||||
len(self.scheduler_processes)))
|
print("Currently running %s scheduler processes" % (
|
||||||
|
len(self.scheduler_processes)))
|
||||||
p.start()
|
p.start()
|
||||||
print("Processes started")
|
print("Processes started")
|
||||||
|
|
||||||
def try_stop_scheduler(self, app):
|
def try_stop_scheduler(self, app, skip_update=False):
|
||||||
if app in self.scheduler_processes:
|
p = None
|
||||||
p = self.scheduler_processes[app]
|
with self.scheduler_processes_lock:
|
||||||
del self.scheduler_processes[app]
|
if app in self.scheduler_processes:
|
||||||
|
p = self.scheduler_processes[app]
|
||||||
|
del self.scheduler_processes[app]
|
||||||
|
if p is not None:
|
||||||
p.terminate()
|
p.terminate()
|
||||||
p.join()
|
p.join()
|
||||||
self.update_schedulers()
|
if not skip_update:
|
||||||
|
self.update_schedulers()
|
||||||
|
|
||||||
def try_start_scheduler(self, app):
|
def try_start_scheduler(self, app):
|
||||||
if app not in self.scheduler_processes:
|
t = None
|
||||||
t = threading.Thread(target=self.start_schedulers, args=(app,))
|
with self.scheduler_processes_lock:
|
||||||
|
if not is_appdir(self.options.folder, app):
|
||||||
|
self.schedmenu.delete("start %s" % app)
|
||||||
|
return
|
||||||
|
if app not in self.scheduler_processes:
|
||||||
|
t = threading.Thread(target=self.start_schedulers, args=(app,))
|
||||||
|
if t is not None:
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def checkTaskBar(self):
|
def checkTaskBar(self):
|
||||||
""" Checks taskbar status """
|
""" Checks taskbar status """
|
||||||
|
tb = self.tb
|
||||||
if self.tb.status:
|
if tb.status:
|
||||||
if self.tb.status[0] == self.tb.EnumStatus.QUIT:
|
st0 = tb.status[0]
|
||||||
|
EnumStatus = tb.EnumStatus
|
||||||
|
if st0 == EnumStatus.QUIT:
|
||||||
self.quit()
|
self.quit()
|
||||||
elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE:
|
elif st0 == EnumStatus.TOGGLE:
|
||||||
if self.root.state() == 'withdrawn':
|
if self.root.state() == 'withdrawn':
|
||||||
self.root.deiconify()
|
self.root.deiconify()
|
||||||
else:
|
else:
|
||||||
self.root.withdraw()
|
self.root.withdraw()
|
||||||
elif self.tb.status[0] == self.tb.EnumStatus.STOP:
|
elif st0 == EnumStatus.STOP:
|
||||||
self.stop()
|
self.stop()
|
||||||
elif self.tb.status[0] == self.tb.EnumStatus.START:
|
elif st0 == EnumStatus.START:
|
||||||
self.start()
|
self.start()
|
||||||
elif self.tb.status[0] == self.tb.EnumStatus.RESTART:
|
elif st0 == EnumStatus.RESTART:
|
||||||
self.stop()
|
self.stop()
|
||||||
self.start()
|
self.start()
|
||||||
del self.tb.status[0]
|
del tb.status[0]
|
||||||
|
|
||||||
self.root.after(1000, self.checkTaskBar)
|
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):
|
def connect_pages(self):
|
||||||
""" Connects pages """
|
""" Connects pages """
|
||||||
# reset the menu
|
# reset the menu,
|
||||||
|
# since applications can disappear (be disinstalled) must
|
||||||
|
# clear the menu (should use tkinter.END or tkinter.LAST)
|
||||||
|
self.pagesmenu.delete(0, 'end')
|
||||||
applications_folder = os.path.join(self.options.folder, 'applications')
|
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||||
available_apps = [
|
available_apps = [
|
||||||
arq for arq in os.listdir(applications_folder)
|
arq for arq in os.listdir(applications_folder)
|
||||||
if os.path.exists(os.path.join(applications_folder, arq, '__init__.py'))
|
if os.path.exists(os.path.join(applications_folder, arq, '__init__.py'))
|
||||||
]
|
]
|
||||||
self.pagesmenu.delete(0, len(available_apps))
|
|
||||||
for arq in available_apps:
|
for arq in available_apps:
|
||||||
url = self.url + arq
|
url = self.url + arq
|
||||||
|
item = lambda a = arq: self.try_start_browser(a)
|
||||||
self.pagesmenu.add_command(
|
self.pagesmenu.add_command(
|
||||||
label=url, command=lambda u=url: start_browser(u))
|
label=url, command=item)
|
||||||
|
|
||||||
|
def try_start_browser(self, app):
|
||||||
|
url = self.url + app
|
||||||
|
if not is_appdir(self.options.folder, app):
|
||||||
|
self.pagesmenu.delete(url)
|
||||||
|
return
|
||||||
|
start_browser(url)
|
||||||
|
|
||||||
def quit(self, justHide=False):
|
def quit(self, justHide=False):
|
||||||
""" Finishes the program execution """
|
""" Finishes the program execution """
|
||||||
@@ -423,16 +439,20 @@ class web2pyDialog(object):
|
|||||||
self.root.withdraw()
|
self.root.withdraw()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
scheds = self.scheduler_processes.keys()
|
with self.scheduler_processes_lock:
|
||||||
|
scheds = list(self.scheduler_processes.keys())
|
||||||
for t in scheds:
|
for t in scheds:
|
||||||
self.try_stop_scheduler(t)
|
self.try_stop_scheduler(t, skip_update=True)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
newcron.stopcron()
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
if self.options.with_cron and not self.options.soft_cron:
|
||||||
|
# shutting down hardcron
|
||||||
|
try:
|
||||||
|
newcron.stopcron()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
|
# HttpServer.stop takes care of stopping softcron
|
||||||
self.server.stop()
|
self.server.stop()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -450,34 +470,40 @@ class web2pyDialog(object):
|
|||||||
import tkMessageBox as messagebox
|
import tkMessageBox as messagebox
|
||||||
else:
|
else:
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
|
|
||||||
messagebox.showerror('web2py start server', message)
|
messagebox.showerror('web2py start server', message)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Starts web2py server """
|
""" Starts web2py server """
|
||||||
|
|
||||||
password = self.password.get()
|
password = self.password.get()
|
||||||
|
|
||||||
if not password:
|
if not password:
|
||||||
self.error('no password, no web admin interface')
|
self.error('no password, no web admin interface')
|
||||||
|
|
||||||
ip = self.selected_ip.get()
|
ip = self.selected_ip.get()
|
||||||
|
|
||||||
if not is_valid_ip_address(ip):
|
if not is_valid_ip_address(ip):
|
||||||
return self.error('invalid host ip address')
|
return self.error('invalid host ip address')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
port = int(self.port_number.get())
|
port = int(self.port_number.get())
|
||||||
except:
|
except ValueError:
|
||||||
return self.error('invalid port number')
|
return self.error('invalid port number')
|
||||||
|
|
||||||
if self.options.server_key and self.options.server_cert:
|
if self.options.server_key and self.options.server_cert:
|
||||||
proto = 'https'
|
proto = 'https'
|
||||||
else:
|
else:
|
||||||
proto = 'http'
|
proto = 'http'
|
||||||
|
|
||||||
self.url = get_url(ip, proto=proto, port=port)
|
self.url = get_url(ip, proto=proto, port=port)
|
||||||
|
|
||||||
self.connect_pages()
|
self.connect_pages()
|
||||||
|
self.update_schedulers()
|
||||||
|
|
||||||
|
# softcron is stopped with HttpServer, thus if starting again
|
||||||
|
# need to reset newcron._cron_stopping to re-enable cron
|
||||||
|
if self.options.soft_cron:
|
||||||
|
newcron.reset()
|
||||||
|
|
||||||
|
# FIXME: if the HttpServer is stopped, then started again,
|
||||||
|
# does not start because of following error:
|
||||||
|
# WARNING:Rocket.Errors.Port8000:Listener started when not ready.
|
||||||
|
|
||||||
self.button_start.configure(state='disabled')
|
self.button_start.configure(state='disabled')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -502,7 +528,7 @@ class web2pyDialog(object):
|
|||||||
path=options.folder,
|
path=options.folder,
|
||||||
interfaces=options.interfaces)
|
interfaces=options.interfaces)
|
||||||
|
|
||||||
thread.start_new_thread(self.server.start, ())
|
threading.Thread(target=self.server.start).start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.button_start.configure(state='normal')
|
self.button_start.configure(state='normal')
|
||||||
return self.error(str(e))
|
return self.error(str(e))
|
||||||
@@ -514,11 +540,14 @@ class web2pyDialog(object):
|
|||||||
self.button_stop.configure(state='normal')
|
self.button_stop.configure(state='normal')
|
||||||
|
|
||||||
if not options.taskbar:
|
if not options.taskbar:
|
||||||
thread.start_new_thread(
|
cpt = threading.Thread(target=start_browser,
|
||||||
start_browser, (get_url(ip, proto=proto, port=port), True))
|
args=(get_url(ip, proto=proto, port=port), True))
|
||||||
|
cpt.setDaemon(True)
|
||||||
|
cpt.start()
|
||||||
|
|
||||||
self.password.configure(state='readonly')
|
self.password.configure(state='readonly')
|
||||||
[ip.configure(state='disabled') for ip in self.ips.values()]
|
for ip in self.ips.values():
|
||||||
|
ip.configure(state='disabled')
|
||||||
self.port_number.configure(state='readonly')
|
self.port_number.configure(state='readonly')
|
||||||
|
|
||||||
if self.tb:
|
if self.tb:
|
||||||
@@ -528,16 +557,15 @@ class web2pyDialog(object):
|
|||||||
for listener in self.server.server.listeners:
|
for listener in self.server.server.listeners:
|
||||||
if listener.ready:
|
if listener.ready:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stops web2py server """
|
""" Stops web2py server """
|
||||||
|
|
||||||
self.button_start.configure(state='normal')
|
self.button_start.configure(state='normal')
|
||||||
self.button_stop.configure(state='disabled')
|
self.button_stop.configure(state='disabled')
|
||||||
self.password.configure(state='normal')
|
self.password.configure(state='normal')
|
||||||
[ip.configure(state='normal') for ip in self.ips.values()]
|
for ip in self.ips.values():
|
||||||
|
ip.configure(state='normal')
|
||||||
self.port_number.configure(state='normal')
|
self.port_number.configure(state='normal')
|
||||||
self.server.stop()
|
self.server.stop()
|
||||||
|
|
||||||
@@ -546,48 +574,41 @@ class web2pyDialog(object):
|
|||||||
|
|
||||||
def update_canvas(self):
|
def update_canvas(self):
|
||||||
""" Updates canvas """
|
""" Updates canvas """
|
||||||
|
|
||||||
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
||||||
|
canvas = self.canvas
|
||||||
try:
|
try:
|
||||||
t1 = os.path.getsize(httplog)
|
t1 = os.path.getsize(httplog)
|
||||||
except:
|
except OSError:
|
||||||
self.canvas.after(1000, self.update_canvas)
|
canvas.after(1000, self.update_canvas)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
points = 400
|
||||||
try:
|
try:
|
||||||
fp = open(httplog, 'r')
|
pvalues = self.p0[1:]
|
||||||
fp.seek(self.t0)
|
with open(httplog, 'r') as fp:
|
||||||
data = fp.read(t1 - self.t0)
|
fp.seek(self.t0)
|
||||||
fp.close()
|
data = fp.read(t1 - self.t0)
|
||||||
value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))]
|
self.p0 = pvalues + [10 + 90.0 / math.sqrt(1 + data.count('\n'))]
|
||||||
self.p0 = value
|
|
||||||
|
|
||||||
for i in xrange(len(self.p0) - 1):
|
for i in xrange(points - 1):
|
||||||
c = self.canvas.coords(self.q0[i])
|
c = canvas.coords(self.q0[i])
|
||||||
self.canvas.coords(self.q0[i],
|
canvas.coords(self.q0[i],
|
||||||
(c[0],
|
(c[0], self.p0[i],
|
||||||
self.p0[i],
|
c[2], self.p0[i + 1]))
|
||||||
c[2],
|
|
||||||
self.p0[i + 1]))
|
|
||||||
self.t0 = t1
|
self.t0 = t1
|
||||||
except BaseException:
|
except AttributeError:
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
self.t0 = t1
|
self.t0 = t1
|
||||||
self.p0 = [100] * 400
|
self.p0 = [100] * points
|
||||||
self.q0 = [self.canvas.create_line(i, 100, i + 1, 100,
|
self.q0 = [canvas.create_line(i, 100, i + 1, 100,
|
||||||
fill='green') for i in xrange(len(self.p0) - 1)]
|
fill='green') for i in xrange(points - 1)]
|
||||||
|
|
||||||
self.canvas.after(1000, self.update_canvas)
|
canvas.after(1000, self.update_canvas)
|
||||||
|
|
||||||
|
|
||||||
def check_existent_app(options, appname):
|
def get_code_for_scheduler(applications_parent, app_groups):
|
||||||
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]
|
app = app_groups[0]
|
||||||
if not check_existent_app(options, app):
|
if not is_appdir(applications_parent, app):
|
||||||
print("Application '%s' doesn't exist, skipping" % app)
|
print("Application '%s' doesn't exist, skipping" % app)
|
||||||
return None, None
|
return None, None
|
||||||
code = 'from gluon.globals import current;'
|
code = 'from gluon.globals import current;'
|
||||||
@@ -599,16 +620,10 @@ def get_code_for_scheduler(app_groups, options):
|
|||||||
|
|
||||||
|
|
||||||
def start_schedulers(options):
|
def start_schedulers(options):
|
||||||
try:
|
from multiprocessing import Process
|
||||||
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]
|
apps = [ag.split(':') for ag in options.schedulers]
|
||||||
if not options.with_scheduler and len(apps) == 1:
|
if not options.with_scheduler and len(apps) == 1:
|
||||||
app, code = get_code_for_scheduler(apps[0], options)
|
app, code = get_code_for_scheduler(options.folder, apps[0])
|
||||||
if not app:
|
if not app:
|
||||||
return
|
return
|
||||||
print('starting single-scheduler for "%s"...' % app)
|
print('starting single-scheduler for "%s"...' % app)
|
||||||
@@ -624,7 +639,7 @@ def start_schedulers(options):
|
|||||||
|
|
||||||
processes = []
|
processes = []
|
||||||
for app_groups in apps:
|
for app_groups in apps:
|
||||||
app, code = get_code_for_scheduler(app_groups, options)
|
app, code = get_code_for_scheduler(options.folder, app_groups)
|
||||||
if not app:
|
if not app:
|
||||||
continue
|
continue
|
||||||
print('starting scheduler for "%s"...' % app)
|
print('starting scheduler for "%s"...' % app)
|
||||||
@@ -652,6 +667,12 @@ def start():
|
|||||||
# get command line arguments
|
# get command line arguments
|
||||||
options = console(version=ProgramVersion)
|
options = console(version=ProgramVersion)
|
||||||
|
|
||||||
|
if options.with_scheduler or len(options.schedulers) > 1:
|
||||||
|
try:
|
||||||
|
from multiprocessing import Process
|
||||||
|
except:
|
||||||
|
die('Sorry, -K/--scheduler only supported for Python 2.6+')
|
||||||
|
|
||||||
if options.gae:
|
if options.gae:
|
||||||
# write app.yaml, gaehandler.py, and exit
|
# write app.yaml, gaehandler.py, and exit
|
||||||
if not os.path.exists('app.yaml'):
|
if not os.path.exists('app.yaml'):
|
||||||
@@ -673,6 +694,7 @@ def start():
|
|||||||
|
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
logger.setLevel(options.log_level)
|
logger.setLevel(options.log_level)
|
||||||
|
logging.getLogger().setLevel(options.log_level) # root logger
|
||||||
|
|
||||||
# on new installation build the scaffolding app
|
# on new installation build the scaffolding app
|
||||||
create_welcome_w2p()
|
create_welcome_w2p()
|
||||||
@@ -807,7 +829,7 @@ end tell
|
|||||||
options.password = getpass.getpass('choose a password:')
|
options.password = getpass.getpass('choose a password:')
|
||||||
|
|
||||||
if not options.password and not options.no_banner:
|
if not options.password and not options.no_banner:
|
||||||
print('no password, disable admin interface')
|
print('no password, no web admin interface')
|
||||||
|
|
||||||
# Use first interface IP and port if interfaces specified, since the
|
# Use first interface IP and port if interfaces specified, since the
|
||||||
# interfaces option overrides the IP (and related) options.
|
# interfaces option overrides the IP (and related) options.
|
||||||
@@ -883,5 +905,4 @@ end tell
|
|||||||
spt.join()
|
spt.join()
|
||||||
except:
|
except:
|
||||||
logger.exception('error terminating schedulers')
|
logger.exception('error terminating schedulers')
|
||||||
pass
|
|
||||||
logging.shutdown()
|
logging.shutdown()
|
||||||
|
|||||||
Reference in New Issue
Block a user