Commit 0b2eefbb authored by Jeremy Pallats's avatar Jeremy Pallats 💬
Browse files

FIX: Linked events like PVPInterdictionKill could be duplicated.

- Add a uniqueness constraint on linked event tables.
- Move to using functions to link events that query and return existing
  if found, else add.
parent c09d9293
Loading
Loading
Loading
Loading
Loading
+126 −34
Original line number Diff line number Diff line
@@ -238,6 +238,124 @@ def parse_cmdr_name(data):
    return cmdr


def link_interdiction_to_kill(eddb_session, interdiction, kill):
    """
    Link a PVPInterdiction event to a PVPKill event.

    Args:
        eddb_session: A session onto the EDDB db.
        interdiction: An PVPInterdiction event.
        kill: A PVPKill event.

    Returns: The new or existing PVPInterdictionKill object.
    """
    try:
        linked_event = eddb_session.query(pvp.schema.PVPInterdictionKill).\
            filter(pvp.schema.PVPInterdictionKill.pvp_interdiction_id == interdiction.id,
                   pvp.schema.PVPInterdictionKill.pvp_kill_id == kill.id).\
            one()
    except sqla.exc.NoResultFound:
        linked_event = pvp.schema.PVPInterdictionKill(
            cmdr_id=kill.cmdr_id,
            pvp_interdiction_id=interdiction.id,
            pvp_kill_id=kill.id,
            event_at=kill.event_at,
        )
        eddb_session.add(linked_event)
        interdiction.survived = False
        eddb_session.flush()

    return linked_event


def link_interdiction_to_death(eddb_session, interdiction, death):
    """
    Link a PVPInterdiction event to a PVPDeath event.

    Args:
        eddb_session: A session onto the EDDB db.
        interdiction: An PVPInterdiction event.
        death: A PVPDeath event.

    Returns: The new or existing PVPInterdictionKill object.
    """
    try:
        linked_event = eddb_session.query(pvp.schema.PVPInterdictionDeath).\
            filter(pvp.schema.PVPInterdictionDeath.pvp_interdiction_id == interdiction.id,
                   pvp.schema.PVPInterdictionDeath.pvp_death_id == death.id).\
            one()
    except sqla.exc.NoResultFound:
        linked_event = pvp.schema.PVPInterdictionDeath(
            cmdr_id=death.cmdr_id,
            pvp_interdiction_id=interdiction.id,
            pvp_death_id=death.id,
            event_at=death.event_at,
        )
        eddb_session.add(linked_event)
        eddb_session.flush()

    return linked_event


def link_interdicted_to_kill(eddb_session, interdicted, kill):
    """
    Link a PVPInterdicted event to a PVPKill event.

    Args:
        eddb_session: A session onto the EDDB db.
        interdicted: An PVPInterdicted event.
        kill: A PVPKill event.

    Returns: The new or existing PVPInterdictedKill object.
    """
    try:
        linked_event = eddb_session.query(pvp.schema.PVPInterdictedKill).\
            filter(pvp.schema.PVPInterdictedKill.pvp_interdicted_id == interdicted.id,
                   pvp.schema.PVPInterdictedKill.pvp_kill_id == kill.id).\
            one()
    except sqla.exc.NoResultFound:
        linked_event = pvp.schema.PVPInterdictedKill(
            cmdr_id=kill.cmdr_id,
            pvp_interdicted_id=interdicted.id,
            pvp_kill_id=kill.id,
            event_at=kill.event_at,
        )
        eddb_session.add(linked_event)
        eddb_session.flush()

    return linked_event


def link_interdicted_to_death(eddb_session, interdicted, death):
    """
    Link a PVPInterdicted event to a PVPDeath event.

    Args:
        eddb_session: A session onto the EDDB db.
        interdiction: An PVPInterdicted event.
        death: A PVPDeath event.

    Returns: The new or existing PVPInterdictionKill object.
    """
    try:
        linked_event = eddb_session.query(pvp.schema.PVPInterdictedDeath).\
            filter(pvp.schema.PVPInterdictedDeath.pvp_interdicted_id == interdicted.id,
                   pvp.schema.PVPInterdictedDeath.pvp_death_id == death.id).\
            one()
    except sqla.exc.NoResultFound:
        linked_event = pvp.schema.PVPInterdictedDeath(
            cmdr_id=death.cmdr_id,
            pvp_interdicted_id=interdicted.id,
            pvp_death_id=death.id,
            event_at=death.event_at,
        )
        interdicted.survived = False
        eddb_session.add(linked_event)
        eddb_session.flush()

    return linked_event


