51
53
"""Create a TextUIFactory.
53
:param bar_type: The type of progress bar to create. Deprecated
54
and ignored; a TextProgressView is always used.
56
55
super(TextUIFactory, self).__init__()
57
56
# TODO: there's no good reason not to pass all three streams, maybe we
85
84
# end-of-file; possibly should raise an error here instead
87
def get_integer(self, prompt):
90
line = self.stdin.readline()
88
96
def get_non_echoed_password(self):
89
97
isatty = getattr(self.stdin, 'isatty', None)
90
98
if isatty is not None and isatty():
150
158
return NullProgressView()
160
def _make_output_stream_explicit(self, encoding, encoding_type):
161
if encoding_type == 'exact':
162
# force sys.stdout to be binary stream on win32;
163
# NB: this leaves the file set in that mode; may cause problems if
164
# one process tries to do binary and then text output
165
if sys.platform == 'win32':
166
fileno = getattr(self.stdout, 'fileno', None)
169
msvcrt.setmode(fileno(), os.O_BINARY)
170
return TextUIOutputStream(self, self.stdout)
172
encoded_stdout = codecs.getwriter(encoding)(self.stdout,
173
errors=encoding_type)
174
# For whatever reason codecs.getwriter() does not advertise its encoding
175
# it just returns the encoding of the wrapped file, which is completely
176
# bogus. So set the attribute, so we can find the correct encoding later.
177
encoded_stdout.encoding = encoding
178
return TextUIOutputStream(self, encoded_stdout)
152
180
def note(self, msg):
153
181
"""Write an already-formatted message, clearing the progress bar if necessary."""
154
182
self.clear_term()
176
204
self._progress_view.show_transport_activity(transport,
177
205
direction, byte_count)
207
def log_transport_activity(self, display=False):
208
"""See UIFactory.log_transport_activity()"""
209
log = getattr(self._progress_view, 'log_transport_activity', None)
213
def show_error(self, msg):
215
self.stderr.write("bzr: error: %s\n" % msg)
217
def show_message(self, msg):
220
def show_warning(self, msg):
222
self.stderr.write("bzr: warning: %s\n" % msg)
179
224
def _progress_updated(self, task):
180
225
"""A task has been updated and wants to be displayed.
210
255
self._term_file = term_file
211
256
# true when there's output on the screen we may need to clear
212
257
self._have_output = False
213
# XXX: We could listen for SIGWINCH and update the terminal width...
214
# https://launchpad.net/bugs/316357
215
self._width = osutils.terminal_width()
216
258
self._last_transport_msg = ''
217
259
self._spin_pos = 0
218
260
# time we last repainted the screen
222
264
self._last_task = None
223
265
self._total_byte_count = 0
224
266
self._bytes_since_update = 0
267
self._bytes_by_direction = {'unknown': 0, 'read': 0, 'write': 0}
268
self._first_byte_time = None
225
269
self._fraction = 0
270
# force the progress bar to be off, as at the moment it doesn't
271
# correspond reliably to overall command progress
272
self.enable_bar = False
227
274
def _show_line(self, s):
228
275
# sys.stderr.write("progress %r\n" % s)
230
self._term_file.write('\r%-*.*s\r' % (n, n, s))
276
width = osutils.terminal_width()
277
if width is not None:
278
# we need one extra space for terminals that wrap on last char
280
s = '%-*.*s' % (width, width, s)
281
self._term_file.write('\r' + s + '\r')
233
284
if self._have_output:
237
288
def _render_bar(self):
238
289
# return a string for the progress bar itself
239
if (self._last_task is None) or self._last_task.show_bar:
290
if self.enable_bar and (
291
(self._last_task is None) or self._last_task.show_bar):
240
292
# If there's no task object, we show space for the bar anyhow.
241
293
# That's because most invocations of bzr will end showing progress
242
294
# at some point, though perhaps only after doing some initial IO.
258
310
markers = int(round(float(cols) * completion_fraction)) - 1
259
311
bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
261
elif self._last_task.show_spinner:
313
elif (self._last_task is None) or self._last_task.show_spinner:
262
314
# The last task wanted just a spinner, no bar
263
315
spin_str = r'/-\|'[self._spin_pos % 4]
264
316
self._spin_pos += 1
326
378
This may update a progress bar, spinner, or similar display.
327
379
By default it does nothing.
329
# XXX: Probably there should be a transport activity model, and that
330
# too should be seen by the progress view, rather than being poked in
332
if not self._have_output:
333
# As a workaround for <https://launchpad.net/bugs/321935> we only
334
# show transport activity when there's already a progress bar
335
# shown, which time the application code is expected to know to
336
# clear off the progress bar when it's going to send some other
337
# output. Eventually it would be nice to have that automatically
381
# XXX: there should be a transport activity model, and that too should
382
# be seen by the progress view, rather than being poked in here.
340
383
self._total_byte_count += byte_count
341
384
self._bytes_since_update += byte_count
385
if self._first_byte_time is None:
386
# Note that this isn't great, as technically it should be the time
387
# when the bytes started transferring, not when they completed.
388
# However, we usually start with a small request anyway.
389
self._first_byte_time = time.time()
390
if direction in self._bytes_by_direction:
391
self._bytes_by_direction[direction] += byte_count
393
self._bytes_by_direction['unknown'] += byte_count
394
if 'no_activity' in debug.debug_flags:
395
# Can be used as a workaround if
396
# <https://launchpad.net/bugs/321935> reappears and transport
397
# activity is cluttering other output. However, thanks to
398
# TextUIOutputStream this shouldn't be a problem any more.
342
400
now = time.time()
343
401
if self._total_byte_count < 2000:
344
402
# a little resistance at first, so it doesn't stay stuck at 0
357
415
self._bytes_since_update = 0
358
416
self._last_transport_msg = msg
419
def _format_bytes_by_direction(self):
420
if self._first_byte_time is None:
423
transfer_time = time.time() - self._first_byte_time
424
if transfer_time < 0.001:
425
transfer_time = 0.001
426
bps = self._total_byte_count / transfer_time
428
msg = ('Transferred: %.0fKiB'
429
' (%.1fK/s r:%.0fK w:%.0fK'
430
% (self._total_byte_count / 1024.,
432
self._bytes_by_direction['read'] / 1024.,
433
self._bytes_by_direction['write'] / 1024.,
435
if self._bytes_by_direction['unknown'] > 0:
436
msg += ' u:%.0fK)' % (
437
self._bytes_by_direction['unknown'] / 1024.
443
def log_transport_activity(self, display=False):
444
msg = self._format_bytes_by_direction()
446
if display and self._total_byte_count > 0:
448
self._term_file.write(msg + '\n')
451
class TextUIOutputStream(object):
452
"""Decorates an output stream so that the terminal is cleared before writing.
454
This is supposed to ensure that the progress bar does not conflict with bulk
457
# XXX: this does not handle the case of writing part of a line, then doing
458
# progress bar output: the progress bar will probably write over it.
459
# one option is just to buffer that text until we have a full line;
460
# another is to save and restore it
462
# XXX: might need to wrap more methods
464
def __init__(self, ui_factory, wrapped_stream):
465
self.ui_factory = ui_factory
466
self.wrapped_stream = wrapped_stream
467
# this does no transcoding, but it must expose the underlying encoding
468
# because some callers need to know what can be written - see for
469
# example unescape_for_display.
470
self.encoding = getattr(wrapped_stream, 'encoding', None)
473
self.ui_factory.clear_term()
474
self.wrapped_stream.flush()
476
def write(self, to_write):
477
self.ui_factory.clear_term()
478
self.wrapped_stream.write(to_write)
480
def writelines(self, lines):
481
self.ui_factory.clear_term()
482
self.wrapped_stream.writelines(lines)