#!/usr/bin/python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2010 Canonical Ltd.
# 
# Authors:
#   Andres Rodriguez <andreserl@ubuntu.com>
# 
# This program is free software: you can redistribute it and/or modify it 
# under the terms of the GNU General Public License version 3, as published 
# by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranties of 
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
### END LICENSE

import sys
import os
import gtk
import time

import gettext
from gettext import gettext as _
gettext.textdomain('testdrive')

from testdrive import testdrive
from testdrive.virt import kvm, parallels, virtualbox
import threading, subprocess, commands
import random
import re

gtk.gdk.threads_init()

TAB_LABEL = []
TAB_LABEL.append({"dist":"ubuntu", "label":"Ubuntu"})
TAB_LABEL.append({"dist":"kubuntu", "label":"Kubuntu"})
TAB_LABEL.append({"dist":"xubuntu", "label":"Xubuntu"})
TAB_LABEL.append({"dist":"ubuntu-server", "label":"Server"})
TAB_LABEL.append({"dist":"edubuntu", "label":"Edubuntu"})
TAB_LABEL.append({"dist":"mythbuntu", "label":"Mythbuntu"})
TAB_LABEL.append({"dist":"ubuntustudio", "label":"Ubuntu Studio"})
TAB_LABEL.append({"dist":"lubuntu", "label":"Lubuntu"})
TAB_LABEL.append({"dist":"ubuntukylin", "label":"UbuntuKylin"})
TAB_LABEL.append({"dist":"other", "label":_("Other")})

global ISOLIST

ISOLIST = []

# Add project root directory (enable symlink, and trunk execution).
PROJECT_ROOT_DIRECTORY = os.path.abspath(
    os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))

if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'testdrivegtk'))
    and PROJECT_ROOT_DIRECTORY not in sys.path):
    sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
    os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY) # for subprocesses

try:
    import pynotify
    pynotify.init('testdrive-gtk')
    imageURI = 'file://' + PROJECT_ROOT_DIRECTORY + '/data/media/testdrive.png'
    notifications_available = True
except:
    notifications_available = False

from testdrivegtk import (
    AboutTestdrivegtkDialog, PreferencesTestdrivegtkDialog, AddOtherTestdrivegtkDialog)
from testdrivegtk.helpers import get_builder

try:
    #import indicate
    import gobject
    import webbrowser
    #message_indicator = True
    message_indicator = False
except:
    message_indicator = False

try:
    import appindicator
    imageURI = 'file://' + PROJECT_ROOT_DIRECTORY + '/data/media/testdrive.png'
    application_indicator = True
except:
    application_indicator = False


