#! /usr/bin/env python
# -*- encoding: iso-8859-1 -*-

#
#	Script de rcupration de backups (en partenariat avec /usr/local/Claranet/Configs-Backups/backup.pl)
#
#	2004.03.03	Yann Grossel
#

# TODO	Pouvoir utiliser ce script pour grer la configuration des backups :
#			- dition de la liste des cibles  utiliser pour les backups
#			- (moins important) dition des fichiers / rpertoires  backuper, avec ou sans versionning...

# TODO	Remettre les vrais droits sur les fichiers restaurs (si droits connus): owner, group, suid/sgid etc !

# TODO	Finir la section permettant de choisir une target !

# TODO	Grer les touches page up, page down

# TODO 	La touche Home ne fonctionne qu'a moit dans le cas ou il y a des lignes non slectionnable au dbut

# TODO	Un changement de taille du terminal ne change plus la taille des colones dans la fwin !

# TODO	Mettre des fleches indiquant si des lignes ont scroll hors de l'cran (en haut et/ou en bas)

# TODO	En cas de redimmensionnement de terminal, recentrer les lignes pour qu'il n'y ait pas de lignes
#			vides en bas de l'cran (videmment s'il y a suffisament de lignes pour remplir tout l'cran :)

# TODO   Dans certains cas le reimport ne fonctionne pas (reimport d'un seul fichier ?) car le path n'existe pas
# (/var/tmp/REIMPORT-BACKUPS/x/y/z : x/y n'existe pas). A fixer.

import os, sys, time, string, tempfile, whrandom
import curses, curses.wrapper, errno

base      = '/usr/local/Claranet/Configs-Backups/'
perm_file = '/var/tmp/clara-backup.lst'
reimport	 = '/var/tmp/REIMPORT-BACKUPS'

lcd = '$LastChangedDate: 2004-05-06 19:31:31 +0200 (Thu, 06 May 2004) $'
lcr = '$LastChangedRevision: 26 $'

rsync, ssh, targets = None, None, None		# Valeurs lues dans le fichier de config
status = None										# 'ct' = target choose, 'br' = browse files, 're' = restore
help_win = None									# Win Curses si Help ouvert, None sinon
target = None										# Target courante
path = None											# Path courant sur la target courante
sx, sy = 0, 0										# Taille actuelle du terminal
files = {}											# Fichiers dans l'arbo courante (ensemble de sous dicts)
old_versions = {}									# Anciennes versions des fichiers : [ tailles, date, heure ]
perms = {}											# Perms (indiques dans le fichier permfile)
bk_time = None										# Timestamp du fichier perm_file (indique la date du dernier backup)
scr = None											# Screen Curses
fwin = None											# Window Curses contenant la liste des fichiers
fwin_x, fwin_y = 0, 0							# Taille de la fenetre fwin
lines = []											# Contenu des lignes "scrollables" de la fentre
line_first = 0										# N de la ligne logique affiche sur la 1ere ligne physique de l'cran
line_selected = 0									# N de la ligne logique actuellement slectionne
colors = {}											# Dict contenant les couleurs utilises
users = None										# Mapping entre UID et usernames
groups = None										# Mapping entre GID et groupnames
rapth = None										# Path du fichier/rpertoire  restaurer

def read_config():

	# Lit le fichier de configuration, pour rcuprer les valeurs de ssh, rsync et targets

	global rsync, ssh, targets

	conf = base + 'backups.conf'

	try: f = open(conf)
	except IOError, e:
		sys.stderr.write("\nCan't open '%s' !\n\nError: %s\n\n" % (conf, e[1]))
		sys.exit(1)

	for l in f.readlines():
		l = l.strip()
		if not len(l) or l[0] == '#': continue
		l = l.replace('=', ' = ')
		l = l.split()
		if l[0] == 'ssh' and l[1] == '=':
			ssh = l[2]
			continue
		if l[0] == 'rsync' and l[1] == '=':
			rsync = l[2]
			continue

		if l[0] == 'targets' and l[1] == '=':
			targets = l[2:]
			continue

	f.close()

	if not targets:
		sys.stderr.write("\n'targets' not defined in config file !\n\n")
		sys.exit(1)

	if not len(targets):
		sys.stderr.write("\nNo targets defined in config file !\n\n")
		sys.exit(1)

	if not rsync:
		sys.stderr.write("\n'rsync' not defined in config file !\n\n")
		sys.exit(1)

	if not ssh:
		sys.stderr.write("\n'ssh' not defined in config file !\n\n")
		sys.exit(1)

