# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
#
# 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; version 2 of the
# License.
#
# 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 St, Fifth Floor, Boston, MA
# 02110-1301  USA

import shutil
import platform
import os
import errno
import threading
import stat
import tempfile
import StringIO
import subprocess
default_sudo_prefix       = '/usr/bin/sudo -p EnterPasswordHere'

from wb_common import splitpath, Users, sanitize_sudo_output
from wb_admin_ssh import WbAdminSSH

from wb_common import InvalidPasswordError, PermissionDeniedError, joinpath

from grt import log_info, log_error, log_warning, log_debug, log_debug2, log_debug3
_this_file = os.path.basename(__file__)

class wbaOS(object):
    unknown = "unknown"
    windows = "windows"
    linux   = "linux"
    darwin  = "darwin"

    def __setattr__(self, name, value):
        raise NotImplementedError

def quote_path(path):
    if path.startswith("~/"):
        # be careful to not quote shell special chars
        return '~/"%s"' % path[2:]
    else:
        return '"%s"' % path.replace('"', r'\"')

def quote_path_win(path):
    return '"%s"' % path.replace("/", "\\").replace('"', r'\"')


def wrap_for_sudo(command, sudo_prefix, as_user = Users.ADMIN):
    if not command:
        raise Exception("Empty command passed to execution routine")
        
    if not sudo_prefix:
        sudo_prefix = default_sudo_prefix
      
    # If as_user is the CURRENT then there's no need to sudo
    if as_user != Users.CURRENT:

        #sudo needs to use -u <user> for non admin
        if as_user != Users.ADMIN:
            sudo_user = "sudo -u %s" % as_user
            sudo_prefix = sudo_prefix.replace('sudo', sudo_user)

        if '/bin/sh' in sudo_prefix or '/bin/bash' in sudo_prefix:
            command = sudo_prefix + " \"" + command.replace('\\', '\\\\').replace('"', r'\"').replace('$','\\$') + "\""
        else:
            command = sudo_prefix + " /bin/sh -c \"" + command.replace('\\', '\\\\').replace('"', r'\"').replace('$','\\$') + "\""
            
            
    return command

###



class SSH(WbAdminSSH):
    def __init__(self, profile, password_delegate):
        self.mtx = threading.Lock()
        self.wrapped_connect(profile, password_delegate)

    def __del__(self):
        log_debug(_this_file, "Closing SSH connection\n")
        self.close()

    def get_contents(self, filename):
        self.mtx.acquire()
        try:
            ret = WbAdminSSH.get_contents(self, filename)
        finally:
            self.mtx.release()
        return ret

    def set_contents(self, filename, data):
        self.mtx.acquire()
        try:
            ret = WbAdminSSH.set_contents(self, filename, data)
        finally:
            self.mtx.release()
        return ret

    def exec_cmd(self, cmd, as_user = Users.CURRENT, user_password = None, output_handler = None, read_size = 128, get_channel_cb = None):
        output   = None
        retcode  = None

        self.mtx.acquire()
        log_debug3(_this_file, '%s:exec_cmd(cmd="%s", sudo=%s)\n' % (self.__class__.__name__, cmd, str(as_user)) )
        try:
            (output, retcode) = WbAdminSSH.exec_cmd(self, cmd,
                                            as_user=as_user,
                                            user_password=user_password,
                                            output_handler=output_handler,
                                            read_size = read_size,
                                            get_channel_cb = get_channel_cb)
            log_debug3(_this_file, '%s:exec_cmd(): Done cmd="%s"\n' % (self.__class__.__name__, cmd) )
        finally:
            self.mtx.release()

        return (output, retcode)


##===================================================================================================
## Local command execution
def local_run_cmd_linux(command, as_user = Users.CURRENT, user_password=None, sudo_prefix=default_sudo_prefix, output_handler=None):
    # pexpect used only in linux
    import pexpect

    # wrap cmd
    if as_user != Users.CURRENT:
        command = wrap_for_sudo(command, sudo_prefix, as_user)
    script = command.strip(" ")
    if script is None or len(script) == 0:
        return None
    script_to_log = script

    temp_file = tempfile.NamedTemporaryFile()

    script = script + " ; echo CMDRESULT$? >> " + temp_file.name

    result = None

    #if "'" in script:
    #    log_debug2(_this_file, "local_run_cmd_linux(): ' found in script:\n%s\n" %  script )
    #    raise Exception("WBA: Internal error, unexpected character in script to be executed")

    # Exec the command
    waiting_rest_of_password_prompt = False
    child = pexpect.spawn("/bin/bash", ["-c", script]) # script should already have sudo prefix
    if as_user != Users.CURRENT:
        # If sudo is being used, we need to input the password
        try:
            child.expect('assword', timeout=10) # 10s timeout
            if not child.isalive():
                raise Exception("expect died")
            waiting_rest_of_password_prompt = True
            if user_password is not None:
                child.sendline(user_password)
            else:
                child.sendline("")
        except pexpect.TIMEOUT:
            #though we are not able to get the expected output, the password is fed anyway
            if user_password is not None:
                child.sendline(user_password)
            else:
                child.sendline("")
        except pexpect.EOF:
            if output_handler:
                output_handler(child.before.replace("\r\n", "\n"))
            #Nothing we can do, client is terminatd for some reason, try to read anything available
            # usually happens when sudo was executed recently and it doesn't ask for pwd again
            log_debug2(_this_file,"local_run_cmd_linux(): Pipe from sudo is closed. script =\n%s\n" % script )

    if child.isalive():
        should_quit_read_loop = False
        while not should_quit_read_loop and child.isalive():
            try:
                current_text = child.read_nonblocking(256, 30) # 30s timeout
                if waiting_rest_of_password_prompt and current_text.startswith("Here"):
                    waiting_rest_of_password_prompt = False
                    current_text = current_text[4:]

                # If Password prompt shows up again, it means the password we tried earlier was wrong.. so raise an exception
                if as_user != Users.CURRENT and current_text.find('EnterPasswordHere') >= 0:
                    try:
                        child.close()
                    except:
                        pass
                    temp_file.close()
                    raise InvalidPasswordError("Incorrect password for sudo")
                else:
                    if output_handler:
                        # pexpect replaces newlines with \r\n
                        output_handler(current_text.replace("\r\n", "\n"))
            except pexpect.TIMEOUT:
                pass
            except pexpect.EOF:
                should_quit_read_loop = True

    #Try to read anything left
    current_text = child.read()
    if waiting_rest_of_password_prompt and current_text.startswith("Here"):
        waiting_rest_of_password_prompt = False
        current_text = current_text[4:]
    if current_text and output_handler:
        output_handler(current_text.replace("\r\n", "\n"))

    child.close();

    text = temp_file.read()
    
    temp_file.close()
    
    idx = text.rfind("CMDRESULT")
    if (idx != -1):
        result = int(text[idx+9:].strip(" \r\t\n"))

    log_debug3(_this_file, 'local_run_cmd_linux(): script="%s", ret="%s"\n' % (script_to_log, str(result)) )
    return result


