def __init__(self, ca_server):
        """Constructor.

        Args:
            ca_server (CAServer): The CA server used for generating PVs on the fly
        """
        super(BlockServer, self).__init__()

        # Threading stuff
        self.monitor_lock = RLock()
        self.write_lock = RLock()
        self.write_queue = list()

        FILEPATH_MANAGER.initialise(CONFIG_DIR, SCHEMA_DIR)

        self._cas = ca_server
        self._gateway = Gateway(GATEWAY_PREFIX, BLOCK_PREFIX, PVLIST_FILE, MACROS["$(MYPVPREFIX)"])
        self._active_configserver = None
        self._run_control = None
        self._block_cache = None
        self._syn = None
        self._devices = None
        self._filewatcher = None
        self.on_the_fly_handlers = list()
        self._ioc_control = IocControl(MACROS["$(MYPVPREFIX)"])
        self._db_client = DatabaseServerClient(BLOCKSERVER_PREFIX + "BLOCKSERVER:")
        self.bumpstrip = "No"
        self.block_rules = BlockRules(self)
        self.group_rules = GroupRules(self)
        self.config_desc = ConfigurationDescriptionRules(self)

        # Connect to version control
        try:
            self._vc = GitVersionControl(CONFIG_DIR, RepoFactory.get_repo(CONFIG_DIR))
            self._vc.setup()
        except NotUnderVersionControl as err:
            print_and_log("Warning: Configurations not under version control", "MINOR")
            self._vc = MockVersionControl()
        except Exception as err:
            print_and_log("Unable to start version control. Modifications to the instrument setup will not be "
                          "tracked: " + str(err), "MINOR")
            self._vc = MockVersionControl()

        # Create banner object
        self.banner = Banner(MACROS["$(MYPVPREFIX)"])

        # Import data about all configs
        try:
            self._config_list = ConfigListManager(self, SCHEMA_DIR, self._vc, ConfigurationFileManager())
        except Exception as err:
            print_and_log(
                "Error creating inactive config list. Configuration list changes will not be stored " +
                "in version control: %s " % str(err), "MINOR")
            self._config_list = ConfigListManager(self, SCHEMA_DIR, MockVersionControl(), ConfigurationFileManager())

        # Start a background thread for handling write commands
        write_thread = Thread(target=self.consume_write_queue, args=())
        write_thread.daemon = True  # Daemonise thread
        write_thread.start()

        with self.write_lock:
            self.write_queue.append((self.initialise_configserver, (FACILITY,), "INITIALISING"))

        # Starts the Web Server
        self.server = Server()
        self.server.start()