class TestdrivegtkWindow(gtk.Window):
    __gtype_name__ = "TestdrivegtkWindow"

    # To construct a new instance of this method, the following notable 
    # methods are called in this order:
    # __new__(cls)
    # __init__(self)
    # finish_initializing(self, builder)
    # __init__(self)
    #
    # For this reason, it's recommended you leave __init__ empty and put
    # your inialization code in finish_intializing

    def __new__(cls):
        """Special static method that's automatically called by Python when 
        constructing a new instance of this class.

        Returns a fully instantiated TestdrivegtkWindow object.
        """
        builder = get_builder('TestdrivegtkWindow')
        new_object = builder.get_object("testdrivegtk_window")
        new_object.finish_initializing(builder)
        return new_object

    def finish_initializing(self, builder):
        """Called while initializing this instance in __new__

        finish_initalizing should be called after parsing the UI definition
        and creating a TestdrivegtkWindow object with it in order to finish
        initializing the start of the new TestdrivegtkWindow instance.

        Put your initilization code in here and leave __init__ undefined.
        """
        # Get a reference to the builder and set up the signals.
        self.builder = builder
        self.builder.connect_signals(self)
        #self.td = td
        self.virt = None
        self.sync_threads = []
        self.launch_threads = []
        self.isos_to_run = []

        ###################################################################
        ######## Obtaining the settings from the Preferences Class ########
        ###################################################################

        logging.debug(_("Instancing Preferences..."))
        dlg = PreferencesTestdrivegtkDialog.PreferencesTestdrivegtkDialog()
        self.td = dlg.get_preferences()

        self.status_bar_release = builder.get_object("status_bar_release")
        self.status_bar_repo = builder.get_object("status_bar_repo")
        ###################################################################
        ######### Functions to create the UI (ISOs and Buttons) ###########
        ###################################################################

        self.create_launch_buttons()
        self.create_isos_interface()

        ###################################################################
        ######### Messages Indicator / Menu                     ###########
        ###################################################################
        self.notified = False
        if message_indicator:
            self.timea = 5
            self.timeb = 3600
            self.indicator = None
            self.messaging_indicator_menu()

        if application_indicator:
            self.timea = 5
            self.timeb = 3600
            self.appindicator = None
            self.application_indicator_menu()

    def application_indicator_menu(self):
        self.appindicator = appindicator.Indicator ("testdrive", "indicator-messages", appindicator.CATEGORY_APPLICATION_STATUS)
        self.appindicator.set_status (appindicator.STATUS_PASSIVE)
        #self.appindicator.set_icon("testdrive-indicator")
        self.appindicator.set_icon("testdrive-attention")
        #self.appindicator.set_attention_icon("distributor-logo")

        # create a menu
        self.app_indicator_menu = gtk.Menu()

        # create items for the menu - labels, checkboxes, radio buttons and images are supported:
        item = gtk.MenuItem("Pre-release available for testing!")
        item.connect("activate", self.on_indicator_message_clicked, item)
        item.show()
        self.app_indicator_menu.append(item)

        image = gtk.ImageMenuItem(gtk.STOCK_QUIT)
        image.connect("activate", self.quit)
        image.show()

        self.app_indicator_menu.append(image)
        self.app_indicator_menu.show()
        self.appindicator.set_menu(self.app_indicator_menu)

        gobject.timeout_add_seconds(self.timea, self.on_check_qa_releases_available, self.timea)
        gobject.timeout_add_seconds(self.timeb, self.on_check_qa_releases_available, self.timeb)

    # Initializes the indicator
    def messaging_indicator_menu(self):
        DESKTOP_FILE = "/usr/share/applications/testdrive-gtk.desktop"
        # Initializes the Server
        server = indicate.indicate_server_ref_default()
        server.set_type("message.testdrive")
        server.set_desktop_file(DESKTOP_FILE)
        server.connect("server-display", self.on_indicator_server_activate)
        server.show()

        # Initializes the Indicator
        self.indicator = indicate.Indicator()
        self.indicator.set_property("name", "Pre-release available for testing!")
        self.indicator.set_property_time("time", time.time())
        #indicator.show()

        self.indicator.connect("user-display", self.on_indicator_message_clicked)

        # Checks every 3600 seconds - hour - (If TestDrive is open that long)
        gobject.timeout_add_seconds(self.timea, self.on_check_qa_releases_available, self.timea)
        gobject.timeout_add_seconds(self.timeb, self.on_check_qa_releases_available, self.timeb)

    # Checks for the availability of ISOs at iso.qa.ubuntu.com and displays notification if so.
    def on_check_qa_releases_available(self, time):
        output = ""
        if self.notified is True:
            return False
        logging.info(_("Checking available ISOs at the ISO tracker [http://iso.qa.ubuntu.com] every %s seconds") % time)
        try:
            #(status, output) = commands.getstatusoutput("wget -q -O- http://iso.qa.ubuntu.com/qatracker/dllist")
            (status, output) = commands.getstatusoutput("wget -q -O- http://iso.qa.ubuntu.com/qatracker | egrep 'iso.qa.ubuntu.com/qatracker/test'")
        except:
            logging.error(_("Unable to check the ISO tracker"))

        # If output is not empty, display the message
        if output != "":
            global notifications_available
            if notifications_available:
                # Notification
                self.notification = pynotify.Notification("TestDrive an Ubuntu ISO!", "Pre-release available for testing!", imageURI)
                self.notification.show()
            global message_indicator
            if message_indicator:
                self.indicator.show()
                self.indicator.set_property_bool("draw-attention", True)
            global application_indicator
            if application_indicator:
                self.appindicator.set_status(appindicator.STATUS_ACTIVE)
            self.notified = True
            return False

        if time == self.timea:
            return False

        return True

    # Click event for Indicator: Opens website when the notification is being clicked.
    def on_indicator_message_clicked(self, indicator, timestamp):
        webbrowser.open("http://iso.qa.ubuntu.com/")
        if message_indicator:
            self.indicator.hide()
        if application_indicator:
            self.appindicator.set_status(appindicator.STATUS_PASSIVE)

    # Click event for Indicator Server.
    def on_indicator_server_activate(self, server, timestamp):
        # TODO: Should show TestDrive if it has been minimized
        print "Server has been clicked"

    def update_status_bar(self):
        data1 = _("<b>Release:</b> %s") % self.td.r
        data2 = _("<b>ISO Repository:</b> http://%s.ubuntu.com/") % self.td.p
        self.status_bar_release.set_markup(data2)
        self.status_bar_repo.set_markup(data1)

    def about(self, widget, data=None):
        """Display the about box for testdrivegtk."""
        about = AboutTestdrivegtkDialog.AboutTestdrivegtkDialog()
        response = about.run()
        about.destroy()

    def preferences(self, widget, data=None):
        """Display the preferences window for testdrivegtk."""
        prefs = PreferencesTestdrivegtkDialog.PreferencesTestdrivegtkDialog()
        response = prefs.run()
        ui_recreate = False
        if response == gtk.RESPONSE_OK:
            # Make any updates based on changed preferences here.
            #self.td = prefs.get_preferences()
            #self.notebook.destroy()
            #self.td.set_defaults()
            #self.create_isos_interface()

            # Obtain preferences
            td = prefs.get_preferences()
            # If Repo (p), Release, Arch (m), Flavor, or ISO CACHE are different, then, regenerate the UI
            if self.td.p != td.p or self.td.r != td.r or self.td.m != td.m or self.td.f != td.f or self.td.CACHE_ISO != td.CACHE_ISO:
                ui_recreate = True
            # Update preferences
            self.td = td
            self.td.set_defaults()
            # Recreate UI
            if ui_recreate:
                self.notebook.destroy()
                self.create_isos_interface()
            pass
        prefs.destroy()

    def new_other_iso(self, widget, data=None):
        """Display Add Other ISO dialog"""
        other = AddOtherTestdrivegtkDialog.AddOtherTestdrivegtkDialog(self.td.CACHE)
        other.set_title(_("Add an ISO to TestDrive"))
        response = other.run()
        if response == gtk.RESPONSE_OK:
            # Recreate the UI for Other ISOs if saved has been clicked
            self.notebook.destroy()
            self.create_isos_interface()
            self.notebook.set_current_page(-1)
            pass
        other.destroy()

    def open_file(self, widget, data=None):
        ###################################################################
        ######### Opens an ISO or IMG to run it with testdrive -u #########
        ###################################################################

        if commands.getstatusoutput("which testdrive")[0] != 0:
            self.on_error_dialog( _("Unable to open because 'testdrive' is not installed.\n"
                        "Please install testdrive: \n\n"
                        "sudo apt-get install testdrive-cli"))
            return
        title = _("TestDrive an ISO or Disk Image")
        filename = None
        testdrives = gtk.FileFilter()
        testdrives.add_pattern("*.iso")
        testdrives.add_pattern("*.img")

        chooser = gtk.FileChooserDialog(title,action=gtk.FILE_CHOOSER_ACTION_SAVE,
        buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        chooser.add_filter(testdrives)
        # Run Chooser Dialog
        response = chooser.run()

        if response == gtk.RESPONSE_OK:
            filename = chooser.get_filename()
            subprocess.Popen(['testdrive', '-u', filename], stdout=subprocess.PIPE)
            pass
        chooser.destroy()

    def quit(self, widget, data=None):
        """Signal handler for closing the TestdrivegtkWindow."""
        self.destroy()

    def on_destroy(self, widget, data=None):
        """Called when the TestdrivegtkWindow is closed."""
        # Clean up code for saving application state should be added here.
        # Stop all the syncs and launched ISOs
        for t in self.isos_to_run:
            if t[0] != None:
                t[0].stop()
            if t[1] != None:
                t[1].stop()
        # Class function to cleanup the IMG Cache
        self.cleanup_img_cache()
        gtk.main_quit()

    def on_warn_dialog(self, data=None):
        warnbox = gtk.MessageDialog(self, 
            gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, 
            gtk.BUTTONS_CLOSE, data)
        warnbox.run()
        warnbox.destroy()

    def on_error_dialog(self, data=None):
        errorbox = gtk.MessageDialog(self, 
            gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, 
            gtk.BUTTONS_CLOSE, data)
        errorbox.run()
        errorbox.destroy()

    def on_info_dialog(self, data=None):
        infobox = gtk.MessageDialog(self, 
            gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, 
            gtk.BUTTONS_CLOSE, data)
        infobox.run()
        infobox.destroy()

    def cleanup_img_cache(self):
        ###################################################################
        #### Cleans up all the Disk Files that are empty from IMG Cache ###
        ###################################################################

        imagelist = os.listdir(self.td.CACHE_IMG)
        for disk in imagelist:
            path = "%s/%s" % (self.td.CACHE_IMG, disk)
            (status, output) = commands.getstatusoutput("file %s | grep -qs 'empty'" % path)
            if status == 0:
                os.unlink(path)
        for disk in self.isos_to_run:
            self.td.DISK_FILE = disk[2]
            self.td.delete_image()

    def obtain_sync_protocol_cmd(self):
        ###################################################################
        ### Obtains the command for the Sync process based on Protocol ####
        ###################################################################
        logging.info(_("Obtaining the sync protocol for the specified ISO..."))
        cmd = self.td.get_proto()
        if cmd == 1:
            self.on_error_dialog(_("Unsupported protocol [%s]") % self.td.PROTO)
        if cmd != 0:
            return cmd
        return False

    def obtain_virt_method(self):
        ###################################################################
        ##### Obtains the Virtualization Method, if not shows warnings ####
        ###################################################################

        # Choose the virtualization engine
        logging.info(_("Obtaining the virtualization method..."))
        if not self.td.VIRT:
            self.td.VIRT = self.td.get_virt()
        if self.td.VIRT == 1:
            logging.error(_("Your CPU supports KVM acceleration; please install KVM"))
            self.on_warn_dialog(  _("Your CPU supports KVM acceleration; please install KVM:"
                        "\n\n"
                        "sudo apt-get install qemu-kvm"))
        if self.td.VIRT == 0:
            logging.error(_("Your CPU does not support acceleration; run kvm-ok for more information; then install VBox"))
            self.on_warn_dialog(  _("Your CPU does not support acceleration; run kvm-ok for more information;\n"
                        "then please install VirtualBox"
                        "\n\n"
                        "kvm-ok"
                        "\n"
                        "sudo apt-get install virtualbox-ose"))

    def obtain_isos_list(self):
        ###################################################################
        #### Obtains the list of ISO available from the iso list cache ####
        ###################################################################

        # Try to retrieve the ISO list from the cache
        logging.info(_("Retrieving the Ubuntu ISO list from cache..."))
        try:
            isos = self.td.get_ubuntu_iso_list()
        except:
            logging.error(_("Unable to retrieve the Ubuntu ISO list from cache..."))
            self.on_error_dialog(_("Unable to retrieve the Ubuntu ISO list from cache..."))
            return []
        return isos

    def obtain_other_isos_list(self, ISO):
        ###################################################################
        ######## Obtains all the Other ISO's that have been added #########
        ###################################################################
        if os.path.exists("%s/other.isos" % self.td.CACHE):
            try:
                f = open("%s/other.isos" % self.td.CACHE, 'r')
                ISOS = f.readlines()
                f.close
            except IOError:
                pass
        else:
            return ISO

        # Processing ISOS from file into ISO list
        for iso in ISOS:
            category = iso.split()[0]
            distro = iso.split()[1] #This will be the distro showed instead of arch
            url = iso.split()[2]
            if url.partition("://")[0] == "wget":
                                        url = url.replace('wget', 'http')
            name = iso.split()[3]
            ISO.append({"name":name, "url":url, "arch":distro, "category":category})
        return ISO

    def on_distro_tab_change(self, widget, none=None, current_page=None):
        ###################################################################
        ##### Shows the "Add Other ISOs" if "Other" has been selected #####
        ###################################################################
        label = widget.get_tab_label(widget.get_nth_page(current_page))
        text = label.get_text()
        self.btn_add_iso.set_sensitive(text == TAB_LABEL[8]["label"])

    def create_isos_interface(self):
        ###################################################################
        ### Does everything necessary to create the ISOs Part of the UI ###
        ###################################################################
        self.obtain_virt_method()
        isos = self.obtain_isos_list()
        # if no isos exists, program will still create the UI
        if isos and not self.td.r:
                self.td.set_ubuntu_release_codename(isos)
        ISO = self.td.list_isos(isos)
        ISO = self.obtain_other_isos_list(ISO)
        self.create_iso_menu(ISO)
        self.update_status_bar()

    def create_iso_menu(self, iso_list = None):
        ###################################################################
        ############ Creates all the Distro Tabs and the ISO's ############
        ###################################################################
        # Making sure global variable ISOLIST is clean.
        global ISOLIST
        ISOLIST = []
        vm_id = 0
        ISO = iso_list
        # Create TABS
        vbox_tabs = self.builder.get_object("flavor-tabs")
        self.notebook = gtk.Notebook()
        self.notebook.set_scrollable(True)
        self.notebook.set_tab_pos(gtk.POS_TOP)
        self.notebook.set_border_width(10)
        self.notebook.connect("switch-page", self.on_distro_tab_change)
        vbox_tabs.add(self.notebook)
        self.show_tabs = True
        self.show_border = True

        ## DETERMINE WHICH ISO's to Show in TABS ##
        i = 0
        distros = []
        while(i != -1):
            try:
                distros.append(self.td.f.split()[i].replace(',', ''))
            except:
                i = -1
                break
            i = i + 1

        #list.reverse(self.td.m)

        #############################################
        ######## Layout of the UI Creation ##########
        #############################################
        # Scroll
        #       vbox
        #               frame-i386
        #                       vbox2
        #                               table per ISO (with labels, checkbox)
        #               frame-amd64
        #                       vbox2
        #                               table per ISO (with labels, checkbox)
        for dist in distros:
            scroll = gtk.ScrolledWindow(None)
            scroll.set_shadow_type(gtk.SHADOW_NONE)
            scroll.set_name(dist)
            vbox = gtk.VBox()
            scroll.add_with_viewport(vbox)
            scroll.get_children()[0].set_shadow_type(gtk.SHADOW_NONE)
            scroll.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
            scroll.show()
            for arch in self.td.m:
                c = 0
                fr_arch = gtk.Frame(arch)
                fr_arch.set_shadow_type(gtk.SHADOW_NONE)
                fr_arch.set_border_width(10)
                vbox2 = gtk.VBox()
                vbox2.set_border_width(10)
                for iso in ISO:
                    if iso['category'] == dist and iso['arch'] == arch:
                        c = c + 1
                        table = gtk.Table(2, 3, False)
                        lb_iso_name = gtk.CheckButton("%s - (%s)" % (iso["name"], self.td.r))
                        lb_iso_name.show()
                        filename = os.path.basename(iso["url"])
                        path = "%s/%s_%s" % (self.td.CACHE_ISO, iso["category"], filename)
                        if os.path.exists(path):
                            lb_cache = gtk.Label(_("<i>     CACHE: [%s]</i>") % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(path)))))
                        else:
                            lb_cache = gtk.Label(_("<i>     CACHE: [empty]</i>"))
                        lb_cache.set_use_markup(True)
                        # To align to righ otherwise it is centered
                        lb_cache.set_alignment(0,0)
                        lb_cache.show()
                        # Adding the Spiiner
                        spin = gtk.Spinner()
                        spin.set_size_request(18, -1);
                        spin.hide()
                        #lb_iso_name.connect("clicked", self.on_select_iso_clicked, spin, lb_cache, iso["url"], iso["category"], vm_id)
                        lb_iso_name.connect("clicked", self.on_select_iso_clicked, vm_id)
                        ISOLIST.append({"vm_id":vm_id, "url":iso["url"], "prefix":iso["category"], "spinner":spin, "lb_status":lb_cache})
                        vm_id += 1
                        table.attach(lb_iso_name, 0, 3, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND)
                        table.attach(spin, 0, 1, 1, 2, gtk.FILL, gtk.FILL | gtk.EXPAND)
                        table.attach(lb_cache, 1, 2, 1, 2, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND, 0, 5)
                        #table.attach(lb_progress, 2, 3, 1, 2, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND)
                        table.show()
                        viewport = gtk.Viewport()
                        viewport.add(table)
                        viewport.set_border_width(2)
                        viewport.set_shadow_type(gtk.SHADOW_ETCHED_IN)
                        viewport.show()
                        vbox2.add(viewport)

                    if iso['category'] == dist and iso['category'] == 'other':
                        c = c + 1
                        table = gtk.Table(2, 3, False)
                        lb_iso_name = gtk.CheckButton("%s" % iso["name"])
                        lb_iso_name.show()
                        filename = os.path.basename(iso["url"])
                        path = "%s/%s_%s" % (self.td.CACHE_ISO, iso["category"], filename)
                        if os.path.exists(path):
                            lb_cache = gtk.Label(_("<i>     CACHE: [%s]</i>") % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(path)))))
                        else:
                            lb_cache = gtk.Label(_("<i>     CACHE: [empty]</i>"))
                        lb_cache.set_use_markup(True)
                        # To align to righ otherwise it is centered
                        lb_cache.set_alignment(0,0)
                        lb_cache.show()
                        # Adding the Spiiner
                        spin = gtk.Spinner()
                        spin.set_size_request(18, -1);
                        spin.hide()
                        #lb_iso_name.connect("clicked", self.on_select_iso_clicked, spin, lb_cache, iso["url"], iso["category"], vm_id)
                        lb_iso_name.connect("clicked", self.on_select_iso_clicked, vm_id)
                        ISOLIST.append({"vm_id":vm_id, "url":iso["url"], "prefix":iso["category"], "spinner":spin, "lb_status":lb_cache})
                        vm_id += 1
                        table.attach(lb_iso_name, 0, 3, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND)
                        table.attach(spin, 0, 1, 1, 2, gtk.FILL, gtk.FILL | gtk.EXPAND)
                        table.attach(lb_cache, 1, 2, 1, 2, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND, 0, 5)
                        #table.attach(lb_progress, 2, 3, 1, 2, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND)
                        table.show()
                        viewport = gtk.Viewport()
                        viewport.add(table)
                        viewport.set_border_width(2)
                        viewport.set_shadow_type(gtk.SHADOW_ETCHED_IN)
                        viewport.show()
                        vbox2.add(viewport)

                if c == 0:
                    if dist == 'other':
                        no_isos_label = gtk.Label()
                        no_isos_label.set_markup(_("<b><i>There are no Other ISOs yet...</i></b>"))
                    else:
                        no_isos_label = gtk.Label()
                        no_isos_label.set_markup(_("<b><i>There are no ISOs for this architecture yet...</i></b>"))
                    no_isos_label.show()
                    vbox2.add(no_isos_label)

                vbox2.show()

                if dist != 'other':
                    fr_arch.add(vbox2)
                    fr_arch.show()
                    vbox.pack_start(fr_arch, expand=False, fill=False, padding=0)
                    vbox.show()
                else:
                    vbox.pack_start(vbox2, expand=False, fill=False, padding=0)
                    vbox.show()
                    break

            # Create Tabs
            for lb_iso_name in TAB_LABEL:
                if dist == lb_iso_name["dist"]:
                    self.notebook.append_page(scroll, gtk.Label(lb_iso_name["label"]))
                    break
        self.notebook.show()

    def create_launch_buttons(self):
        ###########################################################
        ########## Create CD and Launch Buttons Handling ##########
        ###########################################################
        # obtain vbox from glade
        vbox = self.builder.get_object("vbox2")
        # Create and setup buttonbox
        bbox = gtk.HButtonBox()
        bbox.set_spacing(5)
        bbox.set_layout(gtk.BUTTONBOX_END)
        # Add Button
        self.btn_add_iso = gtk.Button(_("Add ISO"))
        self.btn_add_iso.connect("clicked", self.new_other_iso)
        self.btn_add_iso.set_sensitive(False)
        self.btn_add_iso.show()
        bbox.pack_start(self.btn_add_iso)

        # Create Buttons
        button = gtk.Button(_("Create USB Disk"))
        button.connect("clicked", self.on_create_iso_disk_clicked, 'Create Disk')
        button.show()
        bbox.pack_start(button)

        button = gtk.Button(_("Sync"))
        button.connect("clicked", self.on_sync_iso_clicked, 'Sync')
        button.show()
        bbox.pack_start(button)

        button = gtk.Button(_("Launch"))
        button.connect("clicked", self.on_launch_button_clicked, 'Launch')
        button.show()
        bbox.pack_start(button)
        bbox.show()

        vbox.pack_start(bbox, True, True)

    #def on_select_iso_clicked(self, widget, spin, status_label, url=None, iso_path_header=None, vm_id=None):
    #ISOLIST.append({"vm_id":vm_id, "url":iso["url"], "prefix":iso["category"], "spin":spin, "lb_status":lb_cache})
    #lb_iso_name.connect("clicked", self.on_select_iso_clicked, spin, lb_cache, iso["url"], iso["category"], vm_id)
    def on_select_iso_clicked(self, widget, vm_id):
        ###################################################################
        # When ISO is clicked, does the setup to be ready to launch/sync ##
        ###################################################################
        #ISOLIST[vm_id]["vm_id"]     #
        #ISOLIST[vm_id]["url"]       # self.td.ISO_URL
        #ISOLIST[vm_id]["prefix"]    # self.td.ISO_PATH_HEADER
        #ISOLIST[vm_id]["spinner"]   #
        #ISOLIST[vm_id]["lb_status"] #
        # Added later:
        #ISOLIST[vm_id]["path_to_iso"] # Path to the ISO
        #ISOLIST[vm_id]["sync_cmd"]  # Sync command used
        #ISOLIST[vm_id]["vbox_name"] # Name for VBOX/Parallels
        #ISOLIST[vm_id]["virt"]      # Holds the Virt Object
        #ISOLIST[vm_id]["virt_type"] # Defines the type of virt (kvm, vbox, parallels)

        launch_cmd = None
        sync_cmd = None
        launch_thread = None
        status = None

        self.td.ISO_URL = ISOLIST[vm_id]["url"]
        self.td.ISO_PATH_HEADER = ISOLIST[vm_id]["prefix"]
        self.td.set_launch_path()

        ISOLIST[vm_id]["path_to_iso"] = self.td.PATH_TO_ISO
        # TODO - This is a workaround to issue of zsync command. See testdrive.py 'cd '%s' && zsync etc etc'
        cmd = self.obtain_sync_protocol_cmd()
        if self.td.PROTO == "zsync":
            ISOLIST[vm_id]["sync_cmd"] = cmd.split("&&")[1].strip()
        else:
            ISOLIST[vm_id]["sync_cmd"] = cmd

        if widget.get_active() == True:
            self.td.DISK_FILE = self.td.create_disk_file()
            self.td.VBOX_NAME = "%s-%s" % (self.td.PKG, vm_id)
            ISOLIST[vm_id]["vbox_name"] = self.td.VBOX_NAME

            # Instancing Sync Thread
            sync_thread = SyncThread(vm_id)

            # Selecting virt method and Instancing VIRT object
            if self.td.VIRT == "kvm":
                logging.info(_("Using KVM for virtual machine hosting..."))
                virt = kvm.KVM(self.td)
            if self.td.VIRT == "virtualbox":
                logging.info(_("Using VirtualBox for virtual machine hosting..."))
                virt = virtualbox.VBox(self.td)
            if self.td.VIRT == "paralels":
                logging.info(_("Using Parallels Desktop for virtual machine hosting..."))
                virt = parallels.Parallels(self.td)

            # Passing VIRT object to ISOLIST
            ISOLIST[vm_id]["virt"] = virt
            ISOLIST[vm_id]["virt_type"] = self.td.VIRT

            # Instancing Launch Thread
            launch_thread = LaunchThread(vm_id)

            self.isos_to_run.append([sync_thread, launch_thread, self.td.DISK_FILE, status])

        if widget.get_active() == False:
            for t in self.isos_to_run:
                if t[0].command == ISOLIST[vm_id]["sync_cmd"]:
                    t[0].stop()
                    t[1].stop()
                    self.td.DISK_FILE = t[2]
                    self.td.delete_image()
                    self.isos_to_run.remove(t)

                ###############################################################################
        # TODO - TODO - TODO - Determine better method to stop des-selected ISO's
        """
        if widget.get_active() == False:
            for t in self.isos_to_run:
                #if t[0].command == sync_cmd or t[1].command == launch_cmd:
                if t[0].command == sync_cmd:
                    t[0].stop()
                    t[1].stop()
                    self.td.DISK_FILE = t[2]
                    self.td.delete_image()
                    self.isos_to_run.remove(t)
        """

    def on_sync_iso_clicked(self, widget, data=None):
        ###################################################################
        #################### Launches the ISOs to Sync ####################
        ###################################################################
        if not self.isos_to_run:
            """
            self.on_error_dialog(   "No ISOs have been selected.\n"
                        "\n"
                        "Please select an ISO to start syncing...")
            """
            return
        for t in self.isos_to_run:
            if t[0].p is None and t[1].p is None:
                t[3] = "sync"
                try:
                    t[0].start()
                except:
                    pass
            else:
                logging.debug(_("sync_iso: Thread is executing..."))

    def on_launch_button_clicked(self, widget, data=None):
        ###################################################################
        ################# Launches the ISOs to Run in VM ##################
        ###################################################################
        if not self.isos_to_run:
            """
            self.on_error_dialog(   "No ISOs have been selected.\n"
                        "\n"
                        "Please select an ISO or ISOs to TestDrive!")
            """
            return
        for t in self.isos_to_run:
            if t[0].p is None and t[1].p is None:
                t[3] = "launch"
                try:
                    t[1].start()
                except:
                    pass
            else:
                logging.debug(_("launch_iso: Thread is executing or syncing..."))

    def on_create_iso_disk_clicked(self, widget, data=None):
        ###################################################################
        ############ Launches USB Creator for the selected ISO ############
        ###################################################################
        if not self.isos_to_run:
            self.on_error_dialog( _("No ISO has been selected."
                        "\n\n"
                        "Please select an ISO to create an USB Startup Disk."))
            return
        if len(self.isos_to_run) > 1:
            self.on_error_dialog( _("More than 1 ISO has been selected."
                        "\n\n"
                        "Please select only 1 ISO to continue!"))
            return
        if not os.path.exists(self.td.PATH_TO_ISO):
            self.on_error_dialog( _("The specified ISO does not exist!"
                        "\n\n"
                        "Please, synchronize the ISO to continue."))
            return
        try:
            self.p = subprocess.Popen(["usb-creator-gtk", "-i", self.td.PATH_TO_ISO])
        except:
            self.on_error_dialog(_("Unable to launch USB Creator!"))
            return

