#!/usr/bin/python
# -*- coding: utf-8 -*-

import zipfile, os, gtk, stat, gobject, sys, logging
from lib import wrap_label, scrollframe, urlstuff

class modifs(gtk.Dialog):
	def __init__(self, parent, db):
		self.db = db
		
		self.install_list = []
		self.uninstall_list = []
		self.update_list = []
		
		# Initialisation de la fenetre
		gtk.Dialog.__init__(self, "", parent, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_NO_SEPARATOR, None)
		self.set_icon_list(gtk.gdk.pixbuf_new_from_file("icons/SynApps16.png"), gtk.gdk.pixbuf_new_from_file("icons/SynApps24.png"), gtk.gdk.pixbuf_new_from_file("icons/SynApps32.png"), gtk.gdk.pixbuf_new_from_file("icons/SynApps64.png"), gtk.gdk.pixbuf_new_from_file("icons/SynApps128.png"))
		self.set_border_width(5)
		self.set_default_size(400, 100)
		
		vbox = self.get_content_area()
		
		self.titlelabel = gtk.Label()
		self.titlelabel.set_use_markup(True)
		vbox.pack_start(self.titlelabel, False)
		
		self.desclabel = wrap_label.WrapLabel("")
		vbox.pack_start(self.desclabel, False)
		
		self.progress = gtk.ProgressBar()
		vbox.pack_start(self.progress, False)
		
		self.expander = gtk.Expander("Détails")
		vbox.pack_start(self.expander, True)

		self.liste = progress_list()
		scroll = scrollframe.scrollframe()
		scroll.add_with_viewport(self.liste)
		
		self.expander.add(scroll)
		
	def on_fenetre_destroy(self, widget):
		self.init_modifs()
		
	def set_title(self, title="", desc=""):
		gtk.Dialog.set_title(self, title)
		self.titlelabel.set_markup("<big><b>" + title + "</b></big>")
		self.desclabel.set_text(desc)
	
	def set_progress(self, count, blockSize, totalSize):
		if totalSize != 0:
			pourcentage = float(count)*float(blockSize)/float(totalSize)
			if pourcentage > 1:
				pourcentage = 1
				
			self.liste.store.set_progress(self.current_app, pourcentage)
			
			total = (float(count)*float(blockSize) + float(self.currentsize))/float(self.totalsize)
			if total > 1:
				total = 1
			self.progress.set_fraction(total)
			self.progress.set_text(self.entete + " (" + str(self.currentcount) + " sur " + str(self.totalcount) + ")")
		
		while gtk.events_pending():
			gtk.main_iteration_do(False)
	
	def run(self):
		self.show_all()
		
		self.install_list = [dict(app) for app in self.db.get_apps_to_install()]
		self.uninstall_list = [dict(app) for app in self.db.get_apps_to_uninstall()]
		self.update_list = [dict(app) for app in self.db.get_apps_to_update()]
		
		for app in self.install_list:
			app['dirsaves'] = app['absdirsaves'] = []
		for app in self.uninstall_list:
			app['dirsaves'] = app['absdirsaves'] = []
		for app in self.update_list:
			app['dirsaves'] = [os.path.normpath(os.path.join(app["dir"], dir)) for dir in self.db.get_dirsave(app["appid"])]
			app['absdirsaves'] = [os.path.normpath(os.path.join(self.db.get_config("appspath", ""), app["installdir"], dir)) for dir in app['dirsaves']]
		
		self.install_list.extend(self.update_list)
		self.uninstall_list.extend(self.update_list)
		
		gobject.idle_add(self.execute)
		gtk.Dialog.run(self)
	
	def install_package(self, filename, app):
		self.show_all()
		
		app['package'] = filename
		app['dirsaves'] = app['absdirsaves'] = []
		
		self.install_list = [app]
		self.uninstall_list = []
		self.update_list = []
		
		gobject.idle_add(self.execute)
		gtk.Dialog.run(self)
	
	def update_package(self, filename, app):
		self.show_all()
		
		app['package'] = filename
		app['dirsaves'] = [os.path.normpath(os.path.join(app["dir"], dir)) for dir in app["dirsave"]]
		app['absdirsaves'] = [os.path.normpath(os.path.join(self.db.get_config("appspath", ""), app["installdir"], dir)) for dir in app['dirsaves']]
		
		self.install_list = [app]
		self.uninstall_list = []
		self.update_list = [app]
		
		gobject.idle_add(self.execute)
	
	def execute(self):
		self.expander.grab_add()
		
		logging.info("Lancement des modifications")
		
		self.download()
		self.uninstall()
		self.extract()
		self.delete()
		
		logging.info("Fin des modifications")
		
		self.expander.grab_remove()
		
		self.destroy()

	def download(self):
		logging.info("Téléchargement des paquets")
		download_list = [app for app in self.install_list if not app.has_key('package')]
		if len(download_list) > 0:
			self.set_title("Téléchargement des paquets en cours", "Les paquets sont mis en cache afin d'être installés.")

			self.liste.store.set_apps(download_list)
			
			self.totalcount = len(download_list)
			self.totalsize = 0
			for app in download_list:
				self.totalsize += app["zipsize"]
			
			self.currentcount = 0
			self.currentsize = 0
			
			self.entete = "Téléchargement des paquets "
			
			for app in download_list:
				logging.debug("Sauvegarde du paquet %s" % app["name"])
				self.currentcount += 1
				self.current_app = app["appid"]
				
				dl = self.db.get_downloads(app["appid"])[0]
				
				app['package'] = './cache/packages/' + app["name"] + "-" + dl["version"] + '.fmk.zip'
				
				#try:
				urlstuff.httpDownload(dl["link"], app['package'], self.set_progress)
				#except:
				#	print "Erreur"
				self.currentsize += app["zipsize"]
			
			self.progress.set_fraction(1)
		
	def uninstall(self):
		logging.info("Suppression des applications")
		if len(self.uninstall_list) > 0:
			self.set_title("Suppression des applications en cours", "Les applications à désinstaller ou à mettre à jour sont supprimées.")

			self.liste.store.set_apps(self.uninstall_list)
			
			self.totalcount = len(self.uninstall_list)
			self.totalsize = 0
			for app in self.uninstall_list:
				self.totalsize += get_size(os.path.join(self.db.get_config("appspath", ""), app["installdir"], app["dir"]), app['absdirsaves'])
			
			self.currentcount = 0
			self.currentsize = 0
			
			self.entete = "Suppression des applications "
			
			for app in self.uninstall_list:
				logging.debug("Suppression de l'application %s" % app["name"])
				self.currentcount += 1
				self.current_app = app["appid"]
				
				#try:
				rmtree(os.path.join(self.db.get_config("appspath", ""), app["installdir"], app["dir"]), True, self.set_progress, app['absdirsaves'])
				#except:
				#	print "Erreur"
				self.currentsize += app["size"]
			
			self.progress.set_fraction(1)
		
	def extract(self):
		logging.info("Extraction des paquets")
		if len(self.install_list) > 0:
			self.set_title("Extraction des paquets en cours", "Les applications sont installées dans le dossier Apps.")

			self.liste.store.set_apps(self.install_list)
			
			self.totalcount = len(self.install_list)
			self.totalsize = 0
			for app in self.install_list:
				self.totalsize += app["size"]
			
			self.currentcount = 0
			self.currentsize = 0
			
			self.entete = "Extraction des paquets "
			
			for app in self.install_list:
				logging.debug("Extraction du paquet %s" % app["name"])
				self.currentcount += 1
				self.current_app = app["appid"]
				
				#try:
				zipextractall(app['package'], os.path.join(self.db.get_config("appspath", ""), app["installdir"]) + "/", self.set_progress, exclude=app['dirsaves'])
				#except:
				#	print "Erreur"
				self.currentsize += app["size"]
			
			self.progress.set_fraction(1)
		
	def delete(self):
		for path in os.listdir("cache/packages"):
			os.remove(os.path.join("cache/packages", path))
		for path in os.listdir("cache/backup"):
			os.remove(os.path.join("cache/backup", path))
	
	# Installation de paquet local

