#!/usr/bin/env python
# encoding=utf-8

##    Qemulator 0.4.4 - a qemu gui written in python and GTK/Glade.
##    Copyright (C) 2006  rainer haage
##
##    This program is free software; you can redistribute it and/or
##    modify it under the terms of the GNU General Public License
##    as published by the Free Software Foundation; either version 2
##    of the License, or (at your option) any later version.
##    
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##    
##    You should have received a copy of the GNU General Public License
##    along with this program; if not, write to the Free Software
##    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import sys
import os
from os import path
import popen2
from popen2 import Popen3
import gobject
import gettext
import string
import pickle
import time
import threading
from threading import Thread
import shutil
import webbrowser
try:
    from egg import trayicon
    nognomextras = False
except :
    nognomextras = True
#nognomextras = True


import re
from copy import copy
from copy import deepcopy

try:
    import gettext
    import locale
except:
    print "locale and/or gettext for language support not installed"
    sys.exit(1)


try:
    import pygtk
    pygtk.require("2.0")
    
except:
      print "pygtk 2.0 is not installed"
try:
    import gtk
    import gtk.glade
    gtk.gdk.threads_init()
    
except:
    print "gtk and/or glade is not installed - exiting..."
    sys.exit(1)
    
encoding = locale.getpreferredencoding()
utf8conv = lambda x : unicode(x, encoding).encode('utf8')
    
#for param in os.environ.keys():
#    print "%20s %s" % (param,os.environ[param])
    
current_path = os.path.realpath(__file__)
basedir = os.path.dirname(os.path.realpath(__file__))
if not os.path.exists(os.path.join(basedir, "main.py")):
    if os.path.exists(os.path.join(os.getcwd(), "main.py")):
        basedir = os.getcwd()
sys.path.insert(0, basedir)
os.chdir(basedir)  
#print "basedir: " + basedir
basedir = basedir.replace ( '/bin', '' ) 
basedir = basedir.replace ( '/lib/qemulator', '' ) 
sys.path.append(basedir)   
bindir = basedir + "/bin"
#print "bindir: " + bindir
libdir = basedir + "/lib/qemulator"
#print "libdir: " + libdir
sharedir = basedir + "/share/qemulator"
#print "sharedir: " + sharedir
pixmapdir = basedir + "/share/pixmaps/qemulator"
#print "pixmapdir: " + pixmapdir     
homedir = os.path.expanduser('~/.qemulator')
configdir = homedir + "/config"
#print "configdir: " + configdir           
icondirs = [sharedir + "/icons",homedir + "/icons"] 

## Import all Qemulator classes    
import qml_filehandlers
import qml_style

APP = 'Qemulator'
#DIR = 'locale'
DIR = basedir + '/share/locale'
#print "DIR: " + DIR
locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
_ = gettext.gettext

gtk.glade.bindtextdomain(APP, DIR)
gtk.glade.textdomain(APP)
tooltips = gtk.Tooltips() 

class GTK_tools:
    def __init__(self):
        pass
    
    def search_combolist(self, combobox, searchstring):
        ct = 0
        outct = None
        cdmodel = combobox.get_model()
        for row in cdmodel:

            if searchstring == row[0]:
                outct = ct
            ct = ct + 1           
        return outct
class DelayedActions ( Thread ):

    def __init__ (self, rem_func=None):
        Thread.__init__(self) 
        self.rem_func = rem_func    
        
    def run(self): 
        
        #gtk.gdk.threads_enter()
        self.rem_func()
        #gtk.gdk.threads_leave() 
        
class DelayedGUIactions ( Thread ):

    def __init__ (self, rem_func=None):
        Thread.__init__(self) 
        self.rem_func = rem_func    
        
    def run(self): 
        
        gtk.gdk.threads_enter()
        self.rem_func()
        gtk.gdk.threads_leave()    
    
def run_command(command):
        if command != '':
            child_stdin, child_stdout, child_stderr = os.popen3(command)
            child_stdin.close()    
            errormesg = child_stderr.readlines()
            resultmesg = child_stdout.readlines()
            if errormesg != []:
                #self.general_error = True
                print "Error: " + str(errormesg)
                returnmesg = errormesg
                child_stderr.close()
                child_stdout.close()
            else:
                returnmesg = resultmesg
        else:
            returnmesg = "no command given!"
        return returnmesg 
    
def striplist(l):
    return([x.strip() for x in l])     
         
