Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ttl cache instead of lru #220

Merged
merged 2 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 17 additions & 31 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion spotify_player/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ librespot-connect = { version = "0.4.2", optional = true }
librespot-playback = { version = "0.4.2", optional = true }
librespot-core = "0.4.2"
log = "0.4.18"
lru = "0.10.0"
chrono = "0.4.26"
reqwest = { version = "0.11.18", features = ["json"] }
rpassword = "7.2.0"
Expand Down Expand Up @@ -47,6 +46,7 @@ serde_json = "1.0.96"
once_cell = "1.17.2"
regex = "1.8.3"
daemonize = { version = "0.5.0", optional = true }
ttl_cache = "0.5.1"

[features]
alsa-backend = ["streaming", "librespot-playback/alsa-backend"]
Expand Down
2 changes: 1 addition & 1 deletion spotify_player/src/client/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub async fn start_player_event_watchers(

// request new context's data if not found in memory
if let Some(id) = id {
if !state.data.read().caches.context.contains(&id.uri()) {
if !state.data.read().caches.context.contains_key(&id.uri()) {
client_pub
.send(ClientRequest::GetContext(id.clone()))
.unwrap_or_default();
Expand Down
56 changes: 40 additions & 16 deletions spotify_player/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,17 @@ impl Client {
let client = lyric_finder::Client::from_http_client(&self.http);
let query = format!("{track} {artists}");

if !state.data.read().caches.lyrics.contains(&query) {
if !state.data.read().caches.lyrics.contains_key(&query) {
let result = client.get_lyric(&query).await.context(format!(
"failed to get lyric for track {track} - artists {artists}"
))?;

state.data.write().caches.lyrics.put(query, result);
state
.data
.write()
.caches
.lyrics
.insert(query, result, *CACHE_DURATION);
}
}
ClientRequest::ConnectDevice(id) => {
Expand Down Expand Up @@ -255,44 +260,47 @@ impl Client {
}
ClientRequest::GetUserTopTracks => {
let uri = &USER_TOP_TRACKS_ID.uri;
if !state.data.read().caches.context.contains(uri) {
if !state.data.read().caches.context.contains_key(uri) {
let tracks = self.current_user_top_tracks().await?;
state.data.write().caches.context.put(
state.data.write().caches.context.insert(
uri.to_owned(),
Context::Tracks {
tracks,
desc: "User's top tracks".to_string(),
},
*CACHE_DURATION,
);
}
}
ClientRequest::GetUserSavedTracks => {
let tracks = self.current_user_saved_tracks().await?;
state.data.write().caches.context.put(
state.data.write().caches.context.insert(
USER_LIKED_TRACKS_ID.uri.to_owned(),
Context::Tracks {
tracks: tracks.clone(),
desc: "User's liked tracks".to_string(),
},
*CACHE_DURATION,
);
state.data.write().user_data.saved_tracks = tracks;
}
ClientRequest::GetUserRecentlyPlayedTracks => {
let uri = &USER_RECENTLY_PLAYED_TRACKS_ID.uri;
if !state.data.read().caches.context.contains(uri) {
if !state.data.read().caches.context.contains_key(uri) {
let tracks = self.current_user_recently_played_tracks().await?;
state.data.write().caches.context.put(
state.data.write().caches.context.insert(
uri.to_owned(),
Context::Tracks {
tracks,
desc: "User's recently played tracks".to_string(),
},
*CACHE_DURATION,
);
}
}
ClientRequest::GetContext(context) => {
let uri = context.uri();
if !state.data.read().caches.context.contains(&uri) {
if !state.data.read().caches.context.contains_key(&uri) {
let context = match context {
ContextId::Playlist(playlist_id) => {
self.playlist_context(playlist_id).await?
Expand All @@ -306,30 +314,41 @@ impl Client {
}
};

state.data.write().caches.context.put(uri, context);
state
.data
.write()
.caches
.context
.insert(uri, context, *CACHE_DURATION);
}
}
ClientRequest::Search(query) => {
if !state.data.read().caches.search.contains(&query) {
if !state.data.read().caches.search.contains_key(&query) {
let results = self.search(&query).await?;

state.data.write().caches.search.put(query, results);
state
.data
.write()
.caches
.search
.insert(query, results, *CACHE_DURATION);
}
}
ClientRequest::GetRadioTracks {
seed_uri: uri,
seed_name: name,
} => {
let radio_uri = format!("radio:{uri}");
if !state.data.read().caches.context.contains(&radio_uri) {
if !state.data.read().caches.context.contains_key(&radio_uri) {
let tracks = self.radio_tracks(uri).await?;

state.data.write().caches.context.put(
state.data.write().caches.context.insert(
radio_uri,
Context::Tracks {
tracks,
desc: format!("{name} Radio"),
},
*CACHE_DURATION,
);
}
}
Expand Down Expand Up @@ -812,7 +831,7 @@ impl Client {
.await?;

// After adding a new track to a playlist, remove the cache of that playlist to force refetching new data
state.data.write().caches.context.pop(&playlist_id.uri());
state.data.write().caches.context.remove(&playlist_id.uri());

Ok(())
}
Expand Down Expand Up @@ -1185,13 +1204,18 @@ impl Client {
}

#[cfg(feature = "image")]
if !state.data.read().caches.images.contains(url) {
if !state.data.read().caches.images.contains_key(url) {
let bytes = self.retrieve_image(url, &path, false).await?;
// Get the image from a url
let image =
image::load_from_memory(&bytes).context("Failed to load image from memory")?;

state.data.write().caches.images.put(url.to_owned(), image);
state
.data
.write()
.caches
.images
.insert(url.to_owned(), image, *CACHE_DURATION);
}

// notify user about the playback's change if any
Expand Down
2 changes: 1 addition & 1 deletion spotify_player/src/event/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub fn handle_key_sequence_for_search_page(
};

let data = state.data.read();
let search_results = data.caches.search.peek(current_query);
let search_results = data.caches.search.get(current_query);

match focus_state {
SearchFocusState::Input => anyhow::bail!("user's search input should be handled before"),
Expand Down
2 changes: 1 addition & 1 deletion spotify_player/src/event/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn handle_command_for_focused_context_window(

let data = state.data.read();

match data.caches.context.peek(&context_id.uri()) {
match data.caches.context.get(&context_id.uri()) {
Some(context) => match context {
Context::Artist {
top_tracks,
Expand Down
29 changes: 17 additions & 12 deletions spotify_player/src/state/data.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use std::{collections::HashMap, num::NonZeroUsize};
use std::collections::HashMap;

use once_cell::sync::Lazy;

use super::model::*;

pub type DataReadGuard<'a> = parking_lot::RwLockReadGuard<'a, AppData>;

#[derive(Default, Debug)]
// cache duration, which is default to be 1h
pub static CACHE_DURATION: Lazy<std::time::Duration> =
Lazy::new(|| std::time::Duration::from_secs(60 * 60));

#[derive(Default)]
/// the application's data
pub struct AppData {
pub user_data: UserData,
Expand All @@ -22,15 +28,14 @@ pub struct UserData {
pub saved_tracks: Vec<Track>,
}

#[derive(Debug)]
/// the application's caches
pub struct Caches {
pub context: lru::LruCache<String, Context>,
pub search: lru::LruCache<String, SearchResults>,
pub context: ttl_cache::TtlCache<String, Context>,
pub search: ttl_cache::TtlCache<String, SearchResults>,
#[cfg(feature = "lyric-finder")]
pub lyrics: lru::LruCache<String, lyric_finder::LyricResult>,
pub lyrics: ttl_cache::TtlCache<String, lyric_finder::LyricResult>,
#[cfg(feature = "image")]
pub images: lru::LruCache<String, image::DynamicImage>,
pub images: ttl_cache::TtlCache<String, image::DynamicImage>,
}

#[derive(Default, Debug)]
Expand All @@ -42,19 +47,19 @@ pub struct BrowseData {
impl Default for Caches {
fn default() -> Self {
Self {
context: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
search: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
context: ttl_cache::TtlCache::new(64),
search: ttl_cache::TtlCache::new(64),
#[cfg(feature = "lyric-finder")]
lyrics: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
lyrics: ttl_cache::TtlCache::new(64),
#[cfg(feature = "image")]
images: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
images: ttl_cache::TtlCache::new(64),
}
}
}

impl AppData {
pub fn get_tracks_by_id_mut(&mut self, id: &ContextId) -> Option<&mut Vec<Track>> {
self.caches.context.peek_mut(&id.uri()).map(|c| match c {
self.caches.context.get_mut(&id.uri()).map(|c| match c {
Context::Album { tracks, .. } => tracks,
Context::Playlist { tracks, .. } => tracks,
Context::Artist {
Expand Down
1 change: 0 additions & 1 deletion spotify_player/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub use parking_lot::{Mutex, RwLock};
pub type SharedState = std::sync::Arc<State>;

/// Application's state
#[derive(Debug)]
pub struct State {
pub app_config: config::AppConfig,
pub keymap_config: config::KeymapConfig,
Expand Down
6 changes: 3 additions & 3 deletions spotify_player/src/ui/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn render_search_page(
s => anyhow::bail!("expect a search page state, found {s:?}"),
};

let search_results = data.caches.search.peek(current_query);
let search_results = data.caches.search.get(current_query);

// 2. Construct the page's layout
let rect = construct_and_render_block("Search", &ui.theme, state, Borders::ALL, frame, rect);
Expand Down Expand Up @@ -229,7 +229,7 @@ pub fn render_context_page(
};

let data = state.data.read();
match data.caches.context.peek(&id.uri()) {
match data.caches.context.get(&id.uri()) {
Some(context) => {
// render context description
let chunks = Layout::default()
Expand Down Expand Up @@ -511,7 +511,7 @@ pub fn render_lyric_page(
s => anyhow::bail!("expect a lyric page state, found {s:?}"),
};

let (desc, lyric) = match data.caches.lyrics.peek(&format!("{track} {artists}")) {
let (desc, lyric) = match data.caches.lyrics.get(&format!("{track} {artists}")) {
None => {
frame.render_widget(Paragraph::new("Loading..."), rect);
return Ok(());
Expand Down
Loading