class progress_store(gtk.ListStore):
	def __init__(self):
		gtk.ListStore.__init__(self, str, int, str)
	
	def set_apps(self, liste):
		self.clear()
		self.iters = {}
		
		for app in liste:
			self.iters[app["appid"]] = self.append([app["name"], 0, "0 %"])
	
	def set_progress(self, app, value):
		self.set_value(self.iters[app], 1, value*100)
		self.set_value(self.iters[app], 2, str(int(value*100)) + " %")
		
class progress_list(gtk.TreeView):
	def __init__(self):
		self.store = progress_store()
		gtk.TreeView.__init__(self, self.store)
		self.set_headers_visible(False)
		
		celltext = gtk.CellRendererText()
		cellprogress = gtk.CellRendererProgress()
		
		textcolumn = gtk.TreeViewColumn('App', celltext, markup=0)
		progresscolumn = gtk.TreeViewColumn('Progression', cellprogress, value=1, text=2)
		
		self.append_column(textcolumn)
		self.append_column(progresscolumn)

def rmtree(path, ignore_errors=False, callback=None, exclude=[], onerror=None, foldersize=0, delsize=0):
	"""Recursively delete a directory tree.

	If ignore_errors is set, errors are ignored; otherwise, if onerror
	is set, it is called to handle the error with arguments (func,
	path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
	path is the argument to that function that caused it to fail; and
	exc_info is a tuple returned by sys.exc_info().  If ignore_errors
	is false and onerror is None, an exception is raised.
	"""
	if foldersize == 0 and os.path.isdir(path):
		for (folder, dirs, files) in os.walk(path):
			for file in files:
				foldersize += os.path.getsize(os.path.join(folder, file))
	
	if callback is None:
		def callback(count, blockSize, totalSize):
			return None
	
	if ignore_errors:
		def onerror(*args):
			pass
	elif onerror is None:
		def onerror(*args):
			raise
	try:
		if os.path.islink(path):
			# symlinks to directories are forbidden, see bug #1669
			raise OSError("Cannot call rmtree on a symbolic link")
	except OSError:
		onerror(os.path.islink, path, sys.exc_info())
		# can't continue even if onerror hook returns
		return
	names = []
	try:
		names = os.listdir(path)
	except os.error, err:
		onerror(os.listdir, path, sys.exc_info())
	for name in names:
		fullname = os.path.join(path, name)
		try:
			mode = os.lstat(fullname).st_mode
		except os.error:
			mode = 0
		if not fullname in exclude:
			if stat.S_ISDIR(mode):
				delsize = rmtree(fullname, ignore_errors, callback, exclude, onerror, foldersize, delsize)
			else:
				try:
					delsize += os.path.getsize(fullname)
					os.remove(fullname)
					callback(1, delsize, foldersize)
				except os.error, err:
					onerror(os.remove, fullname, sys.exc_info())
	try:
		os.rmdir(path)
	except os.error:
		onerror(os.rmdir, path, sys.exc_info())
		
	return delsize

