Skip to content

Commit

Permalink
Merge branch 'hotfix/1.2/42561' into 'master'
Browse files Browse the repository at this point in the history
[HOTFIX] Create new database connection for every query

See merge request hull-seals/code/irc/halpybot!47
  • Loading branch information
Rixxan committed Mar 6, 2021
2 parents 533cc7d + 71ef8d1 commit 976fac4
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 129 deletions.
4 changes: 4 additions & 0 deletions config/config_template.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ database =
[Force join command]
joinable = #debrief, #Code-Black, #Repair-Requests, #bot-test, #cybers

[Offline Mode]
Enabled = False
announce_channels = #bot-test

[Logging]
level = 'DEBUG'
82 changes: 58 additions & 24 deletions src/commands/delayedboard/delayedboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"""

from typing import List
from src.packages.database.delayedboard import create_delayed_case, reopen_delayed_case, update_delayed_status, \
update_delayed_notes, check_delayed_cases
from src.packages.database.delayedboard import *
from src.packages.database import NoDatabaseConnection
from src.packages.checks.checks import require_channel, require_permission, DeniedMessage
from .. import Commands

Expand All @@ -39,7 +39,11 @@ async def cmd_createDelayedCase(ctx, args: List[str]):
return await ctx.reply("Cannot create case: maximum length for notes is 400 characters.")

# Create the case
results = await create_delayed_case(case_status, message, ctx.sender)
try:
results = await create_delayed_case(case_status, message, ctx.sender)
except NoDatabaseConnection:
return await ctx.reply("Cannot create case: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if results[3]:
await ctx.reply("WARNING: characters incompatible with the database have been removed from the notes.")
Expand Down Expand Up @@ -68,7 +72,12 @@ async def cmd_ReopenDelayedCase(ctx, args: List[str]):

cID = int(args[0])
casestat = args[1]
results = await reopen_delayed_case(cID, casestat, ctx.sender)

try:
results = await reopen_delayed_case(cID, casestat, ctx.sender)
except NoDatabaseConnection:
return await ctx.reply("Cannot reopen case: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if results[1] == 0:
return await ctx.reply(f"Successfully reopened Delayed Case #{results[0]}.")
Expand All @@ -91,7 +100,12 @@ async def cmd_closeDelayedCase(ctx, args: List[str]):
return await ctx.reply("Cannot comply: no valid case number was provided.")

cID = int(args[0])
results = await update_delayed_status(cID, 3, ctx.sender) # set casestat to 3 to close case

try:
results = await update_delayed_status(cID, 3, ctx.sender) # set casestat to 3 to close case
except NoDatabaseConnection:
return await ctx.reply("Cannot update case: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if results[1] == 0:
return await ctx.reply(f"Case #{results[0]} closed.")
Expand All @@ -118,7 +132,12 @@ async def cmd_updateDelayedStatus(ctx, args: List[str]):

cID = int(args[0])
casestat = int(args[1])
results = await update_delayed_status(cID, casestat, ctx.sender)

try:
results = await update_delayed_status(cID, casestat, ctx.sender)
except NoDatabaseConnection:
return await ctx.reply("Cannot update case: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if results[1] == 0:
return await ctx.reply(f"Case #{results[0]} now has status {casestat}.")
Expand Down Expand Up @@ -150,7 +169,11 @@ async def cmd_updateDelayedNotes(ctx, args: List[str]):

cID = int(args[0])

results = await update_delayed_notes(cID, message, ctx.sender)
try:
results = await update_delayed_notes(cID, message, ctx.sender)
except NoDatabaseConnection:
return await ctx.reply("Cannot update case: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if results[3]:
await ctx.reply("WARNING: characters incompatible with the database have been removed from the notes.")
Expand All @@ -168,7 +191,13 @@ async def cmd_checkDelayedCases(ctx, args: List[str]):
Usage: !delaystatys
Aliases: checkstatus
"""
count = await check_delayed_cases()

