Ejemplo n.º 1
0
    def find_cell(self, val=""):
        '''
        Gets data from cell with value of val. Returns none if cell cannot be found.

        :param val: Str
        :return: Cell | None
        '''

        # Throw error if val is not a Str
        assert type(val) == str, "'val' parameter passed '" + str(
            val) + "' should be Str but isn't"

        # sheet.find() throws an error if cell cannot be found, so this is handled by returning 'none'
        try:
            # Try to get cell
            dbg.log("Attempting to find cell with value '" + val + "'")
            cell = self.sheet.find(val)

            # Log success if cell if found
            dbg.success("Found cell with value '" + val + "'")

            return cell
        except gs_exceptions.CellNotFound:
            dbg.warn("Could not find cell with value '" + val + "'")
            return None
Ejemplo n.º 2
0
    def get_cell(self, x=0, y=0):
        '''
        Gets data from cell at (x, y). Coords start at (0, 0).

        :param x: Int
        :param y: Int
        :return: Cell
        '''

        dbg.log("Attempting to get data at cell (" + str(x) + "," + str(y) +
                ")")

        # Throw error if x or y are out of range
        assert x < 1000, "Specified x coord '" + str(
            x
        ) + "' is too high. x can be 999 at max because there are only 1000 rows."
        assert y < 26, "Specified y coord '" + str(
            y
        ) + "' is too high. y can be 25 at max because there are only 25 columns."
        assert x >= 0, "Specified x coord '" + str(
            x) + "' is too low. x can be 0 at minimum."
        assert y >= 0, "Specified y coord '" + str(
            y) + "' is too low. y can be 0 at minimum."

        # Log success
        dbg.success("Data successfully gotten")

        # Return cell value by converting to Google Sheet coords (instead of (0, 0) being min, (1, 1) is)
        return self.sheet.cell(x + 1, y + 1)
Ejemplo n.º 3
0
    def get_range(self, p1=(0, 0), p2=(0, 0)):
        '''
        Gets all cells in range of the 2 tuples passed in. Returns a list of cells.

        :param p1: Tuple with values (int x, int y)
        :param p2: Tuple with values (int x, int y)
        :return: Cell[]
        '''

        # Throw error if p1 or p2 are out of range
        assert 0 <= p1[0] < 26, "p1 'x' parameter '" + str(
            p1[0]) + "' is out of range [0, 25]"
        assert 0 <= p2[0] < 26, "p2 'x' parameter '" + str(
            p2[0]) + "' is out of range [0, 25]"
        assert 0 <= p1[1] < 26, "p1 'y' parameter '" + str(
            p1[1]) + "' is out of range [0, 1000]"
        assert 0 <= p2[1] < 26, "p1 'y' parameter '" + str(
            p2[1]) + "' is out of range [0, 1000]"

        # Log range getting status (for lack of a better term)
        dbg.log("Attempting to get all cells in range (" + str(p1[0]) + ',' +
                str(p1[1]) + ") and (" + str(p2[0]) + ',' + str(p2[1]) + ')')

        # Get cells in range of points p1 and p2 using GSheet coords (converting from cartesian coords)
        data = self.sheet.range(p1[0] + 1, p1[1] + 1, p2[0] + 1, p2[1] + 1)

        # Log success
        dbg.success("Successfully got all cells in range (" + str(p1[0]) +
                    ',' + str(p1[1]) + ") and (" + str(p2[0]) + ',' +
                    str(p2[1]) + ')')

        return data
Ejemplo n.º 4
0
    def __init__(self, doc_ID='', wsheet_ID=0, creds={}):
        '''
        Initialize new Database class with credentials, a document ID and a worksheet ID.

        :param doc_ID: Str: Document ID of Google Sheets document to be linked with Database class
        :param wsheet_ID: Int: Worksheet ID of Google Sheets data to link against
        :param creds: ServiceAccountCredentials: Credentials used to gain Google Sheets permissions
        '''

        try:
            # Attempt to log into Google Sheets and make new gspread instance
            dbg.log("Attempting Google Sheets authorization...")
            self.creds = creds
            self.gs = gspread.authorize(self.creds)

            # Log Google Sheet authorization success if authorisation is successful
            dbg.success("Authorization successful")
        except:
            # Throw error if 'creds' passed are invalid
            raise ValueError(
                "Invalid creds object passed into 'Database' constructor: " +
                str(creds))

        # Set document ID and worksheet ID of Database to what was passed in through constructor args
        self.set_doc(doc_ID, wsheet_ID)
