#
# TortoiseHg dialog to start web server
#
# Copyright (C) 2007 Steve Borho <steve@borho.org>
# Copyright (C) 2007 TK Soh <teekaysoh@gmail.com>
#
try:
import pygtk
pygtk.require("2.0")
except:
pass
import gtk
import gobject
import httplib
import os
import pango
import Queue
import socket
import sys
import threading
import time
import hglib
from dialog import question_dialog, error_dialog
from mercurial import hg, ui, commands, cmdutil, util
from mercurial.repo import RepoError
from mercurial.hgweb import server
from mercurial.i18n import _
from shlib import set_tortoise_icon
gservice = None
class ServeDialog(gtk.Window):
""" Dialog to run web server"""
def __init__(self, cwd='', root=''):
""" Initialize the Dialog """
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
set_tortoise_icon(self, 'proxy.ico')
self.connect('delete-event', self._delete)
# Pipe stderr, stdout to self.write
self._queue = Queue.Queue()
sys.stdout = self
sys.stderr = self
# Override mercurial.commands.serve() with our own version
# that supports being stopped
commands.table.update(thg_serve_cmd)
self._url = None
self._root = root
if cwd:
os.chdir(cwd)
self._get_config()
self.set_default_size(500, 300)
# toolbar
self.tbar = gtk.Toolbar()
self._button_start = self._toolbutton(gtk.STOCK_MEDIA_PLAY,
'Start',
self._on_start_clicked,
None)
self._button_stop = self._toolbutton(gtk.STOCK_MEDIA_STOP,
'Stop',
self._on_stop_clicked,
None)
self._button_browse = self._toolbutton(gtk.STOCK_HOME,
'Browse',
self._on_browse_clicked,
None)
self._button_conf = self._toolbutton(gtk.STOCK_PREFERENCES,
'Configure',
self._on_conf_clicked,
None)
tbuttons = [
self._button_start,
self._button_stop,
gtk.SeparatorToolItem(),
self._button_browse,
gtk.SeparatorToolItem(),
self._button_conf,
]
for btn in tbuttons:
self.tbar.insert(btn, -1)
vbox = gtk.VBox()
self.add(vbox)
vbox.pack_start(self.tbar, False, False, 2)
# revision input
revbox = gtk.HBox()
lbl = gtk.Label("HTTP Port:")
lbl.set_property("width-chars", 16)
lbl.set_alignment(0, 0.5)
self._port_input = gtk.Entry()
self._port_input.set_text(self.defport)
revbox.pack_start(lbl, False, False)
revbox.pack_start(self._port_input, False, False)
vbox.pack_start(revbox, False, False, 2)
scrolledwindow = gtk.ScrolledWindow()
scrolledwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN)
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.textview = gtk.TextView(buffer=None)
self.textview.set_editable(False)
self.textview.modify_font(pango.FontDescription("Monospace"))
scrolledwindow.add(self.textview)
self.textview.set_editable(False)
self.textbuffer = self.textview.get_buffer()
vbox.pack_start(scrolledwindow, True, True)
self._set_button_states()
def _get_config(self):
try:
repo = hg.repository(ui.ui(), path=self._root)
except RepoError:
print 'no repository found'
gtk.main_quit()
self.defport = repo.ui.config('web', 'port') or '8000'
self.webname = repo.ui.config('web', 'name') or \
os.path.basename(self._root)
self.set_title("hg serve - " + self.webname)
def _toolbutton(self, stock, label, handler, menu=None, userdata=None):
if menu:
tbutton = gtk.MenuToolButton(stock)
tbutton.set_menu(menu)
else:
tbutton = gtk.ToolButton(stock)
tbutton.set_label(label)
tbutton.connect('clicked', handler, userdata)
return tbutton
def _delete(self, widget, event):
if self._server_stopped() == True:
gtk.main_quit()
else:
return True
def _server_stopped(self):
'''
check if server is running, or to terminate if running
'''
if gservice and not gservice.stopped:
if question_dialog(self, "Really Exit?",
"Server process is still running\n" +
"Exiting will stop the server.") != gtk.RESPONSE_YES:
return False
else:
self._stop_server()
return True
else:
return True
def _set_button_states(self):
if gservice and not gservice.stopped:
self._button_start.set_sensitive(False)
self._button_stop.set_sensitive(True)
self._button_browse.set_sensitive(True)
self._button_conf.set_sensitive(False)
else:
self._button_start.set_sensitive(True)
self._button_stop.set_sensitive(False)
self._button_browse.set_sensitive(False)
self._button_conf.set_sensitive(True)
def _on_start_clicked(self, *args):
self._start_server()
self._set_button_states()
def _on_stop_clicked(self, *args):
self._stop_server()
def _on_browse_clicked(self, *args):
''' launch default browser to view repo '''
if self._url:
def start_browser():
if os.name == 'nt':
try:
import win32api, win32con
win32api.ShellExecute(0, "open", self._url, None, "",
win32con.SW_SHOW)
except:
# Firefox likes to create exceptions at launch,
# the user doesn't need to be bothered by them
pass
else:
import gconf
client = gconf.client_get_default()
browser = client.get_string(
'/desktop/gnome/url-handlers/http/command') + '&'
os.system(browser % self._url)
threading.Thread(target=start_browser).start()
def _on_conf_clicked(self, *args):
from thgconfig import ConfigDialog
dlg = ConfigDialog(self._root, True)
dlg.show_all()
dlg.focus_field('web.name')
dlg.run()
dlg.hide()
self._get_config()
def _start_server(self):
# gather input data
try:
port = int(self._port_input.get_text())
except:
try: port = int(self.defport)
except: port = 8000
error_dialog(self, "Invalid port 2048..65535", "Defaulting to " +
self.defport)
global gservice
gservice = None
args = [self._root, self._queue, 'serve', '--name', self.webname,
'--port', str(port)]
thread = threading.Thread(target=hglib.hgcmd_toq, args=args)
thread.start()
while not gservice or not hasattr(gservice, 'httpd'):
time.sleep(0.1)
self._url = 'http://%s:%d/' % (gservice.httpd.fqaddr, port)
gobject.timeout_add(10, self.process_queue)
def _stop_server(self):
if gservice and not gservice.stopped:
gservice.stop()
def flush(self, *args):
pass
def write(self, msg):
self._queue.put(msg)
def _write(self, msg, append=True):
msg = hglib.toutf(msg)
if append:
enditer = self.textbuffer.get_end_iter()
self.textbuffer.insert(enditer, msg)
else:
self.textbuffer.set_text(msg)
def process_queue(self):
"""
Handle all the messages currently in the queue (if any).
"""
while self._queue.qsize():
try:
msg = self._queue.get(0)
self._write(msg)
except Queue.Empty:
pass
if gservice and gservice.stopped:
self._set_button_states()
return False # Stop polling this function
else:
return True
def thg_serve(ui, repo, **opts):
class service:
def init(self):
self.stopped = True
util.set_signal_handler()
try:
parentui = ui.parentui or ui
optlist = ("name templates style address port prefix ipv6"
" accesslog errorlog webdir_conf certificate")
for o in optlist.split():
if opts[o]:
parentui.setconfig("web", o, str(opts[o]))
if (repo is not None) and (repo.ui != parentui):
repo.ui.setconfig("web", o, str(opts[o]))
self.httpd = server.create_server(ui, repo)
except socket.error, inst:
raise util.Abort(_('cannot start server: ') + inst.args[1])
if self.httpd.prefix:
prefix = self.httpd.prefix.strip('/') + '/'
else:
prefix = ''
port = ':%d' % self.httpd.port
if port == ':80':
port = ''
ui.status(_('listening at http://%s%s/%s (%s:%d)\n') %
(self.httpd.fqaddr, port, prefix, self.httpd.addr, self.httpd.port))
def stop(self):
self.stopped = True
# issue request to trigger handle_request() and quit
addr = '%s:%d' % (self.httpd.fqaddr, self.httpd.port)
conn = httplib.HTTPConnection(addr)
conn.request("GET", "/")
res = conn.getresponse()
res.read()
conn.close()
def run(self):
self.stopped = False
while not self.stopped:
self.httpd.handle_request()
self.httpd.server_close() # release port
global gservice
gservice = service()
cmdutil.service(opts, initfn=gservice.init, runfn=gservice.run)
thg_serve_cmd = {"^serve":
(thg_serve,
[('A', 'accesslog', '', _('name of access log file to write to')),
('d', 'daemon', None, _('run server in background')),
('', 'daemon-pipefds', '', _('used internally by daemon mode')),
('E', 'errorlog', '', _('name of error log file to write to')),
('p', 'port', 0, _('port to use (default: 8000)')),
('a', 'address', '', _('address to use')),
('', 'prefix', '', _('prefix path to serve from (default: server root)')),
('n', 'name', '',
_('name to show in web pages (default: working dir)')),
('', 'webdir-conf', '', _('name of the webdir config file'
' (serve more than one repo)')),
('', 'pid-file', '', _('name of file to write process ID to')),
('', 'stdio', None, _('for remote clients')),
('t', 'templates', '', _('web templates to use')),
('', 'style', '', _('template style to use')),
('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
('', 'certificate', '', _('SSL certificate file'))],
_('hg serve [OPTION]...'))}
def run(cwd='', root='', **opts):
dialog = ServeDialog(cwd, root)
dialog.show_all()
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
if __name__ == "__main__":
import sys
opts = {}
opts['cwd'] = os.getcwd()
opts['root'] = len(sys.argv) > 1 and sys.argv[1] or ''
run(**opts)