def datetime_to_tstamp(date_string):
    """
    Convert a tiemstamp string in a log line to a simpler integer timestamp
@@ -344,48 +462,22 @@ class Parser():
        if event == 'PVPKill' and self.data.get('Interdiction') and\
                result.victim_name == self.data['Interdiction'].victim_name and\
                result.event_at >= self.data['Interdiction'].event_at:
            self.eddb_session.add(pvp.schema.PVPInterdictionKill(
                cmdr_id=result.cmdr_id,
                pvp_interdiction_id=self.data['Interdiction'].id,
                pvp_kill_id=result.id,
                event_at=result.event_at,
            ))
            self.data.get('Interdiction').survived = False
            self.eddb_session.flush()

        elif event == 'PVPKill' and self.data.get('Interdicted') and\
                result.victim_name == self.data['Interdicted'].interdictor_name and\
                result.event_at >= self.data['Interdicted'].event_at:
            self.eddb_session.add(pvp.schema.PVPInterdictedKill(
                cmdr_id=result.cmdr_id,
                pvp_interdicted_id=self.data['Interdicted'].id,
                pvp_kill_id=result.id,
                event_at=result.event_at,
            ))
            self.eddb_session.flush()
            link_interdiction_to_kill(self.eddb_session, self.data['Interdiction'], result)

        elif event == 'Died' and self.data.get('Interdiction') and\
                result.killed_by(self.data['Interdiction'].victim_name) and\
                result.event_at >= self.data['Interdiction'].event_at:
            self.eddb_session.add(pvp.schema.PVPInterdictionDeath(
                cmdr_id=result.cmdr_id,
                pvp_interdiction_id=self.data['Interdiction'].id,
                pvp_death_id=result.id,
                event_at=result.event_at,
            ))
            self.eddb_session.flush()
            link_interdiction_to_death(self.eddb_session, self.data['Interdiction'], result)

        elif event == 'PVPKill' and self.data.get('Interdicted') and\
                result.victim_name == self.data['Interdicted'].interdictor_name and\
                result.event_at >= self.data['Interdicted'].event_at:
            link_interdicted_to_kill(self.eddb_session, self.data['Interdicted'], result)

        elif event == 'Died' and self.data.get('Interdicted') and\
                result.killed_by(self.data['Interdicted'].interdictor_name) and\
                result.event_at >= self.data['Interdicted'].event_at:
            self.eddb_session.add(pvp.schema.PVPInterdictedDeath(
                cmdr_id=result.cmdr_id,
                pvp_interdicted_id=self.data['Interdicted'].id,
                pvp_death_id=result.id,
                event_at=result.event_at,
            ))
            self.data.get('Interdicted').survived = False
            self.eddb_session.flush()
            link_interdicted_to_death(self.eddb_session, self.data['Interdicted'], result)

        # Always store event in data for later use
        if event in ['Interdiction', 'Interdicted', 'PVPKill']:
+12 −0
Original line number Diff line number Diff line
@@ -267,6 +267,9 @@ class PVPInterdictionKill(ReprMixin, TimestampMixin, EventTimeMixin, Base):
    Table to store the event where an interdiction lead to a pvp kill.
    """
    __tablename__ = 'pvp_interdictions_kills'
    __table_args__ = (
        UniqueConstraint('pvp_interdiction_id', 'pvp_kill_id', name='_pvp_interdiction_kill_unique'),
    )
    _repr_keys = ['id', 'cmdr_id', 'pvp_interdiction_id', 'pvp_kill_id', 'created_at', 'event_at']

    id = sqla.Column(sqla.BigInteger, primary_key=True)
@@ -292,6 +295,9 @@ class PVPInterdictionDeath(ReprMixin, TimestampMixin, EventTimeMixin, Base):
    Table to store the event where an interdiction lead to a cmdr death.
    """
    __tablename__ = 'pvp_interdictions_deaths'
    __table_args__ = (
        UniqueConstraint('pvp_interdiction_id', 'pvp_death_id', name='_pvp_interdiction_death_unique'),
    )
    _repr_keys = ['id', 'cmdr_id', 'pvp_interdiction_id', 'pvp_death_id', 'created_at', 'event_at']

    id = sqla.Column(sqla.BigInteger, primary_key=True)
@@ -357,6 +363,9 @@ class PVPInterdictedKill(ReprMixin, TimestampMixin, EventTimeMixin, Base):
    Table to store the event where a cmdr was interdicted and pvp killed.
    """
    __tablename__ = 'pvp_interdicteds_kills'
    __table_args__ = (
        UniqueConstraint('pvp_interdicted_id', 'pvp_kill_id', name='_pvp_interdicted_kill_unique'),
    )
    _repr_keys = ['id', 'cmdr_id', 'pvp_interdicted_id', 'pvp_kill_id', 'created_at', 'event_at']

    id = sqla.Column(sqla.BigInteger, primary_key=True)