def local_run_cmd_windows(command, as_user=Users.CURRENT, user_password=None, sudo_prefix=None, output_handler=None):
    # wrap cmd
    out_str =""
    retcode = 1

    if as_user != Users.CURRENT:

        command = "cmd.exe /C " + command

        try:
            from ctypes import c_int, WINFUNCTYPE, windll
            from ctypes.wintypes import HWND, LPCSTR, UINT
            prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, LPCSTR, LPCSTR, UINT)
            scriptparts = command.partition(" ")
            cmdname = scriptparts[0]
            cmdparams = scriptparts[2]
            paramflags = (1, "hwnd", 0), (1, "operation", "runas"), (1, "file", cmdname), (1, "params", cmdparams), (1, "dir", None), (1, "showcmd", 0)
            SHellExecute = prototype(("ShellExecuteA", windll.shell32), paramflags)
            ret = SHellExecute()
            # > 32 is OK, < 32 is error code
            retcode = 1
            if ret > 32:
                retcode = 0
            else:
                if ret == 0:
                    log_error(_this_file, 'local_run_cmd_windows(): Out of memory executing "%s"\n' % command)
                else:
                    log_error(_this_file, 'local_run_cmd_windows(): Error %i executing "%s"\n' % (ret, command) )
            return retcode
        except:
            import traceback
            traceback.print_exc()
    else:
        try:
            process = subprocess.Popen(command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, shell=True)

            for line in iter(process.stdout.readline, ""):
                if output_handler:
                    output_handler(line)

            process.wait()

            retcode = process.returncode
        except Exception, exc:
            import traceback
            traceback.print_exc()
            retcode = 1
            out_str = "Internal error: %s"%exc

    return retcode


if platform.system() == "Windows":
    local_run_cmd = local_run_cmd_windows
else:
    local_run_cmd = local_run_cmd_linux

def local_get_cmd_output(command, as_user=Users.CURRENT, user_password=None):
    output = []
    output_handler = lambda line, l=output: l.append(line)
    rc = local_run_cmd(command=command, as_user=as_user, user_password=user_password, sudo_prefix=None, output_handler=output_handler)
    return ("\n".join(output), rc)

##===================================================================================================
## Process Execution


_process_ops_classes = []


class ProcessOpsBase(object):
    cmd_output_encoding = ""

    def __init__(self, **kwargs):
        pass

    def post_init(self):
        pass

    def expand_path_variables(self, path):
        return path

    def get_cmd_output(self, command, as_user=Users.CURRENT, user_password=None):
        output = []
        output_handler = lambda line, l=output: l.append(line)
        rc = self.exec_cmd(command, as_user, user_password, output_handler)
        return ("\n".join(output), rc)


class ProcessOpsNope(ProcessOpsBase):
    @classmethod
    def match(cls, (host, target, connect)):
        return connect == 'none'

    def expand_path_variables(self, path):
        return path

    def exec_cmd(self, command, as_user=Users.CURRENT, user_password=None, output_handler=None):
        return None

    def get_cmd_output(self, command, as_user=Users.CURRENT, user_password=None):
        return ("", None)

_process_ops_classes.append(ProcessOpsNope)


class ProcessOpsLinuxLocal(ProcessOpsBase):
    @classmethod
    def match(cls, (host, target, connect)):
        return connect == 'local' and (host in (wbaOS.linux, wbaOS.darwin) and target in (wbaOS.linux, wbaOS.darwin))

    def __init__(self, **kwargs):
        ProcessOpsBase.__init__(self, **kwargs)
        self.sudo_prefix= kwargs.get("sudo_prefix", default_sudo_prefix)

    def exec_cmd(self, command, as_user=Users.CURRENT, user_password=None, output_handler=None):
        return local_run_cmd_linux(command, as_user, user_password, self.sudo_prefix, output_handler)

_process_ops_classes.append(ProcessOpsLinuxLocal)


class ProcessOpsLinuxRemote(ProcessOpsBase):
    @classmethod
    def match(cls, (host, target, connect)):
        # host doesn't matter
        return connect == 'ssh' and target in (wbaOS.linux, wbaOS.darwin)

    def __init__(self, **kwargs): # Here should be at least commented list of args
        ProcessOpsBase.__init__(self, **kwargs)

        self.sudo_prefix= kwargs.get("sudo_prefix", default_sudo_prefix)
        self.ssh = kwargs["ssh"]

    def exec_cmd(self, command, as_user=Users.CURRENT, user_password=None, output_handler=None):
        #if not self.ssh:
        #    raise Exception("No SSH session active")

        if as_user != Users.CURRENT:
            command = wrap_for_sudo(command, self.sudo_prefix, as_user)

        def ssh_output_handler(ssh_channel, handler):
            import socket
            loop = True
            while loop:
                try:
                    chunk = ssh_channel.recv(128)
                    if chunk is not None and chunk != "":
                        handler(chunk)
                    else:
                        loop = False
                except socket.timeout:
                    loop = False

        if output_handler:
            handler = lambda chan, h=output_handler: ssh_output_handler(chan, h)
        else:
            handler = None

        if self.ssh:
            # output_handler taken by ssh.exec_cmd is different from the one used elsewhere
            dummy_text, ret = self.ssh.exec_cmd(command,
                    as_user=as_user, user_password=user_password,
                    output_handler=handler)
        else:
            ret = 1
            if output_handler:
                output_handler("No SSH connection is active")
            else:
                print("No SSH connection is active")
                log_info(_this_file, 'No SSH connection is active\n')

        return ret

_process_ops_classes.append(ProcessOpsLinuxRemote)




WIN_REG_QUERY_PROGRAMFILES = 'reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion /v "ProgramFilesDir"'
WIN_REG_QUERY_PROGRAMFILES_x86 = 'reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion /v "ProgramFilesDir (x86)"'

WIN_PROGRAM_FILES_VAR = "%ProgramFiles%"
WIN_PROGRAM_FILES_X86_VAR = "%ProgramFiles(x86)%"
WIN_PROGRAM_FILES_X64_VAR = "%ProgramW6432%"


class ProcessOpsWindowsLocal(ProcessOpsBase):
    @classmethod
    def match(cls, (host, target, connect)):
        return (host == wbaOS.windows and target == wbaOS.windows and connect in ('wmi', 'local'))

    def __init__(self, **kwargs):
        ProcessOpsBase.__init__(self, **kwargs)
        self.target_shell_variables = {}

    def post_init(self):
        self.fetch_windows_shell_info()

    def exec_cmd(self, command, as_user, user_password, output_handler=None):
        return local_run_cmd_windows(command, as_user, user_password, None, output_handler)

    def expand_path_variables(self, path):
        """
        Expand some special variables in the path, such as %ProgramFiles% and %ProgramFiles(x86)% in
        Windows. Uses self.target_shell_variables for the substitutions, which should have been
        filled when the ssh connection to the remote host was made.
        """
        for k, v in self.target_shell_variables.iteritems():
            path = path.replace(k, v)
        return path

    def fetch_windows_shell_info(self):
        # get some info from the remote shell
        result, code = self.get_cmd_output("chcp.com")
        if code == 0:
            result = result.strip(" .\r\n").split()
            if len(result) > 0:
                self.cmd_output_encoding = "cp" + result[-1]
        else:
            print "WARNING: Unable to determine codepage from shell: %s" % result
            log_warning(_this_file, '%s.fetch_windows_shell_info(): WARNING: Unable to determine codepage from shell: "%s"\n' % (self.__class__.__name__, str(result)) )

        result, code = self.get_cmd_output("echo %PROCESSOR_ARCHITECTURE%")
        if result:
            result = result.strip()
        ProgramFilesVar = None
        x86var = None
        if result != "x86":#we are on x64 win in x64 mode
            x86var = WIN_PROGRAM_FILES_X86_VAR
            ProgramFilesVar = WIN_PROGRAM_FILES_VAR
        else:
            result, code = self.get_cmd_output("echo %PROCESSOR_ARCHITEW6432%")
            if result:
                result = result.strip()
            if result == "%PROCESSOR_ARCHITEW6432%":#we are on win 32
                x86var = WIN_PROGRAM_FILES_VAR
                ProgramFilesVar = WIN_PROGRAM_FILES_VAR
            else:#32bit app on x64 win
                x86var = WIN_PROGRAM_FILES_VAR
                ProgramFilesVar = WIN_PROGRAM_FILES_X64_VAR

        result, code = self.get_cmd_output("echo "+ ProgramFilesVar)
        if code == 0:
            self.target_shell_variables["%ProgramFiles%"] = result.strip("\r\n")
            if ProgramFilesVar != "%ProgramFiles%":
                self.target_shell_variables[ProgramFilesVar] = result.strip("\r\n")
        else:
            print "WARNING: Unable to fetch ProgramFiles value in Windows machine: %s"%result
            log_warning(_this_file, '%s.fetch_windows_shell_info(): WARNING: Unable to fetch ProgramFiles value in Windows machine: "%s"\n' % (self.__class__.__name__, str(result)) )

        # this one only exists in 64bit windows
        result, code = self.get_cmd_output("echo "+ x86var)
        if code == 0:
            self.target_shell_variables["%ProgramFiles(x86)%"] = result.strip("\r\n")
        else:
            print "WARNING: Unable to fetch ProgramFiles(x86) value in local Windows machine: %s"%result
            log_warning(_this_file, '%s.fetch_windows_shell_info(): WARNING: Unable to fetch ProgramFiles(x86) value in local Windows machine: "%s"\n' % (self.__class__.__name__, str(result)) )

        log_debug(_this_file, '%s.fetch_windows_shell_info(): Encoding: "%s", Shell Variables: "%s"\n' % (self.__class__.__name__, self.cmd_output_encoding, str(self.target_shell_variables)))


