vapour_protocol/
library.rs1use 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}