try:
count = await check_delayed_cases()
except NoDatabaseConnection:
return await ctx.reply("Cannot connect to board: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if count == 0:
return await ctx.reply("No Cases marked Delayed. Good Job, Seals!")
else:
Expand All @@ -192,22 +221,27 @@ async def cmd_updateDelayedCase(ctx, args: List[str]):
return await ctx.reply("Cannot comply: no new case status and/or notes were provided.")

# If only a new status or notes are supplied, yeet it at their own commands
if len(args) == 2 and args[1].isnumeric():
return await cmd_updateDelayedStatus(ctx, args)
if len(args) >= 2 and not args[1].isnumeric():
return await cmd_updateDelayedNotes(ctx, args)

# Both: call procedures back to back
cID = int(args[0])
casestat = int(args[1])
message = ' '.join(args[2:])

# One more round of input validation
if casestat not in [1, 2]:
return await ctx.reply("Cannot comply: please set a valid status code")

statusout = await update_delayed_status(cID, casestat, ctx.sender)
notesout = await update_delayed_notes(cID, message, ctx.sender)
try:
if len(args) == 2 and args[1].isnumeric():
return await cmd_updateDelayedStatus(ctx, args)
if len(args) >= 2 and not args[1].isnumeric():
return await cmd_updateDelayedNotes(ctx, args)

# Both: call procedures back to back
cID = int(args[0])
casestat = int(args[1])
message = ' '.join(args[2:])

# One more round of input validation
if casestat not in [1, 2]:
return await ctx.reply("Cannot comply: please set a valid status code")

statusout = await update_delayed_status(cID, casestat, ctx.sender)
notesout = await update_delayed_notes(cID, message, ctx.sender)

except NoDatabaseConnection:
return await ctx.reply("Cannot update case: running in OFFLINE MODE. "
"Contact a cyberseal immediately!")

if notesout[3]:
await ctx.reply("WARNING: characters incompatible with the database have been removed from the notes.")
Expand Down
43 changes: 24 additions & 19 deletions src/commands/fact/fact.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from src.packages.database.facts import update_fact_index, basic_facts, clear_facts, get_facts
from typing import List
import logging
from src.packages.database.facts import add_fact, remove_fact, DatabaseConnection
from src.packages.database.facts import add_fact, remove_fact, get_offline_facts
from src.packages.database import NoDatabaseConnection
from .. import Commands

@require_dm()
Expand All @@ -41,13 +42,18 @@ async def cmd_manual_ufi(ctx, args: List[str]):
Aliases: fact_update
"""
logging.info(f"FACT INDEX UPDATE by {ctx.sender}")
#if not has_cnx:
#return await ctx.reply("Cannot update cache: bot running in offline mode!")
await ctx.reply("Updating...")
await clear_facts()
await get_facts()
await update_fact_index()
await ctx.reply("Done.")
try:
await ctx.reply("Updating...")
await clear_facts()
await get_facts()
await update_fact_index()
await ctx.reply("Done.")
except NoDatabaseConnection:
await ctx.reply("Cannot update fact cache, running in OFFLINE MODE. "
"Contact a cyberseal immediately!")
# Fetch offline facts, just in case the cache was flushed
await get_offline_facts()
await update_fact_index()


@require_permission(req_level="ADMIN", message=DeniedMessage.ADMIN)
Expand All @@ -59,13 +65,13 @@ async def cmd_addfact(ctx, args: List[str]):
Usage: !addfact [name] (--dm) [facttext]
Aliases: n/a
"""
# Check if running on online mode
#if not has_cnx:
#return await ctx.reply("Cannot add fact: bot running in offline mode!")
# Else, add fact
factname = args[0]
facttext = ' '.join(arg for arg in args[1:])
await add_fact(ctx, factname, facttext)
try:
await add_fact(ctx, factname, facttext)
except NoDatabaseConnection:
return await ctx.reply("Cannot delete fact: running in OFFLINE MODE! "
"Contact a cyberseal immediately.")


@require_permission(req_level="ADMIN", message=DeniedMessage.ADMIN)
Expand All @@ -77,10 +83,9 @@ async def cmd_deletefact(ctx, args: List[str]):
Usage: !deletefact [factname]
Aliases: n/a
"""
# Check if running on online mode
#if not has_cnx:
#return await ctx.reply("Cannot remove fact: bot running in offline mode!")
factname = args[0]
await remove_fact(ctx, factname)


try:
await remove_fact(ctx, factname)
except NoDatabaseConnection:
await ctx.reply("Cannot delete fact: running in OFFLINE MODE! "
"Contact a cyberseal immediately.")
35 changes: 29 additions & 6 deletions src/packages/database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
See license.md
"""

# PyCharm tells me these imports are not used, but they are. Do not remove.
import configparser
import mysql.connector
from mysql.connector import Error as NoDatabaseConnection
import logging

from ..database import *

config = configparser.ConfigParser()
config.read('config/config.ini')

Expand All @@ -23,11 +25,23 @@
"host": config['Database']['host'],
"database": config['Database']['database']}

class DatabaseConnection:
# Assume not in offline mode
offline_mode: bool = config.getboolean('Offline Mode', 'Enabled')

OM = False
om_channels = [entry.strip() for entry in config.get('Offline Mode', 'announce_channels').split(',')]

class NoDatabaseConnection(ConnectionError):
"""
Raised when 3 consecutive attempts at reconnection are unsuccessful
"""
pass

class DatabaseConnection:

def __init__(self):
global offline_mode
if offline_mode is True:
raise NoDatabaseConnection
for _ in range(3):
# Attempt to connect to the DB
try:
Expand All @@ -38,14 +52,23 @@ def __init__(self):
self.cursor = cursor
logging.info("Connection established.")
break
except NoDatabaseConnection as er:
except mysql.connector.Error as er:
logging.error(f"Unable to connect to DB, attempting a reconnect: {er}")
# And we do the same for when the connection fails
if _ == 2:
logging.error("ABORTING CONNECTION - CONTINUING IN OFFLINE MODE")
OM = True
# TODO send messages to channels
# Set offline mode, can only be removed by restart
offline_mode = True
# TODO send announcement message
raise NoDatabaseConnection
continue

def close(self):
self.cnx.close()


# async def announce_offline(self):
# for ch in om_channels:
# await main.HalpyBOT.message(ch, "ATTENTION: HalpyBOT has entered OFFLINE MODE. "
# "Database-related functions are no longer available. "
# "Please contact a cyberseal immediately!")
Loading

0 comments on commit 976fac4

Please sign in to comment.