Clean up possible orphan profiles for DDR Ace.
This commit is contained in:
parent
831548715c
commit
eed148f956
@ -1,13 +1,13 @@
|
|||||||
# vim: set fileencoding=utf-8
|
# vim: set fileencoding=utf-8
|
||||||
import base64
|
import base64
|
||||||
from typing import Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
from typing_extensions import Final
|
from typing_extensions import Final
|
||||||
|
|
||||||
from bemani.backend.ess import EventLogHandler
|
from bemani.backend.ess import EventLogHandler
|
||||||
from bemani.backend.ddr.base import DDRBase
|
from bemani.backend.ddr.base import DDRBase
|
||||||
from bemani.backend.ddr.ddr2014 import DDR2014
|
from bemani.backend.ddr.ddr2014 import DDR2014
|
||||||
from bemani.common import Profile, ValidatedDict, VersionConstants, CardCipher, Time, ID, intish
|
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
|
from bemani.protocol import Node
|
||||||
|
|
||||||
|
|
||||||
@ -105,6 +105,37 @@ class DDRAce(
|
|||||||
def previous_version(self) -> Optional[DDRBase]:
|
def previous_version(self) -> Optional[DDRBase]:
|
||||||
return DDR2014(self.data, self.config, self.model)
|
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
|
@property
|
||||||
def supports_paseli(self) -> bool:
|
def supports_paseli(self) -> bool:
|
||||||
if self.model.dest != 'J':
|
if self.model.dest != 'J':
|
||||||
@ -681,6 +712,7 @@ class DDRAce(
|
|||||||
}
|
}
|
||||||
|
|
||||||
profile.replace_dict('usergamedata', usergamedata)
|
profile.replace_dict('usergamedata', usergamedata)
|
||||||
|
profile.replace_int('write_time', Time.now())
|
||||||
self.put_profile(userid, profile)
|
self.put_profile(userid, profile)
|
||||||
|
|
||||||
playerdata.add_child(Node.s32('result', 0))
|
playerdata.add_child(Node.s32('result', 0))
|
||||||
|
@ -724,6 +724,21 @@ class UserData(BaseData):
|
|||||||
if profile.extid == 0:
|
if profile.extid == 0:
|
||||||
profile.extid = self.get_extid(game, version, userid)
|
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]:
|
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.
|
Given a game/version/userid and achievement id/type, find that achievement.
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -10,6 +10,7 @@ var possible_events = [
|
|||||||
'pcbevent',
|
'pcbevent',
|
||||||
'paseli_transaction',
|
'paseli_transaction',
|
||||||
'pnm_course',
|
'pnm_course',
|
||||||
|
'ddr_profile_purge',
|
||||||
];
|
];
|
||||||
|
|
||||||
var event_names = {
|
var event_names = {
|
||||||
@ -22,6 +23,7 @@ var event_names = {
|
|||||||
'pnm_course': 'Pop\'n Music Course',
|
'pnm_course': 'Pop\'n Music Course',
|
||||||
'pcbevent': 'PCB Events',
|
'pcbevent': 'PCB Events',
|
||||||
'paseli_transaction': 'PASELI Transactions',
|
'paseli_transaction': 'PASELI Transactions',
|
||||||
|
'ddr_profile_purge': 'DDR Ace Profile Purge',
|
||||||
};
|
};
|
||||||
|
|
||||||
var mergehandler = new MergeManager(function(evt) { return evt.id; }, MergeManager.MERGE_POLICY_DROP);
|
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} />;
|
return <PASELITransactionEvent event={event} users={this.state.users} arcades={this.state.arcades} />;
|
||||||
} else if(event.type == 'pnm_course') {
|
} else if(event.type == 'pnm_course') {
|
||||||
return <PopnMusicCourseEvent event={event} versions={this.state.pnmversions} songs={this.state.pnmsongs} />;
|
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 {
|
} else {
|
||||||
return <UnknownEvent event={event} />;
|
return <UnknownEvent event={event} />;
|
||||||
}
|
}
|
||||||
|
@ -133,3 +133,7 @@ table.events td.pcbevent div.circle {
|
|||||||
table.events td.transaction div.circle {
|
table.events td.transaction div.circle {
|
||||||
background-color: #f9ed00;
|
background-color: #f9ed00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.events td.profilepurge div.circle {
|
||||||
|
background-color: rgb(214, 72, 72);
|
||||||
|
}
|
||||||
|
@ -133,3 +133,7 @@ table.events td.pcbevent div.circle {
|
|||||||
table.events td.transaction div.circle {
|
table.events td.transaction div.circle {
|
||||||
background-color: #f9ed00;
|
background-color: #f9ed00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.events td.profilepurge div.circle {
|
||||||
|
background-color: rgb(214, 72, 72);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user