class ImageInfo:
    def __init__ (self, imagepath=None):
        #Thread.__init__(self)
        self.imagepath = imagepath
        self.filename = None
        self.type = None
        self.filesize = None
        info = None
        
    def get_file_info(self, imagepath=None):
        '''Returns the result of file [imagepath] as a dictionary
        that contains all returned parameters.'''
        info = None
        result = None
        if imagepath!= None:
            self.imagepath = imagepath
        if self.imagepath!= 'None'  and self.imagepath!= '' and self.imagepath!= None:
            #print 'self.imagepath: ' + str(self.imagepath)
            command = "file -s -b -L " + str(self.imagepath)
            result = run_command(command)
            print 'result: ' + str(result)
        info = self.parse_file_info(result)
        return info 
    
    def parse_file_info(self, data): 
        '''Returns the data, which must be a result of "file command" as a dictionary
        that contains all returned parameters.'''
        self.infodic = {}
        expressions = {}
        filters = {}
        checkname = None
        resultline = ""
        bootable = True

        expressions['isoboot'] = re.compile('(ISO 9660 CD-ROM filesystem data)(\W+)(\\\')(.+)(\\\')(\W+)(\()(bootable)(\))')
        #expressions['isonoboot'] = re.compile('(ISO 9660 CD-ROM filesystem data)(\W+)(\\\')(.+)(\\\')(\W*$)')
        expressions['qcownoboot'] = re.compile('(data)(\W+)(\\\'*)(.*)(\\\'*)(\W*$)')
        expressions['xboot'] = re.compile('(x86 boot sector)(,?)(\W*)(.*)')
        expressions['isonoboot'] = re.compile('(ISO 9660 CD-ROM filesystem data)(\W+)(\\\')(\W*$)')
        expressions['qcow1'] = re.compile('(QEMU Copy-On-Write disk image version 1)(,)(\W+)(.+)')
        expressions['qcow2'] = re.compile('(QEMU Copy-On-Write disk image version 2)(,)(\W+)(.+)')
        expressions['block'] = re.compile('(block \W{1}\w+)(,)(\W+)(\(.+\))')
        
        filters['isoboot'] = "\g<1> ,\g<4>, ,\g<6>"
        filters['isonoboot'] = "\g<1> ,\g<4>"
        filters['qcownoboot'] = "\g<1> ,\g<4>"
        filters['xboot'] = "\g<1> ,\g<4>"
        filters['qcow1'] = "\g<1> ,\g<4>"
        filters['qcow2'] = "\g<1> ,\g<4>"
        filters['block'] = "\g<1> ,\g<4>"

        self.infodic['bootable'] = False
        self.infodic['medium'] = None
        self.infodic['infotext'] = None
        self.infodic['infoname'] = None
        self.infodic['infodata'] = None
        suggestion_suffix = _('If this suggestion is not correct, please change the settings to your needs below.')
        suggestion_suffix_noboot = _('If you are sure that it is still a bootable image, please go on with this settings.')
        suggestion_hda = _('The medium seems to be a bootable harddisk (image). ') + suggestion_suffix
        suggestion_cdrom = _('The medium seems to be a bootable cdrom (image). ') + suggestion_suffix
        suggestion_cdrom_noboot = '<span foreground="red">' + _('The medium seems to be a non-bootable cdrom (image). ') + suggestion_suffix_noboot + '</span>'
        suggestion_hda_noboot = '<span foreground="red">' + _('The medium seems to be a non-bootable harddisk (image). ') + suggestion_suffix_noboot + '</span>'
        suggestion_block = _('The medium seems to be a block device. I guess this is a bootable cdrom. ') + suggestion_suffix
        suggestion_noboot = '<span foreground="red">' + _('The medium type could not be detected! If you are sure that the selected boot medium is, a bootable image or device, please select the required type and go on with the setup.') + '</span>'
        
        if data != None and data != 'None':   
            data = str(data)
            print 'data: ' + str(data)
            line = string.strip(data)
            lc = 0
            #self.snapshots_list = [] 
                       
            for key in expressions.keys():
                checkname = key
                print 'checking key: ' + str(checkname)
                expression = expressions[key]
                checktype = expression.search(line)
                print 'checktype: ' + str(checktype)
                if checktype != None and checktype != '':
                    resultline = checktype.group()
                    resultline = string.strip(resultline)
                    print 'break at: ' + str(checkname)
                    break
            if checkname != None:
                infostring = expressions[checkname].sub(filters[checkname], resultline)
                print 'infostring: ' + str(infostring)
                infolist = infostring.split(",")
                infolist = striplist(infolist)
                #for val in infolist:
                #for key in infolist.keys():
                    #val = string.strip(val)
                    #infolist[key] = string.strip(infolist[key])
                print 'infolist: ' + str(infolist)
                if infolist != ['']:
                    if checkname == 'isoboot':
                        self.infodic['bootable'] = True
                        self.infodic['medium'] = 'cdrom'
                        self.infodic['infotext'] = string.strip(infolist[0])
                        self.infodic['infoname'] = string.strip(infolist[1])
                        self.infodic['infodata'] = None
                        self.infodic['suggestion'] = suggestion_cdrom
                    elif checkname == 'isonoboot':
                        self.infodic['bootable'] = False
                        self.infodic['medium'] = 'cdrom'  
                        self.infodic['infotext'] = string.strip(infolist[0])
                        self.infodic['infoname'] = string.strip(infolist[1])
                        self.infodic['infodata'] = None   
                        self.infodic['suggestion'] = suggestion_cdrom_noboot  
                    elif checkname == 'qcownoboot':
                        self.infodic['bootable'] = False
                        self.infodic['medium'] = 'hda'  
                        self.infodic['infotext'] = string.strip(infolist[0])
                        self.infodic['infoname'] = string.strip(infolist[1])
                        self.infodic['infodata'] = None   
                        self.infodic['suggestion'] = suggestion_hda_noboot                                    
                    elif checkname == 'xboot':
                        self.infodic['bootable'] = True
                        self.infodic['medium'] = 'hda'
                        self.infodic['infotext'] = string.strip(infolist[0])
                        self.infodic['infoname'] = None
                        self.infodic['infodata'] = string.strip(infolist[1])
                        self.infodic['suggestion'] = suggestion_hda
                    elif checkname == 'qcow1' or checkname == 'qcow2':
                        self.infodic['bootable'] = True
                        self.infodic['medium'] = 'hda'
                        self.infodic['infotext'] = string.strip(infolist[0])
                        self.infodic['infoname'] = None
                        self.infodic['infodata'] = string.strip(infolist[1])  
                        self.infodic['suggestion'] = suggestion_hda                  
                    elif checkname == 'block':
                        self.infodic['bootable'] = True
                        self.infodic['medium'] = 'cdrom'
                        self.infodic['infotext'] = string.strip(infolist[0])
                        self.infodic['infoname'] = None
                        self.infodic['infodata'] = string.strip(infolist[2])
                        self.infodic['suggestion'] = suggestion_block                    
                                                
                    #return self.infodic  
            else:
                self.infodic['bootable'] = False
                self.infodic['medium'] = None
                self.infodic['infotext'] = None
                self.infodic['infoname'] = None
                self.infodic['infodata'] = None
        else:
            self.infodic['bootable'] = False
            self.infodic['medium'] = None
            self.infodic['infotext'] = None
            self.infodic['infoname'] = None
            self.infodic['infodata'] = None
            self.infodic['suggestion'] = suggestion_noboot
        return self.infodic
        
       

    def get_qemuimg_info(self, imagepath=None):
        '''Returns the result of qemu-img info [imagepath] as a dictionary
        that contains all returned parameters.'''
        info = None
        if imagepath!= None:
            self.imagepath = imagepath
        if self.imagepath!= None:            
            #print 'self.imagepath: ' + str(self.imagepath)
            command = "qemu-img info " + str(self.imagepath)
            result = run_command(command)
            info = self.parse_qemuimg_info(result)
        return info
        
    def parse_qemuimg_info(self, data): 
        '''Returns the data, which must be a result of qemu-img info as a dictionary
        that contains all returned parameters.'''         
        outlist = filter(None, data)
        lc = 0
        #self.snapshots_list = []
        self.infodic = {}

        infoexp = re.compile('([0-9a-zA-Z_\W]+)(:)(\W+)(.+)')
        vsizeexp = re.compile('([0-9a-zA-Z_\W]+)(\W+)(\()(.+)(\))')

        infogroups = "\g<1>, ,\g<4>"
        sizegroup_short = "\g<1>"
        #print '-------Snapshots------------------'
        for line in outlist:
            line = string.strip(line)
            #print '---'
            #print 'line: ' + str(line)
            
            searchinfo = infoexp.search(line)
            if searchinfo != None and searchinfo != '':
                resultline = searchinfo.group()
                resultline = string.strip(resultline)
                #snapsub = listexp1.sub(versolate1, snapstr)
                #print 'resultline: ' + str(resultline) 
                outdata = resultline.split(":")
                #for data in outdata:
                var = outdata[0]
                var = string.strip(var)
                val = outdata[1]
                val = string.strip(val)
                if var == 'virtual size':
                    vsize_short = vsizeexp.sub("\g<1>", val)
                    vsize_long = vsizeexp.sub("\g<4>", val)
                    self.infodic['vsize_short'] = vsize_short                
                    self.infodic['vsize_long'] = vsize_long
                else:
                    if var != 'Snapshot list':
                        self.infodic[var] = val
        return self.infodic
            
                
    def parse_qemuimg_snapshots(self, data):  
        outlist = filter(None, data)
        lc = 0
        self.snapshots_list = []
        self.infodic = {}
        
        #listexp = re.compile('([0-9]+)(\W+)(\B|\w+)(\W+)(\b\w+)(\W+)([0-9\-]+)(\W+)([0-9:]+)(\W+)([0-9:\.]+)')
        listexp1 = re.compile('([0-9]+)(\W+)(\w+)(\W+)(\w+)(\W+)([0-9]{4}-[0-9]{2}-[0-9]{2}\W{1}[0-9]{2}:[0-9]{2}:[0-9]{2})(\W+)([0-9:\.]+)')
        listexp2 = re.compile('([0-9]+)(\W+)(\w+)(\W+)([0-9]{4}-[0-9]{2}-[0-9]{2}\W{1}[0-9]{2}:[0-9]{2}:[0-9]{2})(\W+)([0-9:\.]+)')

        versolate1 = "\g<1>,\g<3>,\g<5>,\g<7>,\g<9>"
        versolate2 = "\g<1>, ,\g<3>,\g<5>,\g<7>"
        #print '-------Snapshots------------------'
        for line in outlist:
            line = string.strip(line)
            #print '---'
            #print 'line: ' + str(line)
            #snaprender = listexp.search(line)
            
            snaprender = None
            snaprender1 = listexp1.search(line)
            snaprender2 = listexp2.search(line)
            
            if snaprender1 != None and snaprender1 != '':
                snapstr = snaprender1.group()
                snapstr = string.strip(snapstr)
                snapsub = listexp1.sub(versolate1, snapstr)
                #print 'snapsub:' + str(snapsub) 
                devdata = snapsub.split(",", 5)
                self.snapshots_list.append(devdata)
            elif snaprender2 != None and snaprender2 != '':
                snapstr = snaprender2.group()
                snapstr = string.strip(snapstr)
                snapsub = listexp2.sub(versolate2, snapstr)
                #print 'snapsub:' + str(snapsub)    
                devdata = snapsub.split(",", 5)    
                self.snapshots_list.append(devdata)                  
        return self.snapshots_list
       
        
