Source

thg / tortoisehg / hgtk / hgthread.py

Full commit
# hgthread.py - Gtk UI class TortoiseHg
#
# Copyright 2009 Steve Borho <steve@borho.org>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

import gtk
import Queue
import time
import urllib2

from mercurial import ui, util, error

from tortoisehg.util.i18n import _
from tortoisehg.util import hglib, thread2

from tortoisehg.hgtk import dialog, gdialog

class GtkUi(ui.ui):
    '''
    PyGtk enabled mercurial.ui subclass.  All this code will be running
    in a background thread, so it cannot directly call into Gtk.
    Instead, it places output and dialog requests onto queues for the
    main thread to pickup.
    '''
    def __init__(self, src=None, outputq=None, errorq=None, dialogq=None,
            responseq=None, progressq=None):
        super(GtkUi, self).__init__(src)
        if src:
            self.outputq = src.outputq
            self.errorq = src.errorq
            self.dialogq = src.dialogq
            self.responseq = src.responseq
            self.progressq = src.progressq
        else:
            self.outputq = outputq
            self.errorq = errorq
            self.dialogq = dialogq
            self.responseq = responseq
            self.progressq = progressq
        self.setconfig('ui', 'interactive', 'on')
        self.setconfig('progress', 'disable', 'True')

    def write(self, *args, **opts):
        if self._buffers:
            self._buffers[-1].extend([str(a) for a in args])
        else:
            for a in args:
                self.outputq.put((str(a), opts.get('label', '')))

    def write_err(self, *args, **opts):
        for a in args:
            self.errorq.put(str(a))

    def label(self, msg, label):
        return msg

    def flush(self):
        pass

    def prompt(self, msg, choices=None, default="y"):
        if not self.interactive(): return default
        try:
            # send request to main thread, await response
            self.dialogq.put( (msg, True, choices, None) )
            r = self.responseq.get(True)
            if r is None:
                raise EOFError
            if not r:
                return default
            if choices:
                # return char for Mercurial 1.3
                choice = choices[r]
                return choice[choice.index("&")+1].lower()
            return r
        except EOFError:
            raise util.Abort(_('response expected'))

    def promptchoice(self, msg, choices, default=0):
        if not self.interactive(): return default
        try:
            # send request to main thread, await response
            self.dialogq.put( (msg, True, choices, default) )
            r = self.responseq.get(True)
            if r is None:
                raise EOFError
            return r
        except EOFError:
            raise util.Abort(_('response expected'))

    def getpass(self, prompt=None, default=None):
        # send request to main thread, await response
        self.dialogq.put( (prompt or _('password: '), False, None, default) )
        r = self.responseq.get(True)
        if r is None:
            raise util.Abort(_('response expected'))
        return r

    def progress(self, topic, pos, item='', unit='', total=None):
        self.progressq.put( (topic, item, pos, total, unit) )


class HgThread(thread2.Thread):
    '''
    Run an hg command in a background thread, implies output is being
    sent to a rendered text buffer interactively and requests for
    feedback from Mercurial can be handled by the user via dialog
    windows.
    '''
    def __init__(self, args=[], postfunc=None, parent=None):
        self.outputq = Queue.Queue()
        self.errorq = Queue.Queue()
        self.dialogq = Queue.Queue()
        self.responseq = Queue.Queue()
        self.progressq = Queue.Queue()
        self.ui = GtkUi(None, self.outputq, self.errorq, self.dialogq,
                        self.responseq, self.progressq)
        self.args = args
        self.ret = None
        self.postfunc = postfunc
        self.parent = parent
        thread2.Thread.__init__(self)

    def getqueue(self):
        return self.outputq

    def geterrqueue(self):
        return self.errorq

    def getprogqueue(self):
        return self.progressq

    def return_code(self):
        '''
        None - command is incomplete, possibly exited with exception
        0    - command returned successfully
               else an error was returned
        '''
        return self.ret

    def process_dialogs(self):
        '''Polled every 10ms to serve dialogs for the background thread'''
        try:
            (prompt, visible, choices, default) = self.dialogq.get_nowait()
            if choices:
                dlg = gdialog.CustomPrompt('Hg Prompt', prompt,
                        self.parent, choices, default)
                dlg.connect('response', self.prompt_response)
                dlg.show_all()
            else:
                dlg = dialog.entry_dialog(self.parent, prompt,
                        visible, default, self.dialog_response)
        except Queue.Empty:
            pass

    def prompt_response(self, dialog, response_id):
        dialog.destroy()
        if response_id == gtk.RESPONSE_DELETE_EVENT:
            self.responseq.put(None)
        else:
            self.responseq.put(response_id)

    def dialog_response(self, dialog, response_id):
        if response_id == gtk.RESPONSE_OK:
            text = dialog.entry.get_text()
        else:
            text = None
        dialog.destroy()
        self.responseq.put(text)

    def run(self):
        try:
            for k, v in self.ui.configitems('defaults'):
                self.ui.setconfig('defaults', k, '')
            l = 'control'
            ret = hglib.dispatch._dispatch(self.ui, self.args)
            if ret:
                self.ui.write(_('[command returned code %d ') % int(ret), label=l)
            else:
                self.ui.write(_('[command completed successfully '), label=l)
            self.ui.write(time.asctime() + ']\n', label=l)
            self.ret = ret or 0
            if self.postfunc:
                self.postfunc(ret)
        except util.Abort, e:
            self.ui.write_err(_('abort: ') + str(e) + '\n')
        except (error.RepoError, urllib2.HTTPError), e:
            self.ui.write_err(str(e) + '\n')
        except urllib2.URLError, e:
            import ssl
            err = str(e)
            if isinstance(e.args[0], ssl.SSLError):
                parts = e.args[0].strerror.split(':')
                if len(parts) == 7:
                    file, line, level, errno, lib, func, reason = parts
                    if func == 'SSL3_GET_SERVER_CERTIFICATE':
                        err = _('SSL: Server certificate verify failed')
                    elif errno == '00000000':
                        err = _('SSL: unknown error %s:%s') % (file, line)
                    else:
                        err = _('SSL error: %s') % reason
            self.ui.write_err(err + '\n')
        except (Exception, OSError, IOError), e:
            self.ui.write_err(str(e) + '\n')