Ejemplo n.º 5
0
    def http_homepage():
        '''
        Sends html from ‘./Website/index.html' when client connects
        '''

        # Attempt to send homepage to client
        dbg.log("Attempting to send './Website/index.html' to clients")

        # Test if ./Website/index.html exists.
        try:
            open('./Website/index.html', 'r')
        # Throw error and exit if ./Website/index.html does not exist
        except FileNotFoundError:
            dbg.err(
                "Failed to send homepage to client because './Website/index.html' does not exist."
            )
            input(
                "Server cannot run without homepage... Press any key to exit.")
            os._exit(-1)

        # Send html from ./Website/index.html to client
        html = send_from_directory('./Website/', 'index.html')
        dbg.success(
            "'./Website/index.html' successfully retrieved. Sending to clients..."
        )
        return html
def check_version():
    try:
        if os.path.isfile(globals.INSTALL_DIRECTORY +
                          globals.CLIENT_VERSION_FILE):
            debug.info("Found client version file in " +
                       globals.INSTALL_DIRECTORY)
            with open(globals.INSTALL_DIRECTORY +
                      globals.CLIENT_VERSION_FILE) as current_version_file:
                globals.CLIENT_VERSION = current_version_file.read().replace(
                    "\n", "")
            current_version_file.close()
            debug.info("Contents of client version file: " +
                       globals.CLIENT_VERSION)

        filehandler.download(
            globals.CLIENT_DOWNLOAD_URL + globals.CLIENT_VERSION_FILE,
            globals.CLIENT_VERSION_FILE)
        debug.info("Saved client version file to ./" +
                   globals.CLIENT_VERSION_FILE)
        with open(globals.CLIENT_VERSION_FILE, "r") as online_version_file:
            globals.ONLINE_CLIENT_VERSION = online_version_file.read().replace(
                "\n", "")

        debug.info("Current client version: " + globals.CLIENT_VERSION)
        debug.info("Latest client version: " + globals.ONLINE_CLIENT_VERSION)
        if globals.CLIENT_VERSION == globals.ONLINE_CLIENT_VERSION:
            debug.success("Your client is up to date!")
            os.remove(globals.CLIENT_VERSION_FILE)
            debug.terminate()

        if os.path.isfile(globals.INSTALL_DIRECTORY +
                          globals.CLIENT_VERSION_FILE):
            debug.info("Deleting old version data..")
            os.remove(globals.INSTALL_DIRECTORY + globals.CLIENT_VERSION_FILE)

        debug.success("Deleted old version data!")
        debug.info("Installing new version data..")
        shutil.move(globals.CLIENT_VERSION_FILE,
                    globals.INSTALL_DIRECTORY + globals.CLIENT_VERSION_FILE)
        return os.path.isfile(globals.INSTALL_DIRECTORY +
                              globals.CLIENT_VERSION_FILE)
    except (OSError, URLError) as e:
        debug.error(
            "There was an error while checking the version of your client:")
        print(e)
        return False
Ejemplo n.º 7
0
def emit_pageData():
    '''
    Emits 'pageData' event and sends 'poller.radiothonInfo' RadiothonInfo data structure to clients
    :return: None
    '''

    # Log attempt to emit 'pageData' event
    dbg.log("Attempting to emit 'pageData' event")

    # If radiothonInfo exists, send 'pageData' event containing radiothonInfo to client
    if poller.radiothonInfo:
        socket.emit('pageData', poller.radiothonInfo)
        dbg.success("'pageData' event successfully emitted")

    # Else throw a warning
    else:
        dbg.warn(
            "Was not able to emit 'pageData' SocketIO event. Didn't have enough time to process 'poller.radiothonInfo'"
        )
