1
0
mirror of synced 2025-01-18 14:14:03 +01:00

Clean up possible orphan profiles for DDR Ace.

This commit is contained in:
Jennifer Taylor 2021-09-11 01:06:13 +00:00
parent 831548715c
commit eed148f956
6 changed files with 98 additions and 2 deletions

View File

@ -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))

View File

@ -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.

View File

@ -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 = <span className="placeholder">anonymous account</span>;
} else {
user = <span>{username}</span>;
}
}
return (
<tr key={event.id}>
<td><Timestamp timestamp={event.timestamp} /></td>
<td className="profilepurge">
<div className="circle" />
DDR Ace Profile Purge
</td>
<td className="details">
{ user ?
<div>
<div className="inline">User:</div>
<div className="inline"><a href={Link.get('viewuser', event.data.userid)}>{user}</a></div>
</div> : null
}
<div>Orphaned DDR Ace account was purged from the network.</div>
</td>
</tr>
);
},
});

View File

@ -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 <PASELITransactionEvent event={event} users={this.state.users} arcades={this.state.arcades} />;
} else if(event.type == 'pnm_course') {
return <PopnMusicCourseEvent event={event} versions={this.state.pnmversions} songs={this.state.pnmsongs} />;
} else if(event.type == 'ddr_profile_purge') {
return <DDRProfilePurge event={event} users={this.state.users} />;
} else {
return <UnknownEvent event={event} />;
}

View File

@ -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);
}

View File

@ -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);
}