From 14a004f84c01642dcb8b5efce2fd20ac8fd45bfa Mon Sep 17 00:00:00 2001 From: johannesd3 Date: Sat, 23 Jan 2021 22:37:41 +0100 Subject: [PATCH 1/4] Refactored Cache Proper error handling, and moving the conversion between { credentials, volume } and file into the cache module --- audio/src/fetch.rs | 4 +- core/src/authentication.rs | 29 +-------- core/src/cache.rs | 130 +++++++++++++++++++++++++------------ src/main.rs | 31 +++++---- 4 files changed, 112 insertions(+), 82 deletions(-) diff --git a/audio/src/fetch.rs b/audio/src/fetch.rs index c47cb4d3..c65482bd 100644 --- a/audio/src/fetch.rs +++ b/audio/src/fetch.rs @@ -429,8 +429,10 @@ 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); + if let Err(err) = cache.save_file(file_id, &mut file) { + warn!("Cannot save file to cache: {}", err); + } } else { debug!("File {} complete", file_id); } 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..4dcce968 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -1,9 +1,7 @@ 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; @@ -27,51 +25,93 @@ fn mkdir_existing(path: &Path) -> io::Result<()> { } 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( + audio_location: PathBuf, + system_location: PathBuf, + use_audio_cache: bool, + ) -> io::Result { + if use_audio_cache { + mkdir_existing(&audio_location)?; + mkdir_existing(&audio_location.join("files"))?; } - mkdir_existing(&system_location).unwrap(); + mkdir_existing(&system_location)?; - Cache { + Ok(Cache { audio_root: audio_location, system_root: system_location, - use_audio_cache: use_audio_cache, - } + use_audio_cache, + }) } } impl Cache { - fn credentials_path(&self) -> PathBuf { - self.system_root.join("credentials.json") + fn open_credentials_file(&self) -> io::Result { + File::open(self.system_root.join("credentials.json")) + } + + fn read_credentials(&self) -> io::Result { + let mut file = self.open_credentials_file()?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + serde_json::from_str(&contents).map_err(|e| Error::new(ErrorKind::InvalidData, e)) } pub fn credentials(&self) -> Option { - let path = self.credentials_path(); - Credentials::from_file(path) + match self.read_credentials() { + Ok(c) => Some(c), + Err(e) => { + 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); + let result = self + .open_credentials_file() + .and_then(|mut file| write!(file, "{}", serde_json::to_string(cred)?)); + if let Err(e) = result { + warn!("Cannot save credentials to cache: {}", e); + } } } // cache volume to system_root/volume impl Cache { - fn volume_path(&self) -> PathBuf { - self.system_root.join("volume") + fn open_volume_file(&self) -> io::Result { + File::open(self.system_root.join("volume")) } - pub fn volume(&self) -> Option { - let path = self.volume_path(); - Volume::from_file(path) + fn read_volume(&self) -> io::Result { + let mut file = self.open_volume_file()?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + contents + .parse() + .map_err(|e| Error::new(ErrorKind::InvalidData, e)) + } + + pub fn volume(&self) -> Option { + match self.read_volume() { + 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); + let result = self + .open_volume_file() + .and_then(|mut file| write!(file, "{}", volume)); + if let Err(e) = result { + warn!("Cannot save volume to cache: {}", e); + } } } @@ -85,31 +125,39 @@ impl Cache { } 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) { + pub fn save_file(&self, file: FileId, contents: &mut F) -> io::Result<()> { if self.use_audio_cache { let path = self.file_path(file); - mkdir_existing(path.parent().unwrap()).unwrap(); + mkdir_existing(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 mut cache_file = File::create(path).or_else(|_| { + fs::remove_dir_all(&self.audio_root.join("files"))?; + mkdir_existing(&self.audio_root.join("files"))?; 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(); + mkdir_existing(path.parent().unwrap())?; + File::create(path) + })?; + + io::copy(contents, &mut cache_file).or_else(|_| { + fs::remove_dir_all(&self.audio_root.join("files"))?; + mkdir_existing(&self.audio_root.join("files"))?; 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() - }); + mkdir_existing(path.parent().unwrap())?; + let mut file = File::create(path)?; + io::copy(contents, &mut file) + })?; } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 4f80657e..8c029f5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -254,18 +254,24 @@ 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 use_audio_cache = !matches.opt_present("disable-audio-cache"); - Cache::new( - PathBuf::from(cache_path), - PathBuf::from(system_cache_directory), - use_audio_cache, - ) - }); + let cache_directory = matches.opt_str("c").unwrap_or(String::from("")); + let system_cache_directory = matches + .opt_str("system-cache") + .unwrap_or(String::from(cache_directory.clone())); + + let cache = match Cache::new( + PathBuf::from(cache_directory), + PathBuf::from(system_cache_directory), + use_audio_cache, + ) { + Ok(cache) => Some(cache), + Err(e) => { + warn!("Cannot create cache: {}", e); + None + } + }; let initial_volume = matches .opt_str("initial-volume") @@ -276,8 +282,9 @@ fn setup(args: &[String]) -> Setup { } (volume as i32 * 0xFFFF / 100) as u16 }) + .map(Volume) .or_else(|| cache.as_ref().and_then(Cache::volume)) - .unwrap_or(0x8000); + .unwrap_or(Volume(0x8000)); let zeroconf_port = matches .opt_str("zeroconf-port") From fa5c9f7d11367e6d298b11e4d4aaad26d5f21472 Mon Sep 17 00:00:00 2001 From: johannesd3 Date: Mon, 25 Jan 2021 02:22:25 +0100 Subject: [PATCH 2/4] Made locations in cache optional The locations of credentials, volume and audio are now stored in three separate Optionals. Removed the clearing of the cache if an error occurs. This might be added again later. --- audio/src/fetch.rs | 4 +- core/src/cache.rs | 179 ++++++++++++++++++++------------------------- src/main.rs | 42 +++++++---- 3 files changed, 108 insertions(+), 117 deletions(-) diff --git a/audio/src/fetch.rs b/audio/src/fetch.rs index c65482bd..bae69419 100644 --- a/audio/src/fetch.rs +++ b/audio/src/fetch.rs @@ -430,9 +430,7 @@ impl AudioFile { .map(move |mut file| { if let Some(cache) = session_.cache() { debug!("File {} complete, saving to cache", file_id); - if let Err(err) = cache.save_file(file_id, &mut file) { - warn!("Cannot save file to cache: {}", err); - } + cache.save_file(file_id, &mut file); } else { debug!("File {} complete", file_id); } diff --git a/core/src/cache.rs b/core/src/cache.rs index 4dcce968..76d49fa9 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -7,59 +7,58 @@ 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, - ) -> io::Result { - if use_audio_cache { - mkdir_existing(&audio_location)?; - mkdir_existing(&audio_location.join("files"))?; + 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)?; - Ok(Cache { - audio_root: audio_location, - system_root: system_location, - use_audio_cache, - }) - } -} + if let Some(location) = &audio_location { + fs::create_dir_all(location)?; + } -impl Cache { - fn open_credentials_file(&self) -> io::Result { - File::open(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")); - fn read_credentials(&self) -> io::Result { - let mut file = self.open_credentials_file()?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - serde_json::from_str(&contents).map_err(|e| Error::new(ErrorKind::InvalidData, e)) + let cache = Cache { + credentials_location, + volume_location, + audio_location, + }; + + Ok(cache) } pub fn credentials(&self) -> Option { - match self.read_credentials() { + 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); } @@ -69,32 +68,31 @@ impl Cache { } pub fn save_credentials(&self, cred: &Credentials) { - let result = self - .open_credentials_file() - .and_then(|mut file| write!(file, "{}", serde_json::to_string(cred)?)); - if let Err(e) = result { - warn!("Cannot save credentials to cache: {}", e); + 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) + }); + + if let Err(e) = result { + warn!("Cannot save credentials to cache: {}", e) + } } } -} - -// cache volume to system_root/volume -impl Cache { - fn open_volume_file(&self) -> io::Result { - File::open(self.system_root.join("volume")) - } - - fn read_volume(&self) -> io::Result { - let mut file = self.open_volume_file()?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - contents - .parse() - .map_err(|e| Error::new(ErrorKind::InvalidData, e)) - } pub fn volume(&self) -> Option { - match self.read_volume() { + 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 { @@ -106,26 +104,25 @@ impl Cache { } pub fn save_volume(&self, volume: Volume) { - let result = self - .open_volume_file() - .and_then(|mut file| write!(file, "{}", volume)); - if let Err(e) = result { - warn!("Cannot save volume to cache: {}", e); + 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)) + File::open(self.file_path(file)?) .map_err(|e| { if e.kind() != ErrorKind::NotFound { warn!("Error reading file from cache: {}", e) @@ -134,30 +131,16 @@ impl Cache { .ok() } - pub fn save_file(&self, file: FileId, contents: &mut F) -> io::Result<()> { - if self.use_audio_cache { - let path = self.file_path(file); - mkdir_existing(path.parent().unwrap())?; + pub fn save_file(&self, file: FileId, contents: &mut F) { + if let Some(path) = self.file_path(file) { + let parent = path.parent().unwrap(); + let result = fs::create_dir_all(parent) + .and_then(|_| File::create(path)) + .and_then(|mut file| io::copy(contents, &mut file)); - let mut cache_file = File::create(path).or_else(|_| { - fs::remove_dir_all(&self.audio_root.join("files"))?; - mkdir_existing(&self.audio_root.join("files"))?; - - let path = self.file_path(file); - mkdir_existing(path.parent().unwrap())?; - File::create(path) - })?; - - io::copy(contents, &mut cache_file).or_else(|_| { - fs::remove_dir_all(&self.audio_root.join("files"))?; - mkdir_existing(&self.audio_root.join("files"))?; - - let path = self.file_path(file); - mkdir_existing(path.parent().unwrap())?; - let mut file = File::create(path)?; - io::copy(contents, &mut file) - })?; + if let Err(e) = result { + warn!("Cannot save file to cache: {}", e) + } } - Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 8c029f5a..b22d7d5f 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,22 +254,32 @@ fn setup(args: &[String]) -> Setup { mapped_volume: !matches.opt_present("mixer-linear-volume"), }; - let use_audio_cache = !matches.opt_present("disable-audio-cache"); + 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()); + } - let cache_directory = matches.opt_str("c").unwrap_or(String::from("")); - let system_cache_directory = matches - .opt_str("system-cache") - .unwrap_or(String::from(cache_directory.clone())); - - let cache = match Cache::new( - PathBuf::from(cache_directory), - PathBuf::from(system_cache_directory), - use_audio_cache, - ) { - Ok(cache) => Some(cache), - Err(e) => { - warn!("Cannot create cache: {}", e); - None + match Cache::new(system_dir, audio_dir) { + Ok(cache) => Some(cache), + Err(e) => { + warn!("Cannot create cache: {}", e); + None + } } }; From fd1f04957211c504491864a8fd8cf0fa324dd2f9 Mon Sep 17 00:00:00 2001 From: johannesd3 Date: Mon, 25 Jan 2021 10:52:06 +0100 Subject: [PATCH 3/4] Removed volume struct --- connect/src/spirc.rs | 3 +-- core/src/cache.rs | 5 ++--- core/src/lib.rs | 1 - core/src/volume.rs | 33 --------------------------------- src/main.rs | 3 +-- 5 files changed, 4 insertions(+), 41 deletions(-) delete mode 100644 core/src/volume.rs 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/cache.rs b/core/src/cache.rs index 76d49fa9..018c4c25 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -5,7 +5,6 @@ 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)] @@ -80,7 +79,7 @@ impl Cache { } } - pub fn volume(&self) -> Option { + pub fn volume(&self) -> Option { let location = self.volume_location.as_ref()?; let read = || { @@ -103,7 +102,7 @@ impl Cache { } } - pub fn save_volume(&self, volume: Volume) { + 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 { 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 b22d7d5f..f900f681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -292,9 +292,8 @@ fn setup(args: &[String]) -> Setup { } (volume as i32 * 0xFFFF / 100) as u16 }) - .map(Volume) .or_else(|| cache.as_ref().and_then(Cache::volume)) - .unwrap_or(Volume(0x8000)); + .unwrap_or(0x8000); let zeroconf_port = matches .opt_str("zeroconf-port") From efedc678d09e18e48f50e48f4f36070668a9fadc Mon Sep 17 00:00:00 2001 From: johannesd3 Date: Sun, 31 Jan 2021 10:00:02 +0100 Subject: [PATCH 4/4] Handle cache full situation --- core/src/cache.rs | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/core/src/cache.rs b/core/src/cache.rs index 018c4c25..00f0f407 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -131,15 +131,35 @@ impl Cache { } pub fn save_file(&self, file: FileId, contents: &mut F) { - if let Some(path) = self.file_path(file) { - let parent = path.parent().unwrap(); - let result = fs::create_dir_all(parent) - .and_then(|_| File::create(path)) - .and_then(|mut file| io::copy(contents, &mut file)); + let path = if let Some(path) = self.file_path(file) { + path + } else { + return; + }; + let parent = path.parent().unwrap(); - if let Err(e) = result { - warn!("Cannot save file to cache: {}", e) + let result = fs::create_dir_all(parent) + .and_then(|_| File::create(&path)) + .and_then(|mut file| io::copy(contents, &mut file)); + + 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"); + + 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) } } }