_process_ops_classes.append(ProcessOpsWindowsLocal)


class ProcessOpsWindowsRemoteSSH(ProcessOpsWindowsLocal):
    @classmethod
    def match(cls, (host, target, connect)):
        # host doesn't matter
        return (target == wbaOS.windows and connect == 'ssh')

    def __init__(self, **kwargs):
        ProcessOpsWindowsLocal.__init__(self, **kwargs)

        self.ssh = kwargs["ssh"]


    def post_init(self):
        if self.ssh:
            self.fetch_windows_shell_info()


    def exec_cmd(self, command, as_user=Users.CURRENT, user_password=None, output_handler=None):
        command = "cmd.exe /c " + command

        if not self.ssh:
            raise Exception("No SSH session active")

        def ssh_output_handler(ssh_channel, handler):
            import socket
            loop = True
            while loop:
                try:
                    chunk = ssh_channel.recv(128)
                    if chunk is not None and chunk != "":
                        handler(chunk)
                    else:
                        loop = False
                except socket.timeout:
                    loop = False

        if output_handler:
            handler = lambda chan, h=output_handler: ssh_output_handler(chan, h)
        else:
            handler = None

        # output_handler taken by ssh.exec_cmd is different from the one used elsewhere
        dummy_text, ret = self.ssh.exec_cmd(command,
                as_user=as_user, user_password=user_password,
                output_handler=handler)
        return ret




_process_ops_classes.append(ProcessOpsWindowsRemoteSSH)



##===================================================================================================
## File Operations

_file_ops_classes = []

class FileOpsNope(object):
    @classmethod
    def match(cls, target_os, connection_method):
        return connection_method == "none"

    def __init__(self, process_ops, ssh = None, target_os = None):
        pass

    def save_file_content(self, filename, content, as_user = Users.CURRENT, user_password = None):
        pass

    def save_file_content_and_backup(self, filename, content, backup_extension, as_user = Users.CURRENT, user_password = None):
        pass

    def get_file_content(self, filename, as_user = Users.CURRENT, user_password = None):
        return ""

    def _copy_file(self, source, dest, as_user = Users.CURRENT, user_password = None): # not used externally
        pass

    def check_dir_writable(self, path, as_user=Users.CURRENT, user_password=None):
        return False

    def file_exists(self, path, as_user = Users.CURRENT, user_password = None):
        return False

    def get_file_owner(self, path, as_user = Users.CURRENT, user_password = None):
        return False
        
    def create_directory(self, path, as_user = Users.CURRENT, user_password = None):
        pass

    def get_available_space(self, path, as_user = Users.CURRENT, user_password = None):
        return False

    # Return format is list of entries in dir (directories go first, each dir name is follwoed by /)
    def listdir(self, path, as_user = Users.CURRENT, user_password = None): # base operation to build file_exists and remote file selector
        return []

    def get_owner(self, path): # base operation to build file_exists and remote file selector
        return []
_file_ops_classes.append(FileOpsNope)


