# mainwindow.py
# 
# Copyright (C) 2008 Stefan Betz <stefan_betz@gmx.net>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

# Stdlib Imports
import subprocess
import gettext
import Queue
from threading import Thread
# Gtk Imports
import pygtk
pygtk.require('2.0')
import gtk
from gtk.gdk import threads_enter, threads_leave
# Gobject Imports
from gobject import timeout_add
# Dialog Imports
from about import AboutDialog
from options import OptionsDialog
from mountwindow import MountWindow
from addwindow import AddWindow
# pyNeighborhood Imports
from pyneighborhood import config, db, dblock
from pyneighborhood.misc import sharepath, events, push_notification, INFO
from pyneighborhood.events import browsingevent,syncevent

_ = gettext.gettext

class SyncThread(Thread):
    """
    Sync TreeView/TreeStore with Database.
    """

    def __init__(self, mainwindow = None):
        """
        Constructor
        """
        Thread.__init__(self)
        self.mainwindow = mainwindow
        self.daemon = True

    def run(self):
        """
        Sync me...
        """
        while True:
            syncevent.wait()
            dblock.acquire()
            cur = db.cursor()
            push_notification(_("Starting sync...\n"))
            threads_enter()
            # Append new items to tree storage
            workgroups = cur.execute("""SELECT id,name FROM workgroups WHERE treeview ISNULL""").fetchall()
            for workgroup in workgroups:
                workgroup_iter = self.mainwindow.treestore.append(None, [workgroup[1], self.mainwindow.GROUP_ICON, None, None, False, False, False])
                cur.execute("""UPDATE workgroups SET treeview = ?, validated = 1 WHERE id = ?""", ( self.mainwindow.treestore.get_string_from_iter(workgroup_iter), workgroup[0] ))
            hosts = cur.execute("""SELECT hosts.id, hosts.name, hosts.comment, workgroups.treeview FROM hosts INNER JOIN workgroups ON hosts.workgroup = workgroups.id WHERE hosts.treeview ISNULL AND hosts.validated = 0 AND workgroups.treeview NOTNULL""").fetchall()
            for host in hosts:
                workgroup_iter = self.mainwindow.treestore.get_iter_from_string(host[3])
                host_iter = self.mainwindow.treestore.append(workgroup_iter, [host[1], self.mainwindow.HOST_ICON, host[2], None, False, False, False])
                cur.execute("""UPDATE hosts SET treeview = ?, validated = 1 WHERE id = ?""", ( self.mainwindow.treestore.get_string_from_iter(host_iter), host[0]))
            shares = cur.execute("""SELECT shares.id, shares.name, shares.comment, hosts.treeview FROM shares INNER JOIN hosts ON shares.host = hosts.id WHERE shares.treeview ISNULL AND shares.validated = 0 AND hosts.treeview NOTNULL""").fetchall()
            for share in shares:
                host_iter = self.mainwindow.treestore.get_iter_from_string(share[3])
                share_iter = self.mainwindow.treestore.append(host_iter, [share[1], self.mainwindow.SHARE_ICON, share[2], None, False, False, False])
                cur.execute("""UPDATE shares SET treeview = ?, validated = 1 WHERE id = ?""", ( self.mainwindow.treestore.get_string_from_iter(share_iter), share[0]))
            # Remove old Items from TreeStorage
            shares = cur.execute("""SELECT treeview FROM shares WHERE validated = 0 AND manual = 0""").fetchall()
            for share in shares:
                share_iter = self.mainwindow.treestore.get_iter_from_string(share[0])
                self.mainwindow.treestore.remove(share_iter)
            hosts = cur.execute("""SELECT treeview FROM hosts WHERE validated = 0 AND manual = 0""").fetchall()
            for host in hosts:
                host_iter = self.mainwindow.treestore.get_iter_from_string(host[0])
                self.mainwindow.treestore.remove(host_iter)
            workgroups = cur.execute("""SELECT treeview FROM workgroups WHERE validated = 0 AND manual = 0""").fetchall()
            for workgroup in workgroups:
                workgroup_iter = self.mainwindow.treestore.get_iter_from_string(workgroup[0])
                self.mainwindow.treestore.remove(workgroup_iter)
            self.mainwindow.reload_browser_button.set_sensitive(True)
            threads_leave()
            cur.execute("""DELETE FROM shares WHERE validated = 0 AND manual = 0""")
            cur.execute("""DELETE FROM hosts WHERE validated = 0 AND manual = 0""")
            cur.execute("""DELETE FROM workgroups WHERE validated = 0 AND manual = 0""")
            dblock.release()
            syncevent.clear()