def dirname(path):
	p = '/'.join(path.split('/')[0:-1])
	if p == '': p = '/'
	return p

def mktemp(base):
	# Renvoie un nom de fichier temporaire alatoire, dans /var/tmp, bas sur 'base'
	tmpname = '.'
	for a in range(0, 6): tmpname += whrandom.choice('azertyuiopmlkjhgfdsqwxcvbnAQWXSZEDCVFRTGBNHYUJKIOLMP0987654321')
	tmpname += '.' + base
	tempfile.tempdir = '/var/tmp'
	return tempfile.mktemp(tmpname)

def load_files():
	# Charge la liste des fichiers sur la target courante.

	global target, rsync, ssh, files, perms, old_versions, bk_time, users, groups

	win = popup_box("Reading file list...")

	# On commence par rcuprer le fichier de permissions.

	# Pour ca rappatrie le fichier dans /var/tmp, sous un nom temporaire. On charge ensuite le contenu du fichier
	# dans le dict 'perms'.

	tmpname = mktemp(perm_file.split('/')[-1])

	os.system("%s -a -q -e '%s -2 -F %s/ssh.config' %s/%s %s 2>/dev/null" % (rsync, ssh, base, target, perm_file, tmpname))

	perms = {}

	bk_time = time.localtime(os.stat(tmpname)[8])	# st_mtime : Date de dernire modification == date dernier backup

	try: f = open(tmpname)
	except IOError: pass
	else:
		for l in f.readlines():
			l = l.strip('\n').split(' ', 3)
			perms[l[3]] = [ int(l[0]), int(l[1]), int(l[2]) ]
		f.close()
		os.unlink(tmpname)

	# Si possible, on rappatrie les fichiers /etc/passwd et /etc/group pour les mapping uid/gid

	users, groups = {}, {}

	# os.system("%s -a -q -e '%s -2 -F %s/ssh.config' %s/%s %s 2>/dev/null" % (rsync, ssh, base, target, '/etc/passwd', tmpname))

	try: f = open(tmpname)
	except IOError: users = None
	else:
		for l in f.readlines():
			l = l.strip('\n').split(':')
			if l[0][0] != '#' and not users.has_key(int(l[2])): users[int(l[2])] = l[0]
		f.close()
		os.unlink(tmpname)

	# os.system("%s -a -q -e '%s -2 -F %s/ssh.config' %s/%s %s 2>/dev/null" % (rsync, ssh, base, target, '/etc/group', tmpname))

	try: f = open(tmpname)
	except IOError: groups = None
	else:
		for l in f.readlines():
			l = l.strip('\n').split(':')
			if l[0][0] != '#' and not groups.has_key(int(l[2])): groups[int(l[2])] = l[0]
		f.close()
		os.unlink(tmpname)

	# Ensuite, on lit l'arborescence complte des fichiers prsents (Versions actuelles et aussi anciennes versions),
	# et on la place dans le dict 'files'.

	files, old_versions = {}, {}

	def add_file(p):
		p = p.split('/')
		parent, file = p[:-1], p[-1]
		parent = '/'.join(parent)
		if parent == '': parent = '/'
		if files[parent][0] != 'd':
			# Un lement qui n'est pas un rpertoire ne peut contenir d'autres lements !
			sys.stderr.write("Would add %s to %s (not a directory !?)" % (file, parent))
			sys.exit(1)
		if file not in files[parent][5]:
			files[parent][5].append(file)

	pipe = os.popen("%s -a -e '%s -F %s/ssh.config' %s 2>/dev/null" % (rsync, ssh, base, target), 'r')

	for l in pipe.readlines():
		l = l.strip()
		l = string.split(l, maxsplit = 4)

		if l[4] == '.': l[4] = '/'
		else: l[4] = '/' + l[4]

		l[0] = l[0][0]

		# XXX Si un lien comporte un ' -> ' dans son nom, ou s'il pointe vers une cible qui contient
		# XXX ' -> ' dans son nom, ca va tout exploser. Comment faire ?!
		if l[0] == 'l': l[4] = l[4].split(' -> ')[0]

		# Old-Versions

		if l[4][0:13] == '/Old-Versions':

			l[4] = l[4][13:]					# On supprime le '/Old-Versions'

			if l[0] == 'd':					# Les rpertoires sont intgrs dans l'arborescence
				if l[4] == '': continue		# Le rpertoire Old-Versions lui-mme doit tre ignor

			elif l[0] == '-':
				v = l[4][-10:]					# Version (yyyy.mm.dd)

				old_versions[l[4]] = [ int(l[1]), l[2], l[3] ]

				l[4] = l[4][:-11]				# Nom sans le '.yyyy.mm.dd'

				if files.has_key(l[4]): files[l[4]][4].append(v)
				else:
					files[l[4]] = [ '-', 0, '????/??/??', '??:??:??', [ v ], [ ] ]
					add_file(l[4])

				continue

			else:
				continue								# Ni rpertoire, ni fichier : on ignore

		# Type, Size, Date, Heure, Old-Versions, Contenu
		#	0		1		 2		 3			4				 5

		if files.has_key(l[4]):
			files[l[4]] = [ l[0], int(l[1]), l[2], l[3], files[l[4]][4], files[l[4]][5] ]
		else:
			files[l[4]] = [ l[0], int(l[1]), l[2], l[3], [], [] ]

		if l[4] != '/': add_file(l[4])

	pipe.close()

	for f in files.keys():
		files[f][4].sort()		# On trie les versions archives
		files[f][4].reverse()	# Ordre : plus rcente vers plus ancienne
		files[f][5].sort()		# On trie les contenu des rpertoires

	# On supprime perm_file et ses rpertoires parents, s'ils sont vides (l'user n'a pas besoin de savoir
	# qu'on a backup un fichier priv :)

	p = perm_file

	while p != '/':
		file = None
		if not files.has_key(p): break
		if not len(files[p][5]):			# Le rpertoire/fichier n'a pas de contenu
			del files[p]						# On le supprime
			file = p.split('/')[-1]			# On note son nom, pour le supprimer de la liste du contenu du parent
		p = dirname(p)
		if file and files.has_key(p): files[p][5].remove(file)

	del win

	screen_update()				# Pour mettre  jour la date de dernier backup

