From eed148f956846ec91281bc5565dfee8cc635de0a Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Sat, 11 Sep 2021 01:06:13 +0000 Subject: [PATCH] Clean up possible orphan profiles for DDR Ace. --- bemani/backend/ddr/ddrace.py | 36 +++++++++++++++++- bemani/data/mysql/user.py | 15 ++++++++ .../static/components/events.react.js | 37 +++++++++++++++++++ .../static/controllers/admin/events.react.js | 4 ++ bemani/frontend/static/themes/dark/table.css | 4 ++ .../frontend/static/themes/default/table.css | 4 ++ 6 files changed, 98 insertions(+), 2 deletions(-) diff --git a/bemani/backend/ddr/ddrace.py b/bemani/backend/ddr/ddrace.py index 5777f8f..7a45f06 100644 --- a/bemani/backend/ddr/ddrace.py +++ b/bemani/backend/ddr/ddrace.py @@ -1,13 +1,13 @@ # vim: set fileencoding=utf-8 import base64 -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple from typing_extensions import Final from bemani.backend.ess import EventLogHandler from bemani.backend.ddr.base import DDRBase from bemani.backend.ddr.ddr2014 import DDR2014 from bemani.common import Profile, ValidatedDict, VersionConstants, CardCipher, Time, ID, intish -from bemani.data import Achievement, Machine, Score, UserID +from bemani.data import Data, Achievement, Machine, Score, UserID from bemani.protocol import Node @@ -105,6 +105,37 @@ class DDRAce( def previous_version(self) -> Optional[DDRBase]: return DDR2014(self.data, self.config, self.model) + @classmethod + def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + # DDR Ace has a weird bug where it sends a profile save for a blank + # profile before reading it back when creating a new profile. If there + # is no profile on read-back, it errors out, and it also uses the name + # and area ID as the takeover/succession data if the user had previous + # data on an old game. However, if for some reason the user cancels out + # of the name entry, loses power or disconnects from the network at the + # right time, then the profile exists in a broken state forever until they + # edit it on the front-end. As a work-around to this, we remember the last + # time each profile was written to, and we look up profiles that are older + # than a few minutes (the maximum possible time for DDR Ace to write back + # a new profile after creating a blank one) and have blank names and delete + # them in order to keep the profiles on the network in sane order. This + # should normally never delete any profiles. + profiles = data.local.user.get_all_profiles(cls.game, cls.version) + several_minutes_ago = Time.now() - (Time.SECONDS_IN_MINUTE * 5) + events = [] + + for userid, profile in profiles: + if profile.get_str('name') == "" and profile.get_int('write_time') < several_minutes_ago: + data.local.user.delete_profile(cls.game, cls.version, userid) + events.append(( + 'ddr_profile_purge', + { + 'userid': userid, + }, + )) + + return events + @property def supports_paseli(self) -> bool: if self.model.dest != 'J': @@ -681,6 +712,7 @@ class DDRAce( } profile.replace_dict('usergamedata', usergamedata) + profile.replace_int('write_time', Time.now()) self.put_profile(userid, profile) playerdata.add_child(Node.s32('result', 0)) diff --git a/bemani/data/mysql/user.py b/bemani/data/mysql/user.py index 63b4357..5500623 100644 --- a/bemani/data/mysql/user.py +++ b/bemani/data/mysql/user.py @@ -724,6 +724,21 @@ class UserData(BaseData): if profile.extid == 0: profile.extid = self.get_extid(game, version, userid) + def delete_profile(self, game: GameConstants, version: int, userid: UserID) -> None: + """ + Given a game/version/userid, delete any associated profile. + + Parameters: + game - Enum value identifier of the game looking up the user. + version - Integer version of the game looking up the user. + userid - Integer user ID, as looked up by one of the above functions. + """ + refid = self.get_refid(game, version, userid) + + # Delete profile JSON to unlink the profile for this game/version. + sql = "DELETE FROM profile WHERE refid = :refid LIMIT 1" + self.execute(sql, {'refid': refid}) + def get_achievement(self, game: GameConstants, version: int, userid: UserID, achievementid: int, achievementtype: str) -> Optional[ValidatedDict]: """ Given a game/version/userid and achievement id/type, find that achievement. diff --git a/bemani/frontend/static/components/events.react.js b/bemani/frontend/static/components/events.react.js index 8b72824..dbc4557 100644 --- a/bemani/frontend/static/components/events.react.js +++ b/bemani/frontend/static/components/events.react.js @@ -341,3 +341,40 @@ var PopnMusicCourseEvent = React.createClass({ ); }, }); + +var DDRProfilePurge = React.createClass({ + render: function() { + var event = this.props.event; + var username = null; + var user = null; + if (this.props.users) { + if (this.props.users[event.data.userid]) { + username = this.props.users[event.data.userid]; + } + if (username == null) { + user = anonymous account; + } else { + user = {username}; + } + } + + return ( + + + +
+ DDR Ace Profile Purge + + + { user ? +
+
User:
+
{user}
+
: null + } +
Orphaned DDR Ace account was purged from the network.
+ + + ); + }, +}); diff --git a/bemani/frontend/static/controllers/admin/events.react.js b/bemani/frontend/static/controllers/admin/events.react.js index 3e8cab8..15127f1 100644 --- a/bemani/frontend/static/controllers/admin/events.react.js +++ b/bemani/frontend/static/controllers/admin/events.react.js @@ -10,6 +10,7 @@ var possible_events = [ 'pcbevent', 'paseli_transaction', 'pnm_course', + 'ddr_profile_purge', ]; var event_names = { @@ -22,6 +23,7 @@ var event_names = { 'pnm_course': 'Pop\'n Music Course', 'pcbevent': 'PCB Events', 'paseli_transaction': 'PASELI Transactions', + 'ddr_profile_purge': 'DDR Ace Profile Purge', }; var mergehandler = new MergeManager(function(evt) { return evt.id; }, MergeManager.MERGE_POLICY_DROP); @@ -176,6 +178,8 @@ var audit_events = React.createClass({ return ; } else if(event.type == 'pnm_course') { return ; + } else if(event.type == 'ddr_profile_purge') { + return ; } else { return ; } diff --git a/bemani/frontend/static/themes/dark/table.css b/bemani/frontend/static/themes/dark/table.css index c49d445..a3971bb 100644 --- a/bemani/frontend/static/themes/dark/table.css +++ b/bemani/frontend/static/themes/dark/table.css @@ -133,3 +133,7 @@ table.events td.pcbevent div.circle { table.events td.transaction div.circle { background-color: #f9ed00; } + +table.events td.profilepurge div.circle { + background-color: rgb(214, 72, 72); +} diff --git a/bemani/frontend/static/themes/default/table.css b/bemani/frontend/static/themes/default/table.css index 52fdb2f..5fcc416 100644 --- a/bemani/frontend/static/themes/default/table.css +++ b/bemani/frontend/static/themes/default/table.css @@ -133,3 +133,7 @@ table.events td.pcbevent div.circle { table.events td.transaction div.circle { background-color: #f9ed00; } + +table.events td.profilepurge div.circle { + background-color: rgb(214, 72, 72); +}