class BlockServer(Driver):
    """The class for handling all the static PV access and monitors etc.
    """

    def __init__(self, ca_server):
        """Constructor.

        Args:
            ca_server (CAServer): The CA server used for generating PVs on the fly
        """
        super(BlockServer, self).__init__()

        # Threading stuff
        self.monitor_lock = RLock()
        self.write_lock = RLock()
        self.write_queue = list()

        FILEPATH_MANAGER.initialise(CONFIG_DIR, SCHEMA_DIR)

        self._cas = ca_server
        self._gateway = Gateway(GATEWAY_PREFIX, BLOCK_PREFIX, PVLIST_FILE, MACROS["$(MYPVPREFIX)"])
        self._active_configserver = None
        self._run_control = None
        self._block_cache = None
        self._syn = None
        self._devices = None
        self._filewatcher = None
        self.on_the_fly_handlers = list()
        self._ioc_control = IocControl(MACROS["$(MYPVPREFIX)"])
        self._db_client = DatabaseServerClient(BLOCKSERVER_PREFIX + "BLOCKSERVER:")
        self.bumpstrip = "No"
        self.block_rules = BlockRules(self)
        self.group_rules = GroupRules(self)
        self.config_desc = ConfigurationDescriptionRules(self)

        # Connect to version control
        try:
            self._vc = GitVersionControl(CONFIG_DIR, RepoFactory.get_repo(CONFIG_DIR))
            self._vc.setup()
        except NotUnderVersionControl as err:
            print_and_log("Warning: Configurations not under version control", "MINOR")
            self._vc = MockVersionControl()
        except Exception as err:
            print_and_log("Unable to start version control. Modifications to the instrument setup will not be "
                          "tracked: " + str(err), "MINOR")
            self._vc = MockVersionControl()

        # Create banner object
        self.banner = Banner(MACROS["$(MYPVPREFIX)"])

        # Import data about all configs
        try:
            self._config_list = ConfigListManager(self, SCHEMA_DIR, self._vc, ConfigurationFileManager())
        except Exception as err:
            print_and_log(
                "Error creating inactive config list. Configuration list changes will not be stored " +
                "in version control: %s " % str(err), "MINOR")
            self._config_list = ConfigListManager(self, SCHEMA_DIR, MockVersionControl(), ConfigurationFileManager())

        # Start a background thread for handling write commands
        write_thread = Thread(target=self.consume_write_queue, args=())
        write_thread.daemon = True  # Daemonise thread
        write_thread.start()

        with self.write_lock:
            self.write_queue.append((self.initialise_configserver, (FACILITY,), "INITIALISING"))

        # Starts the Web Server
        self.server = Server()
        self.server.start()

    def initialise_configserver(self, facility):
        """Initialises the ActiveConfigHolder.

        Args:
            facility (string): The facility using the BlockServer
        """
        # This is in a separate method so it can be sent to the thread queue
        arch = ArchiverManager(ARCHIVE_UPLOADER, ARCHIVE_SETTINGS)

        self._active_configserver = ActiveConfigHolder(MACROS, arch, self._vc, ConfigurationFileManager(),
                                                       self._ioc_control)

        if facility == "ISIS":
            self._run_control = RunControlManager(MACROS["$(MYPVPREFIX)"], MACROS["$(ICPCONFIGROOT)"],
                                                  MACROS["$(ICPVARDIR)"], self._ioc_control, self._active_configserver,
                                                  self)
            self.on_the_fly_handlers.append(self._run_control)
            self._block_cache = BlockCacheManager(self._ioc_control)

        # Import all the synoptic data and create PVs
        self._syn = SynopticManager(self, SCHEMA_DIR, self._vc, self._active_configserver)
        self.on_the_fly_handlers.append(self._syn)

        # Import all the devices data and create PVs
        self._devices = DevicesManager(self, SCHEMA_DIR, self._vc)
        self.on_the_fly_handlers.append(self._devices)

        # Start file watcher
        self._filewatcher = ConfigFileWatcherManager(SCHEMA_DIR, self._config_list, self._syn, self._devices)

        try:
            if self._gateway.exists():
                print_and_log("Found gateway")
                self.load_last_config()
            else:
                print_and_log("Could not connect to gateway - is it running?")
                self.load_last_config()
        except Exception as err:
            print_and_log("Could not load last configuration. Message was: %s" % err, "MAJOR")
            self._active_configserver.clear_config()
            self._initialise_config()

    def read(self, reason):
        """A method called by SimpleServer when a PV is read from the BlockServer over Channel Access.

        Args:
            reason (string): The PV that is being requested (without the PV prefix)

        Returns:
            string : A compressed and hexed JSON formatted string that gives the desired information based on reason.
            If an Exception is thrown in the reading of the information this is returned in compressed and hexed JSON.
        """
        try:
            if reason == BlockserverPVNames.GROUPS:
                grps = ConfigurationJsonConverter.groups_to_json(self._active_configserver.get_group_details())
                value = compress_and_hex(grps)
            elif reason == BlockserverPVNames.CONFIGS:
                value = compress_and_hex(convert_to_json(self._config_list.get_configs()))
            elif reason == BlockserverPVNames.COMPS:
                value = compress_and_hex(convert_to_json(self._config_list.get_components()))
            elif reason == BlockserverPVNames.BLANK_CONFIG:
                js = convert_to_json(self.get_blank_config())
                value = compress_and_hex(js)
            elif reason == BlockserverPVNames.BUMPSTRIP_AVAILABLE:
                value = compress_and_hex(self.bumpstrip)
            elif reason == BlockserverPVNames.BANNER_DESCRIPTION:
                value = compress_and_hex(self.banner.get_description())
            else:
                # Check to see if it is a on-the-fly PV
                for handler in self.on_the_fly_handlers:
                    if handler.read_pv_exists(reason):
                        return handler.handle_pv_read(reason)

                value = self.getParam(reason)
        except Exception as err:
            value = compress_and_hex(convert_to_json("Error: " + str(err)))
            print_and_log(str(err), "MAJOR")
        return value

    def write(self, reason, value):
        """A method called by SimpleServer when a PV is written to the BlockServer over Channel Access. The write
            commands are queued as Channel Access is single-threaded.

            Note that the filewatcher is disabled as part of the write queue so any operations that intend to modify
            files should use the write queue.

        Args:
            reason (string): The PV that is being requested (without the PV prefix)
            value (string): The data being written to the 'reason' PV

        Returns:
            string : "OK" in compressed and hexed JSON if function succeeds. Otherwise returns the Exception in
            compressed and hexed JSON.
        """
        status = True
        try:
            data = dehex_and_decompress(value).strip('"')
            if reason == BlockserverPVNames.LOAD_CONFIG:
                with self.write_lock:
                    self.write_queue.append((self.load_config, (data,), "LOADING_CONFIG"))
            elif reason == BlockserverPVNames.SAVE_CONFIG:
                with self.write_lock:
                    self.write_queue.append((self.save_active_config, (data,), "SAVING_CONFIG"))
            elif reason == BlockserverPVNames.RELOAD_CURRENT_CONFIG:
                with self.write_lock:
                    self.write_queue.append((self.reload_current_config, (), "RELOAD_CURRENT_CONFIG"))
            elif reason == BlockserverPVNames.START_IOCS:
                with self.write_lock:
                    self.write_queue.append((self.start_iocs, (convert_from_json(data),), "START_IOCS"))
            elif reason == BlockserverPVNames.STOP_IOCS:
                with self.write_lock:
                    self.write_queue.append((self._ioc_control.stop_iocs, (convert_from_json(data),), "STOP_IOCS"))
            elif reason == BlockserverPVNames.RESTART_IOCS:
                with self.write_lock:
                    self.write_queue.append((self._ioc_control.restart_iocs, (convert_from_json(data), True),
                                             "RESTART_IOCS"))
            elif reason == BlockserverPVNames.SET_CURR_CONFIG_DETAILS:
                with self.write_lock:
                    self.write_queue.append((self._set_curr_config, (convert_from_json(data),), "SETTING_CONFIG"))
            elif reason == BlockserverPVNames.SAVE_NEW_CONFIG:
                with self.write_lock:
                    self.write_queue.append((self.save_inactive_config, (data,), "SAVING_NEW_CONFIG"))
            elif reason == BlockserverPVNames.SAVE_NEW_COMPONENT:
                with self.write_lock:
                    self.write_queue.append((self.save_inactive_config, (data, True), "SAVING_NEW_COMP"))
            elif reason == BlockserverPVNames.DELETE_CONFIGS:
                with self.write_lock:
                    self.write_queue.append((self._config_list.delete, (convert_from_json(data),),
                                             "DELETE_CONFIGS"))
            elif reason == BlockserverPVNames.DELETE_COMPONENTS:
                with self.write_lock:
                    self.write_queue.append((self._config_list.delete, (convert_from_json(data), True),
                                             "DELETE_COMPONENTS"))
            elif reason == BlockserverPVNames.BUMPSTRIP_AVAILABLE_SP:
                self.bumpstrip = data
                with self.write_lock:
                    self.write_queue.append((self.update_bumpstrip_availability, None, "UPDATE_BUMPSTRIP"))
            else:
                status = False
                # Check to see if it is a on-the-fly PV
                for h in self.on_the_fly_handlers:
                    if h.write_pv_exists(reason):
                        with self.write_lock:
                            self.write_queue.append((h.handle_pv_write, (reason, data), "SETTING_CONFIG"))
                        status = True
                        break

        except Exception as err:
            value = compress_and_hex(convert_to_json("Error: " + str(err)))
            print_and_log(str(err), "MAJOR")
        else:
            if status:
                value = compress_and_hex(convert_to_json("OK"))

        # store the values
        if status:
            self.setParam(reason, value)
        return status

    def load_last_config(self):
        """Loads the last configuration used.

        The information is saved in a text file.
        """
        last = self._active_configserver.load_last_config()
        if last is None:
            print_and_log("Could not retrieve last configuration - starting blank configuration")
            self._active_configserver.clear_config()
        else:
            print_and_log("Loaded last configuration: %s" % last)
        self._initialise_config()

    def _set_curr_config(self, details):
        """Sets the current configuration details to that defined in the XML, saves to disk, then initialises it.

        Args:
            details (string): the configuration XML
        """
        self._active_configserver.set_config_details(details)
        # Need to save the config to file before we initialize or the changes won't be propagated to IOCS
        self.save_active_config(self._active_configserver.get_config_name())
        self._initialise_config()

    def _initialise_config(self, init_gateway=True, full_init=False):
        """Responsible for initialising the configuration.
        Sets all the monitors, initialises the gateway, etc.

        Args:
            init_gateway (bool, optional): whether to initialise the gateway
            full_init (bool, optional): whether this requires a full initialisation, e.g. on loading a new
                configuration
        """
        # First stop all IOCS, then start the ones for the config
        # TODO: Should we stop all configs?
        iocs_to_start, iocs_to_restart = self._active_configserver.iocs_changed()

        if len(iocs_to_start) > 0 or len(iocs_to_restart) > 0:
            self._stop_iocs_and_start_config_iocs(iocs_to_start, iocs_to_restart)
        # Set up the gateway
        if init_gateway:
            self._gateway.set_new_aliases(self._active_configserver.get_block_details())

        self._config_list.active_config_name = self._active_configserver.get_config_name()
        self._config_list.active_components = self._active_configserver.get_component_names()
        self._config_list.update_monitors()

        self.update_blocks_monitors()

        self.update_get_details_monitors()
        self._active_configserver.update_archiver()

        for h in self.on_the_fly_handlers:
            h.initialise(full_init)

        # Update Web Server text
        self.server.set_config(convert_to_json(self._active_configserver.get_config_details()))

        # Restart the Blocks cache
        if self._block_cache is not None:
            print_and_log("Restarting block cache...")
            self._block_cache.restart()

    def _stop_iocs_and_start_config_iocs(self, iocs_to_start, iocs_to_restart):
        """ Stop all IOCs and start the IOCs that are part of the configuration."""
        # iocs_to_start, iocs_to_restart are not used at the moment, but longer term they could be used
        # for only restarting IOCs for which the setting have changed.
        non_conf_iocs = [x for x in self._get_iocs() if x not in self._active_configserver.get_ioc_names()]
        self._ioc_control.stop_iocs(non_conf_iocs)
        self._start_config_iocs()

    def _start_config_iocs(self):
        # Start the IOCs, if they are available and if they are flagged for autostart
        # Note: autostart means the IOC is started when the config is loaded,
        # restart means the IOC should automatically restart if it stops for some reason (e.g. it crashes)
        for n, ioc in self._active_configserver.get_all_ioc_details().iteritems():
            try:
                # IOCs are restarted if and only if auto start is True. Note that auto restart instructs proc serv to
                # restart an IOC if it terminates unexpectedly and does not apply here.
                if ioc.autostart:
                    # Throws if IOC does not exist
                    running = self._ioc_control.get_ioc_status(n)
                    if running == "RUNNING":
                        # Restart it
                        self._ioc_control.restart_ioc(n)
                    else:
                        # Start it
                        self._ioc_control.start_ioc(n)
            except Exception as err:
                print_and_log("Could not (re)start IOC %s: %s" % (n, str(err)), "MAJOR")

        # Give it time to start as IOC has to be running to be able to set restart property
        sleep(2)
        for n, ioc in self._active_configserver.get_all_ioc_details().iteritems():
            if ioc.autostart:
                # Set the restart property
                print_and_log("Setting IOC %s's auto-restart to %s" % (n, ioc.restart))
                self._ioc_control.set_autorestart(n, ioc.restart)

    def _get_iocs(self, include_running=False):
        # Get IOCs from DatabaseServer
        try:
            return self._db_client.get_iocs()
        except Exception as err:
            print_and_log("Could not retrieve IOC list: %s" % str(err), "MAJOR")
            return []

    def load_config(self, config, is_component=False):
        """Load a configuration.

        Args:
            config (string): The name of the configuration
            is_component (bool): Whether it is a component or not
        """
        try:
            if is_component:
                print_and_log("Loading component: %s" % config)
                self._active_configserver.load_active(config, True)
            else:
                print_and_log("Loading configuration: %s" % config)
                self._active_configserver.load_active(config)
            # If we get this far then assume the config is okay
            self._initialise_config(full_init=True)
        except Exception as err:
            print_and_log(str(err), "MAJOR")

    def reload_current_config(self):
        """Reload the current configuration."""
        try:
            print_and_log("Reloading current configuration")
            self._active_configserver.reload_current_config()
            # If we get this far then assume the config is okay
            self._initialise_config(full_init=True)
        except Exception as err:
            print_and_log(str(err), "MAJOR")

    def save_inactive_config(self, json_data, as_comp=False):
        """Save an inactive configuration.

        Args:
            json_data (string): The JSON data containing the configuration/component
            as_comp (bool): Whether it is a component or not
        """
        new_details = convert_from_json(json_data)
        inactive = InactiveConfigHolder(MACROS, self._vc, ConfigurationFileManager())

        history = self._get_inactive_history(new_details["name"], as_comp)

        inactive.set_config_details(new_details)

        # Set updated history
        history.append(self._get_timestamp())
        inactive.set_history(history)

        config_name = inactive.get_config_name()
        self._check_config_inactive(config_name, as_comp)
        try:
            if not as_comp:
                print_and_log("Saving configuration: %s" % config_name)
                inactive.save_inactive()
                self._config_list.update_a_config_in_list(inactive)
            else:
                print_and_log("Saving component: %s" % config_name)
                inactive.save_inactive(as_comp=True)
                self._config_list.update_a_config_in_list(inactive, True)
            print_and_log("Saved")
        except Exception as err:
            print_and_log("Problem occurred saving configuration: %s" % err)

        # Reload configuration if a component has changed
        if as_comp and new_details["name"] in self._active_configserver.get_component_names():
            self.load_last_config()

    def _get_inactive_history(self, name, is_component=False):
        # If it already exists load it
        try:
            inactive = InactiveConfigHolder(MACROS, self._vc, ConfigurationFileManager())
            inactive.load_inactive(name, is_component)
            # Get previous history
            history = inactive.get_history()
        except IOError as err:
            # Config doesn't exist therefore start new history
            history = list()
        return history

    def _get_timestamp(self):
        return datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')

    def save_active_config(self, name):
        """Save the active configuration.

        Args:
            name (string): The name to save it under
        """
        try:
            print_and_log("Saving active configuration as: %s" % name)
            oldname = self._active_configserver.get_cached_name()
            if oldname != "" and name != oldname:
                # New name or overwriting another config (Save As)
                history = self._get_inactive_history(name)
            else:
                # Saving current config (Save)
                history = self._active_configserver.get_history()

            # Set updated history
            history.append(self._get_timestamp())
            self._active_configserver.set_history(history)

            self._active_configserver.save_active(name)
            self._config_list.update_a_config_in_list(self._active_configserver)
        except Exception as err:
            print_and_log("Problem occurred saving configuration: %s" % err)

    def update_blocks_monitors(self):
        """Updates the monitors for the blocks and groups, so the clients can see any changes.
        """
        with self.monitor_lock:
            # Blocks
            bn = convert_to_json(self._active_configserver.get_blocknames())
            self.setParam(BlockserverPVNames.BLOCKNAMES, compress_and_hex(bn))
            # Groups
            # Update the PV, so that groupings are updated for any CA monitors
            grps = ConfigurationJsonConverter.groups_to_json(self._active_configserver.get_group_details())
            self.setParam(BlockserverPVNames.GROUPS, compress_and_hex(grps))
            # Update them
            self.updatePVs()

    def update_server_status(self, status=""):
        """Updates the monitor for the server status, so the clients can see any changes.

        Args:
            status (string): The status to set
        """
        if self._active_configserver is not None:
            d = dict()
            d['status'] = status
            with self.monitor_lock:
                self.setParam(BlockserverPVNames.SERVER_STATUS, compress_and_hex(convert_to_json(d)))
                self.updatePVs()

    def update_get_details_monitors(self):
        """Updates the monitor for the active configuration, so the clients can see any changes.
        """
        with self.monitor_lock:
            js = convert_to_json(self._active_configserver.get_config_details())
            self.setParam(BlockserverPVNames.GET_CURR_CONFIG_DETAILS, compress_and_hex(js))
            self.updatePVs()

    def update_bumpstrip_availability(self):
        """Updates the monitor for the configurations, so the clients can see any changes.
            """
        with self.monitor_lock:
            # set the available configs
            self.setParam(BlockserverPVNames.BUMPSTRIP_AVAILABLE, compress_and_hex(self.bumpstrip))
            # Update them
            self.updatePVs()

    def consume_write_queue(self):
        """Actions any requests on the write queue.

        Queue items are tuples with three values:
        the method to call; the argument(s) to send (tuple); and, the description of the state (string))

        For example:
            self.load_config, ("configname",), "LOADING_CONFIG")
        """
        while True:
            while len(self.write_queue) > 0:
                if self._filewatcher is not None: self._filewatcher.pause()
                with self.write_lock:
                    cmd, arg, state = self.write_queue.pop(0)
                self.update_server_status(state)
                try:
                    if arg is not None:
                        cmd(*arg)
                    else:
                        cmd()
                except Exception as err:
                    print_and_log(
                        "Error executing write queue command %s for state %s: %s" % (cmd.__name__, state, err.message),
                        "MAJOR")
                self.update_server_status("")
                if self._filewatcher is not None: self._filewatcher.resume()
            sleep(1)

    def get_blank_config(self):
        """Get a blank configuration which can be used to create a new configuration from scratch.

        Returns:
            dict : A dictionary containing all the details of a blank configuration
        """
        temp_config = InactiveConfigHolder(MACROS, self._vc, ConfigurationFileManager())
        return temp_config.get_config_details()

    def _check_config_inactive(self, inactive_name, is_component=False):
        if not is_component:
            if inactive_name == self._active_configserver.get_config_name():
                raise ValueError("Cannot change config, use SET_CURR_CONFIG_DETAILS to change the active config")
        else:
            pass
            # TODO: check not a component of active, don't know what do to for this case?

    def start_iocs(self, iocs):
        # If the IOC is in the config and auto-restart is set to true then
        # reapply the auto-restart setting after starting.
        # This is because stopping an IOC via procServ turns auto-restart off.
        conf_iocs = self._active_configserver.get_all_ioc_details()

        # Request IOCs to start
        for i in iocs:
            self._ioc_control.start_ioc(i)

        # Once all IOC start requests issued, wait for running and apply auto restart as needed
        for i in iocs:
            if i in conf_iocs and conf_iocs[i].restart:
                # Give it time to start as IOC has to be running to be able to set restart property
                print "Re-applying auto-restart setting to %s" % i
                self._ioc_control.waitfor_running(i)
                self._ioc_control.set_autorestart(i, True)

    # Code for handling on-the-fly PVs
    def does_pv_exist(self, name):
        return name in manager.pvs[self.port]

    def delete_pv_from_db(self, name):
        if name in manager.pvs[self.port]:
            print_and_log("Removing PV %s" % name)
            fullname = manager.pvs[self.port][name].name
            del manager.pvs[self.port][name]
            del manager.pvf[fullname]
            del self.pvDB[name]
            del PVDB[name]

    def add_string_pv_to_db(self, name, count=1000):
        # Check name not already in PVDB and that a PV does not already exist
        if name not in PVDB and name not in manager.pvs[self.port]:
            try:
                print_and_log("Adding PV %s" % name)
                PVDB[name] = {
                    'type': 'char',
                    'count': count,
                    'value': [0],
                }
                self._cas.createPV(BLOCKSERVER_PREFIX, PVDB)
                # self.configure_pv_db()
                data = Data()
                data.value = manager.pvs[self.port][name].info.value
                self.pvDB[name] = data
            except Exception as err:
                print_and_log("Unable to add PV %S" % name,"MAJOR")