#!/usr/bin/python3
###########################################################################
# fuser.py - description                                                  #
# ------------------------------                                          #
# begin     : Wed Jun 15 2005                                             #
# copyright : (C) 2005-2006 by Sebastian Kuegler                          #
# email     : sebas@vizZzion.org                                          #
#                                                                         #
###########################################################################
#                                                                         #
#   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.                                   #
#                                                                         #
###########################################################################
"""
TODO:
- Fix running standalone:
    * TDECmdLineArgs stuff.
"""

import sys
import os
from PyTQt.tqt import *
from tdeui import *
#import kdedesigner
from fuser_ui import *
from SimpleCommandRunner import *

standalone = __name__ == "__main__"

class FileProcess(TQListViewItem):
    """ A FileProcess is simply one line from lsof, one filedescriptor that's in use 
        by a process represented as a listviewitem in the lsof processtable. """

    # Available signals.
    signals = {
        "TERM":15,
        "KILL":9 }

    # Column names mapping.
    cols = {
        "pname":0,
        "pid":1,
        "powner":2,
        "pfile":3 }

    def __init__(self,parent,pid,isparent=False):
        TQListViewItem.__init__(self,parent)
        self.setPid(pid)
        self.isparent = isparent
        self.pfile = ""
        self.pix = None

    def setPid(self,pid):
        self.pid = pid

    def setName(self,pname):
        self.pname = pname

    def setOwner(self,powner):
        self.powner = powner

    def setFile(self,pfile):
        self.pfile = pfile

    def setPixmaps(self,pix):
        """ Eats a dict with pixmaps. """
        self.pix = pix

    def sendSignal(self,signal):
        """ Parses a signal string representation or a signal number and sends it to
            the process."""
        if not self.isparent:
            print("Item is not a process, only a filedescriptor.")
            return
        try:
            signal_int = int(signal)
        except ValueError:
            try:
                signal_int = self.signals[signal]
            except IndexError:
                print("No known signal received ", signal)
                return False
        try:
            rc = os.kill(int(self.pid),signal_int) # TODO: Catch OSError
        except OSError as message:
            print("OSError: Couldn't %s %s %s" % (signal,self.pname,self.pid))
            print(message)
        if not rc:
            print("Successfully sent signal ", signal_int, " to process ", self.pid)
            return True
        print("Signal %i didn't succeed" % signal_int)
        return False

    def fillColumns(self):
        """ Writes strings into columns once an entry is completed. """
        if self.isparent:
            self.setText(self.cols["pid"],self.pid)
            self.setText(self.cols["pname"],self.pname)
            self.setText(self.cols["powner"],self.powner)
            self.setPixmap(0,self.pix["exec"])
            self.setPixmap(1,self.pix["pid"])
            self.setPixmap(2,self.pix["owner"])
        else:
            self.setText(self.cols["pfile"],self.pfile)
            self.setPixmap(3,self.pix["file"])

