PageRenderTime 57ms CodeModel.GetById 19ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 1ms

/tortoisehg/hgqt/thread.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 314 lines | 298 code | 9 blank | 7 comment | 0 complexity | a3d6edeb72c67c530f92b7cb195d5392 MD5 | raw file
  1# thread.py - A seprated thread to run Mercurial command
  2#
  3# Copyright 2009 Steve Borho <steve@borho.org>
  4# Copyright 2010 Yuki KODAMA <endflow.net@gmail.com>
  5#
  6# This software may be used and distributed according to the terms of the
  7# GNU General Public License version 2, incorporated herein by reference.
  8
  9import os
 10import Queue
 11import time
 12import urllib2
 13
 14from PyQt4.QtCore import *
 15from PyQt4.QtGui import *
 16
 17from mercurial import util, error, dispatch
 18from mercurial import ui as uimod
 19
 20from tortoisehg.util import thread2, hglib
 21from tortoisehg.hgqt.i18n import _, localgettext
 22from tortoisehg.hgqt import qtlib
 23
 24local = localgettext()
 25
 26class DataWrapper(object):
 27    def __init__(self, data):
 28        self.data = data
 29
 30class UiSignal(QObject):
 31    writeSignal = pyqtSignal(QString, QString)
 32    progressSignal = pyqtSignal(QString, object, QString, QString, object)
 33    interactSignal = pyqtSignal(DataWrapper)
 34
 35    def __init__(self, responseq):
 36        QObject.__init__(self)
 37        self.responseq = responseq
 38
 39    def write(self, *args, **opts):
 40        msg = hglib.tounicode(''.join(args))
 41        label = hglib.tounicode(opts.get('label', ''))
 42        self.writeSignal.emit(msg, label)
 43
 44    def write_err(self, *args, **opts):
 45        msg = hglib.tounicode(''.join(args))
 46        label = hglib.tounicode(opts.get('label', 'ui.error'))
 47        self.writeSignal.emit(msg, label)
 48
 49    def prompt(self, msg, choices, default):
 50        try:
 51            r = self._waitresponse(msg, False, choices, None)
 52            if r is None:
 53                raise EOFError
 54            if not r:
 55                return default
 56            if choices:
 57                # return char for Mercurial 1.3
 58                choice = choices[r]
 59                return choice[choice.index('&')+1].lower()
 60            return r
 61        except EOFError:
 62            raise util.Abort(local._('response expected'))
 63
 64    def promptchoice(self, msg, choices, default):
 65        try:
 66            r = self._waitresponse(msg, False, choices, default)
 67            if r is None:
 68                raise EOFError
 69            return r
 70        except EOFError:
 71            raise util.Abort(local._('response expected'))
 72
 73    def getpass(self, prompt, default):
 74        r = self._waitresponse(prompt, True, None, default)
 75        if r is None:
 76            raise util.Abort(local._('response expected'))
 77        return r
 78
 79    def _waitresponse(self, msg, password, choices, default):
 80        """Request interaction with GUI and wait response from it"""
 81        data = DataWrapper((msg, password, choices, default))
 82        self.interactSignal.emit(data)
 83        # await response
 84        return self.responseq.get(True)
 85
 86    def progress(self, topic, pos, item, unit, total):
 87        topic = hglib.tounicode(topic)
 88        item = hglib.tounicode(item)
 89        unit = hglib.tounicode(unit)
 90        self.progressSignal.emit(topic, pos, item, unit, total)
 91
 92class QtUi(uimod.ui):
 93    def __init__(self, src=None, responseq=None):
 94        super(QtUi, self).__init__(src)
 95
 96        if src:
 97            self.sig = src.sig
 98        else:
 99            self.sig = UiSignal(responseq)