def zipextractall(zip, path=None, callback=None, members=None, pwd=None, exclude=[]):
	"""Extract all members from the archive to the current working
	   directory. `path' specifies a different directory to extract to.
	   `members' is optional and must be a subset of the list returned
	   by namelist().
	"""
	zip = zipfile.ZipFile(zip, 'r')
	
	if callback is None:
		def callback(count, blockSize, totalSize):
			return None
	
	if members is None:
		members = zip.namelist()
	
	for i in exclude:
		for j in members:
			if os.path.normpath(j).startswith(i):
				members.remove(j)
	
	zipsize = 0
	for infos in zip.infolist():
		zipsize += infos.file_size
	dirsize = 0
	
	for zipinfo in members:
		zip.extract(zipinfo, path, pwd)
		dirsize += zip.getinfo(zipinfo).file_size
		callback(1, dirsize, zipsize)

	zip.close()

def create_zip(zip, root, paths, callback=None):
	if callback == None:
		def callback(count, blockSize, totalSize):
			return None
	
	if os.path.isfile(zip):
		os.remove(zip)
	
	totalsize = 0
	currentsize = 0
	
	for path in paths:
		totalsize += get_size(os.path.join(root, path))
	
	if totalsize > 0:
		zip = zipfile.ZipFile(zip, 'w')
		
		for path in paths:
			for file in get_files(root, path):
				currentsize += os.path.getsize(os.path.join(root, file))
				zip.write(os.path.join(root, file), file)
				callback(1.0, currentsize, totalsize)
				
		zip.close()

def get_zip_size(zip):
	try:
		zip = zipfile.ZipFile(zip, 'r')
	except:
		return 0
	
	zipsize = 0
	for infos in zip.infolist():
		zipsize += infos.file_size

	zip.close()
	
	return zipsize

"""def get_size(path, exclude=[]):
	size = 0
	if os.path.isdir(path):
		for root, dirs, files in os.walk(path): 
			for file in files:
				size += os.path.getsize(os.path.join(root, file))
	if os.path.isfile(path):
		size += os.path.getsize(path)
	return size"""
	
def get_size(path, exclude=[]):
	size = 0
	if os.path.isdir(path):
		for filename in os.listdir(path):
			if not os.path.join(path, filename) in exclude:
				size += get_size(os.path.join(path, filename), exclude)
	if os.path.isfile(path):
		size += os.path.getsize(path)
	return size


def get_files(root, path):
	files = []
	
	if os.path.isfile(os.path.join(root, path)):
		files.append(os.path.relpath(os.path.join(root, path), root))
	if os.path.isdir(os.path.join(root, path)):
		for dirpath, dirnames, filenames in os.walk(os.path.join(root, path)):
			for dirname in dirnames:
				files.append(os.path.relpath(os.path.join(dirpath, dirname), root))
			for filename in filenames:
				files.append(os.path.relpath(os.path.join(dirpath, filename), root))
	return files