def popup_box(msg):
	l = len(msg) + 12
	h = 5
	win = curses.newwin(h, l, (sy - h) / 2, (sx - l) / 2)
	win.bkgd(' ', colors[2])
	win.border()
	win.addstr(2, 6, msg, colors[2])
	win.refresh()
	return win

def do_restore():
	global status, path, rpath
	win = popup_box("Restauration en cours...")
	try: os.mkdir(reimport)
	except OSError, e:
		if e[0] == errno.EEXIST: pass
		else: raise

	if rpath[0:14] == '/Old-Versions/': p = rpath[13:-11]
	else: p = rpath

	tmpname = mktemp('reimport')

	f = open(tmpname, 'w')
	f.write('%s%s\0' % (target, rpath))
	f.close()
	
	raise ("%s -q -ar --no-implied-dirs -e '%s -F %s/ssh.config' --file-from=%s --from0 %s" % (rsync, ssh, base, tmpname, reimport + '/' + p))
	os.system("%s -q -ar --no-implied-dirs -e '%s -F %s/ssh.config' --file-from=%s --from0 %s" % (rsync, ssh, base, tmpname, reimport + '/' + p))
	time.sleep(1)
	del win
	status = 'br'
	path_change(path)

def get_perms(file):

	if not perms.has_key(file): return ' ' * 21

	e = perms[file][0] / 1000				# Bits s, s, t

	pl = [ 'r', 'w', 'x' ]
	el = [ 's', 's', 't' ]
	h  = []

	for v in [perms[file][0] / 100, perms[file][0] / 10, perms[file][0]]:
		for b in [2, 1, 0]:
			if (v % 10) & (2 ** b):	h.append(pl[2 - b])
			else:							h.append('-')

		if e & 4:
			if h[-1] == '-': el[0] = el[0].upper()
			h[-1] = el[0]

		el, e = el[1:], e << 1

	if users and users.has_key(perms[file][1]):
		u = users[perms[file][1]].ljust(5)[0:5]
	else:
		u = '%5d' % perms[file][1]

	if groups and groups.has_key(perms[file][2]):
		g = groups[perms[file][2]].ljust(5)[0:5]
	else:
		g = '%5d' % perms[file][2]

	return '%s %s %s' % (''.join(h), u, g)

