#!/usr/bin/env python
"""
 :author: Josh Moore, josh at glencoesoftware.com

 OMERO Grid admin controller

 This is a python wrapper around icegridregistry/icegridnode for master
 and various other tools needed for administration.

 Copyright 2008 Glencoe Software, Inc.  All Rights Reserved.
 Use is subject to license terms supplied in LICENSE.txt

"""

import re
import os
import sys
import stat
import platform
import exceptions
import portalocker

from path import path

import omero
import omero.config

from omero.cli import CLI
from omero.cli import BaseControl
from omero.cli import DirectoryType
from omero.cli import NonZeroReturnCode
from omero.cli import VERSION

from omero.plugins.prefs import with_config

from omero_ext.which import whichall
from omero_version import ice_compatibility

try:
    import pywintypes
    import win32service
    import win32evtlogutil
    import win32api
    import win32security
    has_win32 = True
except ImportError:
    has_win32 = False

HELP="""Administrative tools including starting/stopping OMERO.

Environment variables:
 OMERO_MASTER
 OMERO_NODE

Configuration properties:
 omero.windows.user
 omero.windows.pass

""" + "\n" + "="*50 + "\n"


class AdminControl(BaseControl):

    def _complete(self, text, line, begidx, endidx):
        """
        Returns a file after "deploy", "start", or "startasync"
        and otherwise delegates to the BaseControl
        """
        for s in (" deploy ", " start ", " startasync "):
            l = len(s)
            i = line.find(s)
            if i >= 0:
                f = line[i+l:]
                return self._complete_file(f)
        return BaseControl._complete(self, text, line, begidx, endidx)

    def _configure(self, parser):
        sub = parser.sub()
        self.actions = {}

        class Action(object):
            def __init__(this, name, help):
                this.parser = sub.add_parser(name, help=help, description=help)
                this.parser.set_defaults(func=getattr(self, name))
                self.actions[name] = this.parser

        Action("start", """Start icegridnode daemon and waits for required components to come up, i.e. status == 0

             If the first argument can be found as a file, it will
             be deployed as the application descriptor rather than
             etc/grid/default.xml. All other arguments will be used
             as targets to enable optional sections of the descriptor""")

        Action("startasync", """The same as start but returns immediately.""",)

        Action("restart", """stop && start""",)

        Action("restartasync", """The same as restart but returns as soon as starting has begun.""",)

        Action("status", """Status of server.
             Returns with 0 status if a node ping is successful
             and if some SessionManager returns an OMERO-specific exception on
             a bad login. This can be used in shell scripts, e.g.:

                 omero admin status && echo "server started"
            """)

        Action("stop", """Initiates node shutdown and waits for status to return a non-0 value""")

        Action("stopasync", """The same as stop but returns immediately.""")

        Action("deploy", """Deploy the given deployment descriptor. See etc/grid/*.xml
             If the first argument is not a file path, etc/grid/default.xml
             will be deployed by default. Same functionality as start, but
             requires that the node already be running. This may automatically
             restart some server components.""")

        Action("ice", """Drop user into icegridadmin console or execute arguments""")

        Action("diagnostics", """Run a set of checks on the current, preferably active server""")

        Action("waitup", """Used by start after calling startasync to wait on status==0""")

        Action("waitdown", """Used by stop after calling stopasync to wait on status!=0""")

        reindex = Action("reindex", """Re-index the Lucene index

Command-line tool for re-index the database. This command must be run on the machine where
/OMERO/FullText is located.

Examples:
  bin/omero admin reindex --full                                                # All objects
  bin/omero admin reindex --reindex ome.model.core.Image                        # Only images
  JAVA_OPTS="-Dlog4j.configuration=stderr.xml" bin/omero admin reindex --full   # Passing arguments to Java


LIMITATION: omero.db.pass values do not currently get passed to the Java process. You will
            need to all passwordless login to PostgreSQL. In fact, only the following properties
	    are passed:

	    omero.data.dir
	    omero.search.*
	    omero.db.* (excluding pass)

""").parser
        reindex.add_argument("--jdwp", help = "Activate remote debugging")
        group = reindex.add_mutually_exclusive_group()
        group.add_argument("--full", action="store_true", help = "Reindexes all non-excluded tables sequentially")
        group.add_argument("--events", action="store_true", help = "Reindexes all non-excluded event logs chronologically")
        group.add_argument("--class", nargs="+", help = "Reindexes the given classes sequentially")

        ports = Action("ports", """Allows modifying the ports from a standard OMERO install

To have two OMERO's running on the same machine, several ports must be modified from their default values.
Internally, this command uses the omero.install.change_ports module. Changing the ports on a running server
is usually not what you want and will be prevented. Use --skipcheck to change the ports anyway.

Examples:

    %(prog)s --prefix=1                             # sets ports to: 14061, 14063, 14064
    %(prog)s --prefix=1 --revert                    # sets ports back to: 4061, 4063, 4064
    %(prog)s --registry=4444 --tcp=5555 --ssl=6666  # sets ports to: 4444 5555 6666

""").parser
        ports.add_argument("--prefix", help = "Adds a prefix to each port ON TOP OF any other settings")
        ports.add_argument("--registry", help = "Registry port. (default: %(default)s)", default = "4061")
        ports.add_argument("--tcp", help = "The tcp port to be used by Glacier2 (default: %(default)s)", default = "4063")
        ports.add_argument("--ssl", help = "The ssl port to be used by Glacier2 (default: %(default)s)", default = "4064")
        ports.add_argument("--revert", action="store_true", help = "Used to rollback from the given settings to the defaults")
        ports.add_argument("--skipcheck", action="store_true", help = "Skips the check if the server is already running")

        sessionlist = Action("sessionlist", """List currently running sessions""").parser
        sessionlist.add_login_arguments()

        cleanse = Action("cleanse", """Remove binary data files from OMERO.

Deleting an object from OMERO currently does not remove the binary data. Use this
command either manually or in a cron job periodically to remove Pixels and other data.

This is done by checking that for all the files in the given directory, a matching entry
exists on the server. THE /OMERO DIRECTORY MUST MATCH THE DATABASE YOU ARE RUNNING AGAINST.

This command must be run on the machine where, for example, /OMERO/ is located.

Examples:
  bin/omero admin cleanse --dry-run /OMERO                                         # Lists files that will be deleted
  bin/omero admin cleanse /OMERO                                                   # Actually delete them.
  bin/omero admin cleanse /volumes/data/OMERO                                      # Delete from a standard location.

""").parser
        cleanse.add_argument("--dry-run", action = "store_true", help = "Print out which files would be deleted")
        cleanse.add_argument("data_dir", type=DirectoryType(), help = "omero.data.dir directory value (e.g. /OMERO")
        cleanse.add_login_arguments()

        Action("checkwindows", """Run simple check of the local installation (Windows-only)""")
        Action("checkice", """Run simple check of the Ice installation""")

        Action("events", """Print event log (Windows-only)""")

        self.actions["ice"].add_argument("argument", nargs="*", help="""Arguments joined together to make an Ice command.
        If not present, the user will enter a console""")

        self.actions["status"].add_argument("node", nargs="?", default="master")
        self.actions["status"].add_argument("--nodeonly", action="store_true",
            help="""If set, then only tests if the icegridnode is running""")

        for name in ("start", "startasync"):
            self.actions[name].add_argument("-u","--user",
                help="Service Log On As user name. If none given, the value of omero.windows.user will be used. (Windows-only)")
            self.actions[name].add_argument("-w","--password",
                help="Service Log On As user password. If none given, the value of omero.windows.pass will be used. (Windows-only)")

        for k in ("start", "startasync", "deploy", "restart", "restartasync"):
            self.actions[k].add_argument("file", nargs="?",
                help="""Application descriptor. If not provided, a default will be used""")
            self.actions[k].add_argument("targets", nargs="*",
                help="""Targets within the application descriptor which should be activated.
                        Common values are: "debug", "trace" """)

        DISABLED = """ see: http://www.zeroc.com/forums/bug-reports/4237-sporadic-freeze-errors-concurrent-icegridnode-access.html
           restart [filename] [targets]      : Calls stop followed by start args
           restartasync [filename] [targets] : Calls stop followed by startasync args
        """

    #
    # Windows utility methods
    #
    if has_win32:
        def _query_service(unused, svc_name):
            hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
            try:
                try:
                    hs = win32service.OpenService(hscm, svc_name, win32service.SERVICE_ALL_ACCESS)
                except:
                    return "DOESNOTEXIST"
                try:
                    q = win32service.QueryServiceStatus(hs)
                    type, state, ctrl, err, svcerr, svccp, svcwh = q
                    if state == win32service.SERVICE_STOPPED:
                        return "STOPPED"
                    else:
                        return "unknown"
                finally:
                    win32service.CloseServiceHandle(hs)
            finally:
                win32service.CloseServiceHandle(hscm)

        def events(self, svc_name):
            def DumpRecord(record):
                if str(record.SourceName) == svc_name:
                    self.ctx.out("Time: %s" % record.TimeWritten)
                    self.ctx.out("Rec:  %s" % record.RecordNumber)
                    for si in record.StringInserts:
                        self.ctx.out(si)
                    self.ctx.out("="*20)
            win32evtlogutil.FeedEventLogRecords(DumpRecord)

    else:

        def events(self, svc_name):
            self.ctx.die(666, "Could not import win32service and/or win32evtlogutil")

        def _query_service(self, svc_name):
            self.ctx.die(666, "Could not import win32service and/or win32evtlogutil")

    #
    # End Windows Methods
    #

    def _node(self, omero_node = None):
        """ Overrides the regular node() logic to return the value of OMERO_MASTER or "master" """
        if omero_node != None:
            os.environ["OMERO_MASTER"] = omero_node

        if os.environ.has_key("OMERO_MASTER"):
            return os.environ["OMERO_MASTER"]
        else:
            return "master"

    def _cmd(self, *command_arguments):
        """
        Used to generate an icegridadmin command line argument list
        """
        command = ["icegridadmin", self._intcfg() ]
        command.extend(command_arguments)
        return command

    def _descript(self, args):
        if args.file != None:
            # Relative to cwd
            descript = path(args.file).abspath()
            if not descript.exists():
                self.ctx.dbg("No such file: %s -- Using as target" % descript)
                args.targets.insert(0, args.file)
                descript = None
        else:
            descript = None

        if descript == None:
            __d__ = "default.xml"
            if self._isWindows():
                __d__ = "windefault.xml"
            descript = self.ctx.dir / "etc" / "grid" / __d__
            self.ctx.err("No descriptor given. Using %s" % os.path.sep.join(["etc","grid",__d__]))
        return descript

    def checkwindows(self, args):
        """
        Checks that the templates file as defined in etc\Windows.cfg
        can be found.
        """
        self.check_access(os.R_OK)
        if not self._isWindows():
            self.ctx.die(123, "Not Windows")

        import Ice
        key = "IceGrid.Node.Data"
        properties = Ice.createProperties([self._icecfg()])
        nodedata = properties.getProperty(key)
        if not nodedata:
            self.ctx.die(300, "Bad configuration: No IceGrid.Node.Data property")
        nodepath = path(nodedata)
        pp = nodepath.parpath(self.ctx.dir)
        if pp:
            return
        if nodepath == r"c:\omero_dist\var\master":
            self.ctx.out("Found default value: %s" % nodepath)
            self.ctx.out("Attempting to correct...")
            from omero.install.win_set_path import win_set_path
            count = win_set_path(dir = self.ctx.dir)
            if count:
                return
        self.ctx.die(400, """

            %s is not in this directory. Aborting...

            Please see the installation instructions on modifying
            the files for your installation (%s)
            with bin\winconfig.bat

            """ % (nodedata, self.ctx.dir))

    ##############################################
    #
    # Commands
    #

    @with_config
    def startasync(self, args, config):
        """
        First checks for a valid installation, then checks the grid,
        then registers the action: "node HOST start"
        """

        self.check_access(config=config)
        self.checkice()
        self.check_node(args)
        if self._isWindows():
            self.checkwindows(args)

        if 0 == self.status(args, node_only=True):
            self.ctx.die(876, "Server already running")

        self._initDir()
        props = self._properties()
        # Do a check to see if we've started before.
        self._regdata()
        self.check([])

        user = args.user
        pasw = args.password
        descript = self._descript(args)

        if self._isWindows():
            svc_name = "OMERO.%s" % args.node
            output = self._query_service(svc_name)

            # Now check if the server exists
            if 0 <= output.find("DOESNOTEXIST"):
                binpath = """icegridnode.exe "%s" --deploy "%s" --service %s""" % (self._icecfg(), descript, svc_name)

                # By default: "NT Authority\Local System"
                if not user:
                    try:
                        user = config.as_map()["omero.windows.user"]
                    except KeyError:
                        user = None
                if user is not None and len(user) > 0:
                    if not "\\" in user:
                        computername = win32api.GetComputerName()
                        user = "\\".join([computername, user])
                    try:
                        # See #9967, code based on http://mail.python.org/pipermail/python-win32/2010-October/010791.html
                        self.ctx.out("Granting SeServiceLogonRight to service user \"%s\"" % user)
                        policy_handle = win32security.LsaOpenPolicy(None, win32security.POLICY_ALL_ACCESS)
                        sid_obj, domain, tmp = win32security.LookupAccountName(None, user)
                        win32security.LsaAddAccountRights(policy_handle, sid_obj, ('SeServiceLogonRight',))
                        win32security.LsaClose(policy_handle)
                    except pywintypes.error, details:
                        self.ctx.die(200, "Error during service user set up: (%s) %s" % (details[0], details[2]))
                    if not pasw:
                        try:
                            pasw = config.as_map()["omero.windows.pass"]
                        except KeyError:
                            pasw = self._ask_for_password(" for service user \"%s\"" % user)
                else:
                    pasw = None

                hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
                try:
                    self.ctx.out("Installing %s Windows service." % svc_name)
                    hs = win32service.CreateService(hscm, svc_name, svc_name, win32service.SERVICE_ALL_ACCESS,
                            win32service.SERVICE_WIN32_OWN_PROCESS, win32service.SERVICE_DEMAND_START,
                            win32service.SERVICE_ERROR_NORMAL, binpath, None, 0, None, user, pasw)
                    self.ctx.out("Successfully installed %s Windows service." % svc_name)
                    win32service.CloseServiceHandle(hs)
                finally:
                    win32service.CloseServiceHandle(hscm)

            # Then check if the server is already running
            if 0 <= output.find("RUNNING"):
                self.ctx.die(201, "%s is already running. Use stop first" % svc_name)

            # Finally, try to start the service - delete if startup fails
            hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
            try:
                try:
                    hs = win32service.OpenService(hscm, svc_name, win32service.SC_MANAGER_ALL_ACCESS)
                    win32service.StartService(hs, None)
                    self.ctx.out("Starting %s Windows service." % svc_name)
                except pywintypes.error, details:
                    self.ctx.out("%s service startup failed: (%s) %s" % (svc_name, details[0], details[2]))
                    win32service.DeleteService(hs)
                    self.ctx.die(202, "%s service deleted." % svc_name)
            finally:
                win32service.CloseServiceHandle(hs)
                win32service.CloseServiceHandle(hscm)

        else:
            command = ["icegridnode","--daemon","--pidfile",str(self._pid()),"--nochdir",self._icecfg(),"--deploy",str(descript)] + args.targets
            self.ctx.rv = self.ctx.call(command)

    @with_config
    def start(self, args, config):
        self.startasync(args, config)
        try:
            self.waitup(args)
        except NonZeroReturnCode, nzrc:
            # stop() may itself throw,
            # if it does not, then we rethrow
            # the original
            self.ctx.err('Calling "stop" on remaining components')
            self.stop(args, config)
            raise nzrc

    @with_config
    def deploy(self, args, config):
        self.check_access()
        self.checkice()
        descript = self._descript(args)

        # TODO : Doesn't properly handle whitespace
        # Though users can workaround with something like:
        # bin/omero admin deploy etc/grid/a\\\\ b.xml
        command = ["icegridadmin",self._intcfg(),"-e"," ".join(["application","update", str(descript)] + args.targets)]
        self.ctx.call(command)

    def status(self, args, node_only = False):
        self.check_node(args)
        command = self._cmd("-e","node ping %s" % self._node())
        self.ctx.rv = self.ctx.popen(command).wait() # popen

        # node_only implies that "up" need not check for all
        # of blitz to be accessible but just that if the node
        # is running.
        if not node_only:
            node_only = getattr(args, "nodeonly", False)

        if self.ctx.rv == 0 and not node_only:
            try:
                import Ice
                ic = Ice.initialize([self._intcfg()])
                try:
                    sm = self.session_manager(ic)
                    try:
                        sm.create("####### STATUS CHECK ########", None) # Not adding "omero.client.uuid"
                    except omero.WrappedCreateSessionException, wcse:
                        # Only the server will throw one of these
                        self.ctx.dbg("Server reachable")
                        self.ctx.rv = 0
                finally:
                    ic.destroy()
            except exceptions.Exception, exc:
                self.ctx.rv = 1
                self.ctx.dbg("Server not reachable: "+str(exc))

        return self.ctx.rv

    def wait_for_icedb(self, args, config):
        """
        Since the stop and start are separately protected by
        the lock on config.xml, we need to wait for some time
        to hopefully let the icegridnode process release the
        file locks.
        """
        self.ctx.sleep(1)  # put in sleep to try to prevent "db locked" (#7325)

    @with_config
    def restart(self, args, config):
        if not self.stop(args, config):
            self.ctx.die(54, "Failed to shutdown")
        self.wait_for_icedb(args, config)
        self.start(args, config)

    @with_config
    def restartasync(self, args, config):
        if not self.stop(args, config):
            self.ctx.die(54, "Failed to shutdown")
        self.wait_for_icedb(args, config)
        self.startasync(args, config)

    def waitup(self, args):
        """
        Loops 30 times with 10 second pauses waiting for status()
        to return 0. If it does not, then ctx.die() is called.
        """
        self.check_access(os.R_OK)
        self.ctx.out("Waiting on startup. Use CTRL-C to exit")
        count = 30
        while True:
            count = count - 1
            if count == 0:
                self.ctx.die(43, "\nFailed to startup some components after 5 minutes")
            elif 0 == self.status(args, node_only = False):
                break
            else:
                self.ctx.out(".", newline = False)
                self.ctx.sleep(10)

    def waitdown(self, args):
        """
        Returns true if the server went down
        """
        self.check_access(os.R_OK)
        self.ctx.out("Waiting on shutdown. Use CTRL-C to exit")
        count = 30
        while True:
            count = count - 1
            if count == 0:
                self.ctx.die(44, "\nFailed to shutdown some components after 5 minutes")
                return False
            elif 0 != self.status(args, node_only = True):
                break
            else:
                self.ctx.out(".", newline = False)
                self.ctx.sleep(10)
        self.ctx.rv = 0
        return True

    @with_config
    def stopasync(self, args, config):
        """
        Returns true if the server was already stopped
        """
        self.check_node(args)
        if 0 != self.status(args, node_only=True):
            self.ctx.err("Server not running")
            return True
        elif self._isWindows():
            svc_name = "OMERO.%s" % args.node
            output = self._query_service(svc_name)
            if 0 <= output.find("DOESNOTEXIST"):
                self.ctx.die(203, "%s does not exist. Use 'start' first." % svc_name)
            hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
            try:
                hs = win32service.OpenService(hscm, svc_name, win32service.SC_MANAGER_ALL_ACCESS)
                win32service.ControlService(hs, win32service.SERVICE_CONTROL_STOP)
                win32service.DeleteService(hs)
                self.ctx.out("%s service deleted." % svc_name)
            finally:
                win32service.CloseServiceHandle(hs)
                win32service.CloseServiceHandle(hscm)
        else:
            command = self._cmd("-e","node shutdown %s" % self._node())
            try:
                self.ctx.call(command)
            except NonZeroReturnCode, nzrc:
                self.ctx.rv = nzrc.rv
                self.ctx.out("Was the server already stopped?")

    @with_config
    def stop(self, args, config):
        if not self.stopasync(args, config):
            return self.waitdown(args)
        return True

    def check(self, args):
        # print "Check db. Have a way to load the db control"
        pass

    def ice(self, args):
        self.check_access()
        command = self._cmd()
        if len(args.argument) > 0:
            command.extend(["-e", " ".join(args.argument)])
            return self.ctx.call(command)
        else:
            rv = self.ctx.call(command)

    @with_config
    def diagnostics(self, args, config):
        self.check_access()
        config = config.as_map()
        omero_data_dir = '/OMERO'
        try:
            omero_data_dir = config['omero.data.dir']
        except KeyError:
            pass
        self.ctx.out("""
%s
OMERO Diagnostics %s
%s
        """ % ("="*80, VERSION, "="*80))

        def sz_str(sz):
            for x in ["KB", "MB", "GB"]:
                sz /= 1000
                if sz < 1000:
                    break
            sz = "%.1f %s" % (sz, x)
            return sz

        def item(cat, msg):
            cat = cat + ":"
            cat = "%-12s" % cat
            self.ctx.out(cat, False)
            msg = "%-30s " % msg
            self.ctx.out(msg, False)

        def exists(p):
            if p.isdir():
                if not p.exists():
                    self.ctx.out("doesn't exist")
                else:
                    self.ctx.out("exists")
            else:
                if not p.exists():
                    self.ctx.out("n/a")
                else:
                    warn = 0
                    err = 0
                    for l in p.lines():
                        if l.find("ERROR") >= 0:
                            err += 1
                        elif l.find("WARN") >= 0:
                            warn += 1
                    msg = ""
                    if warn or err:
                        msg = " errors=%-4s warnings=%-4s" % (err, warn)
                    self.ctx.out("%-12s %s" % (sz_str(p.size), msg))

        def version(cmd):
            """
            Returns a true response only
            if a valid version was found.
            """
            item("Commands","%s" % " ".join(cmd))
            try:
                p = self.ctx.popen(cmd)
            except OSError:
                self.ctx.err("not found")
                return False

            rv = p.wait()
            io = p.communicate()
            try:
                v = io[0].split()
                v.extend(io[1].split())
                v = "".join(v)
                m = re.match("^\D*(\d[.\d]+\d)\D?.*$", v)
                v = "%-10s" % m.group(1)
                self.ctx.out(v, False)
                try:
                    where = whichall(cmd[0])
                    sz = len(where)
                    if sz == 0:
                        where = "unknown"
                    else:
                        where = where[0]
                        if sz > 1:
                            where += " -- %s others" % sz

                except:
                    where = "unknown"
                self.ctx.out("(%s)" % where)
                return True
            except exceptions.Exception, e:
                self.ctx.err("error:%s" % e)
                return False


        import logging
        logging.basicConfig()
        from omero.util.upgrade_check import UpgradeCheck
        check = UpgradeCheck("diagnostics")
        check.run()
        if check.isUpgradeNeeded():
            self.ctx.out("")

        version(["java",         "-version"])
        version(["python",       "-V"])
        version(["icegridnode",  "--version"])
        iga = version(["icegridadmin", "--version"])
        version(["psql",         "--version"])


        def get_ports(input):
            router_lines = [line for line in input.split("\n") if line.find("ROUTER") >= 0]

            ssl_port = None
            tcp_port = None
            for line in router_lines:
                if not ssl_port and line.find("ROUTERPORT") >= 0:
                    m = re.match(".*?(\d+).*?$", line)
                    if m:
                        ssl_port = m.group(1)

                if not tcp_port and line.find("INSECUREROUTER") >= 0:
                    m = re.match("^.*?-p (\d+).*?$", line)
                    if m:
                        tcp_port = m.group(1)
            return ssl_port, tcp_port

        self.ctx.out("")
        if not iga:
            self.ctx.out("No icegridadmin available: Cannot check server list")
        else:
            item("Server", "icegridnode")
            p = self.ctx.popen(self._cmd("-e", "server list")) # popen
            rv = p.wait()
            io = p.communicate()
            if rv != 0:
                self.ctx.out("not started")
                self.ctx.dbg("""
                Stdout:\n%s
                Stderr:\n%s
                """ % io)
            else:
                self.ctx.out("running")
                servers = io[0].split()
                servers.sort()
                for s in servers:
                    item("Server", "%s" % s)
                    p2 = self.ctx.popen(self._cmd("-e", "server state %s" % s)) # popen
                    rv2 = p2.wait()
                    io2 = p2.communicate()
                    if io2[1]:
                        self.ctx.err(io2[1].strip())
                    elif io2[0]:
                        self.ctx.out(io2[0].strip())
                    else:
                        self.ctx.err("UNKNOWN!")
            if self._isWindows():
                # Print the OMERO server Windows service details
                hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
                services = win32service.EnumServicesStatus(hscm)
                omesvcs = tuple((sname, fname) for sname,fname,status in services if "OMERO" in fname)
                for sname,fname in omesvcs:
                    item("Server", fname)
                    hsc = win32service.OpenService(hscm, sname, win32service.SC_MANAGER_ALL_ACCESS)
                    logonuser = win32service.QueryServiceConfig(hsc)[7]
                    if win32service.QueryServiceStatus(hsc)[1] == win32service.SERVICE_RUNNING:
                        self.ctx.out("active (running as %s)" % logonuser)
                    else:
                        self.ctx.out("inactive")
                    win32service.CloseServiceHandle(hsc)
                win32service.CloseServiceHandle(hscm)

            # List SSL & TCP ports of deployed applications
            self.ctx.out("")
            p = self.ctx.popen(self._cmd("-e", "application list")) # popen
            rv = p.wait()
            io = p.communicate()
            if rv != 0:
                self.ctx.out("Cannot list deployed applications.")
                self.ctx.dbg("""
                Stdout:\n%s
                Stderr:\n%s
                """ % io)
            else:
                applications = io[0].split()
                applications.sort()
                for s in applications:
                    p2 = self.ctx.popen(self._cmd("-e", "application describe %s" % s)) # popen
                    io2 = p2.communicate()
                    if io2[1]:
                        self.ctx.err(io2[1].strip())
                    elif io2[0]:
                        ssl_port, tcp_port = get_ports(io2[0])
                        item("%s" % s, "SSL port")
                        if not ssl_port:
                            self.ctx.err("Not found")
                        else:
                            self.ctx.out("%s" % ssl_port)

                        item("%s" % s, "TCP port")
                        if not tcp_port:
                            self.ctx.err("Not found")
                        else:
                            self.ctx.out("%s" % tcp_port)
                    else:
                        self.ctx.err("UNKNOWN!")

        def log_dir(log, cat, cat2, knownfiles):
            self.ctx.out("")
            item(cat, "%s" % log.abspath())
            exists(log)
            self.ctx.out("")

            if log.exists():
                files = log.files()
                files = set([x.basename() for x in files])
                # Adding known names just in case
                for x in knownfiles:
                    files.add(x)
                files = list(files)
                files.sort()
                for x in files:
                    item(cat2, x)
                    exists(log / x)
                item(cat2, "Total size")
                sz = 0
                for x in log.walkfiles():
                    sz += x.size
                self.ctx.out("%-.2f MB" % (float(sz)/1000000.0))

        log_dir(self.ctx.dir / "var" / "log", "Log dir", "Log files",\
            ["Blitz-0.log", "Tables-0.log", "Processor-0.log", "Indexer-0.log", "FileServer.log", "MonitorServer.log", "DropBox.log", "TestDropBox.log", "OMEROweb.log"])

        # Parsing well known issues
        self.ctx.out("")
        ready = re.compile(".*?ome.services.util.ServerVersionCheck.*OMERO.Version.*Ready..*?")
        db_ready = re.compile(".*?Did.you.create.your.database[?].*?")
        data_dir = re.compile(".*?Unable.to.initialize:.FullText.*?")
        pg_password = re.compile(".*?org.postgresql.util.PSQLException:.FATAL:.password.*?authentication.failed.for.user.*?")
        pg_user = re.compile(""".*?org.postgresql.util.PSQLException:.FATAL:.role.".*?".does.not.exist.*?""")
        pg_conn = re.compile(""".*?org.postgresql.util.PSQLException:.Connection.refused.""")


        issues = {
            ready : "=> Server restarted <=",
            db_ready : "Your database configuration is invalid",
            data_dir : "Did you create your omero.data.dir? E.g. /OMERO",
            pg_password : "Your postgres password seems to be invalid",
            pg_user: "Your postgres user is invalid",
            pg_conn: "Your postgres hostname and/or port is invalid"
        }

        try:
            for file in ('Blitz-0.log',):

                p = self.ctx.dir / "var" / "log" / file
                import fileinput
                for line in fileinput.input([str(p)]):
                    lno = fileinput.filelineno()
                    for k, v in issues.items():
                        if k.match(line):
                            item('Parsing %s' % file, "[line:%s] %s" % (lno, v))
                            self.ctx.out("")
                            break
        except:
            self.ctx.err("Error while parsing logs")

        self.ctx.out("")

        def env_val(val):
            item("Environment","%s=%s" % (val, os.environ.get(val, "(unset)")))
            self.ctx.out("")
        env_val("OMERO_HOME")
        env_val("OMERO_NODE")
        env_val("OMERO_MASTER")
        env_val("PATH")
        env_val("ICE_HOME")
        env_val("LD_LIBRARY_PATH")
        env_val("DYLD_LIBRARY_PATH")

        self.ctx.out("")
        exists = os.path.exists(omero_data_dir)
        is_writable = os.access(omero_data_dir, os.R_OK|os.W_OK)
        self.ctx.out("OMERO data dir: '%s'\tExists? %s\tIs writable? %s" % \
            (omero_data_dir, exists, is_writable))
        from omero.plugins.web import WebControl
        try:
            WebControl().status(args)
        except:
            self.ctx.out("OMERO.web not installed!")

    def session_manager(self, communicator):
        import IceGrid, Glacier2
        iq = communicator.stringToProxy("IceGrid/Query")
        iq = IceGrid.QueryPrx.checkedCast(iq)
        sm = iq.findAllObjectsByType("::Glacier2::SessionManager")[0]
        sm = Glacier2.SessionManagerPrx.checkedCast(sm)
        return sm

    def can_access(self, filepath, mask=os.R_OK|os.W_OK):
        """
        Check that the given path belongs to
        or is accessible by the current user
        on Linux systems.
        """

        if "Windows" == platform.system():
            return

        pathobj = path(filepath)

        if not pathobj.exists():
            self.ctx.die(8, "FATAL: OMERO directory does not exist: %s" % pathobj)

        owner = os.stat(filepath)[stat.ST_UID]
        if owner == 0:
            msg = ""
            msg += "FATAL: OMERO directory which needs to be writeable belongs to root: %s\n" % filepath
            msg += "Please use \"chown -R NEWUSER %s\" and run as then run %s as NEWUSER" % (filepath, sys.argv[0])
            self.ctx.die(9, msg)
        else:
            if not os.access(filepath, mask):
                self.ctx.die(10, "FATAL: Cannot access %s, a required file/directory for OMERO" % filepath)

    def check_access(self, mask=os.R_OK|os.W_OK, config=None):
        """Check that 'var' is accessible by the current user."""

        var = self.ctx.dir / 'var'
        if not os.path.exists(var):
            self.ctx.out("Creating directory %s" % var)
            os.makedirs(var, 0700)
        else:
            self.can_access(var, mask)

        if config is not None:
            omero_data_dir = '/OMERO'
            config = config.as_map()
            try:
                omero_data_dir = config['omero.data.dir']
            except KeyError:
                pass
            self.can_access(omero_data_dir)
        for p in os.listdir(var):
            subpath = os.path.join(var, p)
            if os.path.isdir(subpath):
                self.can_access(subpath, mask)

    def check_node(self, args):
        """
        If the args argparse.Namespace argument has no "node" attribute,
        then assign one.
        """
        if not hasattr(args, "node"):
            args.node = self._node()

    def checkice(self, args=None):
        """
        Checks for Ice version 3.4

        See ticket:2514, ticket:1260
        """

        def _check(msg, vers):
            compat = ice_compatibility.split(".")
            vers = vers.split(".")
            if compat[0:2] != vers[0:2]:
                self.ctx.die(164, "%s is not compatible with %s: %s" % \
                        (msg, ".".join(compat), ".".join(vers)))

        import Ice
        vers = Ice.stringVersion()
        _check("IcePy version", vers)

        popen = self.ctx.popen(["icegridnode", "--version"])
        vers = popen.communicate()[1]
        _check("icegridnode version", vers)

    def open_config(self, unused):
        """
        Callers are responsible for closing the
        returned ConfigXml object.
        """
        cfg_xml = self.ctx.dir / "etc" / "grid" / "config.xml"
        cfg_tmp = self.ctx.dir / "etc" / "grid" / "config.xml.tmp"
        grid_dir = self.ctx.dir / "etc" / "grid"
        if not cfg_xml.exists() and self.can_access(grid_dir):
            if cfg_tmp.exists() and self.can_access(cfg_tmp):
                self.ctx.dbg("Removing old config.xml.tmp")
                cfg_tmp.remove()
            config = omero.config.ConfigXml(str(cfg_tmp))
            try:
                self.ctx.controls["config"].upgrade(None, config)
            finally:
                config.close()
            self.ctx.err("Creating %s" % cfg_xml)
            cfg_tmp.rename(str(cfg_xml))

        try:
            try:
                config = omero.config.ConfigXml(str(cfg_xml))
            except Exception, e:
                self.ctx.die(577, str(e))
            if config.save_on_close:
                config.save()
            else:
                self.ctx.err("%s read-only" % cfg_xml)
        except portalocker.LockException:
            try:
                config.close()
            except:
                pass
            self.ctx.die(111, "Could not acquire lock on %s" % cfg_xml)

        return config

    @with_config
    def reindex(self, args, config):
        self.check_access(config=config)
        import omero.java
        server_dir = self.ctx.dir / "lib" / "server"
        log4j = "-Dlog4j.configuration=log4j-cli.properties"
        classpath = [ file.abspath() for file in server_dir.files("*.jar") ]
        xargs = [ log4j, "-Xmx1024M", "-cp", os.pathsep.join(classpath) ]

        cfg = config.as_map()
        for x in ("name", "user", "host", "port"): # NOT passing password on command-line
            k = "omero.db.%s" % x
            if k in cfg:
                v = cfg[k]
                xargs.append("-D%s=%s" % (k, v))
        if "omero.data.dir" in cfg:
            xargs.append("-Domero.data.dir=%s" % cfg["omero.data.dir"])
        for k, v in cfg.items():
            if k.startswith("omero.search"):
                xargs.append("-D%s=%s" % (k, cfg[k]))

        cmd = ["ome.services.fulltext.Main"]

        if args.full:
            cmd.append("full")
        elif args.events:
            cmd.append("events")
        elif getattr(args, "class"):
            cmd.append("reindex")
            cmd.extend(getattr(args, "class"))
        else:
            self.ctx.die(502, "No valid action: %s" % args)

        debug = False
        if getattr(args, "jdwp"):
            debug = True

        self.ctx.dbg("Launching Java: %s, debug=%s, xargs=%s" % (cmd, debug, xargs))
        p = omero.java.popen(cmd, debug=debug, xargs=xargs, stdout=sys.stdout, stderr=sys.stderr) # FIXME. Shouldn't use std{out,err}
        self.ctx.rv = p.wait()

    def ports(self, args):
        self.check_access()
        from omero.install.change_ports import change_ports
        if not args.skipcheck:
            if 0 == self.status(args, node_only=True):
                self.ctx.die(100, "Can't change ports while the server is running!")

            # Resetting return value.
            self.ctx.rv = 0

        if args.prefix:
            for x in ("registry", "tcp", "ssl"):
                setattr(args, x, "%s%s" % (args.prefix, getattr(args, x)))
        change_ports(args.ssl, args.tcp, args.registry, args.revert, dir=self.ctx.dir)

    def cleanse(self, args):
        self.check_access()
        from omero.util.cleanse import cleanse
        client = self.ctx.conn(args)
        key = client.getSessionId()
        cleanse(data_dir=args.data_dir, dry_run=args.dry_run, \
            query_service=client.sf.getQueryService(), \
            config_service=client.sf.getConfigService())

    def sessionlist(self, args):
        client = self.ctx.conn(args)
        service = client.sf.getQueryService()
        params = omero.sys.ParametersI()
        query = "select s from Session s join fetch s.node n join fetch s.owner o where s.closed is null and n.id != 0"
        results = service.findAllByQuery(query, params)
        mapped = list()
        for s in results:
            rv = list()
            mapped.append(rv)
            if not s.isLoaded():
                rv.append("")
                rv.append("id=%s" % s.id.val)
                rv.append("")
                rv.append("")
                rv.append("")
                rv.append("insufficient privileges")
            else:
                rv.append(s.node.id)
                rv.append(s.uuid)
                rv.append(s.started)
                rv.append(s.owner.omeName)
                if s.userAgent is None:
                    rv.append("")
                else:
                    rv.append(s.userAgent)
                if client.getSessionId() == s.uuid.val:
                    rv.append("current session")
                else:
                    rv.append("")
        self.ctx.controls["hql"].display(mapped, ("node", "session", "started", "owner", "agent", "notes"))
try:
    register("admin", AdminControl, HELP)
except NameError:
    if __name__ == "__main__":
        cli = CLI()
        cli.register("admin", AdminControl, HELP)
        cli.invoke(sys.argv[1:])