100
101        self.setconfig('ui', 'interactive', 'on')
102        self.setconfig('progress', 'disable', 'True')
103        os.environ['TERM'] = 'dumb'
104
105    def write(self, *args, **opts):
106        if self._buffers:
107            self._buffers[-1].extend([str(a) for a in args])
108        else:
109            self.sig.write(*args, **opts)
110
111    def write_err(self, *args, **opts):
112        self.sig.write_err(*args, **opts)
113
114    def label(self, msg, label):
115        return msg
116
117    def flush(self):
118        pass
119
120    def prompt(self, msg, choices=None, default='y'):
121        if not self.interactive(): return default
122        return self.sig.prompt(msg, choices, default)
123
124    def promptchoice(self, msg, choices, default=0):
125        if not self.interactive(): return default
126        return self.sig.promptchoice(msg, choices, default)
127
128    def getpass(self, prompt=_('password: '), default=None):
129        return self.sig.getpass(prompt, default)
130
131    def progress(self, topic, pos, item='', unit='', total=None):
132        return self.sig.progress(topic, pos, item, unit, total)
133
134
135class CmdThread(QThread):
136    """Run an Mercurial command in a background thread, implies output
137    is being sent to a rendered text buffer interactively and requests
138    for feedback from Mercurial can be handled by the user via dialog
139    windows.
140    """
141    # (msg=str, label=str)
142    outputReceived = pyqtSignal(QString, QString)
143
144    # (topic=str, pos=int, item=str, unit=str, total=int)
145    # pos and total are emitted as object, since they may be None
146    progressReceived = pyqtSignal(QString, object, QString, QString, object)
147
148    # result: -1 - command is incomplete, possibly exited with exception
149    #          0 - command is finished successfully
150    #          others - return code of command
151    commandFinished = pyqtSignal(int)
152
153    def __init__(self, cmdline, display, parent=None):
154        super(CmdThread, self).__init__(parent)
155
156        self.cmdline = cmdline
157        self.display = display
158        self.ret = -1
159        self.abortbyuser = False
160        self.responseq = Queue.Queue()
161        self.rawoutput = QStringList()
162        self.topics = {}
163        self.curstrs = QStringList()
164        self.curlabel = None
165        self.timer = QTimer(self)
166        self.timer.timeout.connect(self.flush)
167        self.timer.start(100)
168        self.finished.connect(self.thread_finished)
169
170    def abort(self):
171        if self.isRunning() and hasattr(self, 'thread_id'):
172            self.abortbyuser = True
173            try:
174                thread2._async_raise(self.thread_id, KeyboardInterrupt)
175            except ValueError:
176                pass
177
178    def thread_finished(self):
179        self.timer.stop()
180        self.flush()
181        self.commandFinished.emit(self.ret)
182
183    def flush(self):
184        if self.curlabel is not None:
185            self.outputReceived.emit(self.curstrs.join(''), self.curlabel)
186        self.curlabel = None
187        if self.timer.isActive():
188            keys = self.topics.keys()
189            for topic in keys:
190                pos, item, unit, total = self.topics[topic]
191                self.progressReceived.emit(topic, pos, item, unit, total)
192                if pos is None:
193                    del self.topics[topic]
194        else:
195            # Close all progress bars
196            for topic in self.topics:
197                self.progressReceived.emit(topic, None, '', '', None)
198            self.topics = {}
199
200    @pyqtSlot(QString, QString)
201    def output_handler(self, msg, label):
202        if label != 'control':
203            self.rawoutput.append(msg)
204
205        if label == self.curlabel:
206            self.curstrs.append(msg)
207        else:
208            if self.curlabel is not None:
209                self.outputReceived.emit(self.curstrs.join(''), self.curlabel)
210            self.curstrs = QStringList(msg)
211            self.curlabel = label
212
213    @pyqtSlot(QString, object, QString, QString, object)
214    def progress_handler(self, topic, pos, item, unit, total):
215        self.topics[topic] = (pos, item, unit, total)
216
217    @pyqtSlot(DataWrapper)
218    def interact_handler(self, wrapper):
219        prompt, password, choices, default = wrapper.data
220        prompt = hglib.tounicode(prompt)
221        if choices:
222            dlg = QMessageBox(QMessageBox.Question,
223                        _('TortoiseHg Prompt'), prompt,
224                        QMessageBox.Yes | QMessageBox.Cancel, self.parent())
225            dlg.setDefaultButton(QMessageBox.Cancel)
226            dlg.setWindowFlags(Qt.Sheet)
227            dlg.setWindowModality(Qt.WindowModal)
228            rmap = {}
229            for index, choice in enumerate(choices):
230                button = dlg.addButton(hglib.tounicode(choice),
231                                       QMessageBox.ActionRole)
232                rmap[id(button)] = index
233            dlg.exec_()
234            button = dlg.clickedButton()
235            if button is 0:
236                result = default
237            else:
238                result = rmap[id(button)]
239            self.responseq.put(result)
240        else:
241            mode = password and QLineEdit.Password \
242                             or QLineEdit.Normal
243            dlg = QInputDialog(self.parent(), Qt.Sheet)
244            dlg.setWindowModality(Qt.WindowModal)
245            dlg.setWindowTitle(_('TortoiseHg Prompt'))
246            dlg.setLabelText(prompt.title())
247            dlg.setTextEchoMode(mode)
248            if dlg.exec_():
249                text = hglib.fromunicode(dlg.textValue())
250            else:
251                text = None
252            self.responseq.put(text)
253
254    def run(self):
255        ui = QtUi(responseq=self.responseq)
256        ui.sig.writeSignal.connect(self.output_handler,
257                Qt.QueuedConnection)
258        ui.sig.progressSignal.connect(self.progress_handler,
259                Qt.QueuedConnection)
260        ui.sig.interactSignal.connect(self.interact_handler,
261                Qt.QueuedConnection)
262
263        if self.display:
264            cmd = '%% hg %s\n' % self.display
265        else:
266            cmd = '%% hg %s\n' % ' '.join(self.cmdline)
267        ui.write(cmd, label='control')
268
269        try:
270            # save thread id in order to terminate by KeyboardInterrupt
271            self.thread_id = int(QThread.currentThreadId())
272
273            for k, v in ui.configitems('defaults'):
274                ui.setconfig('defaults', k, '')
275            self.ret = 255
276            self.ret = dispatch._dispatch(ui, self.cmdline) or 0
277        except util.Abort, e:
278            ui.write_err(local._('abort: ') + str(e) + '\n')
279            if e.hint:
280                ui.write_err(local._('hint: ') + str(e.hint) + '\n')
281        except (error.RepoError, urllib2.HTTPError), e:
282            ui.write_err(str(e) + '\n')
283        except urllib2.URLError, e:
284            import ssl
285            err = str(e)
286            if isinstance(e.args[0], ssl.SSLError):
287                parts = e.args[0].strerror.split(':')
288                if len(parts) == 7:
289                    file, line, level, errno, lib, func, reason = parts
290                    if func == 'SSL3_GET_SERVER_CERTIFICATE':
291                        err = local._('SSL: Server certificate verify failed')
292                    elif errno == '00000000':
293                        err = local._('SSL: unknown error %s:%s') % (file, line)
294                    else:
295                        err = local._('SSL error: %s') % reason
296            ui.write_err(err + '\n')
297        except (Exception, OSError, IOError), e:
298            if 'THGDEBUG' in os.environ:
299                import traceback
300                traceback.print_exc()
301            ui.write_err(str(e) + '\n')
302        except KeyboardInterrupt:
303            self.ret = -1
304
305        if self.ret == -1:
306            if self.abortbyuser:
307                msg = _('[command terminated by user %s]')
308            else:
309                msg = _('[command interrupted %s]')
310        elif self.ret:
311            msg = _('[command returned code %d %%s]') % int(self.ret)
312        else:
313            msg = _('[command completed successfully %s]')
314        ui.write(msg % time.asctime() + '\n', label='control')