Ejemplo n.º 8
0
def update_console():
    debug.info("Saving current updater files..")
    try:
        os.rename(
            globals.APPLICATION_PATH +
            globals.MAIN_EXECUTABLE[globals.OPERATING_SYSTEM],
            globals.APPLICATION_PATH +
            globals.BACKUP_EXECUTABLE[globals.OPERATING_SYSTEM])
        debug.success("Successfully saved updater files!")
        debug.info("Downloading new updater files.")

        debug.info("This may take a minute.")
        if filehandler.download(
                globals.CONSOLE_DOWNLOAD_URL + globals.OPERATING_SYSTEM + "/" +
                globals.MAIN_EXECUTABLE[globals.OPERATING_SYSTEM],
                globals.APPLICATION_PATH +
                globals.MAIN_EXECUTABLE[globals.OPERATING_SYSTEM]):
            debug.success("Downloaded updater files.")
        else:
            debug.error("Failed to download new updater files.")
            debug.error("Ending the update process.")
            debug.terminate()

        if globals.OPERATING_SYSTEM == "Linux" or globals.OPERATING_SYSTEM == "Darwin":
            debug.info("Applying file permissions..")
            os.system("sudo chmod +x " + globals.APPLICATION_PATH + "main")

        debug.success("Update completed!")
        debug.terminate()
    except URLError as e:
        debug.error("Failed to download new updater files.")
        print(e)
        debug.info("Loading saved files.")
        os.rename(globals.APPLICATION_PATH + globals.BACKUP_EXECUTABLE,
                  globals.APPLICATION_PATH + globals.MAIN_EXECUTABLE)
        debug.success("Loaded saved files!")
        debug.success("Ending update process.")
        debug.terminate()
    except OSError as e:
        debug.error("The updater encountered an error:")
        print(e)
        debug.error("Terminating update.")
        debug.terminate()
Ejemplo n.º 9
0
    def set_doc(self, doc_ID='', wsheet_ID=0):
        '''
        Link with Google Sheets document ID 'doc_ID' and worksheet ID 'wsheet_ID'

        :param doc_ID: Str
        :param wsheet_ID: ID
        :return: None
        '''

        try:
            # Attempt to link 'sheet' against GSheets document with ID 'doc_ID' and worksheet ID 'wsheet_ID'
            dbg.log("Attempting to link with Google Sheets document...")
            self.sheet = self.gs.open_by_key(doc_ID).get_worksheet(wsheet_ID)

            # Print success message if able to succesfully link with GSheets document
            dbg.success("Successfully linked with Google Sheets document")
        except gs_exceptions.APIError as e:
            # Throw error and exit if read permissions were not set
            if e.response.json()['error']['code'] == 403:
                dbg.err(
                    "Didn't have permissions to read Google Sheets document. "
                    +
                    "Please make document public or create a shareable link to it."
                )
                input("Press any key to exit...")
                os._exit(-1)
            # Throw error and exit if document does not exist
            elif e.response.json()['error']['code'] == 404:
                dbg.err(
                    "Tried to read Google Sheets document, but the document does not exist."
                )
                input("Press any key to exit...")
                os._exit(-1)
            # Throw error and exit if any other bad response is received
            else:
                dbg.err(
                    "Tried to read Google Sheets document, but got the following error response instead:"
                )
                dbg.err(e.response.text)
                input("Press any key to exit...")
                os._exit(-1)
Ejemplo n.º 10
0
    def http_other(path):
        '''
        Sends html from ‘./Website/{path} when client connects
        '''

        # Attempt to send homepage to client
        dbg.log("Attempting to send './Website/" + path + "' to clients")

        # Test if ./Website/<path> exists. Throw warning if it doesn't.
        # Wont exit because something like 'favicon.ico' might not be integral to the application
        try:
            open('./Website/' + path, 'r')
        except FileNotFoundError:
            dbg.warn("Failed to send file './Website/" + path +
                     "' to clients . File does not exist.")

        # Send html to client
        html = send_from_directory('./Website/', path)
        dbg.success("'./Website/" + path +
                    "' successfully retrieved. Sending to client...")
        return html
Ejemplo n.º 11
0
    def get_row(self, y=0):
        '''
        Gets every value at row y. ‘y’ coord starts from 0.

        :param y: Int
        :return: List
        '''

        # Log row getting status (for lack of a better term)
        dbg.log("Attempting to get data from row " + str(y) + "...")

        # Throw error if y is out of bounds
        assert y < 1000, "'y' parameter '" + str(
            y
        ) + "' is too high. Must be at most 999 because there are 1000 rows in a sheet."
        assert y >= 0, "'y' parameter '" + str(
            y) + "' is too low. Must be 0 at minimum."

        # Return data from column by converting from cartesian to GSheet coords
        data = self.sheet.row_values(y + 1)
        dbg.success("Successfully got data from row " + str(y))
        return data