class MainWindow(object):
    """
    Base Class of the MainWindow Widget
    """

    def delete_event(self, event=None, widget=None):
        """
        Delete Window Event
        """
        gtk.main_quit()
        return True

    def about(self, action):
        """
        Show the About Dialog
        """
        AboutDialog()

    def preferences(self, action):
        """
        Show the Preferences Dialog
        """
        prefs = OptionsDialog(self.window)

    def reload_browser(self, event=None, widget=None):
        """
        Reloades the Browser TreeView
        """
        self.reload_browser_button.set_sensitive(False)
        browsingevent.set()

    def reload_mounts(self, event=None, widget=None):
        """
        Reloades the Mounts TreeView
        """
        self.reload_mounts_button.set_sensitive(False)
        for each in self.mountsstore:
            del self.mountsstore[each.iter]
        f = open("/proc/mounts", "r")
        mtab = f.readlines()
        f.close()
        for mount in mtab:
            tmount = mount.split()
            if tmount[2] == "cifs":
                servicetype = "CIFS"
            # elif tmount[2] == "smbfs":
            #     servicetype = "SMB"
            else:
                servicetype = "?"
            if servicetype == "?":
                continue
            self.mountsstore.append(None, [servicetype,tmount[0].replace("\\040", " "),tmount[1].replace("\\040", " ")])
        self.reload_mounts_button.set_sensitive(True)

    def mounts_recv_data(self, treeview=None, context=None, x=None, y=None, selection=None, info=None, timestamp=None):
        """
        Mounts TreeView Handler for Drag and Drop Operations, Receive
        """
        iter = self.treestore.get_iter_from_string(selection.data)
        self.connect_share(iter=iter)
        treeview.emit_stop_by_name("drag_data_received")
        return

    def browser_send_data(self, treeview=None, context=None, selection=None, info=None, timestamp=None):
        """
        Browser TreeView Handler for Drag and Drop Operations, Transmit
        """
        treeselection = treeview.get_selection()
        model, iter = treeselection.get_selected()
        data = model.get_string_from_iter(iter)
        selection.set_text(data, -1)
        return

    def disconnect_share(self, widget):
        """
        Share Disconnect Callback
        """
        selection = self.mounts.get_selection()
        model, iter = selection.get_selected()
        mountpoint = self.mountsstore.get_value(iter, 2).rstrip("/")
        service = self.mountsstore.get_value(iter, 1)
        type = config.get("CIFS", "unmount")
        self.mountsstore.remove(iter)
        self.disconnectbutton.set_sensitive(False)
        disconnectcommand = []
        if config.get("Main", "enable_sudo") == "True":
            disconnectcommand.append(config.get("Main", "sudo"))
        disconnectcommand.append(type)
        disconnectcommand.append(mountpoint)
        process = subprocess.Popen(disconnectcommand, executable = disconnectcommand[0],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = process.communicate()
        if process.returncode == 0:
            push_notification(_("Share %s unmounted successfully.\n") % service, INFO)
        else:
            push_notification(_("Error unmounting share %(service)s: %(error)s.\n") % { "service": service, "error": stderr }, INFO)

    def connect_share(self, widget = None, iter = None):
        """
        Share Connect Callback
        """
        if widget:
            selection = self.browser.get_selection()
            if selection.get_mode() == gtk.SELECTION_SINGLE:
                model, iter = selection.get_selected()
        if iter:
            cur = db.cursor()
            shareid = cur.execute("""SELECT id FROM shares WHERE treeview = ?""", (self.treestore.get_string_from_iter(iter),)).fetchone()[0]
            mounter = MountWindow(self, shareid)

    def add(self, action=None):
        """
        Handler for the Add-Button
        """
        AddWindow(self)

    def mounts_cursor_changed(self, treeview):
        """
        Callback for cursor-changed Events
        """
        selection = treeview.get_selection()
        if selection.get_mode() == gtk.SELECTION_SINGLE:
            self.disconnectbutton.set_sensitive(True)
        else:
            self.disconnectbutton.set_sensitive(False)

    def browser_cursor_changed(self, treeview):
        """
        Callback for cursor-changed Events
        """
        self.connectbutton.set_sensitive(False)
        selection = treeview.get_selection()
        if selection.get_mode() == gtk.SELECTION_SINGLE:
            model, iter = selection.get_selected()
            if iter:
                cur = db.cursor()
                if len(cur.execute("""SELECT id FROM shares WHERE treeview = ?""", (self.treestore.get_string_from_iter(iter),)).fetchall()):
                    self.connectbutton.set_sensitive(True)

    def browser_row_collapsed_expanded(self, treeview, iter, path):
        """
        Callback for row-expanded and row-collapsed events
        """
        self.browser_cursor_changed(treeview)

    def process_events(self):
        """
        Process events like information, warning or error messages.

        This Method is never called directly, instead the gtk mainloop calls
        this automaticaly after some time.
        """
        enditer = self.eventsbuffer.get_end_iter()
        try:
            event = events.get(False)
            icon = self.events.render_icon(event[0],gtk.ICON_SIZE_MENU)
            self.eventsbuffer.insert_pixbuf(enditer, icon)
            self.eventsbuffer.insert(enditer, event[1])
            self.events.scroll_to_iter(enditer, 0)
        except Queue.Empty:
            pass
        return True

    def clear_events(self,widget = None):
        """
        Clean the events viewer.
        """
        startiter = self.eventsbuffer.get_start_iter()
        enditer = self.eventsbuffer.get_end_iter()
        self.eventsbuffer.delete(startiter, enditer)

    def events_resize(self, widget = None):
        """
        Resize widgets if the events expander has been toggled.
        """
        self.mainpaned.set_position(-1)
    
    def __init__(self):
        """
        Constructor
        """ 
        # Create the Main Window
        self.xml = gtk.Builder()
        self.xml.set_translation_domain('pyNeighborhood')
        self.xml.add_from_file(sharepath('ui/mainwindow.glade')) 
        self.xml.get_objects()[1].show()
        self.xml.connect_signals(self)
        self.window = self.xml.get_object("mainwindow")
        self.xml.get_object("maintoolbar").unset_style()
        
        self.window.set_icon_from_file(sharepath('icons/pyneighborhood.png'))
        # Main TreeStore
        self.treestore = gtk.TreeStore(str, gtk.gdk.Pixbuf, str, str, bool, bool, bool)
        self.NAME = 0
        self.ICON = 1
        self.COMMENT = 2
        self.MOUNTPOINT = 3
        theme = gtk.icon_theme_get_default()
        if theme.has_icon('network-server'):
            self.HOST_ICON = theme.load_icon('network-server',32,gtk.ICON_LOOKUP_USE_BUILTIN)
        else:
            self.HOST_ICON = gtk.gdk.pixbuf_new_from_file(sharepath('icons/host.png'))
        if theme.has_icon('network-workgroup'):
            self.GROUP_ICON = theme.load_icon('network-workgroup',32,gtk.ICON_LOOKUP_USE_BUILTIN)
        else:
            self.GROUP_ICON = gtk.gdk.pixbuf_new_from_file(sharepath('icons/workgroup.png'))
        if theme.has_icon('folder-remote'):
            self.SHARE_ICON = theme.load_icon('folder-remote',32,gtk.ICON_LOOKUP_USE_BUILTIN)
        else:
            self.SHARE_ICON = gtk.gdk.pixbuf_new_from_file(sharepath('icons/share.png'))
        # Browser TreeView
        self.browser = self.xml.get_object("browser")
        self.browser.set_model(self.treestore)
        self.browserimagecell = gtk.CellRendererPixbuf()
        self.browsertextcell = gtk.CellRendererText()
        self.browsercolumn = gtk.TreeViewColumn()
        self.browsercolumn.pack_start(self.browserimagecell, False)
        self.browsercolumn.add_attribute(self.browserimagecell, "pixbuf", self.ICON)
        self.browsercolumn.pack_start(self.browsertextcell, True)
        self.browsercolumn.add_attribute(self.browsertextcell, "text", self.NAME)
        self.browser.append_column(self.browsercolumn)
        self.browser.set_tooltip_column(2)
        self.browser.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [("text/plain", gtk.TARGET_SAME_APP, 0)], gtk.gdk.ACTION_COPY)
        # Connections Buttons
        self.disconnectbutton = self.xml.get_object("disconnect-button")
        self.disconnectbutton.set_sensitive(False)
        self.connectbutton = self.xml.get_object("connect-button")
        self.connectbutton.set_sensitive(False)
        # Mounts TreeView
        self.mounts = self.xml.get_object("mounts")
        self.mountsstore = gtk.TreeStore(str, str, str)
        self.mounts.set_model(self.mountsstore)
        self.mountstypecell = gtk.CellRendererText()
        self.mountstypecolumn = gtk.TreeViewColumn(_("Type"), self.mountstypecell, text=0)
        self.mountshostcell = gtk.CellRendererText()
        self.mountshostcolumn = gtk.TreeViewColumn(_("Service"), self.mountshostcell, text=1)
        self.mountsmountpointcell = gtk.CellRendererText()
        self.mountsmountpointcolumn = gtk.TreeViewColumn(_("Mountpoint"), self.mountsmountpointcell, text=2)
        self.mounts.append_column(self.mountstypecolumn)
        self.mounts.append_column(self.mountshostcolumn)
        self.mounts.append_column(self.mountsmountpointcolumn)
        # Reload Buttons
        self.reload_browser_button = self.xml.get_object("reload-browser-button")
        self.reload_mounts_button = self.xml.get_object("reload-mounts-button")
        # Events View
        self.events = self.xml.get_object("events")
        self.eventsbuffer = self.events.get_buffer()
        self.mainpaned = self.xml.get_object("mainpaned")
        # Drag & Drop for Mounts:
        self.mounts.enable_model_drag_dest([("text/plain", gtk.TARGET_SAME_APP, 0)], gtk.gdk.ACTION_COPY)
        # Host Objects Dict
        self.host_objects = {}
        # Autoreload Mount's View
        self.reload_mounts()
        # Start Events Processing
        timeout_add(50,self.process_events)
        # Start frist Browsing
        browsingevent.set()
