forked from Hay1tsme/artemis
wip: use SQL's limit/offset pagination for nextIndex/maxCount requests
This commit is contained in:
@ -1,16 +1,16 @@
|
||||
import logging
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from time import strftime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import pytz
|
||||
from typing import Dict, Any, List
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.chuni.config import ChuniConfig
|
||||
from titles.chuni.const import ChuniConstants, ItemKind
|
||||
from titles.chuni.database import ChuniData
|
||||
from titles.chuni.config import ChuniConfig
|
||||
SCORE_BUFFER = {}
|
||||
|
||||
|
||||
class ChuniBase:
|
||||
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
|
||||
@ -277,35 +277,39 @@ class ChuniBase:
|
||||
}
|
||||
|
||||
async def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||
characters = await self.data.item.get_characters(data["userId"])
|
||||
user_id = int(data["userId"])
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
|
||||
# add one to the limit so we know if there's a next page of items
|
||||
characters = await self.data.item.get_characters(
|
||||
user_id, limit=max_ct + 1, offset=next_idx
|
||||
)
|
||||
|
||||
if characters is None:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"length": 0,
|
||||
"nextIndex": -1,
|
||||
"userCharacterList": [],
|
||||
}
|
||||
|
||||
character_count = len(characters)
|
||||
character_list = []
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
|
||||
for x in range(next_idx, len(characters)):
|
||||
for x in range(min(character_count, max_ct)):
|
||||
tmp = characters[x]._asdict()
|
||||
tmp.pop("user")
|
||||
tmp.pop("id")
|
||||
character_list.append(tmp)
|
||||
|
||||
if len(character_list) >= max_ct:
|
||||
break
|
||||
|
||||
if len(characters) >= next_idx + max_ct:
|
||||
if character_count > max_ct:
|
||||
next_idx += max_ct
|
||||
else:
|
||||
next_idx = -1
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"length": len(character_list),
|
||||
"nextIndex": next_idx,
|
||||
"userCharacterList": character_list,
|
||||
@ -335,29 +339,31 @@ class ChuniBase:
|
||||
}
|
||||
|
||||
async def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
||||
user_course_list = await self.data.score.get_courses(data["userId"])
|
||||
if user_course_list is None:
|
||||
user_id = int(data["userId"])
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
|
||||
rows = await self.data.score.get_courses(
|
||||
user_id, limit=max_ct + 1, offset=next_idx
|
||||
)
|
||||
|
||||
if rows is None or len(rows) == 0:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"length": 0,
|
||||
"nextIndex": -1,
|
||||
"userCourseList": [],
|
||||
}
|
||||
|
||||
course_list = []
|
||||
next_idx = int(data.get("nextIndex", 0))
|
||||
max_ct = int(data.get("maxCount", 300))
|
||||
|
||||
for x in range(next_idx, len(user_course_list)):
|
||||
tmp = user_course_list[x]._asdict()
|
||||
for x in range(min(len(rows), max_ct)):
|
||||
tmp = rows[x]._asdict()
|
||||
tmp.pop("user")
|
||||
tmp.pop("id")
|
||||
course_list.append(tmp)
|
||||
|
||||
if len(user_course_list) >= max_ct:
|
||||
break
|
||||
|
||||
if len(user_course_list) >= next_idx + max_ct:
|
||||
if len(rows) > max_ct:
|
||||
next_idx += max_ct
|
||||
else:
|
||||
next_idx = -1
|
||||
@ -425,75 +431,94 @@ class ChuniBase:
|
||||
}
|
||||
|
||||
async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
|
||||
rival_id = data["rivalId"]
|
||||
next_index = int(data["nextIndex"])
|
||||
max_count = int(data["maxCount"])
|
||||
user_rival_music_list = []
|
||||
user_id = int(data["userId"])
|
||||
rival_id = int(data["rivalId"])
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
rival_levels = [int(x["level"]) for x in data["userRivalMusicLevelList"]]
|
||||
|
||||
# Fetch all the rival music entries for the user
|
||||
all_entries = await self.data.score.get_rival_music(rival_id)
|
||||
music_detail_rows = await self.data.score.get_scores(
|
||||
rival_id,
|
||||
levels=rival_levels,
|
||||
limit=max_ct + 1,
|
||||
offset=next_idx,
|
||||
)
|
||||
|
||||
# Process the entries based on max_count and nextIndex
|
||||
for music in all_entries:
|
||||
music_id = music["musicId"]
|
||||
level = music["level"]
|
||||
score = music["scoreMax"]
|
||||
rank = music["scoreRank"]
|
||||
if music_detail_rows is None or len(music_detail_rows) == 0:
|
||||
return {
|
||||
"userId": user_id,
|
||||
"rivalId": rival_id,
|
||||
"nextIndex": -1,
|
||||
"userRivalMusicList": [],
|
||||
}
|
||||
|
||||
# Create a music entry for the current music_id if it's unique
|
||||
music_entry = next((entry for entry in user_rival_music_list if entry["musicId"] == music_id), None)
|
||||
if music_entry is None:
|
||||
music_entry = {
|
||||
"musicId": music_id,
|
||||
"length": 0,
|
||||
"userRivalMusicDetailList": []
|
||||
}
|
||||
user_rival_music_list.append(music_entry)
|
||||
music_details = [x._asdict() for x in music_detail_rows]
|
||||
returned_music_details_count = 0
|
||||
music_list = []
|
||||
|
||||
# Create a level entry for the current level if it's unique or has a higher score
|
||||
level_entry = next((entry for entry in music_entry["userRivalMusicDetailList"] if entry["level"] == level), None)
|
||||
if level_entry is None:
|
||||
level_entry = {
|
||||
"level": level,
|
||||
"scoreMax": score,
|
||||
"scoreRank": rank
|
||||
}
|
||||
music_entry["userRivalMusicDetailList"].append(level_entry)
|
||||
elif score > level_entry["scoreMax"]:
|
||||
level_entry["scoreMax"] = score
|
||||
level_entry["scoreRank"] = rank
|
||||
# note that itertools.groupby will only work on sorted keys, which is already sorted by
|
||||
# the query in get_scores
|
||||
for music_id, details_iter in itertools.groupby(music_details, key=lambda x: x["musicId"]):
|
||||
details: list[dict[Any, Any]] = [
|
||||
{"level": d["level"], "scoreMax": d["scoreMax"]}
|
||||
for d in details_iter
|
||||
]
|
||||
|
||||
# Calculate the length for each "musicId" by counting the unique levels
|
||||
for music_entry in user_rival_music_list:
|
||||
music_entry["length"] = len(music_entry["userRivalMusicDetailList"])
|
||||
music_list.append({"musicId": music_id, "length": len(details), "userMusicDetailList": details})
|
||||
returned_music_details_count += len(details)
|
||||
|
||||
# Prepare the result dictionary with user rival music data
|
||||
result = {
|
||||
"userId": data["userId"],
|
||||
"rivalId": data["rivalId"],
|
||||
"nextIndex": str(next_index + len(user_rival_music_list[next_index: next_index + max_count]) if max_count <= len(user_rival_music_list[next_index: next_index + max_count]) else -1),
|
||||
"userRivalMusicList": user_rival_music_list[next_index: next_index + max_count]
|
||||
if len(music_list) >= max_ct:
|
||||
break
|
||||
|
||||
# if we returned fewer PBs than we originally asked for from the database, that means
|
||||
# we queried for the PBs of max_ct + 1 songs.
|
||||
if returned_music_details_count < len(music_detail_rows):
|
||||
next_idx += max_ct
|
||||
else:
|
||||
next_idx = -1
|
||||
|
||||
return {
|
||||
"userId": user_id,
|
||||
"rivalId": rival_id,
|
||||
"length": len(music_list),
|
||||
"nextIndex": next_idx,
|
||||
"userRivalMusicList": music_list,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
|
||||
user_id = int(data["userId"])
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
kind = int(data["kind"])
|
||||
is_all_favorite_item = str(data["isAllFavoriteItem"]) == "true"
|
||||
|
||||
user_fav_item_list = []
|
||||
|
||||
# still needs to be implemented on WebUI
|
||||
# 1: Music, 2: User, 3: Character
|
||||
fav_list = await self.data.item.get_all_favorites(
|
||||
data["userId"], self.version, fav_kind=int(data["kind"])
|
||||
user_id,
|
||||
self.version,
|
||||
fav_kind=kind,
|
||||
limit=max_ct + 1,
|
||||
offset=next_idx,
|
||||
)
|
||||
|
||||
if fav_list is not None:
|
||||
for fav in fav_list:
|
||||
user_fav_item_list.append({"id": fav["favId"]})
|
||||
|
||||
if fav_list is None or len(fav_list) <= max_ct:
|
||||
next_idx = -1
|
||||
else:
|
||||
next_idx += max_ct
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"length": len(user_fav_item_list),
|
||||
"kind": data["kind"],
|
||||
"nextIndex": -1,
|
||||
"kind": kind,
|
||||
"nextIndex": next_idx,
|
||||
"userFavoriteItemList": user_fav_item_list,
|
||||
}
|
||||
|
||||
@ -505,36 +530,39 @@ class ChuniBase:
|
||||
return {"userId": data["userId"], "length": 0, "userFavoriteMusicList": []}
|
||||
|
||||
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||
kind = int(int(data["nextIndex"]) / 10000000000)
|
||||
next_idx = int(int(data["nextIndex"]) % 10000000000)
|
||||
user_item_list = await self.data.item.get_items(data["userId"], kind)
|
||||
user_id = int(data["userId"])
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
|
||||
if user_item_list is None or len(user_item_list) == 0:
|
||||
kind = next_idx // 10000000000
|
||||
next_idx = next_idx % 10000000000
|
||||
rows = await self.data.item.get_items(
|
||||
user_id, kind, limit=max_ct + 1, offset=next_idx
|
||||
)
|
||||
|
||||
if rows is None or len(rows) == 0:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"nextIndex": -1,
|
||||
"itemKind": kind,
|
||||
"userItemList": [],
|
||||
}
|
||||
|
||||
items: List[Dict[str, Any]] = []
|
||||
for i in range(next_idx, len(user_item_list)):
|
||||
tmp = user_item_list[i]._asdict()
|
||||
|
||||
for i in range(min(len(rows), max_ct)):
|
||||
tmp = rows[i]._asdict()
|
||||
tmp.pop("user")
|
||||
tmp.pop("id")
|
||||
items.append(tmp)
|
||||
if len(items) >= int(data["maxCount"]):
|
||||
break
|
||||
|
||||
xout = kind * 10000000000 + next_idx + len(items)
|
||||
|
||||
if len(items) < int(data["maxCount"]):
|
||||
next_idx = 0
|
||||
if len(rows) > max_ct:
|
||||
next_idx = kind * 10000000000 + next_idx + max_ct
|
||||
else:
|
||||
next_idx = xout
|
||||
next_idx = -1
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"nextIndex": next_idx,
|
||||
"itemKind": kind,
|
||||
"length": len(items),
|
||||
@ -586,62 +614,55 @@ class ChuniBase:
|
||||
}
|
||||
|
||||
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||
music_detail = await self.data.score.get_scores(data["userId"])
|
||||
if music_detail is None:
|
||||
user_id = int(data["userId"])
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
|
||||
rows = await self.data.score.get_scores(
|
||||
user_id, limit=max_ct + 1, offset=next_idx
|
||||
)
|
||||
|
||||
if rows is None or len(rows) == 0:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userId": user_id,
|
||||
"length": 0,
|
||||
"nextIndex": -1,
|
||||
"userMusicList": [], # 240
|
||||
}
|
||||
|
||||
song_list = []
|
||||
next_idx = int(data["nextIndex"])
|
||||
max_ct = int(data["maxCount"])
|
||||
music_details = [x._asdict() for x in rows]
|
||||
returned_music_details_count = 0
|
||||
music_list = []
|
||||
|
||||
for x in range(next_idx, len(music_detail)):
|
||||
found = False
|
||||
tmp = music_detail[x]._asdict()
|
||||
tmp.pop("user")
|
||||
tmp.pop("id")
|
||||
# note that itertools.groupby will only work on sorted keys, which is already sorted by
|
||||
# the query in get_scores
|
||||
for _music_id, details_iter in itertools.groupby(music_details, key=lambda x: x["musicId"]):
|
||||
details: list[dict[Any, Any]] = []
|
||||
|
||||
for song in song_list:
|
||||
score_buf = SCORE_BUFFER.get(str(data["userId"])) or []
|
||||
if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]:
|
||||
found = True
|
||||
song["userMusicDetailList"].append(tmp)
|
||||
song["length"] = len(song["userMusicDetailList"])
|
||||
score_buf.append(tmp["musicId"])
|
||||
SCORE_BUFFER[str(data["userId"])] = score_buf
|
||||
for d in details_iter:
|
||||
d.pop("id")
|
||||
d.pop("user")
|
||||
|
||||
score_buf = SCORE_BUFFER.get(str(data["userId"])) or []
|
||||
if not found and tmp["musicId"] not in score_buf:
|
||||
song_list.append({"length": 1, "userMusicDetailList": [tmp]})
|
||||
score_buf.append(tmp["musicId"])
|
||||
SCORE_BUFFER[str(data["userId"])] = score_buf
|
||||
details.append(d)
|
||||
|
||||
if len(song_list) >= max_ct:
|
||||
music_list.append({"length": len(details), "userMusicDetailList": details})
|
||||
returned_music_details_count += len(details)
|
||||
|
||||
if len(music_list) >= max_ct:
|
||||
break
|
||||
|
||||
for songIdx in range(len(song_list)):
|
||||
for recordIdx in range(x+1, len(music_detail)):
|
||||
if song_list[songIdx]["userMusicDetailList"][0]["musicId"] == music_detail[recordIdx]["musicId"]:
|
||||
music = music_detail[recordIdx]._asdict()
|
||||
music.pop("user")
|
||||
music.pop("id")
|
||||
song_list[songIdx]["userMusicDetailList"].append(music)
|
||||
song_list[songIdx]["length"] += 1
|
||||
|
||||
if len(song_list) >= max_ct:
|
||||
next_idx += len(song_list)
|
||||
|
||||
# if we returned fewer PBs than we originally asked for from the database, that means
|
||||
# we queried for the PBs of max_ct + 1 songs.
|
||||
if returned_music_details_count < len(rows):
|
||||
next_idx += max_ct
|
||||
else:
|
||||
next_idx = -1
|
||||
SCORE_BUFFER[str(data["userId"])] = []
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"length": len(song_list),
|
||||
"userId": user_id,
|
||||
"length": len(music_list),
|
||||
"nextIndex": next_idx,
|
||||
"userMusicList": song_list, # 240
|
||||
"userMusicList": music_list,
|
||||
}
|
||||
|
||||
async def handle_get_user_option_api_request(self, data: Dict) -> Dict:
|
||||
|
Reference in New Issue
Block a user