###################################################################
########## Class that support threading for ISO Syncing ###########
###################################################################
class SyncThread(threading.Thread):
    #def __init__(self, label, spinner, progress, cmd, url):
    def __init__(self, vm_id):
        threading.Thread.__init__ (self)
        self.spin = ISOLIST[vm_id]["spinner"]
        self.status_label = ISOLIST[vm_id]["lb_status"]
        self.previous_text = self.status_label.get_text()
        self.stopthread = threading.Event()
        self.url = ISOLIST[vm_id]["path_to_iso"]
        self.p = None
        self.proto = ISOLIST[vm_id]["url"].split("://")[0]
        self.command = ISOLIST[vm_id]["sync_cmd"]

    def run(self):
        cmd = self.command.split()
        self.p = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines = True)
        percent = re.compile(r'(\d+)%')
        self.status_label.show()
        self.spin.show()
        self.spin.start()
        text = _(" Downloading %s (0%%)") % os.path.basename(self.url).partition("_")[2]
        self.status_label.set_markup("<b><span size='10000'>%s</span></b>" % text)
        prev = None
        while not self.stopthread.isSet():
            if self.p.poll() != None:
                self.update_status_label()
                self.p = None
                self.spin.stop()
                self.spin.hide()
                break

            #gtk.gdk.threads_enter()
            #self.status_label.set_markup("<b><i>%s</i></b>" % text)
            #gtk.gdk.threads_leave()
            line = self.p.stdout.readline(1024).strip()
            match = percent.search(line)
            if match != None:
                cur = match.group(1)
                if prev != cur:
                    prev = match.group(1)
                    text = _(" Downloading %s (%s%%)") % (os.path.basename(self.url).partition("_")[2], prev)
                    self.status_label.set_markup("<b><span size='10000'>%s</span></b>" % text)
            time.sleep(1)

    def stop(self):
        try:
            if self.p.poll() is None:
                self.p.terminate()
                self.update_status_label()
        except:
            pass
        self.spin.stop()
        self.spin.hide()
        self.p = None
        self.stopthread.set()

    def update_status_label(self):
        if os.path.exists(self.url):
            self.status_label.set_markup(_("<i>     CACHE: [%s]</i>") % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(self.url)))))
        else:
            self.status_label.set_markup(_("<i>     CACHE: [empty]</i>"))

