|
| 1 | +use std::collections::HashMap; |
| 2 | + |
1 | 3 | use super::DataSource; |
2 | 4 | use crate::config::BemaniutilsConfig; |
3 | | -use crate::model::{FullRecord, LevelStat}; |
4 | | -use crate::Result; |
| 5 | +use crate::model::{FullRecord, LevelStat, self}; |
| 6 | +use crate::{Result, errors}; |
| 7 | +use mysql::prelude::*; |
| 8 | +use mysql::*; |
| 9 | +use serde::Deserialize; |
| 10 | +use rust_fuzzy_search::fuzzy_compare; |
5 | 11 |
|
6 | | -pub struct BemaniutilsDataSource {} |
| 12 | +pub struct BemaniutilsDataSource { |
| 13 | + records: Vec<FullRecord>, |
| 14 | +} |
7 | 15 |
|
8 | 16 | impl DataSource for BemaniutilsDataSource { |
9 | 17 | /// Get records of music_ids |
10 | 18 | fn get_record_by_id(&self, music_id: Vec<u16>) -> Vec<FullRecord> { |
11 | | - vec![] |
| 19 | + self.records.iter().filter(|r| {music_id.contains(&r.music_id)}).cloned().collect() |
12 | 20 | } |
13 | 21 | /// Get records by name. The implementation is probably fuzzy search. |
14 | 22 | fn get_record_by_name(&self, name: String) -> Vec<FullRecord> { |
15 | | - vec![] |
| 23 | + self.records |
| 24 | + .iter() |
| 25 | + .filter(|r| {fuzzy_compare(&name.to_lowercase(), &r.music_name) > 0.5}) |
| 26 | + .cloned() |
| 27 | + .collect() |
16 | 28 | } |
17 | 29 | /// Get best 50 records of current user. |
18 | 30 | fn get_best50_records(&self) -> Vec<FullRecord> { |
19 | | - vec![] |
| 31 | + self.records.iter().take(50).cloned().collect() |
20 | 32 | } |
21 | 33 | /// Show how many CLEARs and GRADEs dose the user have at each type at the level. |
22 | 34 | /// If `level` is `None`, return all level stats. |
23 | 35 | fn get_level_stat(&self, level: Option<u8>) -> Vec<LevelStat> { |
24 | | - vec![] |
25 | | - } |
26 | | - /// Show how many musics dose the user have played at the level. |
27 | | - fn get_level_count(&self, level: u8) -> usize { |
28 | | - 0 |
| 36 | + let mut level_stat: HashMap<u8, LevelStat> = HashMap::new(); |
| 37 | + for r in self |
| 38 | + .records |
| 39 | + .iter() |
| 40 | + .filter(|r| match level { |
| 41 | + Some(l) => r.get_level() == l, |
| 42 | + None => true, |
| 43 | + }) |
| 44 | + { |
| 45 | + let mut stat = LevelStat::new(r.get_level(), 0, 0, 0, 0, 0, 0, 0, 1); |
| 46 | + match r.get_clear_type() { |
| 47 | + model::ClearType::Complete => stat.incr_nc_num(1), |
| 48 | + model::ClearType::HardComplete => stat.incr_hc_num(1), |
| 49 | + model::ClearType::UltimateChain => stat.incr_uc_num(1), |
| 50 | + model::ClearType::PerfectUltimateChain => stat.incr_puc_num(1), |
| 51 | + _ => {} |
| 52 | + } |
| 53 | + match r.get_grade() { |
| 54 | + model::Grade::AAA => stat.incr_ta_num(1), |
| 55 | + model::Grade::AAAPlus => stat.incr_tap_num(1), |
| 56 | + model::Grade::S => stat.incr_s_num(1), |
| 57 | + _ => {} |
| 58 | + } |
| 59 | + if let Some(old_stat) = level_stat.get_mut(&r.get_level()) { |
| 60 | + *old_stat = *old_stat + stat; |
| 61 | + } else { |
| 62 | + level_stat.insert(r.get_level(), stat); |
| 63 | + } |
| 64 | + } |
| 65 | + let mut r = level_stat |
| 66 | + .iter() |
| 67 | + .map(|(_, &s)| s) |
| 68 | + .collect::<Vec<LevelStat>>(); |
| 69 | + r.sort_by_key(|&s| s.get_level()); |
| 70 | + r |
29 | 71 | } |
30 | 72 | } |
31 | 73 |
|
32 | 74 | impl BemaniutilsDataSource { |
33 | 75 | pub fn open(conf: BemaniutilsConfig) -> Result<Self> { |
34 | | - println!("{:?}", conf); |
| 76 | + // read all need data when open |
| 77 | + let url = format!( |
| 78 | + "mysql://{}:{}@{}:{}/{}", |
| 79 | + conf.db_user, conf.db_password, conf.db_address, conf.db_port, conf.db_name |
| 80 | + ); |
| 81 | + let pool = Pool::new(mysql::Opts::from_url(url.as_str()).unwrap())?; |
| 82 | + let mut conn = pool.get_conn()?; |
| 83 | + // get user id by username first |
| 84 | + let user_id: u16 = |
| 85 | + if let Some(id) = conn.exec_first("SELECT id FROM user WHERE username = ?", (conf.username,))? { |
| 86 | + id |
| 87 | + } else { |
| 88 | + return Err(errors::Error::OtherError("bemanitutils: username not found".to_string())); |
| 89 | + }; |
| 90 | + // get records by user id |
| 91 | + #[derive(Debug, Deserialize)] |
| 92 | + struct Records { |
| 93 | + songid: u16, |
| 94 | + name: String, |
| 95 | + chart: u8, |
| 96 | + points: u32, |
| 97 | + sdata: String, |
| 98 | + mdata: String, |
| 99 | + } |
| 100 | + |
| 101 | + let sql = |
| 102 | + "SELECT music.songid AS songid, music.name AS name, music.chart AS chart, score.points AS points, score.data AS sdata, music.data AS mdata \ |
| 103 | + FROM score, music \ |
| 104 | + WHERE score.userid = ? AND score.musicid = music.id AND music.game = 'sdvx' AND music.version = ?"; |
| 105 | + let result: Vec<Records> = conn.exec_map( |
| 106 | + sql, |
| 107 | + (user_id, conf.game_version,), |
| 108 | + |(songid, name, chart, points, sdata, mdata)| { |
| 109 | + Records { songid, name, chart, points, sdata, mdata } |
| 110 | + } |
| 111 | + )?; |
| 112 | + |
| 113 | + let mut full_records = result.into_iter().map(|r| { |
| 114 | + #[derive(Debug, Deserialize)] |
| 115 | + struct Mdata { difficulty: u8 } |
| 116 | + #[derive(Debug, Deserialize)] |
| 117 | + struct SData { grade: u16, clear_type: u16 } |
| 118 | + let mdata: Mdata = serde_json::from_str(r.mdata.as_str()).unwrap(); |
| 119 | + let sdata: SData = serde_json::from_str(r.sdata.as_str()).unwrap(); |
| 120 | + let grade = model::Grade::from(sdata.grade); |
| 121 | + let clear_type = model::ClearType::from(sdata.clear_type); |
| 122 | + |
| 123 | + FullRecord { |
| 124 | + music_id: r.songid, |
| 125 | + music_name: r.name, |
| 126 | + difficulty: model::Difficulty::from(r.chart), |
| 127 | + level: mdata.difficulty, |
| 128 | + score: r.points, |
| 129 | + grade: grade, |
| 130 | + clear_type:clear_type, |
| 131 | + volfoce: model::compute_volforce(mdata.difficulty, r.points, grade, clear_type), |
| 132 | + } |
| 133 | + }).collect::<Vec<FullRecord>>(); |
| 134 | + |
| 135 | + full_records.sort_by_key(|rec| rec.get_volforce()); |
| 136 | + |
| 137 | + println!("{} records loaded.", full_records.len()); |
35 | 138 | println!("data loaded from Bemaniutils server database succeeded!"); |
36 | | - Ok(Self {}) |
| 139 | + Ok(Self {records: full_records.into_iter().rev().collect()}) |
37 | 140 | } |
38 | 141 | } |
0 commit comments