diff --git a/audio/src/fetch.rs b/audio/src/fetch.rs index c47cb4d3..bae69419 100644 --- a/audio/src/fetch.rs +++ b/audio/src/fetch.rs @@ -429,8 +429,8 @@ impl AudioFile { complete_rx .map(move |mut file| { if let Some(cache) = session_.cache() { - cache.save_file(file_id, &mut file); debug!("File {} complete, saving to cache", file_id); + cache.save_file(file_id, &mut file); } else { debug!("File {} complete", file_id); } diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs index 5e3ba389..352a3fcf 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -21,7 +21,6 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError}; use librespot_core::util::url_encode; use librespot_core::util::SeqGenerator; use librespot_core::version; -use librespot_core::volume::Volume; enum SpircPlayStatus { Stopped, @@ -1297,7 +1296,7 @@ impl SpircTask { self.mixer .set_volume(volume_to_mixer(volume, &self.config.volume_ctrl)); if let Some(cache) = self.session.cache() { - cache.save_volume(Volume { volume }) + cache.save_volume(volume) } self.player.emit_volume_set_event(volume); } diff --git a/core/src/authentication.rs b/core/src/authentication.rs index 36cbd439..9109c7fb 100644 --- a/core/src/authentication.rs +++ b/core/src/authentication.rs @@ -1,16 +1,10 @@ use aes::Aes192; -use base64; use byteorder::{BigEndian, ByteOrder}; use hmac::Hmac; use pbkdf2::pbkdf2; use protobuf::ProtobufEnum; -use serde; -use serde_json; use sha1::{Digest, Sha1}; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::ops::FnOnce; -use std::path::Path; +use std::io::{self, Read}; use crate::protocol::authentication::AuthenticationType; @@ -112,27 +106,6 @@ impl Credentials { auth_data: auth_data, } } - - fn from_reader(mut reader: R) -> Credentials { - let mut contents = String::new(); - reader.read_to_string(&mut contents).unwrap(); - - serde_json::from_str(&contents).unwrap() - } - - pub(crate) fn from_file>(path: P) -> Option { - File::open(path).ok().map(Credentials::from_reader) - } - - fn save_to_writer(&self, writer: &mut W) { - let contents = serde_json::to_string(&self.clone()).unwrap(); - writer.write_all(contents.as_bytes()).unwrap(); - } - - pub(crate) fn save_to_file>(&self, path: P) { - let mut file = File::create(path).unwrap(); - self.save_to_writer(&mut file) - } } fn serialize_protobuf_enum(v: &T, ser: S) -> Result diff --git a/core/src/cache.rs b/core/src/cache.rs index e711777c..00f0f407 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -1,115 +1,165 @@ use std::fs; use std::fs::File; -use std::io; -use std::io::Read; -use std::path::Path; -use std::path::PathBuf; +use std::io::{self, Error, ErrorKind, Read, Write}; +use std::path::{Path, PathBuf}; use crate::authentication::Credentials; use crate::spotify_id::FileId; -use crate::volume::Volume; +/// A cache for volume, credentials and audio files. #[derive(Clone)] pub struct Cache { - audio_root: PathBuf, - system_root: PathBuf, - use_audio_cache: bool, -} - -fn mkdir_existing(path: &Path) -> io::Result<()> { - fs::create_dir(path).or_else(|err| { - if err.kind() == io::ErrorKind::AlreadyExists { - Ok(()) - } else { - Err(err) - } - }) + credentials_location: Option, + volume_location: Option, + audio_location: Option, } impl Cache { - pub fn new(audio_location: PathBuf, system_location: PathBuf, use_audio_cache: bool) -> Cache { - if use_audio_cache == true { - mkdir_existing(&audio_location).unwrap(); - mkdir_existing(&audio_location.join("files")).unwrap(); + pub fn new>( + system_location: Option

, + audio_location: Option

, + ) -> io::Result { + if let Some(location) = &system_location { + fs::create_dir_all(location)?; } - mkdir_existing(&system_location).unwrap(); - Cache { - audio_root: audio_location, - system_root: system_location, - use_audio_cache: use_audio_cache, + if let Some(location) = &audio_location { + fs::create_dir_all(location)?; } - } -} -impl Cache { - fn credentials_path(&self) -> PathBuf { - self.system_root.join("credentials.json") + let audio_location = audio_location.map(|p| p.as_ref().to_owned()); + let volume_location = system_location.as_ref().map(|p| p.as_ref().join("volume")); + let credentials_location = system_location + .as_ref() + .map(|p| p.as_ref().join("credentials.json")); + + let cache = Cache { + credentials_location, + volume_location, + audio_location, + }; + + Ok(cache) } pub fn credentials(&self) -> Option { - let path = self.credentials_path(); - Credentials::from_file(path) + let location = self.credentials_location.as_ref()?; + + // This closure is just convencience to enable the question mark operator + let read = || { + let mut file = File::open(location)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + serde_json::from_str(&contents).map_err(|e| Error::new(ErrorKind::InvalidData, e)) + }; + + match read() { + Ok(c) => Some(c), + Err(e) => { + // If the file did not exist, the file was probably not written + // before. Otherwise, log the error. + if e.kind() != ErrorKind::NotFound { + warn!("Error reading credentials from cache: {}", e); + } + None + } + } } pub fn save_credentials(&self, cred: &Credentials) { - let path = self.credentials_path(); - cred.save_to_file(&path); - } -} + if let Some(location) = &self.credentials_location { + let result = File::create(location).and_then(|mut file| { + let data = serde_json::to_string(cred)?; + write!(file, "{}", data) + }); -// cache volume to system_root/volume -impl Cache { - fn volume_path(&self) -> PathBuf { - self.system_root.join("volume") + if let Err(e) = result { + warn!("Cannot save credentials to cache: {}", e) + } + } } pub fn volume(&self) -> Option { - let path = self.volume_path(); - Volume::from_file(path) + let location = self.volume_location.as_ref()?; + + let read = || { + let mut file = File::open(location)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + contents + .parse() + .map_err(|e| Error::new(ErrorKind::InvalidData, e)) + }; + + match read() { + Ok(v) => Some(v), + Err(e) => { + if e.kind() != ErrorKind::NotFound { + warn!("Error reading volume from cache: {}", e); + } + None + } + } } - pub fn save_volume(&self, volume: Volume) { - let path = self.volume_path(); - volume.save_to_file(&path); + pub fn save_volume(&self, volume: u16) { + if let Some(ref location) = self.volume_location { + let result = File::create(location).and_then(|mut file| write!(file, "{}", volume)); + if let Err(e) = result { + warn!("Cannot save volume to cache: {}", e); + } + } } -} -impl Cache { - fn file_path(&self, file: FileId) -> PathBuf { - let name = file.to_base16(); - self.audio_root - .join("files") - .join(&name[0..2]) - .join(&name[2..]) + fn file_path(&self, file: FileId) -> Option { + self.audio_location.as_ref().map(|location| { + let name = file.to_base16(); + let mut path = location.join(&name[0..2]); + path.push(&name[2..]); + path + }) } pub fn file(&self, file: FileId) -> Option { - File::open(self.file_path(file)).ok() + File::open(self.file_path(file)?) + .map_err(|e| { + if e.kind() != ErrorKind::NotFound { + warn!("Error reading file from cache: {}", e) + } + }) + .ok() } - pub fn save_file(&self, file: FileId, contents: &mut dyn Read) { - if self.use_audio_cache { - let path = self.file_path(file); - mkdir_existing(path.parent().unwrap()).unwrap(); + pub fn save_file(&self, file: FileId, contents: &mut F) { + let path = if let Some(path) = self.file_path(file) { + path + } else { + return; + }; + let parent = path.parent().unwrap(); - let mut cache_file = File::create(path).unwrap_or_else(|_e| { - ::std::fs::remove_dir_all(&self.audio_root.join("files")).unwrap(); - mkdir_existing(&self.audio_root.join("files")).unwrap(); + let result = fs::create_dir_all(parent) + .and_then(|_| File::create(&path)) + .and_then(|mut file| io::copy(contents, &mut file)); - let path = self.file_path(file); - mkdir_existing(path.parent().unwrap()).unwrap(); - File::create(path).unwrap() - }); - ::std::io::copy(contents, &mut cache_file).unwrap_or_else(|_e| { - ::std::fs::remove_dir_all(&self.audio_root.join("files")).unwrap(); - mkdir_existing(&self.audio_root.join("files")).unwrap(); + if let Err(e) = result { + if e.kind() == ErrorKind::Other { + // Perhaps there's no space left in the cache + // TODO: try to narrow down the error (platform-dependently) + info!("An error occured while writing to cache, trying to flush the cache"); - let path = self.file_path(file); - mkdir_existing(path.parent().unwrap()).unwrap(); - let mut file = File::create(path).unwrap(); - ::std::io::copy(contents, &mut file).unwrap() - }); + if fs::remove_dir_all(self.audio_location.as_ref().unwrap()) + .and_then(|_| fs::create_dir_all(parent)) + .and_then(|_| File::create(&path)) + .and_then(|mut file| io::copy(contents, &mut file)) + .is_ok() + { + // It worked, there's no need to print a warning + return; + } + } + + warn!("Cannot save file to cache: {}", e) } } } diff --git a/core/src/lib.rs b/core/src/lib.rs index c65878c2..a00d30bf 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -54,4 +54,3 @@ pub mod session; pub mod spotify_id; pub mod util; pub mod version; -pub mod volume; diff --git a/core/src/volume.rs b/core/src/volume.rs deleted file mode 100644 index 6b456d1f..00000000 --- a/core/src/volume.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::fs::File; -use std::io::{Read, Write}; -use std::path::Path; - -#[derive(Clone, Copy, Debug)] -pub struct Volume { - pub volume: u16, -} - -impl Volume { - // read volume from file - fn from_reader(mut reader: R) -> u16 { - let mut contents = String::new(); - reader.read_to_string(&mut contents).unwrap(); - contents.trim().parse::().unwrap() - } - - pub(crate) fn from_file>(path: P) -> Option { - File::open(path).ok().map(Volume::from_reader) - } - - // write volume to file - fn save_to_writer(&self, writer: &mut W) { - writer - .write_all(self.volume.to_string().as_bytes()) - .unwrap(); - } - - pub(crate) fn save_to_file>(&self, path: P) { - let mut file = File::create(path).unwrap(); - self.save_to_writer(&mut file) - } -} diff --git a/src/main.rs b/src/main.rs index 4f80657e..f900f681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use sha1::{Digest, Sha1}; use std::env; use std::io::{self, stderr, Write}; use std::mem; -use std::path::PathBuf; +use std::path::Path; use std::process::exit; use std::str::FromStr; use std::time::Instant; @@ -254,18 +254,34 @@ fn setup(args: &[String]) -> Setup { mapped_volume: !matches.opt_present("mixer-linear-volume"), }; - let cache = matches.opt_str("c").map(|cache_path| { - let use_audio_cache = !matches.opt_present("disable-audio-cache"); - let system_cache_directory = matches - .opt_str("system-cache") - .unwrap_or(String::from(cache_path.clone())); + let cache = { + let audio_dir; + let system_dir; + if matches.opt_present("disable-audio-cache") { + audio_dir = None; + system_dir = matches + .opt_str("system-cache") + .or_else(|| matches.opt_str("c")) + .map(|p| p.into()); + } else { + let cache_dir = matches.opt_str("c"); + audio_dir = cache_dir + .as_ref() + .map(|p| AsRef::::as_ref(p).join("files")); + system_dir = matches + .opt_str("system-cache") + .or_else(|| cache_dir) + .map(|p| p.into()); + } - Cache::new( - PathBuf::from(cache_path), - PathBuf::from(system_cache_directory), - use_audio_cache, - ) - }); + match Cache::new(system_dir, audio_dir) { + Ok(cache) => Some(cache), + Err(e) => { + warn!("Cannot create cache: {}", e); + None + } + } + }; let initial_volume = matches .opt_str("initial-volume")