def fwin_scroll_up(n_lines = 1):
	# Scroll l'affichage vers le haut, mais n'effectue pas de refresh().
	global fwin, line_first, fwin_y, lines, line_selected
	if line_first - n_lines < 0: raise "fwin_scroll_up: would scroll above first line !"
	line_first -= n_lines
	if line_selected < line_first: line_selected = line_first
	elif line_selected >= line_first + fwin_y: line_selected = line_first + fwin_y - 1
	if n_lines >= fwin_y: fwin_update()
	else:
		fwin.move(0, 0)
		fwin.insdelln(n_lines)
		for ln in range(line_first, min(line_first + n_lines, len(lines))):
			fwin_draw_line(ln, ln == line_selected)

def fwin_scroll_down(n_lines = 1):
	# Scroll l'affichage vers le bas, mais n'effectue pas de refresh().
	global fwin, line_first, fwin_y, lines, line_selected
	if line_first + n_lines >= len(lines): raise "fwin_scroll_down: would scroll below last line !"
	line_first += n_lines
	if line_selected < line_first: line_selected = line_first
	elif line_selected >= line_first + fwin_y: line_selected = line_first + fwin_y - 1
	if n_lines >= fwin_y: fwin_update()
	else:
		fwin.move(0, 0)
		fwin.insdelln(-n_lines)
		for ln in range(line_first + fwin_y - 1 - n_lines, line_first + fwin_y):
			fwin_draw_line(ln, ln == line_selected)

def fwin_draw_line(ln, is_selected = 0):
	global line_first, fwin_y
	if not lines[ln][2]: return
	if is_selected:	attr_index = 2
	else:					attr_index = 1
	positions = lines[ln][2].keys()
	positions.sort()
	l = ln - line_first
	for pos in positions:
		if l == fwin_y - 1 and pos == positions[-1]:
			# Bidouille afin de pouvoir crire sur le dernier caractre de la dernire ligne de la fentre...
			fwin.addstr(l, pos, lines[ln][2][pos][0][1:], lines[ln][2][pos][attr_index])
			fwin.insch(l, pos, lines[ln][2][pos][0][1], lines[ln][2][pos][attr_index])
		else:
			fwin.addstr(l, pos, lines[ln][2][pos][0], lines[ln][2][pos][attr_index])

def fwin_select():
	# Selectionne la 1ere ligne selectionnable sur l'cran, en partant du haut.
	global lines, line_selected, line_first, fwin_y

	line_selected = -1

	for l in range(line_first, line_first + fwin_y):
		if lines[l][0] == True: line_selected = l; return

