<commit_before>'''
Stateful, singleton, command and control center. This is the brains of the three 
paradrop components: tools, router, and server.

Takes over the functionality of the following modules, globals, and all around cruft:
    settings
    storage (keys, settings, names, permissions)
    chuteStore
    port/host consts
    portal and server administration

Additionally:
    Persists data to disk synchronously (for now, unless it gets hairy.)
    Manages init and deinit
    Document versioning, migrations, and validations through barit.

This object is meant to be subclassed by each of the three PD components. 
They should build upon the methods below as it suits their needs-- for example, 
tools may need to store information about routers, routers will store 
chutes, and the server will rely heavily on riffle callbacks.

Should make this a singleton. And make a print thread for this guy, just for safety.

The base class does not implement any riffle integration other than 
setting up the keys and default connection information. You should register for 
connection callbacks using smokesignals (see paradrop/main.py). DO NOT
store connections yourself-- riffle already does that and you'll screw up the portal!

Version 2 changes: 
    Should be able to call nexus.core.paths.logPath
'''

import os
import yaml

from enum import Enum
import smokesignal
from twisted.internet import reactor

from pdtools.lib import store, riffle, output

# Global access. Assign this wherever you instantiate the Nexus object:
#       nexus.core = MyNexusSubclass()
core = None

# The type and mode of this nexus instance
Type = Enum('Type', 'router, tools, server')
Mode = Enum('Mode', 'production, development, test, local')


class NexusBase(object):

    '''
    These are the boilerplate, initial settings values. 

    DO NOT MODIFY THESE AT RUNTIME. Instead, query the nexus object for 
    the appropriate attribute. These are here for two purposes: declare
    which settings will be present in the nexus object, and show you their names.

    There are three steps that take place in resolving these valus to their 
    final forms:
        1- this module is imported, initial values assigned (as written below)
        2- this class is instatiated, reading in environment variables and replacing 
            any those it finds
        3- runtime level modification of settings as appropriate

    Note the last step. Some variables simply cannot be changed. 

    Not implemented: settings, chutes
    '''

    ###############################################################################
    # Paths. Dump: 'print nexus.core.paths'
    ###############################################################################

    # The three options for root file directory. Eventually end up in nexus.core.paths.root
    PATH_ROOT = None                                    # nexus.core.paths.root
    PATH_SNAPPY = os.getenv("SNAP_APP_DATA_PATH", None)
    PATH_LOCAL = os.path.expanduser('~') + '/.paradrop/local/'
    PATH_HOME = os.path.expanduser('~') + '/.paradrop/'
    PATH_CURRENT = os.getcwd() + '/.paradrop/'

    PATH_LOG = '/logs/'                                 # nexus.core.paths.log
    PATH_KEY = '/keys/'                                 # nexus.core.paths.key
    PATH_MISC = '/misc/'                                # nexus.core.paths.misc
    PATH_CONFIG = '/config'                             # nexus.core.paths.config

    ###############################################################################
    # Net. Dump: 'print nexus.core.net'
    ###############################################################################

    # Note: one of the options are picked  based on the mode
    HOST_HTTP_PRODUCTION = 'https://paradrop.io'        # nexus.core.net.webHost
    HOST_HTTP_DEVELOPMENT = 'https://paradrop.io'
    HOST_HTTP_TEST = 'localhost'
    HOST_HTTP_LOCAL = 'localhost'

    PORT_HTTP_PRODUCTION = '14321'                      # nexus.core.net.webPort
    PORT_HTTP_DEVELOPMENT = '14321'
    PORT_HTTP_TEST = '14321'
    PORT_HTTP_LOCAL = '14321'

    # Web Sockets host and port
    HOST_WS_PRODUCTION = 'ws://paradrop.io:PORT/ws'     # nexus.core.net.host
    HOST_WS_DEVELOPMENT = "ws://paradrop.io:PORT/ws"
    HOST_WS_TEST = "ws://127.0.0.1:PORT/ws"
    HOST_WS_LOCAL = "ws://127.0.0.1:PORT/ws"

    PORT_WS_PRODUCTION = '9080'                         # nexus.core.net.port
    PORT_WS_DEVELOPMENT = '9080'
    PORT_WS_TEST = '9080'
    PORT_WS_DEVELOPMENT = '9080'

    ###############################################################################
    # Meta. Dump: 'print nexus.core.meta'
    ###############################################################################

    # One of the enum values above this class. Note: the value here is *not*
    # applied to the nexus object (except for version) since they are received
    # as direct parameters to the initialization method. You can still set
    # environment variables that match these names, they will overwrite the final values
    TYPE = None                                         # nexus.core.meta.type
    MODE = None                                         # nexus.core.meta.mode
    VERSION = 1                                         # nexus.core.meta.version

    ###############################################################################
    # Info. Dump: 'print nexus.core.info'
    ###############################################################################

    # One of the enum values above this class
    PDID = None                                         # nexus.core.info.pdid

    def __init__(self, nexusType, mode=Mode.development, stealStdio=True, printToConsole=True):
        '''
        The one big thing this function leaves out is reactor.start(). Call this externally 
        *after* initializing a nexus object. 

        :param type: one of [router, tool, server]
        :type type: str.
        :param mode: one of [production, development, test, local]. Changes host and ports.
            See section "network resolution" below 
        '''

        # Create the attr redirectors. These allow for nexus.net.stuff
        self.paths = AttrWrapper()
        self.net = AttrWrapper()
        self.meta = AttrWrapper()
        self.info = AttrWrapper()

        # Set meta
        self.meta.type = nexusType
        self.meta.mode = mode
        self.meta.version = NexusBase.VERSION

        # Set paths
        makePaths(self)

        # Set network
        resolveNetwork(self, self.meta.mode)

        # Set info by loading from paths
        loadConfig(self, self.paths.config)

        # Lock all the wrapper objects except info. That one we register to the save function
        [x._lock() for x in [self.paths, self.net, self.meta]]
        self.info.setOnChange(self.onInfoChange)

        # initialize output. If filepath is set, logs to file.
        # If stealStdio is set intercepts all stderr and stdout and interprets it internally
        # If printToConsole is set (defaults True) all final output is rendered to stdout
        # output.out.startLogging(filePath=self.logPath, stealStdio=stealStdio, printToConsole=printToConsole)

        # Asssign global riffle keys
        # riffle.portal.keyPrivate = self.getKey('pub')
        # riffle.portal.certCa = self.getKey('ca')
        # riffle.portal.host = self.serverHost()
        # riffle.portal.port = self.rifflePort()

        # register onStop for the shutdown call
        # reactor.addSystemEventTrigger('before', 'shutdown', self.onStop)
        # reactor.callLater(0, self.onStart)

    def onStart(self):
        pdid = self.get('pdid') if self.provisioned() else 'unprovisioned router'

        output.out.usage('%s (%s) coming up' % (pdid, self.type))

    def onStop(self):
        self.save()

        # riffle.portal.close()

        # remove any and all pubsub registrations. If not done, this can cause<commit_msg>all kinds of nexus goodness<commit_after>'''
