1# -*- coding: utf-8 -*-
2#
3# Copyright 2016 Jerome Duval
4# Distributed under the terms of the MIT License.
5
6# -- Modules ------------------------------------------------------------------
7
8import curses
9import datetime
10import time
11
12
13class DisplayContext(object):
14	def __init__(self):
15		self.stdscr = None
16	def __enter__(self):
17		self.stdscr = curses.initscr()
18		curses.noecho()
19		curses.cbreak()
20		self.stdscr.keypad(1)
21		try:
22			curses.start_color()
23			curses.curs_set(0)
24		except:
25			pass
26		return self
27	def __exit__(self, ignoredType, value, traceback):
28		if self.stdscr:
29			curses.curs_set(2)
30			self.stdscr.keypad(0)
31			curses.echo()
32			curses.nocbreak()
33			curses.endwin()
34
35
36class Display(object):
37	def __init__(self, stdscr, builders_used):
38		self.builders_used = builders_used
39
40		termsize = stdscr.getmaxyx()
41		y, x = termsize
42		ncols = 80
43
44		curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
45		curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
46		curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
47		curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)
48		curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_BLACK)
49		curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
50		curses.init_pair(7, curses.COLOR_BLUE, curses.COLOR_BLACK)
51		curses.init_pair(8, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
52		curses.init_pair(9, curses.COLOR_BLUE, curses.COLOR_WHITE)
53		self.c_standard = curses.color_pair(1)
54		self.c_success = curses.color_pair(2)
55		self.c_failure = curses.color_pair(3)
56		self.c_lost = curses.color_pair(4)
57		self.c_blocked = curses.color_pair(5)
58		self.c_sumlabel = curses.color_pair(6) | curses.A_BOLD
59		self.c_dashes = curses.color_pair(7) | curses.A_BOLD
60		self.c_duration = curses.color_pair(4)
61		self.c_tableheader = curses.color_pair(1)
62		self.c_portname = curses.color_pair(6)
63		self.c_bldphase = curses.color_pair(4)
64		self.c_shutdown = curses.color_pair(1)
65		self.c_advisory = curses.color_pair(4)
66
67		self.c_slave = []
68		self.c_slave.append(curses.color_pair(1) | curses.A_BOLD)
69		self.c_slave.append(curses.color_pair(2) | curses.A_BOLD)
70		self.c_slave.append(curses.color_pair(4) | curses.A_BOLD)
71		self.c_slave.append(curses.color_pair(8) | curses.A_BOLD)
72		self.c_slave.append(curses.color_pair(3) | curses.A_BOLD)
73		self.c_slave.append(curses.color_pair(7) | curses.A_BOLD)
74		self.c_slave.append(curses.color_pair(6) | curses.A_BOLD)
75		self.c_slave.append(curses.color_pair(5) | curses.A_BOLD)
76		self.c_slave.append(curses.color_pair(1))
77		self.c_slave.append(curses.color_pair(2))
78		self.c_slave.append(curses.color_pair(4))
79		self.c_slave.append(curses.color_pair(8))
80		self.c_slave.append(curses.color_pair(3))
81		self.c_slave.append(curses.color_pair(7))
82		self.c_slave.append(curses.color_pair(6))
83		self.c_slave.append(curses.color_pair(9))
84
85		for i in range(16):
86			self.c_slave.append(self.c_slave[i] | curses.A_UNDERLINE)
87		for i in range(32):
88			self.c_slave.append(self.c_slave[i])
89
90		self.zone_summary = curses.newwin(2, ncols, 0, 0)
91		self.zone_builders = curses.newwin(self.builders_used+4, ncols, 2, 0)
92		self.maxrows = y - (self.builders_used+4+2)
93		self.zone_history = curses.newwin(self.maxrows, ncols,
94			self.builders_used+4+2, 0)
95
96		self.zone_summary.addstr(0, 0,
97			" Total         Built        Ignored        Load  0.00  Pkg/hour                ",
98			self.c_sumlabel)
99		self.zone_summary.addstr(1, 0,
100			"  Left        Failed        Skipped        Swap  0.0%   Impulse	      0:00:00  ",
101			self.c_sumlabel)
102
103		dashes = "=" * (ncols-1)
104		self.zone_builders.addstr(0, 0, dashes, self.c_dashes)
105		self.zone_builders.addstr(2, 0, dashes, self.c_dashes)
106		self.zone_builders.addstr(self.builders_used+4-1, 0, dashes,
107			self.c_dashes)
108
109		self.zone_builders.addstr(1, 0,
110			" ID  Duration  Build Phase      Port Name                                Lines ",
111			self.c_tableheader)
112		for i in range(3, self.builders_used + 4 - 1):
113			self.zone_builders.addstr(i, 0, " " * ncols, self.c_standard)
114
115		stdscr.refresh()
116		self.zone_summary.refresh()
117		self.zone_builders.refresh()
118		self.zone_history.refresh()
119
120	def updateSummary(self, data):
121		self.zone_summary.addstr(0, 7, str(data['builds']['total']).ljust(4),
122			self.c_standard)
123		self.zone_summary.addstr(0, 21, str(data['builds']['complete']).ljust(4),
124			self.c_success)
125		self.zone_summary.addstr(0, 37, str(data['builds']['blocked']).ljust(4),
126			self.c_blocked)
127		self.zone_summary.addstr(0, 64, str(data['pkg_hour']).ljust(5),
128			self.c_standard)
129		self.zone_summary.addstr(1, 7, str(data['builds']['scheduled']
130			+ data['builds']['active'] + data['builds']['blocked']).ljust(4),
131			self.c_standard)
132		self.zone_summary.addstr(1, 21, str(data['builds']['failed']).ljust(4),
133			self.c_failure)
134		self.zone_summary.addstr(0, 37, str(data['builds']['lost']).ljust(4),
135			self.c_lost)
136		self.zone_summary.addstr(1, 64, str(data['impulse']).ljust(5),
137			self.c_standard)
138		self.zone_summary.addstr(1, 70, str(datetime.timedelta(
139			seconds=int(data['duration']))).zfill(8) if data['duration']
140				else '       ', self.c_duration)
141		self.zone_summary.refresh()
142
143	def updateBuilders(self, data):
144		builders = data['builders']['active']
145		for i in range(0, self.builders_used):
146			currentBuild = builders[i]['currentBuild']
147			self.zone_builders.addstr(3+i, 1, str(i+1).zfill(2)[:2],
148				self.c_slave[i])
149			if currentBuild is None:
150				self.zone_builders.addstr(3+i, 5, ' ' * 75, self.c_standard)
151				continue
152			portName = currentBuild['build']['port']['revisionedName']
153			self.zone_builders.addstr(3+i, 5, str(datetime.timedelta(
154				seconds=int(currentBuild['duration']))
155				).zfill(8)[:8] if currentBuild['duration'] else '        ',
156				self.c_standard)
157			self.zone_builders.addstr(3+i, 15,
158				currentBuild['phase'].ljust(12)[:12], self.c_bldphase)
159			self.zone_builders.addstr(3+i, 32, portName.ljust(38)[:38],
160				self.c_portname)
161			self.zone_builders.addstr(3+i, 71,
162				str(currentBuild['lines']).rjust(7)[:7], self.c_standard)
163
164		self.zone_builders.refresh()
165
166	def updateHistory(self, data):
167		row = 0
168		for build in list(reversed(data)):
169			current = build.status
170			if current is None:
171				self.zone_builders.addstr(row, 5, ' ' * 75, self.c_standard)
172				continue
173			self.zone_history.addstr(row, 10,
174				'[' + str(int(current['builderId'])+1).zfill(2)[:2] + ']',
175				self.c_slave[int(current['builderId'])])
176			portName = current['port']['revisionedName']
177			self.zone_history.addstr(row, 70, str(datetime.timedelta(
178				seconds=int(current['duration']))
179				).zfill(8)[:8] if current['duration'] else '        ',
180				self.c_standard)
181			self.zone_history.addstr(row, 15,
182				'success' if current['buildSuccess'] else 'failed ',
183				self.c_success if current['buildSuccess'] else self.c_failure)
184			self.zone_history.addstr(row, 24, portName.ljust(38)[:38],
185				self.c_portname)
186			self.zone_history.addstr(row, 1, time.strftime('%X',
187				time.localtime(current['startTime'])), self.c_standard)
188			row += 1
189			if row >= self.maxrows:
190				break
191
192		self.zone_history.refresh()
193