Ejemplo n.º 12
0
    def get_col(self, x=0):
        '''
        Gets every value at column x. ‘x’ coord starts from 0.

        :param x: Int
        :return: List
        '''

        # Log column getting status (for lack of a better term)
        dbg.log("Attempting to get data from column " + str(x) + "...")

        # Throw error if y is out of bounds
        assert x < 26, "'y' parameter '" + str(
            x
        ) + "' is too high. Must be at most 25 because there are 26 columns in a sheet."
        assert x >= 0, "'y' parameter '" + str(
            x) + "' is too low. Must be 0 at minimum."

        # Return data from column by converting from cartesian to GSheet coords
        data = self.sheet.col_values(x + 1)
        dbg.success("Successfully got data from column " + str(x))
        return data
Ejemplo n.º 13
0
def install_files():
    try:
        new_client_file = "clientfiles(" + globals.ONLINE_CLIENT_VERSION + ").zip"
        client_zip = zipfile.ZipFile(
            globals.INSTALL_DIRECTORY + new_client_file, "r")
        client_zip.extractall(globals.INSTALL_DIRECTORY)
        client_zip.close()
        for i in range(0, len(globals.FOLDER_NAME)):
            debug.info("Verifying the installation of " +
                       globals.FOLDER_NAME[i] + "...")
            if os.path.isdir(globals.INSTALL_DIRECTORY +
                             globals.FOLDER_NAME[i]):
                debug.success("Installation found: " + globals.FOLDER_NAME[i] +
                              ".")
            else:
                debug.info(globals.FOLDER_NAME[i] + " could not be found!")
                return False
        return True
    except (OSError, IOError) as e:
        debug.error("Failed to install client files.")
        print(e)
        return False
Ejemplo n.º 14
0
def update_radiothonInfo():
    '''
    This method updates the global ‘radiothonInfo’ variable by getting the latest data from GSheets document and
    parsing it and the config into a ‘RadiothonInfo’ data structure.
    This method uses the ‘gsparser.py’ module to process all the data.

    :return: None
    '''

    # Bring global variables into local scope
    global radiothonInfo
    global database
    global config

    # Attempt to refresh GAPI credentials. Needed otherwise Google will reject requests after certain period of time.
    dbg.log("Attempting to refresh GAPI credentials")
    database.gs.login()
    dbg.success("Successfully refreshed GAPI credentials")

    # Update config
    update_config_status()

    # Log radiothonInfo update attempt
    dbg.log("Attempting to update server status (poller.radiothonInfo)")

    # Get all rows from Google sheets document
    dbg.log("Attempting to get all the data from GSheets document.")
    rows = database.get_all_vals()
    dbg.success("Successfully got all data from GSheets document.")

    # Throw error and exit if there is no data in GSheets document at all
    if len(rows) == 0:
        dbg.err("Failed to update server status because there is no data or header in Google Sheets document")
        input("Cannot continue. Press any key to exit.")
        os._exit(-1)

    # Get header from GSheets doc to compare pledges against.
    header = rows[0]

    # PledgeDict accumulator. Will contain a list of 'Pledge' data structures
    pledges = []

    # Get all rows with content excluding header (which is why i starts at 1)
    # from GSheets doc and turn them into PledgeDict's
    for i in range(1, len(rows)):
        pledges.append(gsparser.to_Pledge(rows[i], header))

    # Update radiothonInfo to latest state
    radiothonInfo = gsparser.to_RadiothonInfo(pledges, config)

    # Log that radiothonInfo was successfully updated
    dbg.success("Server status update successful (poller.radiothonInfo updated)")
Ejemplo n.º 15
0
def main():
    # Clear the terminal depending on OS.
    if globals.OPERATING_SYSTEM == "Darwin" or globals.OPERATING_SYSTEM == "Linux":
        os.system("clear")
    if globals.OPERATING_SYSTEM == "Windows":
        os.system("cls")

    debug.info("Welcome to the Banana Kingdom server updater V" +
               globals.CONSOLE_VERSION + "!")

    try:
        debug.info("Checking Internet connection..")
        if validator.check_internet():
            debug.success("Connected to the Internet!")
        else:
            debug.error("Failed Internet connection test.")
            debug.error("Terminating client launch.")
            debug.terminate()

        # If the updater has an update, it'll attempt to update itself.
        if validator.check_console_updates():
            update_executable = {
                "Windows": "update.exe",
                "Linux": "update",
                "Darwin": "update"
            }
            input("Press enter to install the new updates.")

            executable_path = os.path.join(
                globals.APPLICATION_PATH,
                update_executable[globals.OPERATING_SYSTEM])
            os.system(executable_path)
            sys.exit()

        # Delete backup of original executable, if the update was successful.
        if os.path.exists(globals.BACKUP_EXECUTABLE[globals.OPERATING_SYSTEM]):
            os.remove(globals.BACKUP_EXECUTABLE[globals.OPERATING_SYSTEM])

        debug.info("Would you like to check for updates?")
        install_confirm = input("Enter 'y' for yes or 'n' for no.")
        if not install_confirm == "y":
            sys.exit()

        if validator.check_directory():
            debug.success("Found your Minecraft installation in " +
                          globals.INSTALL_DIRECTORY)
        else:
            debug.error("Failed to find Minecraft installation folder.")
            debug.error("Ending the update process.")
            debug.terminate()

        if not validator.check_version():
            debug.error("Failed to check your client version.")
            debug.error("Ending the update process.")
            debug.terminate()

        if filehandler.download_client():
            debug.success("Successfully downloaded client files!")
        else:
            debug.error("Failed to download client files.")
            debug.error("Ending the update process.")
            debug.terminate()

        filehandler.delete_folders()

        # Verify that the files were decompressed correctly.
        if filehandler.install_files():
            debug.success("Update Complete! Welcome to Banana Kingdom V" +
                          globals.ONLINE_CLIENT_VERSION)
        else:
            debug.error("Failed to install required client files.")

    except OSError as e:
        debug.error("There was an error while running this program:")
        print(e)
    debug.terminate()