#===============================================================================
# The local file ops are context free, meaning that they
# do not need active shell session to work on
# local  all  plain
#   save_file_content  - python
#   get_file_content   - python
#   copy_file          - python
#   get_dir_access     - python (returns either rw or ro or none)
#   listdir            - python
# local  all  sudo derives from local-all-plain
#   save_file_content  - shell
#   get_file_content   - python (maybe sudo if file is 0600)
#   copy_file          - shell
#   get_dir_access     - python (returns either rw or ro or none)
#   listdir            - python/shell(for ro-dirs)
class FileOpsLocalUnix(object):
    @classmethod
    def match(cls, target_os, connection_method):
        return connection_method == "local" and target_os in (wbaOS.linux, wbaOS.darwin)

    process_ops = None
    def __init__(self, process_ops, ssh=None, target_os = None):
        self.target_os = target_os
        self.process_ops = process_ops
        self.tempdir = os.path.expanduser('~')

    def file_exists(self, filename, as_user=Users.CURRENT, user_password=None):
        res = self.process_ops.exec_cmd('test -e ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line:None)
        return res == 0

    def get_available_space(self, path, as_user=Users.CURRENT, user_password=None):
        output_text = StringIO.StringIO()
        res = self.process_ops.exec_cmd("df -Ph %s" % quote_path(path),
                            as_user,
                            user_password,
                            output_handler = output_text.write)

        output_text = sanitize_sudo_output(output_text.getvalue()).strip()
        
        available = "Could not determine"
        if output_text:
            tokens = output_text.split("\n")[-1].strip().split()
            available = "%s of %s available" % (tokens[3], tokens[1])
        
        return available
        
    def get_file_owner(self, path, as_user = Users.CURRENT, user_password = None):
        if self.target_os == wbaOS.linux:
          command = 'stat -c %U '
        else:
          command = 'stat -f "%Su" '
      
        output = []
        command = command + quote_path(path)
        
        self.process_ops.exec_cmd(command,
                            as_user,
                            user_password,
                            output_handler= lambda line,l=output: l.append(line))
             
        res = "\n".join(output).strip()
        return res


    # content must be a string
    def save_file_content(self, filename, content, as_user = Users.CURRENT, user_password = None):
        self.save_file_content_and_backup(filename, content, None, as_user, user_password)


    def save_file_content_and_backup(self, filename, content, backup_extension, as_user = Users.CURRENT, user_password = None):
        log_debug(_this_file, '%s: Saving file "%s" with backup (sudo="%s")\n' %  (self.__class__.__name__, filename, as_user) )
        if as_user != Users.CURRENT:
            # The delete argument is only available starting from py 2.6 (NamedTemporaryFile deletes files on close in all cases, unless you pass delete=False)
            tmp = tempfile.NamedTemporaryFile()
            tmp_name = tmp.name
            try:
                log_debug(_this_file, '%s: Writing file contents to tmp file "%s"\n' %  (self.__class__.__name__, tmp_name) )
                tmp.write(content)
                tmp.flush()
                if backup_extension and os.path.exists(filename):
                    log_debug(_this_file, '%s: Creating backup of "%s" to "%s"\n' %  (self.__class__.__name__, filename, filename+backup_extension))
                    
                    # The old file is backed up as the requested user
                    self._copy_file(source = filename, dest = filename + backup_extension,
                                    as_user = as_user, user_password = user_password)

                log_debug(_this_file, '%s: Copying over tmp file to final filename using sudo: %s -> %s\n' % (self.__class__.__name__, tmp_name, filename) )
                
                # The new file is copied as the root user
                self._copy_file(source = tmp_name, dest = filename, as_user = Users.ADMIN, user_password = user_password)
                
                # If needed changes the ownership of the new file to the requested user
                if as_user != Users.ADMIN:
                
                    # TODO: Does this need any validation being executed by root??
                    self.process_ops.exec_cmd("chown %s %s" % (as_user, quote_path(filename)),
                                  as_user   = Users.ADMIN,
                                  user_password = user_password)
                                  
                log_debug(_this_file, '%s: Copying file done\n' % self.__class__.__name__)
                tmp.close()
            except Exception, exc:
                log_error(_this_file, '%s: Exception caught: %s\n' % (self.__class__.__name__, str(exc)) )
                if tmp:
                    tmp.close()
                raise
        else:
            target_dir = splitpath(filename)[0]

            if not os.path.exists(target_dir):
                log_debug(_this_file, '%s: Target directory "%s" does not exist\n' % (self.__class__.__name__, target_dir ) )
                raise IOError(errno.ENOENT, "The directory %s does not exist" % target_dir)

            try:
                self.check_dir_writable(target_dir)
            except Exception, e:
                log_debug(_this_file, '%s: Target directory "%s" is not writable: %s\n' % (self.__class__.__name__, target_dir, e) )
                raise PermissionDeniedError("Cannot write to target directory")

            if os.path.exists(filename) and backup_extension:
                log_debug(_this_file, '%s: Target file "%s" exists, creating backup\n' % (self.__class__.__name__, filename) )
                # backup config file
                self._copy_file(filename, filename+backup_extension)
            try:
                f = open(filename, 'w')
            except OSError, err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not open file %s for writing" % filename)
                raise err
            f.write(content)
            f.close()


    # UseCase: If get_file_content fails with exception of access, try sudo
    def get_file_content(self, filename, as_user = Users.CURRENT, user_password = None):
        cont = []
        if as_user != Users.CURRENT:
            output = StringIO.StringIO()
            local_run_cmd_linux('cat %s' % filename, as_user, user_password, sudo_prefix=self.process_ops.sudo_prefix, output_handler=output.write)
            output = output.getvalue()
            if "No such file or directory" in output:
                error = IOError()
                error.errno = errno.ENOENT
                raise error

            return output
        else:
            try:
                f = open(filename, 'r')
            except (IOError, OSError), e:
                if e.errno == errno.EACCES:
                    raise PermissionDeniedError("Can't open file '%s'" % filename)
                raise e
            cont = f.read()
            f.close()
        return cont


    def create_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                os.mkdir(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not create directory %s" % path)
                raise err
        else:
            output = []
            res = self.process_ops.exec_cmd("/bin/mkdir " + quote_path(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = lambda line, l= output: l.append(line)
                                            )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError(output)


    def remove_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                os.rmdir(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not remove directory %s" % path)
                raise err
        else:
            output = []
            res = self.process_ops.exec_cmd("/bin/rmdir " + quote_path(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = lambda line, l= output: l.append(line)
                                            )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError(output)


    def delete_file(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                os.remove(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not delete file %s" % path)
                raise err
        else:
            output = []
            res = self.process_ops.exec_cmd("/bin/rm " + quote_path(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = lambda line, l= output: l.append(line)
                                            )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError(output)


    def _copy_file(self, source, dest, as_user = Users.CURRENT, user_password = None): # not used externally
        if as_user == Users.CURRENT:
            try:
                shutil.copy(source, dest)
            except (IOError, OSError), e:
                if e.errno == errno.EACCES:
                    raise PermissionDeniedError("Can't copy %s to %s" % (source, dest))
                raise
        else:
            output = []
            res = self.process_ops.exec_cmd("/bin/cp " + quote_path(source) + " " + quote_path(dest),
                          as_user   = as_user,
                          user_password = user_password,
                          output_handler = lambda line, l= output: l.append(line)
                         )

            if res != 0:
                output = "\n".join(output)
                if output.find("Permission denied") != -1:
                  raise PermissionDeniedError("Permission denied copying %s to %s" % (source, dest) )
                else:
                  
                  print "file copy as %s failed: " % as_user, output, res

                # TODO: Add handling of errors

    def check_dir_writable(self, path, as_user=Users.CURRENT, user_password=None):
    
        filename = joinpath(path, '.wb_write_test')
        
        output = []

        res = self.process_ops.exec_cmd('touch ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line, l= output: l.append(line))
        if res == 0:
            self.process_ops.exec_cmd('/bin/rm ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line:None)
        else:
            output = sanitize_sudo_output('\n'.join(output).strip())
            error = None
            if "No such file or directory" in output:
                error = IOError("The directory does not exist")
                error.errno = errno.ENOENT
            elif "Permission denied" in output:
                error = PermissionDeniedError("Permission denied for writing")
                error.errno = errno.EACCES
            else:
                error = Exception("Unable to verify directory is writable: %s" % output)
                
            raise error
          
        return res == 0
        

    # Return format is list of entries in dir (directories go first, each dir name is followed by /)
    def listdir(self, path, as_user = Users.CURRENT, user_password = None): # base operation to build file_exists and remote file selector
        dirlist = []
        if as_user == Users.CURRENT:
            try:
                _path = path
                dlist = os.listdir(_path)
                # mod = ""
                for item in dlist:
                    _path = os.path.join(path, item)
                    item_stat = os.stat(_path).st_mode
                    if stat.S_ISDIR(item_stat):
                        dirlist.insert(0, item + '/')
                    elif stat.S_ISREG(item_stat) or stat.S_ISLNK(item_stat):
                        dirlist.append(item)
            except (IOError, OSError), e:
                if e.errno == errno.EACCES:
                    raise PermissionDeniedError("Permission denied accessing %s" % _path)
                raise
            return dirlist
        else:
            output = StringIO.StringIO()
            res = self.process_ops.exec_cmd('/bin/ls -1 -p ' + quote_path(path),
                                            as_user,
                                            user_password,
                                            output_handler = output.write)
            output = sanitize_sudo_output(output.getvalue())
            if res != 0:
                if "Permission denied" in output:
                    raise PermissionDeniedError("Permission denied accessing %s" % path)
                raise RuntimeError(output)
            else:
                return [s.strip() for s in output.split("\n")]

_file_ops_classes.append(FileOpsLocalUnix)



#===============================================================================
class FileOpsLocalWindows(FileOpsLocalUnix): # Used for remote as well, if not using sftp
    @classmethod
    def match(cls, target_os, connection_method):
        return connection_method in ("local", "wmi") and target_os == wbaOS.windows


    def __init__(self, process_ops, ssh=None, target_os = None):
        FileOpsLocalUnix.__init__(self, process_ops, ssh, target_os)

        tempdir, rc= self.process_ops.get_cmd_output("echo %temp%")
        if tempdir and tempdir.strip():
            self.tempdir = tempdir.strip()


    def get_file_owner(self, path, as_user = Users.CURRENT, user_password = None):
        
        # This doesn't work properly tho is not needed in windows

        command = 'dir /q %s*' % quote_path(path)
      
        output = []
        
        self.process_ops.exec_cmd(command,
                            as_user,
                            user_password,
                            output_handler= lambda line,l=output: l.append(line))

        res = ""
        for line in output:
            line_data = line.split()

            if line[len(line)-1] == path:
                res = line[len(line)-2]

        return res

    def check_dir_writable(self, path, as_user=Users.CURRENT, user_password=None):
    
        filename = joinpath(path, '.wb_write_test')
        
        output = []
        
        res = self.process_ops.exec_cmd('echo "tst" > ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line, l= output: l.append(line))
                            
        if res == 0:
            self.process_ops.exec_cmd('del ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line:None)
        else:
            output = '\n'.join(output)
            error = None
                
            if "The system cannot find the path specified" in output:
                error = IOError("The directory does not exist")
                error.errno = errno.ENOENT
            elif "Access is denied" in output:
                error = PermissionDeniedError("Permission denied for writing")
                error.errno = errno.EACCES
            else:
                error = Exception("Unable to verify directory is writable")
                
            raise error
          
        return res == 0

    def file_exists(self, filename, as_user = Users.CURRENT, user_password=None):
        command = 'exist ' + quote_path_win(filename)

        res = self.process_ops.exec_cmd('dir ' + quote_path_win(filename),
                          as_user   = as_user,
                          user_password = user_password,
                          output_handler = lambda line:None
                         )

        return res == 0

    def get_available_space(self, path, as_user=Users.CURRENT, user_password=None):
        out = []        

        res = self.process_ops.exec_cmd('dir %s' % quote_path(path),
                            as_user,
                            user_password,
                            output_handler = lambda line, l = out:l.append(line))

        available = "Could not determine"
        if res == 0 and len(out):
            measures = ['B', 'KB', 'MB', 'GB', 'TB']
            tokens = out[-1].strip().split()
            
            total = float(tokens[2].replace(",",""))
            index = 0
            while index < len(measures) and total > 1024:
                total = total / 1024
                index = index + 1


            available = "%.2f %s available" % (total, measures[index])
        
        return available


    def save_file_content_and_backup(self, filename, content, backup_extension, as_user = Users.CURRENT, user_password = None):
        log_debug(_this_file, '%s: Saving file "%s" with backup (sudo="%s")\n' % (self.__class__.__name__, filename, str(as_user)) )
        if as_user != Users.CURRENT:
            tmp = tempfile.NamedTemporaryFile("w+b", delete = False)
            tmp_name = tmp.name
            try:
                log_debug(_this_file, '%s: Writing file contents to tmp file "%s"\n' % (self.__class__.__name__, tmp_name) )
                tmp.write(content)
                tmp.close()

                if backup_extension and os.path.exists(filename):
                    #dprint_ex(1, "Creating backup of %s to %s" % (filename, filename+backup_extension))
                    #self._copy_file(source = filename, dest = filename + backup_extension,
                    #                as_user = as_user, user_password = user_password)

                    # Create backup and copy over file to final destination in a single command
                    # This is done because running copy twice, would bring up the UAC dialog twice

                    script = "copy /Y %s %s && copy /Y %s %s" % (quote_path_win(filename), quote_path_win(filename + backup_extension),
                                                                 quote_path_win(tmp_name), quote_path_win(filename))
                    log_debug(_this_file, '%s: Creating backup and commiting tmp file: "%s"\n' % (self.__class__.__name__, script) )
                    output = []
                    res = self.process_ops.exec_cmd(script,
                          as_user   = as_user,
                          user_password = user_password,
                          output_handler = lambda line, l= output: l.append(line)
                         )
                    if res != 0:
                        output = "\n".join(output)
                        raise RuntimeError("Error while executing '%s'. Output = '%s'" % (script, output))
                else:
                    log_debug(_this_file, '%s: Copying over tmp file to final filename using sudo: %s -> %s\n' % (self.__class__.__name__, tmp_name, filename) )
                    self._copy_file(source = tmp_name, dest = filename, as_user = as_user, user_password = user_password)


                log_debug(_this_file, '%s: Delete tmp file "%s"\n' % (self.__class__.__name__, tmp_name) )
                # delete tmp file

                ## BIZARRE STUFF GOING ON HERE
                # commenting out the following line, will make something in committing config file change fail
                # even tho the copies happen before this line.. wtf
               # os.remove(tmp_name)

                log_debug(_this_file, '%s: Done.\n' % self.__class__.__name__)
            except Exception, exc:
                log_error(_this_file, '%s: Exception caught: %s\n' % (self.__class__.__name__, str(exc)) )
                if tmp:
                    tmp.close()
                raise
        else:
            FileOpsLocalUnix.save_file_content_and_backup(self, filename, content, backup_extension, as_user, user_password)

    # UseCase: If get_file_content fails with exception of access, try sudo
    def get_file_content(self, filename, as_user = Users.CURRENT, user_password = None):
        if as_user != Users.CURRENT:
            output = StringIO.StringIO()
            local_run_cmd_windows('type %s' % quote_path_win(filename), Users.ADMIN, user_password, sudo_prefix=None, output_handler=output.write)
            output = output.getvalue()
            if "No such file or directory" in output:
                error = IOError()
                error.errno = errno.ENOENT
                raise error

            return output
        else:
            return FileOpsLocalUnix.get_file_content(self, filename, as_user, user_password)


    def _copy_file(self, source, dest, as_user = Users.CURRENT, user_password = None): # not used externally, but in superclass
        if as_user == Users.CURRENT:
            try:
                shutil.copyfile(source, dest)
            except (IOError, OSError), e:
                if e.errno == errno.EACCES:
                    raise PermissionDeniedError("Can't copy %s to %s" % (source, dest))
                raise
        else:
            output = []
            res = self.process_ops.exec_cmd("copy /Y " + quote_path_win(source) + " " + quote_path_win(dest),
                          as_user   = as_user,
                          user_password = user_password,
                          output_handler = lambda line, l= output: l.append(line)
                         )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError("Error copying file %s to %s\n%s" % (source, dest, output.strip()))


    def create_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                os.mkdir(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not create directory %s" % path)
                raise err
        else:
            output = []
            res = self.process_ops.exec_cmd("mkdir " + quote_path_win(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = lambda line, l= output: l.append(line)
                                            )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError(output)


    def remove_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                os.rmdir(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not remove directory %s" % path)
                raise err
        else:
            output = []
            res = self.process_ops.exec_cmd("rmdir " + quote_path_win(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = lambda line, l= output: l.append(line)
                                            )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError(output)


    def delete_file(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                os.remove(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not delete file %s" % path)
                raise err
        else:
            output = []
            res = self.process_ops.exec_cmd("del " + quote_path_win(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = lambda line, l= output: l.append(line)
                                            )
            if res != 0:
                output = "\n".join(output)
                raise RuntimeError(output.strip())

    def listdir(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT: # derived from FileOpsLocalUnix
            return FileOpsLocalUnix.listdir(self, path)
        else:
            output = StringIO.StringIO()
            res = self.process_ops.exec_cmd("dir /B " + quote_path_win(path),
                                            as_user   = as_user,
                                            user_password = user_password,
                                            output_handler = output.write
                                            )
            if res != 0:
                output = output.getvalue().strip()
                if "File not found" in output:
                    raise IOError(errno.ENOENT, output)
                raise RuntimeError(output)
            else:
                return [s.strip() for s in output.strip().split("\n")]

_file_ops_classes.append(FileOpsLocalWindows)

#===============================================================================
# This remote file ops are shell dependent, they must be
# given active ssh connection, possibly, as argument
# remote unix sudo/non-sudo
#   save_file_content  - shell
#   get_file_content   - shell
#   copy_file          - shell
#   get_dir_access     - shell(returns either rw or ro or none)
#   listdir            - shell(for ro-dirs)
class FileOpsRemoteUnix(object):
    @classmethod
    def match(cls, target_os, connection_method):
        return connection_method == "ssh" and target_os in (wbaOS.linux, wbaOS.darwin)

    def __init__(self, process_ops, ssh, target_os = None):
        self.process_ops = process_ops
        self.ssh = ssh
        self.target_os = target_os


    def file_exists(self, filename, as_user=Users.CURRENT, user_password=None):
        if self.ssh:
            log_debug3(_this_file, '%s: %s\n' % (self.__class__.__name__, 'Checking if file "%s" exists ' + 
                                                'as admin' if as_user == Users.ADMIN else 
                                                ('as the ' + 'regular' if as_user == Users.CURRENT else as_user + ' user') ) )
            if as_user != Users.CURRENT:
                # -t doesn't mean the same thing in osx
                command = wrap_for_sudo('test -e ' + quote_path(filename), self.process_ops.sudo_prefix, as_user)
                out, ret = self.ssh.exec_cmd(command, as_user, user_password)
                if ret != 0:
                    raise InvalidPasswordError('Incorrect password for sudo')
                return True
            else:
                try:
                    return self.ssh.file_exists(filename)
                except IOError, exc:
                    if exc.errno == errno.EACCES:
                        raise PermissionDeniedError("Permission denied attempting to read file %s" % filename)
                    elif exc.errno == errno.ENOENT:
                        return False
                    raise
        else:
            print "Attempt to read remote file with no ssh session"
            log_error(_this_file, '%s: Attempt to read remote file with no ssh session\n' % self.__class__.__name__)
            import traceback
            traceback.print_stack()
            raise Exception("Cannot read remote file without an SSH session")
        return False

    def get_available_space(self, path, as_user=Users.CURRENT, user_password=None):
        output_text = StringIO.StringIO()
        res = self.process_ops.exec_cmd("df -Ph %s" % quote_path(path),
                            as_user,
                            user_password,
                            output_handler = output_text.write)

        output_text = sanitize_sudo_output(output_text.getvalue()).strip()
        
        available = "Could not determine"
        if output_text:
            tokens = output_text.split("\n")[-1].strip().split()
            available = "%s of %s available" % (tokens[3], tokens[1])
        
        return available

    def get_file_owner(self, filename, as_user=Users.CURRENT, user_password=None):
        if self.ssh:
            if self.target_os == wbaOS.linux:
                command = 'stat -c %U '
            else:
                command = 'stat -f "%Su" '
                
            command = command + quote_path(filename)
                  
            if as_user != Users.CURRENT:
                command = wrap_for_sudo(command, self.process_ops.sudo_prefix, as_user)
                
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            
            if ret != 0:
                raise InvalidPasswordError('Incorrect password for sudo')
                
            return out.strip()
        else:
            print "Attempt to read remote file with no ssh session"
            log_error(_this_file, '%s: Attempt to read remote file with no ssh session\n' % self.__class__.__name__)
            import traceback
            traceback.print_stack()
            raise Exception("Cannot read remote file without an SSH session")
        return False
        
    def create_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                self.ssh.mkdir(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not create directory %s" % path)
                raise err
        else:
            output = []
            command = wrap_for_sudo('/bin/mkdir ' + quote_path(path), self.process_ops.sudo_prefix, as_user)
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            if ret != 0:
                raise RuntimeError(out)


    def remove_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                self.ssh.rmdir(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not remove directory %s" % path)
                raise err
        else:
            output = []
            command = wrap_for_sudo('/bin/rmdir ' + quote_path(path), self.process_ops.sudo_prefix, as_user)
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            if ret != 0:
                raise RuntimeError(out)

    def delete_file(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                self.ssh.remove(path)
            except (IOError, OSError), err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not delete file %s" % path)
                raise err
        else:
            output = []
            command = wrap_for_sudo('/bin/rm ' + quote_path(path), self.process_ops.sudo_prefix, as_user)
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            if ret != 0:
                raise RuntimeError(out)

    # UseCase: If get_file_content fails with exception of access, try sudo
    def get_file_content(self, filename, as_user = Users.CURRENT, user_password = None): # may raise IOError
        if self.ssh:
            if as_user != Users.CURRENT:
                command = wrap_for_sudo('cat %s' % filename, self.process_ops.sudo_prefix, as_user)
                out, ret = self.ssh.exec_cmd(command, as_user, user_password)
                
                # Validating the type of error to return the proper exception
                if ret != 0:
                    if 'No such file or directory' in out:
                        error = IOError()
                        error.errno = errno.ENOENT
                        raise error
                    else:
                        raise Exception('Error executing "%s" via SSH in remote server' % command)
                        
                if out.startswith('EnterPasswordHere'):
                    _, out = out.split('\n', 1)  # Strip the first line
                return out
            else:
                try:
                    return self.ssh.get_contents(filename)
                except (IOError, OSError), exc:
                    if exc.errno == errno.EACCES:
                        raise PermissionDeniedError("Permission denied attempting to read file %s" % filename)
        else:
            print "Attempt to read remote file with no ssh session"
            log_error(_this_file, '%s: Attempt to read remote file with no ssh session\n' % self.__class__.__name__)
            import traceback
            traceback.print_stack()
            raise Exception("Cannot read remote file without an SSH session")
        return None

    #-----------------------------------------------------------------------------
    def save_file_content(self, filename, content, as_user = Users.CURRENT, user_password = None):
        self.save_file_content_and_backup(filename, content, None, as_user, user_password)

    #-----------------------------------------------------------------------------
    def save_file_content_and_backup(self, path, content, backup_extension, as_user = Users.CURRENT, user_password = None):
        # Check if dir, where config file will be stored is writable
        dirname, filename = splitpath(path)

        if as_user == Users.CURRENT:
            try:
                self.check_dir_writable(dirname.strip(" \r\t\n"))
            except Exception:
                raise PermissionDeniedError("Cannot write to directory %s" % dirname)

        if self.ssh is not None:
            ## Get home dir for using as tmpdir
            homedir, status = self.process_ops.get_cmd_output("echo ~")
            if type(homedir) is unicode:
                homedir = homedir.encode("utf8")
            if type(homedir) is str:
                homedir = homedir.strip(" \r\t\n")
            else:
                homedir = None
            log_debug2(_this_file, '%s: Got home dir: "%s"\n' % (self.__class__.__name__, homedir) )

            if not homedir:
                raise Exception("Unable to get path for remote home directory")

            tmpfilename = homedir + "/.wba.temp"

            log_debug(_this_file, '%s: Remotely writing contents to temporary file "%s"\n' % (self.__class__.__name__, tmpfilename) )
            log_debug3(_this_file, '%s: %s\n' % (self.__class__.__name__, content) )
            self.ssh.set_contents(tmpfilename, content)

            if backup_extension:
                log_debug(_this_file, '%s: Backing up %s\n' % (self.__class__.__name__, path) )
                backup_cmd = "/bin/cp " + quote_path(path) + " " + quote_path(path+backup_extension)
                self.process_ops.exec_cmd(backup_cmd, as_user, user_password)

            copy_to_dest = "/bin/cp " + quote_path(tmpfilename) + " " + quote_path(path)
            delete_tmp = "/bin/rm " + quote_path(tmpfilename)
            log_debug(_this_file, '%s: Copying file to final destination: "%s"\n' % (self.__class__.__name__, copy_to_dest) )
            
            # The new file is copied as the root user
            self.process_ops.exec_cmd(copy_to_dest, Users.ADMIN, user_password)
            
            # If needed changes the ownership of the new file to the requested user
            if as_user != Users.ADMIN:
                
                # TODO: Does this need any validation being executed by root??
                self.process_ops.exec_cmd("chown %s %s" % (as_user, quote_path(path)),
                                  as_user   = Users.ADMIN,
                                  user_password = user_password)
            
            log_debug(_this_file, '%s: Deleting tmp file: "%s"\n' % (self.__class__.__name__, delete_tmp) )
            self.process_ops.exec_cmd(delete_tmp)
        else:
            raise Exception("No SSH session active, cannot save file remotely")



    def check_dir_writable(self, path, as_user=Users.CURRENT, user_password=None):
        filename = joinpath(path, '.wb_write_test')
        
        output = []
        
        res = self.process_ops.exec_cmd('touch ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line, l= output: l.append(line))
                            
        if res == 0:
            self.process_ops.exec_cmd('/bin/rm ' + quote_path(filename),
                            as_user,
                            user_password,
                            output_handler = lambda line:None)
        else:
            output = '\n'.join(output)
            error = None
            if "No such file or directory" in output:
                error = IOError(errno.ENOENT, "The directory does not exist")
            elif "Permission denied" in output:
                error = PermissionDeniedError("Permission denied for writing")
                error.errno = errno.EACCES
            else:
                error = Exception("Unable to verify directory is writable: %s" % output.strip())
                
            raise error
          
        return res == 0
        


    def listdir(self, path, as_user = Users.CURRENT, user_password = None): # base operation to build file_exists and remote file selector
        dlist = []
        if self.ssh is not None:
            if as_user == Users.CURRENT:
                (output, status) = self.ssh.exec_cmd('/bin/ls -1 -p %s' % path)
                if status == 0:
                    raw_list = output.split('\n')
                    for item in raw_list:
                        if item[-1:] == '/':
                            dlist.insert(0, item)
                        else:
                            dlist.append(item)
            else:
                output = StringIO.StringIO()
                rc = self.process_ops.exec_cmd("/bin/ls -1 -p %s" % quote_path(path),
                                          as_user   = as_user,
                                          user_password = user_password,
                                          output_handler = output.write)
                output = output.getvalue()
                if rc != 0:
                    if "No such file or directory" in output:
                        raise IOError(errno.ENOENT, "Directory %s does not exist" % path)
                    elif "Permission denied" in output:
                        raise PermissionDeniedError("Permission denied for reading from %s" % path)
                    else:
                        raise Exception(output.strip())
                else:
                    dlist = [s.strip() for s in output.strip().split("\n")]
        else:
            raise Exception("No SSH session active, cannot get remote directory list")
            
        return dlist
        

_file_ops_classes.append(FileOpsRemoteUnix)


#===============================================================================
# remote win sudo/non-sudo
#   save_file_content  - sftp
#   get_file_content   - sftp
#   copy_file          - sftp
#   get_dir_access     - sftp(returns either rw or ro or none)
#   listdir            - sftp(for ro-dirs)
class FileOpsRemoteWindows(FileOpsRemoteUnix):
    @classmethod
    def match(cls, target_os, connection_method):
        return connection_method == "ssh" and target_os == wbaOS.windows

    def __init__(self, process_ops, ssh, target_os):
        FileOpsRemoteUnix.__init__(self, process_ops, ssh, target_os)
        self.process_ops = process_ops
        self.ssh = ssh

    def file_exists(self, filename, as_user=Users.CURRENT, user_password=None):
        if self.ssh:
            try:
                return self.ssh.file_exists(filename)
            except IOError, exc:
                raise
        else:
            print "Attempt to read remote file with no ssh session"
            log_error(_this_file, '%s: Attempt to read remote file with no ssh session\n' % self.__class__.__name__)
            import traceback
            traceback.print_stack()
            raise Exception("Cannot read remote file without an SSH session")
        return False

    def get_available_space(self, path, as_user=Users.CURRENT, user_password=None):
        out = []        

        res = self.process_ops.exec_cmd('dir %s' % quote_path(path),
                            as_user,
                            user_password,
                            output_handler = lambda line, l = out:l.append(line))

        available = "Could not determine"
        if res == 0 and len(out):
            measures = ['B', 'KB', 'MB', 'GB', 'TB']
            tokens = out[0].strip().split("\n")[-1].strip().split()
            
            total = float(tokens[2].replace(",",""))
            index = 0
            while index < len(measures) and total > 1024:
                total = total / 1024
                index = index + 1


            available = "%.2f %s available" % (total, measures[index])
        
        return available

    def create_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                self.ssh.mkdir(path)
            except OSError, err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not create directory %s" % path)
                raise err
        else:
            output = []
            command = wrap_for_sudo('mkdir ' + quote_path_win(path), self.process_ops.sudo_prefix, as_user)
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            if ret != 0:
                raise RuntimeError(out)


    def remove_directory(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                self.ssh.rmdir(path)
            except OSError, err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not remove directory %s" % path)
                raise err
        else:
            output = []
            command = wrap_for_sudo('rmdir ' + quote_path_win(path), self.process_ops.sudo_prefix, as_user)
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            if ret != 0:
                raise RuntimeError(out)

    def delete_file(self, path, as_user = Users.CURRENT, user_password = None):
        if as_user == Users.CURRENT:
            try:
                self.ssh.remove(path)
            except OSError, err:
                if err.errno == errno.EACCES:
                    raise PermissionDeniedError("Could not delete file %s" % path)
                raise err
        else:
            output = []
            command = wrap_for_sudo('del ' + quote_path_win(path), self.process_ops.sudo_prefix, as_user)
            out, ret = self.ssh.exec_cmd(command, as_user, user_password)
            if ret != 0:
                raise RuntimeError(out)

    def save_file_content_and_backup(self, path, content, backup_extension, as_user = Users.CURRENT, user_password = None):
        # Check if dir, where config file will be stored is writable
        dirname, filename = splitpath(path)

        if as_user == Users.CURRENT:
            try:
                self.check_dir_writable(dirname.strip(" \r\t\n"))
            except Exception, e:
                print e
                raise PermissionDeniedError("Cannot write to directory %s" % dirname)

        if self.ssh is not None:
            ## Get temp dir for using as tmpdir
            tmpdir, status = self.process_ops.get_cmd_output("echo %temp%")
            if type(tmpdir) is unicode:
                tmpdir = tmpdir.encode("utf8")
            if type(tmpdir) is str:
                tmpdir = tmpdir.strip(" \r\t\n")
                if tmpdir[1] == ":":
                    tmpdir = tmpdir[2:]
                else:
                    log_debug(_this_file, '%s: Temp directory path "%s" is not in expected form. The expected form is something like "C:\\Windows\\Temp"\n' % (self.__class__.__name__, tmpdir) )
                    tmpdir = None
                log_debug2(_this_file, '%s: Got temp dir: "%s"\n' % (self.__class__.__name__, tmpdir) )
            else:
                tmpdir = None

            if not tmpdir:
                tmpdir = dirname

            tmpfilename = tmpdir + r"\workbench-temp-file.ini"

            log_debug(_this_file, '%s: Remotely writing contents to temporary file "%s"\n' % (self.__class__.__name__, tmpfilename) )
            log_debug3(_this_file, '%s: %s\n' % (self.__class__.__name__, content) )
            self.ssh.set_contents(tmpfilename, content)

            if backup_extension:
                log_debug(_this_file, '%s: Backing up "%s"\n' % (self.__class__.__name__, path) )
                backup_cmd = "copy /y " + quote_path_win(path) + " " + quote_path_win(path+backup_extension)
                msg, code = self.process_ops.get_cmd_output(backup_cmd)
                if code != 0:
                    print backup_cmd, "->", msg
                    log_error(_this_file, '%s: Error backing up file: %s\n' % (self.__class__.__name__, backup_cmd+'->'+msg) )
                    raise RuntimeError("Error backing up file: %s" % msg)

            copy_to_dest = "copy /y " + quote_path_win(tmpfilename) + " " + quote_path_win(path)
            delete_tmp = "del " + quote_path_win(tmpfilename)
            log_debug(_this_file, '%s: Copying file to final destination: "%s"\n' % (self.__class__.__name__, copy_to_dest) )
            msg, code = self.process_ops.get_cmd_output(copy_to_dest)
            if code != 0:
                print copy_to_dest, "->", msg
                log_error(_this_file, '%s: Error copying temporary file over destination file: %s\n%s to %s\n' % (self.__class__.__name__, msg, tmpfilename, path) )
                raise RuntimeError("Error copying temporary file over destination file: %s\n%s to %s" % (msg, tmpfilename, path))
            log_debug(_this_file, '%s: Deleting tmp file: "%s"\n' % (self.__class__.__name__, delete_tmp) )
            msg, code = self.process_ops.get_cmd_output(delete_tmp)
            if code != 0:
                print "Could not delete temporary file %s: %s" % (tmpfilename, msg)
                log_info(_this_file, '%s: Could not delete temporary file "%s": %s\n' % (self.__class__.__name__, tmpfilename, msg) )
        else:
            raise Exception("No SSH session active, cannot save file remotely")

    # UseCase: If get_file_content fails with exception of access, try sudo
    def get_file_content(self, filename, as_user = Users.CURRENT, user_password = None):
        if self.ssh:
            # Supposedly in Windows, sshd account has admin privileges, so Users.ADMIN can be ignored
            try:
                return self.ssh.get_contents(filename)
            except IOError, exc:
                if exc.errno == errno.EACCES:
                    raise PermissionDeniedError("Permission denied attempting to read file %s" % filename)
        else:
            print "Attempt to read remote file with no ssh session"
            import traceback
            traceback.print_stack()
            raise Exception("Cannot read remote file without an SSH session")

    def check_dir_writable(self, path, as_user=Users.CURRENT, user_password=None):
        msg, code = self.process_ops.get_cmd_output('echo 1 > ' + quote_path(path + "/wba_tmp_file.bak"))
        ret = (code == 0)
        if ret:
            msg, code = self.process_ops.get_cmd_output('del ' + quote_path(path + "/wba_tmp_file.bak"))
        return ret

    def listdir(self, path, as_user = Users.CURRENT, user_password = None): # base operation to build file_exists and remote file selector
        # TODO: user elevation
        sftp = self.ssh.getftp()
        (dirs, files) = sftp.ls(path)
        ret = []
        for d in dirs:
            ret.append(d + "/")
        return tuple(ret + list(files))
        
_file_ops_classes.append(FileOpsRemoteWindows)


#===============================================================================
#
#===============================================================================
class ServerManagementHelper(object):
    def __init__(self, profile, ssh):
        self.tmp_files = [] # TODO: make sure the files will be deleted on exit

        self.profile = profile

        klass = None
        match_tuple = (profile.host_os, profile.target_os, profile.connect_method)
        for k in _process_ops_classes:
            if k.match(match_tuple):
                klass = k
                break
        if klass:
        
            sudo_prefix=profile.sudo_prefix
            
            if not sudo_prefix:
                sudo_prefix = default_sudo_prefix
        
            # use -k to force the password to be requested, otherwise we'll be stuck waiting for the password prompt
            # This is necessary in osx also because without it an I/O error happens for unknown reasons... in linux this doesn't seem necessary
            
            if profile.target_os == wbaOS.darwin and " -k " not in sudo_prefix:
                sudo_prefix = sudo_prefix.replace("sudo", "sudo -k ")

            self.shell = klass(sudo_prefix=sudo_prefix, ssh=ssh)
            self.shell.post_init()
        else:
            raise Exception("Unsupported administration target type: %s"%str(match_tuple))

        klass = None
        for k in _file_ops_classes:
            if k.match(profile.target_os, profile.connect_method):
                klass = k
                break

        if klass:
            self.file = klass(self.shell, ssh=ssh, target_os = profile.target_os)
        else:
            raise Exception("Unsupported administration target type: %s:%s" % (str(profile.target_os), str(profile.connect_method)))


    @property
    def cmd_output_encoding(self):
        if self.shell:
            return self.shell.cmd_output_encoding
        return ""

    #-----------------------------------------------------------------------------
    def check_dir_writable(self, path, as_user=Users.CURRENT, user_password=None):
        return self.file.check_dir_writable(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def file_exists(self, path, as_user = Users.CURRENT, user_password=None):
        return self.file.file_exists(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def get_available_space(self, path, as_user = Users.CURRENT, user_password=None):
        return self.file.get_available_space(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def get_file_owner(self, path, as_user = Users.CURRENT, user_password=None):
        return self.file.get_file_owner(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def create_directory(self, path, as_user = Users.CURRENT, user_password=None):
        return self.file.create_directory(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def remove_directory(self, path, as_user = Users.CURRENT, user_password=None):
        return self.file.remove_directory(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def delete_file(self, path, as_user = Users.CURRENT, user_password=None):
        return self.file.delete_file(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    # Make sure that file is readable only by user!
    def make_local_tmpfile(self):
        # Here we create that file name blah-blah-blah
        # if total_success:
        #   self.tmp_files.append(filename)
        raise NotImplementedError

    #-----------------------------------------------------------------------------
    def get_file_content(self, path, as_user = Users.CURRENT, user_password = None):
        return self.file.get_file_content(path, as_user=as_user, user_password=user_password)

    #-----------------------------------------------------------------------------
    def set_file_content(self, path, contents, as_user = Users.CURRENT, user_password = None):
        return self.file.save_file_content(path, contents, as_user=as_user, user_password=user_password)

    #-----------------------------------------------------------------------------
    def listdir(self, path, as_user = Users.CURRENT, user_password = None):
        return self.file.listdir(path, as_user, user_password)

    #-----------------------------------------------------------------------------
    def set_file_content_and_backup(self, path, contents, backup_extension, as_user = Users.CURRENT, user_password = None):
        if type(contents) is unicode:
            contents = contents.encode("utf8")
        return self.file.save_file_content_and_backup(path, contents, backup_extension, as_user=as_user, user_password=user_password)

    #-----------------------------------------------------------------------------
    # Returns Status Code
    # Text Output is given to output_handler, if there is any
    def execute_command(self, command, as_user = Users.CURRENT, user_password=None, output_handler=None):
        return self.shell.exec_cmd(command, as_user, user_password, output_handler)
