Work on live score reload with user queue update
This commit is contained in:
parent
8d208feb24
commit
ee619161d2
@ -25,8 +25,6 @@ class UserDetailsController(
|
||||
private val userQueueService: UpdateUserQueueService
|
||||
) {
|
||||
|
||||
|
||||
|
||||
data class UserDetailsResponse(
|
||||
val user_details: UserDetails,
|
||||
val queue_details: UserQueueDetails,
|
||||
|
||||
@ -218,10 +218,10 @@ class ImportScores(
|
||||
// Update the frontend
|
||||
messagingTemplate.convertAndSend(
|
||||
"/topic/live-user/${userId}",
|
||||
currentQueueDetails
|
||||
UpdateUserQueueService.UserQueueWebsocketPacket(message = "UPDATE_PROGRESS", data = currentQueueDetails)
|
||||
)
|
||||
|
||||
this.insertAndProcessNewScore(topScore.beatmap.id, topScore)
|
||||
this.insertAndProcessNewScore(topScore.beatmap.id, topScore, isUserQueue = true)
|
||||
}
|
||||
current += 1
|
||||
}
|
||||
@ -518,7 +518,7 @@ class ImportScores(
|
||||
dslContext.batch(queries).execute()
|
||||
}
|
||||
|
||||
private fun insertAndProcessNewScore(beatmapId: Int, score: OsuApiModels.Score) {
|
||||
private fun insertAndProcessNewScore(beatmapId: Int, score: OsuApiModels.Score, isUserQueue: Boolean = false) {
|
||||
// Check if the score is already in the database
|
||||
val scoreExists = dslContext.fetchExists(SCORES, SCORES.REPLAY_ID.eq(score.best_id))
|
||||
if (scoreExists) {
|
||||
@ -627,10 +627,19 @@ class ImportScores(
|
||||
return
|
||||
}
|
||||
|
||||
if(processedReplay.ur != null && processedReplay.ur < 25.0) {
|
||||
this.logger.info("Inserting user into queue for update: ${score.user_id}")
|
||||
this.logger.info("UR: ${processedReplay.ur} on their replay with id = ${score.best_id}")
|
||||
this.updateUserQueueService.insertUser(score.user_id)
|
||||
if(processedReplay.adjusted_ur != null && processedReplay.adjusted_ur < 25.0) {
|
||||
if(isUserQueue) {
|
||||
messagingTemplate.convertAndSend(
|
||||
"/topic/live-user/${score.user_id}",
|
||||
UpdateUserQueueService.UserQueueWebsocketPacket(message = "UPDATE_SCORES")
|
||||
)
|
||||
}
|
||||
|
||||
if(!isUserQueue) {
|
||||
this.logger.info("Inserting user into queue for update: ${score.user_id}")
|
||||
this.logger.info("UR: ${processedReplay.ur} on their replay with id = ${score.best_id}")
|
||||
this.updateUserQueueService.insertUser(score.user_id)
|
||||
}
|
||||
}
|
||||
|
||||
for (judgement in processedReplay.judgements) {
|
||||
|
||||
@ -19,6 +19,11 @@ class UpdateUserQueueService(
|
||||
|
||||
private val USER_UPDATE_INTERVAL_HOURS = 4
|
||||
|
||||
data class UserQueueWebsocketPacket(
|
||||
val message: String,
|
||||
val data: UserQueueDetails? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieves the user queue details for the given user ID.
|
||||
*
|
||||
@ -38,28 +43,10 @@ class UpdateUserQueueService(
|
||||
.limit(1)
|
||||
.fetchOneInto(OffsetDateTime::class.java)
|
||||
|
||||
val lastCompletedUpdateUser = dslContext.select(USERS.SYS_LAST_UPDATE)
|
||||
.from(USERS)
|
||||
.where(USERS.USER_ID.eq(userId))
|
||||
.fetchOneInto(OffsetDateTime::class.java)
|
||||
|
||||
// Select the most recent
|
||||
val lastCompletedUpdate = lastCompletedUpdateQueue?.let {
|
||||
if (lastCompletedUpdateUser != null) {
|
||||
if (lastCompletedUpdateUser.isAfter(lastCompletedUpdateQueue)) {
|
||||
lastCompletedUpdateUser
|
||||
} else {
|
||||
lastCompletedUpdateQueue
|
||||
}
|
||||
} else {
|
||||
lastCompletedUpdateQueue
|
||||
}
|
||||
} ?: lastCompletedUpdateUser
|
||||
|
||||
var canUpdate = !isProcessing
|
||||
if(lastCompletedUpdate != null) {
|
||||
if(lastCompletedUpdateQueue != null) {
|
||||
val now = OffsetDateTime.now(ZoneOffset.UTC)
|
||||
val hoursSinceLastUpdate = now.hour - lastCompletedUpdate.hour
|
||||
val hoursSinceLastUpdate = now.hour - lastCompletedUpdateQueue.hour
|
||||
|
||||
if(hoursSinceLastUpdate < USER_UPDATE_INTERVAL_HOURS)
|
||||
canUpdate = false
|
||||
@ -78,7 +65,7 @@ class UpdateUserQueueService(
|
||||
|
||||
return UserQueueDetails(
|
||||
isProcessing,
|
||||
lastCompletedUpdate,
|
||||
lastCompletedUpdateQueue,
|
||||
canUpdate,
|
||||
currentProgress?.progressCurrent,
|
||||
currentProgress?.progressTotal
|
||||
@ -123,7 +110,7 @@ class UpdateUserQueueService(
|
||||
// Notify the user that their queue has been processed with fresh info
|
||||
messagingTemplate.convertAndSend(
|
||||
"/topic/live-user/${userId}",
|
||||
this.getUserQueueDetails(userId)
|
||||
UpdateUserQueueService.UserQueueWebsocketPacket(message = "UPDATE_PROGRESS", data = this.getUserQueueDetails(userId))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -26,5 +26,5 @@
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<div class="text-center version">
|
||||
v20240218
|
||||
v20240222
|
||||
</div>
|
||||
|
||||
@ -47,14 +47,14 @@
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!this.userInfo.queue_details.canUpdate">
|
||||
<span class="btn-info">can't force update now</span>
|
||||
<span class="btn-warning">wait a bit to force update</span>
|
||||
</ng-container>
|
||||
<span style="margin-left: 4px">|</span>
|
||||
last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.calculateTimeAgo(this.userInfo.queue_details.lastCompletedUpdate) : 'never'}}
|
||||
</ng-container>
|
||||
<ng-template #updateProgress>
|
||||
<div class="progress">
|
||||
<span class="btn-warning">updating now!</span> <span style="margin-left: 4px">|</span> progress: {{ this.userInfo.queue_details.progressCurrent ? this.userInfo.queue_details.progressCurrent : "?" }}/{{ this.userInfo.queue_details.progressTotal ? this.userInfo.queue_details.progressTotal : "?" }}
|
||||
<span class="btn-info">updating now!</span> <span style="margin-left: 4px">|</span> progress: {{ this.userInfo.queue_details.progressCurrent != null ? this.userInfo.queue_details.progressCurrent : "?" }}/{{ this.userInfo.queue_details.progressTotal != null ? this.userInfo.queue_details.progressTotal : "?" }}
|
||||
<ng-container *ngIf="!this.userInfo.queue_details.progressTotal && !this.userInfo.queue_details.progressCurrent; else loading">
|
||||
<span style="font-weight: bold">(in queue)</span>
|
||||
</ng-container>
|
||||
@ -64,29 +64,74 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
|
||||
<div class="table">
|
||||
<ng-container *ngIf="this.userInfo.suspicious_scores.length > 0">
|
||||
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
|
||||
<div class="table">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Beatmap</th>
|
||||
<th>Date</th>
|
||||
<th>
|
||||
cvUR
|
||||
</th>
|
||||
<th>
|
||||
PP
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let score of this.userInfo.suspicious_scores">
|
||||
<td>
|
||||
<div class="image-container">
|
||||
<a href="https://osu.ppy.sh/beatmaps/{{ score.beatmap_id }}?mode=osu" target="_blank">
|
||||
<img ngSrc="https://assets.ppy.sh/beatmaps/{{ score.beatmap_beatmapset_id }}/covers/cover.jpg"
|
||||
alt="Beatmap Cover" loading="lazy" width="260" height="72">
|
||||
<div class="overlay">
|
||||
{{ score.beatmap_title }}
|
||||
{{ score.beatmap_star_rating | number: '1.0-1' }}★
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ score.date }}</td>
|
||||
<td class="text-center">{{ score.ur | number: '1.2-2' }}</td>
|
||||
<td class="text-center">{{ score.pp | number: '1.0-1' }}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/s/' + score.replay_id]" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
Details
|
||||
</a>
|
||||
<a [href]="'https://osu.ppy.sh/scores/osu/' + score.replay_id" class="btn btn-outline-secondary btn-sm" style="margin-left: 5px"
|
||||
target="_blank">
|
||||
osu!web
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="this.userInfo.similar_replays.length > 0">
|
||||
<h4 class="mt-2">Similar Replays ({{ this.userInfo.similar_replays.length }})</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Beatmap</th>
|
||||
<th>Date</th>
|
||||
<th>
|
||||
cvUR
|
||||
</th>
|
||||
<th>
|
||||
PP
|
||||
</th>
|
||||
<th>Links</th>
|
||||
<th style="text-align: start">Replay 1 Details</th>
|
||||
<th style="text-align: start">Replay 2 Details</th>
|
||||
<th>Similarity</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let score of this.userInfo.suspicious_scores">
|
||||
<tr *ngFor="let score of this.userInfo.similar_replays">
|
||||
<td>
|
||||
<div class="image-container">
|
||||
<a href="https://osu.ppy.sh/beatmaps/{{ score.beatmap_id }}?mode=osu" target="_blank">
|
||||
<img ngSrc="https://assets.ppy.sh/beatmaps/{{ score.beatmap_beatmapset_id }}/covers/cover.jpg"
|
||||
alt="Beatmap Cover" loading="lazy" width="260" height="72">
|
||||
<img ngSrc="https://assets.ppy.sh/beatmaps/{{ score.beatmap_beatmapset_id }}/covers/cover.jpg" width="260" height="72"
|
||||
alt="Beatmap Cover" loading="lazy">
|
||||
<div class="overlay">
|
||||
{{ score.beatmap_title }}
|
||||
{{ score.beatmap_star_rating | number: '1.0-1' }}★
|
||||
@ -94,76 +139,34 @@
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ score.date }}</td>
|
||||
<td class="text-center">{{ score.ur | number: '1.2-2' }}</td>
|
||||
<td class="text-center">{{ score.pp | number: '1.0-1' }}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/s/' + score.replay_id]" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
Details
|
||||
<td class="replay-n-details">
|
||||
<a href="https://osu.ppy.sh/scores/osu/{{ score.replay_id_1 }}" target="_blank">
|
||||
{{ score.replay_date_1 }}
|
||||
<br>
|
||||
User: {{ score.username_1 }}
|
||||
<br>
|
||||
PP: {{ score.replay_pp_1 | number: '1.0-0' }}
|
||||
</a>
|
||||
<a [href]="'https://osu.ppy.sh/scores/osu/' + score.replay_id" class="btn btn-outline-secondary btn-sm"
|
||||
target="_blank">
|
||||
osu!web
|
||||
</td>
|
||||
<td class="replay-n-details">
|
||||
<a href="https://osu.ppy.sh/scores/osu/{{ score.replay_id_2 }}" target="_blank">
|
||||
{{ score.replay_date_2 }}
|
||||
<br>
|
||||
User: {{ score.username_2 }}
|
||||
<br>
|
||||
PP: {{ score.replay_pp_2 | number: '1.0-0' }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ score.similarity | number: '1.0-2' }}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/p/' + score.replay_id_1 + '/' + score.replay_id_2]" class="btn mr-1">
|
||||
Details
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-2">Similar Replays ({{ this.userInfo.similar_replays.length }})</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Beatmap</th>
|
||||
<th style="text-align: start">Replay 1 Details</th>
|
||||
<th style="text-align: start">Replay 2 Details</th>
|
||||
<th>Similarity</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let score of this.userInfo.similar_replays">
|
||||
<td>
|
||||
<div class="image-container">
|
||||
<a href="https://osu.ppy.sh/beatmaps/{{ score.beatmap_id }}?mode=osu" target="_blank">
|
||||
<img ngSrc="https://assets.ppy.sh/beatmaps/{{ score.beatmap_beatmapset_id }}/covers/cover.jpg" width="260" height="72"
|
||||
alt="Beatmap Cover" loading="lazy">
|
||||
<div class="overlay">
|
||||
{{ score.beatmap_title }}
|
||||
{{ score.beatmap_star_rating | number: '1.0-1' }}★
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="replay-n-details">
|
||||
<a href="https://osu.ppy.sh/scores/osu/{{ score.replay_id_1 }}" target="_blank">
|
||||
{{ score.replay_date_1 }}
|
||||
<br>
|
||||
User: {{ score.username_1 }}
|
||||
<br>
|
||||
PP: {{ score.replay_pp_1 | number: '1.0-0' }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="replay-n-details">
|
||||
<a href="https://osu.ppy.sh/scores/osu/{{ score.replay_id_2 }}" target="_blank">
|
||||
{{ score.replay_date_2 }}
|
||||
<br>
|
||||
User: {{ score.username_2 }}
|
||||
<br>
|
||||
PP: {{ score.replay_pp_2 | number: '1.0-0' }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ score.similarity | number: '1.0-2' }}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/p/' + score.replay_id_1 + '/' + score.replay_id_2]" class="btn mr-1">
|
||||
Details
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@ -21,6 +21,11 @@ interface UserInfo {
|
||||
total_scores: number;
|
||||
}
|
||||
|
||||
interface UserQueueWebsocketPacket {
|
||||
message: string;
|
||||
data?: UserQueueDetails;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-view-user',
|
||||
standalone: true,
|
||||
@ -38,8 +43,6 @@ interface UserInfo {
|
||||
})
|
||||
export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
userUpdateIntervalHours = 4
|
||||
|
||||
isLoading = false;
|
||||
notFound = false;
|
||||
userId: string | null = null;
|
||||
@ -70,29 +73,35 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.activatedRoute.params.subscribe(params => {
|
||||
this.userId = params['userId'];
|
||||
if (this.userId) {
|
||||
this.getUserInfo().pipe(
|
||||
catchError(error => {
|
||||
this.userInfo = null;
|
||||
if(error.status == 404) {
|
||||
this.notFound = true;
|
||||
}
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
).subscribe(
|
||||
(response: UserInfo) => {
|
||||
this.notFound = false;
|
||||
this.userInfo = response;
|
||||
this.title.setTitle(`${this.userInfo.user_details.username}`);
|
||||
this.subscribeToUser();
|
||||
}
|
||||
);
|
||||
this.loadUser();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadUser(isScoreUpdate = false) {
|
||||
this.getUserInfo().pipe(
|
||||
catchError(error => {
|
||||
this.userInfo = null;
|
||||
if (error.status == 404) {
|
||||
this.notFound = true;
|
||||
}
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
).subscribe(
|
||||
(response: UserInfo) => {
|
||||
this.notFound = false;
|
||||
this.userInfo = response;
|
||||
if(!isScoreUpdate) {
|
||||
this.title.setTitle(`${this.userInfo.user_details.username}`);
|
||||
this.subscribeToUser();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.liveUserSub?.unsubscribe();
|
||||
}
|
||||
@ -136,7 +145,20 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.liveUserSub = this.rxStompService
|
||||
.watch(`/topic/live-user/${this.userInfo?.user_details.user_id}`)
|
||||
.subscribe((message: Message) => {
|
||||
this.userInfo!.queue_details = JSON.parse(message.body);
|
||||
let queueDetails: UserQueueWebsocketPacket = JSON.parse(message.body);
|
||||
if(queueDetails.message == "UPDATE_SCORES") {
|
||||
this.loadUser(true);
|
||||
} else {
|
||||
if(queueDetails.data != null) {
|
||||
if(queueDetails.data.progressCurrent != null && queueDetails.data.progressTotal != null) {
|
||||
if (queueDetails.data.progressCurrent >= queueDetails.data.progressTotal) {
|
||||
this.loadUser(true);
|
||||
this.liveUserSub?.unsubscribe();
|
||||
}
|
||||
}
|
||||
this.userInfo!.queue_details = queueDetails.data;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user