From 17482c236a64956e85a706f7a003473be37c6108 Mon Sep 17 00:00:00 2001 From: Paul Lietar Date: Sun, 29 Jan 2017 15:36:39 +0000 Subject: [PATCH] Stop caching audio keys, reenable audio file cache --- Cargo.lock | 23 --------- Cargo.toml | 1 - src/audio_file.rs | 100 ++++++++++++++++++++++++++++++------- src/cache/default_cache.rs | 2 - src/cache/mod.rs | 72 ++++++++++++++++++-------- src/lib.rs | 1 - src/main.rs | 11 ++-- src/player.rs | 2 +- src/session.rs | 16 +++--- 9 files changed, 146 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33f10bf..edeaf90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,6 @@ dependencies = [ "libpulse-sys 0.0.0 (git+https://github.com/astro/libpulse-sys)", "librespot-protocol 0.1.0", "linear-map 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lmdb-rs 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mdns 0.2.0 (git+https://github.com/plietar/rust-mdns)", "num-bigint 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -294,15 +293,6 @@ name = "libc" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "liblmdb-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "gcc 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libpulse-sys" version = "0.0.0" @@ -323,17 +313,6 @@ name = "linear-map" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "lmdb-rs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "liblmdb-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "log" version = "0.3.6" @@ -1059,10 +1038,8 @@ dependencies = [ "checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "684f330624d8c3784fb9558ca46c4ce488073a8d22450415c5eb4f4cfb0d11b5" -"checksum liblmdb-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b56d07dcf983f9b6679f768df73c72671d0087bd66329baabb63325f4f592677" "checksum libpulse-sys 0.0.0 (git+https://github.com/astro/libpulse-sys)" = "" "checksum linear-map 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f947d2a0ca958037e42a430bc7ea4369f97b60a2002bd927b84404509cc64cf" -"checksum lmdb-rs 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da5a6654b1d6ed38bed1ed96f601c719b9caacad2f5ff63afc1eb7d6c0011c2b" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum mdns 0.2.0 (git+https://github.com/plietar/rust-mdns)" = "" diff --git a/Cargo.toml b/Cargo.toml index 7151436..96b1f14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ getopts = "0.2.14" hyper = { git = "https://github.com/hyperium/hyper" } lazy_static = "0.2.0" linear-map = "1.0" -lmdb-rs = "0.7.2" log = "0.3.5" num-bigint = "0.1.35" num-integer = "0.1.32" diff --git a/src/audio_file.rs b/src/audio_file.rs index e877865..395bf56 100644 --- a/src/audio_file.rs +++ b/src/audio_file.rs @@ -3,6 +3,7 @@ use byteorder::{ByteOrder, BigEndian, WriteBytesExt}; use futures::Stream; use futures::sync::{oneshot, mpsc}; use futures::{Poll, Async, Future}; +use futures::future::{self, FutureResult}; use std::cmp::min; use std::fs; use std::io::{self, Read, Write, Seek, SeekFrom}; @@ -19,7 +20,25 @@ component! { AudioFileManager : AudioFileManagerInner { } } -pub struct AudioFile { +pub enum AudioFile { + Cached(fs::File), + Streaming(AudioFileStreaming), +} + +pub enum AudioFileOpen { + Cached(FutureResult), + Streaming(AudioFileOpenStreaming), +} + +pub struct AudioFileOpenStreaming { + session: Session, + data_rx: Option, + headers: ChannelHeaders, + file_id: FileId, + complete_tx: Option>, +} + +pub struct AudioFileStreaming { read_file: fs::File, position: u64, @@ -28,14 +47,6 @@ pub struct AudioFile { shared: Arc, } -pub struct AudioFileOpen { - session: Session, - data_rx: Option, - headers: ChannelHeaders, - file_id: FileId, - complete_tx: Option>, -} - struct AudioFileShared { file_id: FileId, chunk_count: usize, @@ -43,8 +54,8 @@ struct AudioFileShared { bitmap: Mutex, } -impl AudioFileOpen { - fn finish(&mut self, size: usize) -> AudioFile { +impl AudioFileOpenStreaming { + fn finish(&mut self, size: usize) -> AudioFileStreaming { let chunk_count = (size + CHUNK_SIZE - 1) / CHUNK_SIZE; let shared = Arc::new(AudioFileShared { @@ -69,7 +80,7 @@ impl AudioFileOpen { ); self.session.spawn(move |_| fetcher); - AudioFile { + AudioFileStreaming { read_file: read_file, position: 0, @@ -85,6 +96,24 @@ impl Future for AudioFileOpen { type Error = ChannelError; fn poll(&mut self) -> Poll { + match *self { + AudioFileOpen::Streaming(ref mut open) => { + let file = try_ready!(open.poll()); + Ok(Async::Ready(AudioFile::Streaming(file))) + } + AudioFileOpen::Cached(ref mut open) => { + let file = try_ready!(open.poll()); + Ok(Async::Ready(AudioFile::Cached(file))) + } + } + } +} + +impl Future for AudioFileOpenStreaming { + type Item = AudioFileStreaming; + type Error = ChannelError; + + fn poll(&mut self) -> Poll { loop { let (id, data) = try_ready!(self.headers.poll()).unwrap(); @@ -99,11 +128,17 @@ impl Future for AudioFileOpen { } impl AudioFileManager { - pub fn open(&self, file_id: FileId) -> (AudioFileOpen, oneshot::Receiver) { + pub fn open(&self, file_id: FileId) -> AudioFileOpen { + let cache = self.session().cache().cloned(); + + if let Some(file) = cache.as_ref().and_then(|cache| cache.file(file_id)) { + return AudioFileOpen::Cached(future::ok(file)); + } + let (complete_tx, complete_rx) = oneshot::channel(); let (headers, data) = request_chunk(&self.session(), file_id, 0).split(); - let open = AudioFileOpen { + let open = AudioFileOpenStreaming { session: self.session(), file_id: file_id, @@ -113,12 +148,21 @@ impl AudioFileManager { complete_tx: Some(complete_tx), }; - (open, complete_rx) + let session = self.session(); + self.session().spawn(move |_| { + complete_rx.map(move |mut file| { + if let Some(cache) = session.cache() { + cache.save_file(file_id, &mut file); + } + }).or_else(|oneshot::Canceled| Ok(())) + }); + + AudioFileOpen::Streaming(open) } } fn request_chunk(session: &Session, file: FileId, index: usize) -> Channel { - debug!("requesting chunk {}", index); + trace!("requesting chunk {}", index); let start = (index * CHUNK_SIZE / 4) as u32; let end = ((index + 1) * CHUNK_SIZE / 4) as u32; @@ -236,7 +280,7 @@ impl Future for AudioFileFetch { Ok(Async::Ready(None)) => { progress = true; - debug!("chunk {} / {} complete", self.index, self.shared.chunk_count); + trace!("chunk {} / {} complete", self.index, self.shared.chunk_count); let full = { let mut bitmap = self.shared.bitmap.lock().unwrap(); @@ -268,7 +312,7 @@ impl Future for AudioFileFetch { } } -impl Read for AudioFile { +impl Read for AudioFileStreaming { fn read(&mut self, output: &mut [u8]) -> io::Result { let index = self.position as usize / CHUNK_SIZE; let offset = self.position as usize % CHUNK_SIZE; @@ -288,7 +332,7 @@ impl Read for AudioFile { } } -impl Seek for AudioFile { +impl Seek for AudioFileStreaming { fn seek(&mut self, pos: SeekFrom) -> io::Result { self.position = try!(self.read_file.seek(pos)); @@ -299,3 +343,21 @@ impl Seek for AudioFile { Ok(self.position) } } + +impl Read for AudioFile { + fn read(&mut self, output: &mut [u8]) -> io::Result { + match *self { + AudioFile::Cached(ref mut file) => file.read(output), + AudioFile::Streaming(ref mut file) => file.read(output), + } + } +} + +impl Seek for AudioFile { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match *self { + AudioFile::Cached(ref mut file) => file.seek(pos), + AudioFile::Streaming(ref mut file) => file.seek(pos), + } + } +} diff --git a/src/cache/default_cache.rs b/src/cache/default_cache.rs index 1a6c1b8..5c7412a 100644 --- a/src/cache/default_cache.rs +++ b/src/cache/default_cache.rs @@ -1,5 +1,3 @@ -use lmdb_rs as lmdb; -use lmdb_rs::core::MdbResult; use std::path::PathBuf; use std::io::Read; use std::fs::File; diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 37bd00a..14f7a6f 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -1,27 +1,57 @@ -use util::{SpotifyId, FileId, ReadSeek}; -use audio_key::AudioKey; -use authentication::Credentials; +use std::path::PathBuf; use std::io::Read; +use std::fs::File; -pub trait Cache { - fn get_audio_key(&self, _track: SpotifyId, _file: FileId) -> Option { - None - } - fn put_audio_key(&self, _track: SpotifyId, _file: FileId, _audio_key: AudioKey) { } +use util::{FileId, mkdir_existing}; +use authentication::Credentials; - fn get_credentials(&self) -> Option { - None - } - fn put_credentials(&self, _cred: &Credentials) { } - - fn get_file(&self, _file: FileId) -> Option> { - None - } - fn put_file(&self, _file: FileId, _contents: &mut Read) { } +pub struct Cache { + root: PathBuf, } -pub struct NoCache; -impl Cache for NoCache { } +impl Cache { + pub fn new(location: PathBuf) -> Cache { + mkdir_existing(&location).unwrap(); + mkdir_existing(&location.join("files")).unwrap(); -mod default_cache; -pub use self::default_cache::DefaultCache; + Cache { + root: location + } + } +} + +impl Cache { + fn credentials_path(&self) -> PathBuf { + self.root.join("credentials.json") + } + + pub fn credentials(&self) -> Option { + let path = self.credentials_path(); + Credentials::from_file(path) + } + + pub fn save_credentials(&self, cred: &Credentials) { + let path = self.credentials_path(); + cred.save_to_file(&path); + } +} + +impl Cache { + fn file_path(&self, file: FileId) -> PathBuf { + let name = file.to_base16(); + self.root.join("files").join(&name[0..2]).join(&name[2..]) + } + + pub fn file(&self, file: FileId) -> Option { + File::open(self.file_path(file)).ok() + } + + pub fn save_file(&self, file: FileId, contents: &mut Read) { + let path = self.file_path(file); + + mkdir_existing(path.parent().unwrap()).unwrap(); + + let mut cache_file = File::create(path).unwrap(); + ::std::io::copy(contents, &mut cache_file).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5c2f520..e1fe4dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,6 @@ extern crate crypto; extern crate getopts; extern crate hyper; extern crate linear_map; -extern crate lmdb_rs; extern crate mdns; extern crate num_bigint; extern crate num_integer; diff --git a/src/main.rs b/src/main.rs index d1e13e6..09b6f90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use tokio_core::reactor::Core; use librespot::spirc::Spirc; use librespot::authentication::{get_credentials, Credentials}; use librespot::audio_backend::{self, Sink, BACKENDS}; -use librespot::cache::{Cache, DefaultCache, NoCache}; +use librespot::cache::Cache; use librespot::player::Player; use librespot::session::{Bitrate, Config, Session}; use librespot::version; @@ -64,7 +64,7 @@ fn list_backends() { struct Setup { backend: &'static (Fn(Option) -> Box + Send + Sync), - cache: Box, + cache: Option, config: Config, credentials: Credentials, device: Option, @@ -116,11 +116,10 @@ fn setup(args: &[String]) -> Setup { let device_id = librespot::session::device_id(&name); let cache = matches.opt_str("c").map(|cache_location| { - Box::new(DefaultCache::new(PathBuf::from(cache_location)).unwrap()) - as Box - }).unwrap_or_else(|| Box::new(NoCache)); + Cache::new(PathBuf::from(cache_location)) + }); - let cached_credentials = cache.get_credentials(); + let cached_credentials = cache.as_ref().and_then(Cache::credentials); let credentials = get_credentials(&name, &device_id, matches.opt_str("username"), diff --git a/src/player.rs b/src/player.rs index 9c7cce6..3725f85 100644 --- a/src/player.rs +++ b/src/player.rs @@ -371,7 +371,7 @@ impl PlayerInternal { let key = self.session.audio_key().request(track.id, file_id).wait().unwrap(); - let (open, _) = self.session.audio_file().open(file_id); + let open = self.session.audio_file().open(file_id); let encrypted_file = open.wait().unwrap(); let audio_file = Subfile::new(AudioDecrypt::new(key, encrypted_file), 0xa7); diff --git a/src/session.rs b/src/session.rs index 5b9945b..dcefddd 100644 --- a/src/session.rs +++ b/src/session.rs @@ -57,8 +57,6 @@ pub struct SessionInternal { config: Config, data: RwLock, - cache: Box, - tx_connection: mpsc::UnboundedSender<(u8, Vec)>, audio_key: Lazy, @@ -66,6 +64,7 @@ pub struct SessionInternal { channel: Lazy, mercury: Lazy, metadata: Lazy, + cache: Option>, handle: Remote, } @@ -84,7 +83,7 @@ pub fn device_id(name: &str) -> String { impl Session { pub fn connect(config: Config, credentials: Credentials, - cache: Box, handle: Handle) + cache: Option, handle: Handle) -> Box> { let access_point = apresolve_or_fallback::(&handle); @@ -102,7 +101,9 @@ impl Session { let result = authentication.map(move |(transport, reusable_credentials)| { info!("Authenticated !"); - cache.put_credentials(&reusable_credentials); + if let Some(ref cache) = cache { + cache.save_credentials(&reusable_credentials); + } let (session, task) = Session::create( &handle, transport, config, cache, reusable_credentials.username.clone() @@ -117,8 +118,7 @@ impl Session { } fn create(handle: &Handle, transport: connection::Transport, - config: Config, cache: Box, - username: String) + config: Config, cache: Option, username: String) -> (Session, BoxFuture<(), io::Error>) { let transport = transport.map(|(cmd, data)| (cmd, data.as_ref().to_owned())); @@ -139,7 +139,7 @@ impl Session { tx_connection: sender_tx, - cache: cache, + cache: cache.map(Arc::new), audio_key: Lazy::new(), audio_file: Lazy::new(), @@ -211,7 +211,7 @@ impl Session { self.0.tx_connection.send((cmd, data)).unwrap(); } - pub fn cache(&self) -> &Cache { + pub fn cache(&self) -> Option<&Arc> { self.0.cache.as_ref() }