Stateful, singleton, command and control center. This is the brains of the three 
paradrop components: tools, router, and server.

Takes over the functionality of the following modules, globals, and all around cruft:
    settings
    storage (keys, settings, names, permissions)
    chuteStore
    port/host consts
    portal and server administration

Additionally:
    Persists data to disk synchronously (for now, unless it gets hairy.)
    Manages init and deinit
    Document versioning, migrations, and validations through barit.

This object is meant to be subclassed by each of the three PD components. 
They should build upon the methods below as it suits their needs-- for example, 
tools may need to store information about routers, routers will store 
chutes, and the server will rely heavily on riffle callbacks.

Should make this a singleton. And make a print thread for this guy, just for safety.

The base class does not implement any riffle integration other than 
setting up the keys and default connection information. You should register for 
connection callbacks using smokesignals (see paradrop/main.py). DO NOT
store connections yourself-- riffle already does that and you'll screw up the portal!

Version 2 changes: 
    Should be able to call nexus.core.paths.logPath
'''

import os
import yaml

from enum import Enum
import smokesignal
from twisted.internet import reactor

from pdtools.lib import store, riffle, output

# Global access. Assign this wherever you instantiate the Nexus object:
#       nexus.core = MyNexusSubclass()
core = None

# The type and mode of this nexus instance
Type = Enum('Type', 'router, tools, server')
Mode = Enum('Mode', 'production, development, test, local')


class NexusBase(object):

    '''
    These are the boilerplate, initial settings values. 

    DO NOT MODIFY THESE AT RUNTIME. Instead, query the nexus object for 
    the appropriate attribute. These are here for two purposes: declare
    which settings will be present in the nexus object, and show you their names.

    There are three steps that take place in resolving these valus to their 
    final forms:
        1- this module is imported, initial values assigned (as written below)
        2- this class is instatiated, reading in environment variables and replacing 
            any those it finds
        3- runtime level modification of settings as appropriate

    Note the last step. Some variables simply cannot be changed. 

    Not implemented: settings, chutes
    '''

    ###############################################################################
    # Paths. Dump: 'print nexus.core.paths'
    ###############################################################################

    # The three options for root file directory. Eventually end up in nexus.core.paths.root
    PATH_ROOT = None                                    # nexus.core.paths.root
    PATH_SNAPPY = os.getenv("SNAP_APP_DATA_PATH", None)
    PATH_LOCAL = os.path.expanduser('~') + '/.paradrop/local/'
    PATH_HOME = os.path.expanduser('~') + '/.paradrop/'
    PATH_CURRENT = os.getcwd() + '/.paradrop/'

    PATH_LOG = '/logs/'                                 # nexus.core.paths.log
    PATH_KEY = '/keys/'                                 # nexus.core.paths.key
    PATH_MISC = '/misc/'                                # nexus.core.paths.misc
    PATH_CONFIG = '/config'                             # nexus.core.paths.config

    ###############################################################################
    # Net. Dump: 'print nexus.core.net'
    ###############################################################################

    # Note: one of the options are picked  based on the mode
    HOST_HTTP_PRODUCTION = 'https://paradrop.io'        # nexus.core.net.webHost
    HOST_HTTP_DEVELOPMENT = 'https://paradrop.io'
    HOST_HTTP_TEST = 'localhost'
    HOST_HTTP_LOCAL = 'localhost'

    PORT_HTTP_PRODUCTION = '14321'                      # nexus.core.net.webPort
    PORT_HTTP_DEVELOPMENT = '14321'
    PORT_HTTP_TEST = '14321'
    PORT_HTTP_LOCAL = '14321'

    # Web Sockets host and port
    HOST_WS_PRODUCTION = 'ws://paradrop.io:PORT/ws'     # nexus.core.net.host
    HOST_WS_DEVELOPMENT = "ws://paradrop.io:PORT/ws"
    HOST_WS_TEST = "ws://127.0.0.1:PORT/ws"
    HOST_WS_LOCAL = "ws://127.0.0.1:PORT/ws"

    PORT_WS_PRODUCTION = '9080'                         # nexus.core.net.port
    PORT_WS_DEVELOPMENT = '9080'
    PORT_WS_TEST = '9080'
    PORT_WS_DEVELOPMENT = '9080'

    ###############################################################################
    # Meta. Dump: 'print nexus.core.meta'
    ###############################################################################

    # One of the enum values above this class. Note: the value here is *not*
    # applied to the nexus object (except for version) since they are received
    # as direct parameters to the initialization method. You can still set
    # environment variables that match these names, they will overwrite the final values
    TYPE = None                                         # nexus.core.meta.type
    MODE = None                                         # nexus.core.meta.mode
    VERSION = 1                                         # nexus.core.meta.version

    ###############################################################################
    # Info. Dump: 'print nexus.core.info'
    ###############################################################################

    # One of the enum values above this class
    PDID = None                                         # nexus.core.info.pdid

    def __init__(self, nexusType, mode=Mode.development, stealStdio=True, printToConsole=True):
        '''
        The one big thing this function leaves out is reactor.start(). Call this externally 
        *after* initializing a nexus object. 

        :param type: one of [router, tool, server]
        :type type: str.
        :param mode: one of [production, development, test, local]. Changes host and ports.
            See section "network resolution" below 
        '''

        # Create the attr redirectors. These allow for nexus.net.stuff
        self.paths = AttrWrapper()
        self.net = AttrWrapper()
        self.meta = AttrWrapper()
        self.info = AttrWrapper()

        # Set meta
        self.meta.type = nexusType
        self.meta.mode = mode
        self.meta.version = NexusBase.VERSION

        # Set paths
        makePaths(self)

        # Set network
        resolveNetwork(self, self.meta.mode)

        # Set info by loading from paths
        loadConfig(self, self.paths.config)

        # Lock all the wrapper objects except info. That one we register to the save function
        [x._lock() for x in [self.paths, self.net, self.meta]]
        self.info.setOnChange(self.onInfoChange)

        # Done with data initizlization. From here on out its configuration and state

        # initialize output. If filepath is set, logs to file.
        # If stealStdio is set intercepts all stderr and stdout and interprets it internally
        # If printToConsole is set (defaults True) all final output is rendered to stdout
        # output.out.startLogging(filePath=self.logPath, stealStdio=stealStdio, printToConsole=printToConsole)

        # register onStop for the shutdown call
        # reactor.addSystemEventTrigger('before', 'shutdown', self.onStop)
        # reactor.callLater(0, self.onStart)

    def onStart(self):
        pdid = self.get('pdid') if self.provisioned() else 'unprovisioned router'

        output.out.usage('%s (%s) coming up' % (pdid, self.type))

    def onStop(self):
        self.save()

        # riffle.portal.close()

        # remove any and all pubsub registrations. If not done, this can cause
{'fraction_non_alphanumeric': 0.15369526702100733, 'fraction_numerical': 0.00683371298405467, 'mean_word_length': 2.715326751292901, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5804088', 'n_tokens_mistral': 4446, 'n_tokens_neox': 4100, 'n_words': 1463}