Ejemplo n.º 16
0
    "-----------------------------------------------------------------------------------------------------------------------------------------------------------\n"
)

# Pause and give user time to read copyright message
sleep(1)

# Load config and GAPI credentials
poller.update_config_status()

# Attempt to load GAPI credentials from 'creds.pickle'
try:
    dbg.log("Attempting to load GAPI credentials file 'creds.pickle'")
    creds = pickle.load(open('creds.pickle', 'rb'))

    # Log success
    dbg.success("Successfully loaded GAPI credentials file 'creds.pickle'")

# Throw error and exit if GAPI credentials file cannot be found
except FileNotFoundError:
    dbg.err(
        "Failed to load 'creds.pickle' because it could not be found. 'creds.pickle' is required for server to run."
    )
    input("Cannot continue. Press any key to exit.")
    exit(-1)

# Throw error that reads off if any other exceptions are raised
except Exception as e:
    dbg.err("Failed to load 'creds.pickle'. Got the following error instead:")
    dbg.err(str(e))
    input("Cannot continue. Press any key to exit.")
    exit(-1)
Ejemplo n.º 17
0
def update_config_status():
    '''
    Updates variable ‘config’ by loading server_config.json file as Dict
    :return: None
    '''

    def handle_corrupt_key(dict={}, key_name="", types_allowed=None):
        '''
        Throws error and quits if a key with name set to value of 'key_name' does not exist or if the value associated
        with the key is not set to type 'types_allowed'
        Used primarily to test for corrupted server_config.json.

        :param dict: ServerConfigDict
        :param key_name: Str
        :param types_allowed: Type[]
        :return: None
        '''

        # Attempt to get value from key
        try:
            # Throw error and exit if value associated with key is not of any types specified in 'types_allowed'
            if type(dict[key_name]) not in types_allowed:
                # Print error message "Failed to read server_config.json. The '<key_name>' key must be set to one of the following data types: ['typename',...]"
                dbg.err(
                    "Failed to read server_config.json. The '" + key_name + "' key needs must be set to one of the following data types: " +
                    str([i.__name__ for i in types_allowed]))

                # Prompt for input then exit
                input("Cannot continue. Press any key to exit..")
                os._exit(-1)

        # Throw error and exit if key cannot be found
        except KeyError:
            dbg.err("Failed to read server_config.json. '" + key_name + "' key does not exist in server_config.json.")
            input("Cannot continue. Press any key to exit..")
            os._exit(-1)

    global config

    # Attempt to load server config and throw error if any keys either don't exist or have an invalid type
    try:
        # Log attempt
        dbg.log("Attempting to update server config status")

        # Attempt to load JSON from server_config.json as type Dict
        config = json.load(open('../server_config.json', 'r'))

        # Throw error if any keys in server_config.json are either missing or invalidly typed
        handle_corrupt_key(config, "radiothon", [dict])
        handle_corrupt_key(config["radiothon"], "goal", [dict])
        handle_corrupt_key(config["radiothon"], "name", [str])
        handle_corrupt_key(config["radiothon"], "start_date", [str])
        handle_corrupt_key(config["radiothon"], "end_date", [str])
        handle_corrupt_key(config["radiothon"]["goal"], "hourly", [float, int])
        handle_corrupt_key(config["radiothon"]["goal"], "daily", [float, int])
        handle_corrupt_key(config["radiothon"]["goal"], "weekly", [float, int])
        handle_corrupt_key(config["radiothon"]["goal"], "total", [float, int])
        handle_corrupt_key(config, "gsheets", [dict])
        handle_corrupt_key(config["gsheets"], "doc_ID", [str])
        handle_corrupt_key(config["gsheets"], "wsheet_ID", [int])
        handle_corrupt_key(config["gsheets"], "poll_interval", [float, int])

    # Throw error if file does not exist
    except FileNotFoundError:
        dbg.err(
            "Failed to read server config because server_config.json does not exist. server_config.json must be in project's root directory.")
        input("Cannot continue. Press any key to exit")
        os._exit(-1)

    # Throw error if JSON from server_config.json could not be parsed
    except json.JSONDecodeError as e:
        dbg.err("Failed to read server_config.json. JSON Decoder threw the following error: \n'" + str(e) + "'")
        input("Cannot continue. Press any key to exit.")
        os._exit(-1)

    # Log config loading success
    dbg.success("Server config status successfully updated")