@@ -382,6 +391,9 @@ class PVPInterdictedDeath(ReprMixin, TimestampMixin, EventTimeMixin, Base):
    Table to store the event where a cmdr was interdicted and died.
    """
    __tablename__ = 'pvp_interdicteds_deaths'
    __table_args__ = (
        UniqueConstraint('pvp_interdicted_id', 'pvp_death_id', name='_pvp_interdicted_death_unique'),
    )
    _repr_keys = ['id', 'cmdr_id', 'pvp_interdicted_id', 'pvp_death_id', 'created_at', 'event_at']

    id = sqla.Column(sqla.BigInteger, primary_key=True)
+49 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ from pvp.schema import (
    PVPInterdictedKill, PVPInterdictedDeath, PVPInterdictionKill, PVPInterdictionDeath,
    PVPInterdicted, PVPInterdiction, PVPDeathKiller, PVPDeath, PVPKill, PVPLocation, PVPCmdr
)
from tests.conftest import PVP_TIMESTAMP

JOURNAL_PATH = os.path.join(cog.util.ROOT_DIR, 'tests', 'pvp', 'player_journal.jsonl')

@@ -119,6 +120,54 @@ def test_parse_fsdjump(f_pvp_testbed, eddb_session):
    assert location.system.name == "LP 98-132"


def test_link_interdiction_to_kill(f_pvp_testbed, eddb_session):
    kill = PVPKill(id=10, cmdr_id=1, system_id=1000, victim_name='LeSuck', victim_rank=3, event_at=PVP_TIMESTAMP + 10)
    interdiction = PVPInterdiction(id=10, cmdr_id=1, system_id=1000, is_player=True, is_success=True, survived=False,
                                   victim_name="LeSuck", victim_rank=3, event_at=PVP_TIMESTAMP + 10)
    eddb_session.add_all([kill, interdiction])
    eddb_session.flush()
    linked = pvp.journal.link_interdiction_to_kill(eddb_session, interdiction, kill)
    linked = pvp.journal.link_interdiction_to_kill(eddb_session, interdiction, kill)  # Will return found event
    assert linked.pvp_kill_id == kill.id
    assert linked.pvp_interdiction_id == interdiction.id


def test_link_interdiction_to_death(f_pvp_testbed, eddb_session):
    death = PVPDeath(id=10, cmdr_id=1, system_id=1000, is_wing_kill=True, event_at=PVP_TIMESTAMP + 10)
    interdiction = PVPInterdiction(id=10, cmdr_id=1, system_id=1000, is_player=True, is_success=True, survived=False,
                                   victim_name="LeSuck", victim_rank=3, event_at=PVP_TIMESTAMP + 10)
    eddb_session.add_all([death, interdiction])
    eddb_session.flush()
    linked = pvp.journal.link_interdiction_to_death(eddb_session, interdiction, death)
    linked = pvp.journal.link_interdiction_to_death(eddb_session, interdiction, death)  # Will return found event
    assert linked.pvp_death_id == death.id
    assert linked.pvp_interdiction_id == interdiction.id


def test_link_interdicted_to_kill(f_pvp_testbed, eddb_session):
    kill = PVPKill(id=10, cmdr_id=1, system_id=1000, victim_name='LeSuck', victim_rank=3, event_at=PVP_TIMESTAMP + 10)
    interdicted = PVPInterdicted(id=10, cmdr_id=1, system_id=1000, is_player=True, did_submit=False, survived=False,
                                 interdictor_name="BadGuyWon", interdictor_rank=7, event_at=PVP_TIMESTAMP + 10)
    eddb_session.add_all([kill, interdicted])
    eddb_session.flush()
    linked = pvp.journal.link_interdicted_to_kill(eddb_session, interdicted, kill)
    linked = pvp.journal.link_interdicted_to_kill(eddb_session, interdicted, kill)  # Will return found event
    assert linked.pvp_kill_id == kill.id
    assert linked.pvp_interdicted_id == interdicted.id


def test_link_interdicted_to_death(f_pvp_testbed, eddb_session):
    death = PVPDeath(id=10, cmdr_id=1, system_id=1000, is_wing_kill=True, event_at=PVP_TIMESTAMP + 10)
    interdicted = PVPInterdicted(id=10, cmdr_id=1, system_id=1000, is_player=True, did_submit=False, survived=False,
                                 interdictor_name="BadGuyWon", interdictor_rank=7, event_at=PVP_TIMESTAMP + 10)
    eddb_session.add_all([death, interdicted])
    eddb_session.flush()
    linked = pvp.journal.link_interdicted_to_death(eddb_session, interdicted, death)
    linked = pvp.journal.link_interdicted_to_death(eddb_session, interdicted, death)  # Will return found event
    assert linked.pvp_death_id == death.id
    assert linked.pvp_interdicted_id == interdicted.id


def test_parse_cmdr_name():
    data = json.loads('{ "timestamp":"2023-01-01T12:43:20Z", "event":"LoadGame", "FID":"F9999999", "Commander":"coolGuy", "Horizons":true, "Odyssey":false, "Ship":"Federation_Corvette", "Ship_Localised":"Federal Corvette", "ShipID":33, "ShipName":"ANS RELIANT", "ShipIdent":"FRC-03", "FuelLevel":32.000000, "FuelCapacity":32.000000, "GameMode":"Solo", "Credits":2969730326, "Loan":0, "language":"English/UK", "gameversion":"4.0.0.1476", "build":"r289925/r0 " }')
    assert 'coolGuy' == pvp.journal.parse_cmdr_name(data)