def fwin_update():

	global lines, line_first, line_selected, fwin_y

	fwin.erase()

	for ln in range(line_first, min(line_first + fwin_y, len(lines))):
		fwin_draw_line(ln, ln == line_selected)

	fwin.refresh()

def path_change(p):

	# Changement du rpertoire/fichier  afficher

	global lines, line_selected, line_first, fwin_x, path, files

	if not files.has_key(p) or files[p][0] not in('d', '-'): return

	old_path = path

	path = p

	screen_update()				# Pour mettre  jour le Path

	line_selected = -1

	lines = []

	p4 = fwin_x - 10
	p3 = p4 - 44
	p2 = p3 - 10
	p1 = 0

	if files[path][0] == '-':
		# Le path indique un fichier. On affiche les diffrentes versions disponibles pour ce fichier

		versions = files[path][4][:]

		nver = len(versions)

		if not nver: nver, av = nver + 1, " (pas de version archive)"
		elif files[path][2] != '????/??/??': nver, av = nver + 1, ''
		elif nver > 1: av = " (toutes archives)"
		else: av = " (version archive)"

		if files[path][2] != '????/??/??': versions.insert(0, 'version la plus rcente')

		if nver > 1: p = 's'
		else: p = ''

		lines.append([False, None,
			{0: [" %d version%s disponible%s pour ce fichier%s" % (nver, p, p, av), colors[4], colors[1]]}])

		lines.append([False, None, {0: [ '', colors[1], colors[1] ]}])

		for v in versions:
			if v[0] == 'v': s, d, h, p = files[path][1], files[path][2], files[path][3], path
			else:
				s, d, h = old_versions[path + '.' + v]
				p = '/Old-Versions%s.%s' % (path, v)
				v = 'backup du %s' % v
			v = '%-35s %10d %s %s' % (v, s, d, h)
			lines.append([True, p, {0: [ ' ' + v.ljust(fwin_x - 1), colors[1], colors[3]]}])

			fwin_select()

	else:
		# Le path indique un rpertoire. On affiche le contenu du rpertoire.

		if path == '/':	lines.append([False, '',            {0: [ '   '.ljust(fwin_x), colors[1], colors[3]]}])
		else:					lines.append([True,  dirname(path), {0: [ ' ..'.ljust(fwin_x), colors[1], colors[3]]}])

		for file in files[path][5]:

			if path != '/':	file_path = path + '/' + file
			else:					file_path = path + file

			if old_path == file_path: line_selected = len(lines)

			if   files[file_path][0] == 'd': ftype, csn, css = '(dir)', colors[6], colors[7]
			elif files[file_path][0] == 'l': ftype, csn, css = '(link)', colors[6], colors[7]
			elif files[file_path][0] in ('b', 'c'): ftype, csn, css = '(device)', colors[6], colors[7]
			else: ftype, csn, css = '%10d' % files[file_path][1], colors[1], colors[3]

			if files[file_path][2] == '????/??/??': ftype, attr, av = '-', ' ' * 21, 0
			else: av, attr = 1, '  %s %s' % (files[file_path][2], files[file_path][3])

			attr += '  %s' % get_perms(file_path)

			ftype = ftype.rjust(10)

			if files[file_path][0] == '-' and len(files[file_path][4]):
				versions = '%4d vers ' % (len(files[file_path][4]) + av)
			else:
				versions = ' ' * 10

			lines.append([True, file_path, {
						p1: [ ' ' + file.ljust(p2 - 1), colors[1], colors[3] ],
						p2: [ ftype,                     csn, css  ],
						p3: [ attr,                     colors[1], colors[3] ],
						p4: [ versions,                 colors[4], colors[5] ]
					}])

	if line_selected == -1: fwin_select()

	fwin_update()