class Dialog:
    def __init__ (self, type, text, title=None, returnfunc=None, return_args=[]):
        #Thread.__init__(self)
        self.type = type
        self.text = text
        self.title = title
        self. returnfunc = returnfunc
        self.return_args = ",".join(return_args)
        self.gladefile = libdir + "/qemulator.glade" 
        #self.run()  
        self.init_dialog() 

    #def run(self): 
    #    self.init_dialog()
            
    def init_dialog(self):
        self.wTree = gtk.glade.XML(self.gladefile, "dialog_universal", APP)
        self.dialog = self.wTree.get_widget("dialog_universal")
        self.dialog.set_keep_above(True)
        
        self.label_messagetext = self.wTree.get_widget("label_messagetext")
        self.textview_longtext = self.wTree.get_widget("textview_longtext")
        self.scrolledwindow_longtext = self.wTree.get_widget("scrolledwindow_longtext")
                
        
        if self.type != "longtext":
            self.label_messagetext.show_all()
            self.scrolledwindow_longtext.hide()
        else:
            #self.textview_longtext = self.wTree.get_widget("textview_longtext")
            self.label_messagetext.hide()
            self.scrolledwindow_longtext.show_all()
            
        self.image_messageitem = self.wTree.get_widget("image_messageitem")
        
        self.applybutton = self.wTree.get_widget("applybutton")
        self.cancelbutton = self.wTree.get_widget("cancelbutton")
        self.okbutton = self.wTree.get_widget("okbutton")
        self.closebutton = self.wTree.get_widget("closebutton")
        dic = {"on_dialog_universal_response" : self.dialog_response,        
        }
        self.wTree.signal_autoconnect(dic)
        if self.type == "info":
            self.applybutton.hide()
            self.cancelbutton.hide()
            self.okbutton.hide()
            self.image_messageitem.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_DIALOG)
        if self.type == "message" or self.type == "longtext":
            self.applybutton.hide()
            self.cancelbutton.hide()
            self.closebutton.hide()
            self.image_messageitem.set_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DIALOG)        
        elif self.type == "warning":
            self.applybutton.hide()
            self.cancelbutton.hide()
            self.closebutton.hide()
            self.image_messageitem.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG)
        elif self.type == "error":
            self.applybutton.hide()
            self.cancelbutton.hide()
            self.closebutton.hide()
            self.image_messageitem.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)            
        elif self.type == "request":
            self.applybutton.hide()
            self.closebutton.hide()
            self.image_messageitem.set_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG)
        elif self.type == "apply1":
            self.closebutton.hide() 
            self.image_messageitem.set_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG)            
        elif self.type == "apply2":
            self.cancelbutton.hide()
            self.image_messageitem.set_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG)

        if self.title != None:
            self.dialog.set_title(self.title)
            
        if self.type != "longtext":            
            self.label_messagetext.set_markup(self.text)
            self.label_messagetext.queue_resize()
        else:
            #self.textview_longtext.set_text(self.text)
            
            #self.textview_messages = self.dialog_messages.get_widget("textview_messages")
            self.messagesbuffer = self.textview_longtext.get_buffer()
            self.messagesbuffer.set_text(self.text)  
                      
            self.textview_longtext.queue_resize()            
            self.textview_longtext.show_all()
        
    def dialog_response(self, widget, response, *entry):      
        if response == gtk.RESPONSE_APPLY or response == gtk.RESPONSE_OK:
            if self. returnfunc != None:
                self. returnfunc(self.return_args)
                time.sleep(0.1) 
            if response == gtk.RESPONSE_OK:
                widget.destroy ()
            return True        
        elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_CLOSE:
            widget.destroy ()
            return False  
        