Ejemplo n.º 18
0
def main():
    '''
    Entry point for host.py module
    :return: void
    '''

    # Bring global variables into local scope
    global http
    global socket

    # These constant variables specify port and address for server to run on
    # '0.0.0.0' tells OS to make server visible to other computers on network
    ADDRESS = '0.0.0.0'
    PORT = 80

    # Thread that the server runs on (daemon thread)
    server_thread = threading.Thread(target=socket.run,
                                     args=[http, ADDRESS, PORT])
    server_thread.setDaemon(True)

    # Sends html from ‘./Website/index.html’
    @http.route('/')
    def http_homepage():
        '''
        Sends html from ‘./Website/index.html' when client connects
        '''

        # Attempt to send homepage to client
        dbg.log("Attempting to send './Website/index.html' to clients")

        # Test if ./Website/index.html exists.
        try:
            open('./Website/index.html', 'r')
        # Throw error and exit if ./Website/index.html does not exist
        except FileNotFoundError:
            dbg.err(
                "Failed to send homepage to client because './Website/index.html' does not exist."
            )
            input(
                "Server cannot run without homepage... Press any key to exit.")
            os._exit(-1)

        # Send html from ./Website/index.html to client
        html = send_from_directory('./Website/', 'index.html')
        dbg.success(
            "'./Website/index.html' successfully retrieved. Sending to clients..."
        )
        return html

    # Sends html from ‘./Website/{path}’
    @http.route('/<path:path>')
    def http_other(path):
        '''
        Sends html from ‘./Website/{path} when client connects
        '''

        # Attempt to send homepage to client
        dbg.log("Attempting to send './Website/" + path + "' to clients")

        # Test if ./Website/<path> exists. Throw warning if it doesn't.
        # Wont exit because something like 'favicon.ico' might not be integral to the application
        try:
            open('./Website/' + path, 'r')
        except FileNotFoundError:
            dbg.warn("Failed to send file './Website/" + path +
                     "' to clients . File does not exist.")

        # Send html to client
        html = send_from_directory('./Website/', path)
        dbg.success("'./Website/" + path +
                    "' successfully retrieved. Sending to client...")
        return html

    # Emit pageData event when clients connect
    @socket.on('connect')
    def socket_connect():
        '''
        Emit a ‘pageData’ event and send poller.radiothonInfo to clients when a client connects through SocketIO
        '''

        emit_pageData()

    # Attempt to start server on port 80
    try:
        # Start server
        dbg.log("Attempting to start SocketIO/HTTP server on address '" +
                ADDRESS + ":" + str(PORT) + "'...")
        server_thread.start()

        # Log success
        dbg.success("Successfully started server")

    # If server fails to start, throw error
    except Exception as e:
        dbg.err("Failed to start SocketIO/HTTP server on address '" + ADDRESS +
                ":" + str(PORT) + "'. Flask threw the following error: \n" +
                str(e))
        print("Cannot continue. Press any key to exit.")
        os._exit(-1)

    # Periodically send 'pageData' event to client at interval specified in config
    while True:
        # Send pageData event and 'radiothonInfo' data structure to client
        emit_pageData()

        # If poller.config has been set
        if poller.config != {}:
            # Sleep for however long was specified in config
            sleep(poller.config['gsheets']['poll_interval'] * 60)
        # Else sleep for a second and try to emit pageData event again
        else:
            sleep(1)