########################################################################################################
class FUser(FUserUI):
    """ done() / result() return 0 on successful umount and 1 if cancelled. """

    def __init__(self,device,parentdialog=None,lsof_bin='/usr/sbin/lsof',tdeApp=None):
        FUserUI.__init__(self,parentdialog,name = None,modal = 0,fl = 0)
        self.device = device
        self.fileprocesses = []
        self.lsof_bin = '/usr/sbin/lsof'
        self.setLsof(lsof_bin)
        self.setApp(tdeApp)

        self.processlist.clear()
        self.processhidden = False
        # We're having processes blocking umounting, show that.
        self.umountbutton.setEnabled(False)

        self.explanationlabel.setText(
            str(i18n("""The volume %s is in use and can not be disabled.<br>
            <br>
            The processes that are blocking %s are listed below. These processes must be closed 
            before %s can be disabled.
            Killing a process may cause data loss! Make sure all work is saved before killing an 
            application.
            """)) % (self.device,self.device,self.device))

        self.connect(self.cancelbutton,TQ_SIGNAL("clicked()"),self.slotCancelButtonClicked)
        self.connect(self.killbutton,TQ_SIGNAL("clicked()"),self.slotKillButtonClicked)
        self.connect(self.killallbutton,TQ_SIGNAL("clicked()"),self.slotKillallButtonClicked)
        self.connect(self.refreshbutton,TQ_SIGNAL("clicked()"),self.refreshProcesslist)
        self.connect(self.processlist,TQ_SIGNAL("selectionChanged()"),self.slotSelectionChanged)
        self.connect(self.umountbutton,TQ_SIGNAL("clicked()"),self.slotUmountButtonClicked)

        # TODO: Make optionsbutton resize dialog if processframe is hidden, hide Optionsbutton until then.
        self.optionsbutton.hide()
        self.readPixmaps()
        self.warningimage.setPixmap(MainBarIcon("messagebox_warning"))

        # Delayed initialisation.
        TQTimer.singleShot(0,self.isMounted)
        TQTimer.singleShot(0,self.refreshProcesslist)

    def setApp(self,app):
        """ We need a reference to the (K|Q)Application for certain things, e.g. setting 
            the MouseCursor. """
        self.app = app

    def setLsof(self,path):
        """ Where's the lsof binary? """
        if os.path.isfile(path):
            self.lsof_bin = path
        else:
            print(path, " is not a valid binary, keeping %s", self.lsof_bin)

    def readPixmaps(self):
        self.pix = {
            "exec": UserIcon("application-x-executable"),
            "owner": UserIcon("user"),
            "pid": UserIcon("tux"),
            "file": UserIcon("file")}

    def refreshProcesslist(self):
        """ Read lsof output and add the processdescriptors to the listview. """
        tdeApp = self.app

        tdeApp.setOverrideCursorT(TQCursor(TQt.BusyCursor))

        self.processlist.clear()
        rc, output = SimpleCommandRunner().run([self.lsof_bin,'-FpcLn',self.device],True)
        procs = output.split()

        self.processes = []
        self.realprocesses = []
        for line in procs:
            line = str(line)
            type = line[0]
            info = line[1:]

            if type == "p":
                pid = info
                parentproc = FileProcess(self.processlist,pid,True)
                self.processes.append(parentproc)
                self.realprocesses.append(parentproc)
                parentproc.setPixmaps(self.pix)
                files = 0

            if type == "c":
                pname = info
                parentproc.setName(pname)

            if type == "L":
                powner = info
                parentproc.setOwner(powner)

            if type == "n":
                pfile = info
                childproc = FileProcess(parentproc,pid)
                self.processes.append(childproc)
                childproc.setPixmaps(self.pix)
                childproc.setFile(pfile)
                childproc.setOwner(powner)
                childproc.setName(pname)
                if files == 0:
                    parentproc.fillColumns()
                files += 1
                childproc.fillColumns()

        tdeApp.restoreOverrideCursor()

        # Enable / disable buttons which are (in)appropriate.
        self.killallbutton.setEnabled(len(self.realprocesses)!=0)
        self.killbutton.setEnabled(len(self.realprocesses)!=0)
        self.umountbutton.setEnabled(len(self.realprocesses)==0)
        if self.processlist.selectedItem() == None:
            self.killbutton.setEnabled(False)

    def isMounted(self):
        rc,output = SimpleCommandRunner().run(["/bin/mount"],False)
        mounts = []
        for line in output.split('\n'):
            try:
                mounts.append(line.split()[0])
            except IndexError:
                pass
        ismounted = self.device in mounts
        self.umountbutton.setEnabled(ismounted)
        return ismounted

    def slotCancelButtonClicked(self):
        self.done(1)

    def slotKillButtonClicked(self):
        try:
            self.processlist.selectedItem().sendSignal("KILL")
            self.refreshProcesslist()
        except AttributeError:
            print("No killable item selected.")

    def slotKillallButtonClicked(self):
        for process in self.realprocesses:
            process.sendSignal("KILL")
        self.refreshProcesslist()

    def slotOptionsButtonCLicked(self):
        self.processhidden = not self.processhidden
        self.processframe.setHidden(self.processhidden)

    def slotSelectionChanged(self):
        """ Check if item is a process or a file, disable killbutton for children. """
        selected = self.processlist.selectedItem()
        if not selected.isparent:
            self.killbutton.setEnabled(False)
        else:
            self.killbutton.setEnabled(True)

    def slotUmountButtonClicked(self):
        SimpleCommandRunner
        rc, output = SimpleCommandRunner().run(['/bin/umount',self.device])
        if rc == 0:
            print("%s successfully unmounted." % self.device)
            # Close dialog and return 0 - sucessfully umounted.
            self.done(0)
        else:
            print("Unmounting %s failed: %s" % (self.device,output[:-1]))
        self.isMounted()

################################################################################################
if standalone:
    device = "/dev/hda1"
    print('Device is ', device)

    cmd_args = TDECmdLineArgs.init(sys.argv, "FUser",
        "A graphical frontend to fuser, but without using it :-)", "0.2")

    # ----------------------------------------------------------------------------
    # FIXME: All the arg-parsing stuff does not work yet since I don't understand TDECmdLineArgs.
    options = [("device <device>", "Device to umount")]
    TDECmdLineArgs.addCmdLineOptions(options)
    args = TDECmdLineArgs.parsedArgs()
    # print args.count()
    # ----------------------------------------------------------------------------

    tdeApp = TDEApplication()
    TDEGlobal.iconLoader().addAppDir("guidance")
    fuserapp = FUser(device)

    fuserapp.setApp(tdeApp)
    tdeApp.setMainWidget(fuserapp)
    fuserapp.show()
    tdeApp.exec_loop()