def menu_restore():
	# Affiche un cran de rstauration de fichier/rpertoire
	global status, lines, files, line_selected, rpath

	if not line_selected: return		# On ne peut pas restaurer '..' (line_selected == 0 => '..')

	status = 're'

	rpath = lines[line_selected][1]

	if rpath[0:14] == '/Old-Versions/': p, s = rpath[13:-11], rpath[-11:]
	else: p, s = rpath, ''

	if files[p][0] == '-':		t = 'fichier'
	elif files[p][0] == 'd':	t = 'rpertoire'
	else: return

	line_selected, lines = -1, []

	lines.append([False, None, None])

	lines.append([False, None, {
		1: ["Restauration du %s : " % t, colors[4]],
		20 + len(t): [rpath, colors[2]]
		}])

	lines.append([False, None, None])
	lines.append([False, None, None])
	lines.append([False, None, {1: ["Ce fichier sera reimport ici : ", colors[1]]}])
	lines.append([False, None, None])
	lines.append([False, None, {1: ["%s%s" % (reimport, p + s), colors[9]]}])
	lines.append([False, None, None])
	lines.append([False, None, None])
	lines.append([False, None, {1: ["Confirmer la restauration [y/n] ?", colors[2]]}])

	fwin_update()

def menu_browse(t):
	# Affiche les fichiers de la target t

	global lines, status, target, fwin_x

	lines = []
	status = 'br'
	target = t
	screen_update()
	scr.refresh()
	load_files()
	path_change('/')

def help_open():
	# Affiche l'aide

	global help_win, sx, sy

	h = 25
	l = 80

	help_win = curses.newwin(h, l, (sy - h) / 2, (sx - l) / 2)
	help_win.bkgd(colors[5])
	help_win.border()

	msg = [ 'Touches :', '',
	   'Esc, q      quitte le programme',
		"h           affiche cet cran d'aide",
		"s           change le serveur de backup utilis",
		'Flches     dplace la slection',
		'Entre      active le rpertoire ou le fichier slectionn',
		'Backspace   revient au rpertoire parent',
		"r           recopie le fichier/rpertoire slectionn sur la machine locale",
		"            (dans %s)" % reimport
	]

	d, rev = lcd[18:37], lcr[22:].split()[0]

	line = "Clara-Backups, revision %s (%s)" % (rev, d)
	help_win.addstr(1, (l - len(line)) / 2, line, colors[10])
	line = " 2004 Yann GROSSEL / Claranet"
	help_win.addstr(2, (l - len(line)) / 2, line, colors[10])

	n = 4
	for line in msg:
		help_win.addstr(n, 2, line, colors[5])
		n += 1

	help_win.refresh()

def help_close():
	global help_win
	help_win = None
	scr.redrawwin()
	fwin.redrawwin()
	scr.refresh()
	fwin.refresh()

def menu_choose_target():
	# Affiche la liste des targets disponibles
	global lines, status, path, target
	status = 'ct'
	path = None

	raise "menu_choose_target(): TODO !"

	lines = []

	fwin.addstr(1, 1, "Veuillez choisir le serveur de backups  utiliser :", colors[1])

	for t in targets:
		lines.append([True, target, {0: [target, colors[1], colors[3]], 40: [str(whrandom.randint(0, 10000000)), colors[2], colors[4]]}])

	screen_update()

def key_down():

	global lines, line_selected, line_first, fwin_y

	if line_selected < len(lines) - 1:
		nl = -1
		for l in range(line_selected + 1, len(lines)):
			if lines[l][0]: nl = l; break
		if nl == -1: return
		fwin_draw_line(line_selected, 0)
		line_selected = nl
		if line_selected - line_first >= fwin_y: fwin_scroll_down()
		else: fwin_draw_line(line_selected, 1)
		fwin.refresh()

def key_up():

	global lines, line_selected, line_first

	if line_selected:
		nl = -1
		for l in range(line_selected -1, -1, -1):
			if lines[l][0]: nl = l; break
		if nl == -1: return
		fwin_draw_line(line_selected, 0)
		line_selected = nl
		if line_selected < line_first: fwin_scroll_up()
		else: fwin_draw_line(line_selected, 1)
		fwin.refresh()