class PopupInfo:
    def __init__ (self, text, title=None, time=8, popup_instance=None):
        self.alive = True
        self.text = text
        self.title = title
        self.gladefile = libdir + "/qemulator.glade"
        self.popup_instance = popup_instance
        
        framecolor = '#3E644A'
        bgcolor = '#D0F6CB'
        
        textlen = len(text)
        #print 'text length: ' + str(textlen)
        duration = textlen/12
        if duration < 5:
            duration = 5
        #print 'rendered duration: ' + str(duration)
        
        if self.popup_instance != None:
             self.popup_instance.close_balloon()
             #self.popup_instance = None
        
        self.wTree = gtk.glade.XML(self.gladefile, "window_traypopup", APP)
        self.window_traypopup = self.wTree.get_widget("window_traypopup")
        self.window_traypopup.set_keep_above(True)
        self.window_traypopup.set_gravity(gtk.gdk.GRAVITY_NORTH_EAST)
        
        self.eventboxPopupInfo = self.wTree.get_widget("eventboxPopupInfo")        
        self.eventboxPopupInfo.set_events (gtk.gdk.BUTTON_PRESS_MASK)        
        
        #self.eventboxPopupInfo.connect("button-press-event",self.close_balloon)
        self.labelPopuptext = self.wTree.get_widget("labelPopuptext")
        self.labelPopuptext.set_markup(text)        
        width, height = self.window_traypopup.get_size()
        self.window_traypopup.set_default_size(width, height)
        width, height = self.window_traypopup.get_size()    
        self.window_traypopup.move(gtk.gdk.screen_width() - width - 50, 50)
        #self.window_traypopup.move(50, 50)

        self.eventboxPopupInfo.resize_children()
        self.window_traypopup.resize_children()
        
        window_traypopup_style = qml_style.WidgetStyle(self.window_traypopup)
        window_traypopup_style.set_bg(framecolor)

        eventboxPopupInfo_style = qml_style.WidgetStyle(self.eventboxPopupInfo)
        eventboxPopupInfo_style.set_bg(bgcolor)               
        
        dic = {"on_eventboxPopupInfo_button_press_event" : self.close_balloon,        
        }
        self.wTree.signal_autoconnect(dic)
                
        self.window_traypopup.present()
        timeout = duration*1000
        gobject.timeout_add(timeout, self.close_balloon)
        
    def close_balloon(self, *widget):
        self.popup_instance = None        
        self.window_traypopup.destroy()
        self.alive = False

