###################################################################
########## Class that supports threading for VM Running ###########
###################################################################
class LaunchThread(threading.Thread):
    ####def __init__(self, label, cmd):
    ###def __init__(self, label, cmd, virt):
    def __init__(self, vm_id):
        threading.Thread.__init__(self)
        self.status_label = ISOLIST[vm_id]["lb_status"]
        self.previous_text = ISOLIST[vm_id]["lb_status"].get_text()
        self.stopthread = threading.Event()
        self.virt = ISOLIST[vm_id]["virt_type"]
        self.ovirt = ISOLIST[vm_id]["virt"]
        self.VBOX_NAME = ISOLIST[vm_id]["vbox_name"]
        self.p = None

    def prepare_to_launch_vm(self):
                ###################################################################
                ######## Prepare the VM to launch and return launch command #######
                ###################################################################
        # TODO TODO TODO - Re-add validation
        self.ovirt.validate_virt()
        self.ovirt.setup_virt()
        return self.ovirt.launch_virt()

    def run(self):
        if self.virt == "virtualbox":
            text = _("    Configuring Virtual Machine...")
            self.status_label.set_markup("<b><i>%s</i></b>" % text)
            self.status_label.show()
        command = self.prepare_to_launch_vm()
        cmd = command.split()
        self.p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        text = _("     Running Virtual Machine...")
        self.status_label.set_markup("<b><i>%s</i></b>" % text)
        self.status_label.show()

        if self.virt == 'kvm':
            while not self.stopthread.isSet():
                if self.p.poll() != None:
                    self.status_label.set_markup("<i>%s</i>" % self.previous_text)
                    self.p = None
                    break
                time.sleep(0.1)

        elif self.virt == 'virtualbox':
            time.sleep(5)
            while not self.stopthread.isSet():
                if commands.getstatusoutput("VBoxManage list runningvms | grep -qs %s" % self.VBOX_NAME)[0] != 0:
                    self.status_label.set_markup("<i>%s</i>" % self.previous_text)
                    self.p = None
                    break
                time.sleep(2)

        elif self.virt == 'parallels':
            while not self.stopthread.isSet():
                if commands.getstatusoutput("prlctl list %s | grep -qs stopped" % td.VBOX_NAME)[0] == 0:
                    self.status_label.set_markup("<i>%s</i>" % self.previous_text)
                    self.p = None
                    break
                time.sleep(2)

    def stop(self):
        try:
            if self.p.poll() is None:
                self.p.terminate()
                self.status_label.set_markup("<i>%s</i>" % self.previous_text)
            if self.virt == "virtualbox":
                os.system("VBoxManage controlvm %s poweroff" % self.VBOX_NAME)
                self.status_label.set_markup("<i>%s</i>" % self.previous_text)
        except:
            pass
        self.p = None
        self.stopthread.set()

if __name__ == "__main__":
    # Support for command line options.
    import logging
    import optparse
    parser = optparse.OptionParser(version="%prog %ver")
    parser.add_option(
        "-v", "--verbose", action="store_true", dest="verbose",
        help=_("Show debug messages"))
    (options, args) = parser.parse_args()

    # Set the logging level to show debug messages.
    if options.verbose:
        logging.basicConfig(level=logging.DEBUG)
        logging = logging.getLogger('testdrive')
        logging.debug('logging enabled')

    # Run the application.
    window = TestdrivegtkWindow()
    window.show()
    gtk.main()