def key_home():
	global line_first, line_selected
	if line_first:
		fwin_draw_line(line_selected, 0)
		fwin_select()
		fwin_scroll_up(line_first)
		fwin.refresh()
	elif line_selected:
		fwin_draw_line(line_selected, 0)
		fwin_select()
		fwin_draw_line(line_selected, 1)
		fwin.refresh()

def key_end():
	global lines, line_first, line_selected, fwin_y
	n = len(lines) - fwin_y
	if n > 0 and line_first != n:
		fwin_draw_line(line_selected, 0)
		line_selected = len(lines) - 1
		fwin_scroll_down(n - line_first)
		fwin.refresh()
	elif line_selected != len(lines) - 1:
		fwin_draw_line(line_selected, 0)
		line_selected = len(lines) - 1
		fwin_draw_line(line_selected, 1)
		fwin.refresh()

def key_page_down():
	pass

def key_page_up():
	pass

def key_enter():
	if not len(lines): return
	data = lines[line_selected][1]
	if status == 'br':
		path_change(data)
		return

def key_backspace():
	# Backspace : on remonte au rpertoire parent, si possible
	global path, status
	if status == 'br':
		if path == '/': return
		path_change(dirname(path))

def screen_update():
	# Met  jour la fenetre principale (suite  un changement de menu par exemple)
	global scr, status, path, bk_time

	# for a in range(3, 6):
	# 	scr.move(a, 1)
	#	scr.clrtoeol()

	if status in ('br', 're'):
		scr.addch(5, 0, curses.ACS_LTEE)
		scr.hline(5, 1, curses.ACS_HLINE, sx - 2)
		scr.addch(5, sx - 1, curses.ACS_RTEE)
		scr.addstr(3, 1, "Serveur:", colors[11])
		scr.addstr(3, sx - 41, "Date dernier backup:", colors[11])
		scr.addstr(4, 1, "Path:", colors[11])
		scr.addstr(3, 10, target, colors[2])

		if path:
			scr.addstr(4, 10, path.ljust(sx - 12), colors[2])

		if bk_time:
			scr.addstr(3, sx - 20, time.strftime("%Y/%m/%d %H:%M:%S", bk_time), colors[2])

def screen_redraw():
	# Redessine compltement la fenetre principale (au dpart, ou suite  un changement de taille du terminal)

	global sx, sy, fwin, target, status, fwin_x, fwin_y
	sy, sx = scr.getmaxyx()

	scr.erase()
	rev = "Clara-Backups (rev. %s)" % lcr[22:].split()[0]
	scr.addstr(1, sx - len(rev) - 1, rev, colors[11])
	scr.addstr(1, 1, hostname, colors[11])
	scr.hline(2, 1, curses.ACS_HLINE, sx - 2)

	if fwin: del fwin
	fwin_x, fwin_y = sx - 2, sy - 7
	fwin = curses.newwin(fwin_y, fwin_x, 6, 1)
	fwin.bkgd(colors[1])

	scr.border()

	scr.addch(2, 0, curses.ACS_LTEE)
	scr.addch(2, sx - 1, curses.ACS_RTEE)

	# Sous Linux, aprs un changement de taille de terminal, le curseur rapparait. Un appel simple
	#  curs_set(0) ne fonctionne pas car visiblement curses pense que le curseur est toujours invisible
	# et ne se donne pas la peine de le cacher de nouveau. On est donc oblig de le rendre visible puis
	# de le recacher immdiatement pour que ca fonctionne...

	curses.curs_set(1)
	curses.curs_set(0)

	screen_update()

