Skip to main content

vapour_protocol/
library.rs

1use std::collections::HashMap;
2
3use crate::{
4    connection::{Connection, ConnectionState},
5    error::Result,
6    friends::ProtocolGame,
7    pics::AppCatalogInfo,
8    protobuf::{CPlayerGetLastPlayedTimesRequest, CPlayerGetLastPlayedTimesResponse},
9    service_method::{ServiceMethod, call_authed},
10};
11
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
13pub struct PlaytimeInfo {
14    pub playtime_forever: i32,
15    pub rtime_last_played: u32,
16}
17
18pub async fn get_last_played_times(
19    connection: &Connection,
20    state: &ConnectionState,
21) -> Result<HashMap<u32, PlaytimeInfo>> {
22    let method = ServiceMethod::new("Player.ClientGetLastPlayedTimes#1");
23    let request = CPlayerGetLastPlayedTimesRequest {
24        min_last_played: Some(0),
25    };
26    tracing::info!("requesting last-played-times (authed)");
27    let response: CPlayerGetLastPlayedTimesResponse =
28        call_authed(connection, state, &method, &request).await?;
29    tracing::debug!(games = response.games.len(), "last-played-times response");
30
31    let playtimes = response
32        .games
33        .into_iter()
34        .filter_map(|game| {
35            let appid = game.appid?;
36            (appid > 0).then_some((
37                appid as u32,
38                PlaytimeInfo {
39                    playtime_forever: game.playtime_forever.unwrap_or(0).max(0),
40                    rtime_last_played: game.last_playtime.unwrap_or(0),
41                },
42            ))
43        })
44        .collect();
45
46    Ok(playtimes)
47}
48
49pub fn recently_played_games(playtimes: &HashMap<u32, PlaytimeInfo>) -> Vec<ProtocolGame> {
50    let mut games: Vec<ProtocolGame> = playtimes
51        .iter()
52        .filter(|(_, playtime)| playtime.rtime_last_played > 0)
53        .map(|(appid, playtime)| ProtocolGame {
54            appid: *appid,
55            name: String::new(),
56            playtime_forever: playtime.playtime_forever,
57            rtime_last_played: playtime.rtime_last_played,
58            img_icon_url: None,
59            app_type: None,
60            installdir: None,
61            launch: Vec::new(),
62        })
63        .collect();
64    games.sort_by(|a, b| {
65        b.rtime_last_played
66            .cmp(&a.rtime_last_played)
67            .then_with(|| b.playtime_forever.cmp(&a.playtime_forever))
68    });
69    games
70}
71
72pub fn merge_catalog_and_playtimes(
73    catalog: Vec<AppCatalogInfo>,
74    playtimes: &HashMap<u32, PlaytimeInfo>,
75) -> Vec<ProtocolGame> {
76    let mut games: Vec<ProtocolGame> = catalog
77        .into_iter()
78        .map(|app| {
79            let playtime = playtimes.get(&app.appid).copied().unwrap_or_default();
80            ProtocolGame {
81                appid: app.appid,
82                name: app.name,
83                playtime_forever: playtime.playtime_forever,
84                rtime_last_played: playtime.rtime_last_played,
85                img_icon_url: app.img_icon_url,
86                app_type: app.app_type,
87                installdir: app.installdir,
88                launch: app.launch,
89            }
90        })
91        .collect();
92
93    games.sort_by(|a, b| {
94        b.playtime_forever
95            .cmp(&a.playtime_forever)
96            .then_with(|| a.name.cmp(&b.name))
97            .then_with(|| a.appid.cmp(&b.appid))
98    });
99    games
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn merge_defaults_never_played_games_to_zero_playtime() {
108        let catalog = vec![
109            AppCatalogInfo {
110                appid: 20,
111                name: "Played".to_owned(),
112                img_icon_url: Some("icon".to_owned()),
113                app_type: Some("game".to_owned()),
114                installdir: None,
115                launch: Vec::new(),
116            },
117            AppCatalogInfo {
118                appid: 10,
119                name: "Never Played".to_owned(),
120                img_icon_url: None,
121                app_type: Some("game".to_owned()),
122                installdir: None,
123                launch: Vec::new(),
124            },
125        ];
126        let playtimes = HashMap::from([(
127            20,
128            PlaytimeInfo {
129                playtime_forever: 120,
130                rtime_last_played: 123,
131            },
132        )]);
133
134        let games = merge_catalog_and_playtimes(catalog, &playtimes);
135
136        assert_eq!(games.len(), 2);
137        assert_eq!(games[0].appid, 20);
138        assert_eq!(games[0].playtime_forever, 120);
139        assert_eq!(games[1].appid, 10);
140        assert_eq!(games[1].playtime_forever, 0);
141    }
142}