def main(stdscr):

	global sx, sy, targets, status, target, path, scr, status, help_win, colors

	bad_term = "\nNeed at least a 80x25 terminal :(\n\n"

	scr = stdscr

	y, x = scr.getmaxyx()

	if (y < 25 or x < 80): return bad_term							# Terminal trop petit

	# Dfinition des couleurs (Attention, utiliser un attribut (Gras...) avec la fonction bkgd() place l'attribue
	# pour toute la fenetre !)

	dcols = [
		[ curses.COLOR_WHITE,  curses.COLOR_BLUE,		0					],		# 1	Blanc sur Bleu
		[ curses.COLOR_YELLOW, curses.COLOR_BLUE,		curses.A_BOLD	],		# 2	Jaune sur Bleu, gras
		[ curses.COLOR_BLUE,   curses.COLOR_WHITE,	0					],		# 3	Bleu sur Blanc
		[ curses.COLOR_GREEN,  curses.COLOR_BLUE,		curses.A_BOLD	],		# 4	Vert sur Bleu, gras
		[ curses.COLOR_RED,    curses.COLOR_WHITE,	0					],		# 5	Rouge sur Blanc
		[ curses.COLOR_CYAN,   curses.COLOR_BLUE,		0					],		# 6	Cyan sur Bleu
		[ curses.COLOR_CYAN,   curses.COLOR_WHITE,	0					],		# 7	Cyan sur Blanc
		[ curses.COLOR_WHITE,  curses.COLOR_CYAN,		0					],		# 8	Blanc sur Cyan
		[ curses.COLOR_WHITE,  curses.COLOR_BLUE,		curses.A_BOLD	],		# 9	Blanc sur Bleu, gras
		[ curses.COLOR_RED,    curses.COLOR_WHITE,	curses.A_BOLD	],		# 10	Rouge sur Blanc, gras
		[ curses.COLOR_WHITE,  curses.COLOR_BLUE,		curses.A_BOLD	],		# 11	Blanc sur Bleu, gras
	]

	colors = {}

	i = 0
	for l in dcols:
		i += 1
		curses.init_pair(i, l[0], l[1])
		colors[i] = curses.color_pair(i)
		if l[2]:
			colors[i] += l[2]

	#

	scr.bkgd(colors[1])

	curses.curs_set(0)

	screen_redraw()

	scr.refresh()

	if len(targets) > 1: menu_choose_target()
	else: menu_browse(targets[0])

	while 1:

		y, x = scr.getmaxyx()

		if (y < 25 or x < 80): return bad_term						# Terminal trop petit

		if sx != x or sy != y: screen_redraw()						# La taille du terminal a chang

		try: c = scr.getch()
		except KeyboardInterrupt: return None

		if c == -1: continue

		if status == 're':
			if c in (89, 121):						# Y, y
				do_restore()
			elif c in (27, 81, 113, 78, 110):	# Esdc, Q, q, N, n
				status = 'br'
				path_change(path)
			continue

		if help_win: help_close(); continue

		if c in (27, 81, 113):						return None									# Esc, Q, q
		if c in (72, 104):							help_open(); continue					# H, h
		if c in (82, 114):							menu_restore(); continue				# R, r
		if c in (83, 115):							menu_choose_target(); continue		# S, s
		if c in (10, curses.KEY_ENTER):			key_enter();		continue
		if c in (curses.KEY_BACKSPACE, 127):	key_backspace();	continue
		if c == curses.KEY_DOWN:					key_down();			continue
		if c == curses.KEY_UP:						key_up();			continue
		if c == curses.KEY_HOME:					key_home();			continue
		if c == curses.KEY_END:						key_end();			continue
		if c == curses.KEY_NPAGE:					key_page_down();	continue
		if c == curses.KEY_PPAGE:					key_page_up();		continue

	 	scr.addstr(1, 1, "KEY : %d    " % c)

#

if os.getuid():
	sys.stdout.write("\nThis program must be run as root !\n\n")
	sys.exit(1)

if not sys.stdout.isatty():
	sys.stdout.write("\nSTDOUT is not a terminal !\n\nAborting now.\n\n")
	sys.exit(1)

hostname = os.uname()[1]
hostname = hostname[0].upper() + hostname[1:]

read_config()

err = curses.wrapper(main)

if err: sys.stderr.write(err)

sys.exit(0)

