mirror of
https://github.com/librespot-org/librespot.git
synced 2025-10-04 02:09:26 +02:00
Change panics into Result<_, librespot_core::Error>
This commit is contained in:
parent
a297c68913
commit
62461be1fc
69 changed files with 2041 additions and 1331 deletions
|
@ -1,34 +1,19 @@
|
|||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use chrono::Local;
|
||||
use protobuf::{Message, ProtobufError};
|
||||
use protobuf::Message;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::date::Date;
|
||||
use super::file_id::FileId;
|
||||
use super::session::Session;
|
||||
use super::spclient::SpClientError;
|
||||
use super::{date::Date, Error, FileId, Session};
|
||||
|
||||
use librespot_protocol as protocol;
|
||||
use protocol::storage_resolve::StorageResolveResponse as CdnUrlMessage;
|
||||
use protocol::storage_resolve::StorageResolveResponse_Result;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CdnUrlError {
|
||||
#[error("no URLs available")]
|
||||
Empty,
|
||||
#[error("all tokens expired")]
|
||||
Expired,
|
||||
#[error("error parsing response")]
|
||||
Parsing,
|
||||
#[error("could not parse protobuf: {0}")]
|
||||
Protobuf(#[from] ProtobufError),
|
||||
#[error("could not complete API request: {0}")]
|
||||
SpClient(#[from] SpClientError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MaybeExpiringUrl(pub String, pub Option<Date>);
|
||||
|
||||
|
@ -48,10 +33,27 @@ impl DerefMut for MaybeExpiringUrls {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CdnUrlError {
|
||||
#[error("all URLs expired")]
|
||||
Expired,
|
||||
#[error("resolved storage is not for CDN")]
|
||||
Storage,
|
||||
}
|
||||
|
||||
impl From<CdnUrlError> for Error {
|
||||
fn from(err: CdnUrlError) -> Self {
|
||||
match err {
|
||||
CdnUrlError::Expired => Error::deadline_exceeded(err),
|
||||
CdnUrlError::Storage => Error::unavailable(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CdnUrl {
|
||||
pub file_id: FileId,
|
||||
pub urls: MaybeExpiringUrls,
|
||||
urls: MaybeExpiringUrls,
|
||||
}
|
||||
|
||||
impl CdnUrl {
|
||||
|
@ -62,7 +64,7 @@ impl CdnUrl {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_audio(&self, session: &Session) -> Result<Self, CdnUrlError> {
|
||||
pub async fn resolve_audio(&self, session: &Session) -> Result<Self, Error> {
|
||||
let file_id = self.file_id;
|
||||
let response = session.spclient().get_audio_urls(file_id).await?;
|
||||
let msg = CdnUrlMessage::parse_from_bytes(&response)?;
|
||||
|
@ -75,37 +77,26 @@ impl CdnUrl {
|
|||
Ok(cdn_url)
|
||||
}
|
||||
|
||||
pub fn get_url(&mut self) -> Result<&str, CdnUrlError> {
|
||||
if self.urls.is_empty() {
|
||||
return Err(CdnUrlError::Empty);
|
||||
}
|
||||
|
||||
// prune expired URLs until the first one is current, or none are left
|
||||
pub fn try_get_url(&self) -> Result<&str, Error> {
|
||||
let now = Local::now();
|
||||
while !self.urls.is_empty() {
|
||||
let maybe_expiring = self.urls[0].1;
|
||||
if let Some(expiry) = maybe_expiring {
|
||||
if now < expiry.as_utc() {
|
||||
break;
|
||||
} else {
|
||||
self.urls.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
let url = self.urls.iter().find(|url| match url.1 {
|
||||
Some(expiry) => now < expiry.as_utc(),
|
||||
None => true,
|
||||
});
|
||||
|
||||
if let Some(cdn_url) = self.urls.first() {
|
||||
Ok(&cdn_url.0)
|
||||
if let Some(url) = url {
|
||||
Ok(&url.0)
|
||||
} else {
|
||||
Err(CdnUrlError::Expired)
|
||||
Err(CdnUrlError::Expired.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CdnUrlMessage> for MaybeExpiringUrls {
|
||||
type Error = CdnUrlError;
|
||||
type Error = crate::Error;
|
||||
fn try_from(msg: CdnUrlMessage) -> Result<Self, Self::Error> {
|
||||
if !matches!(msg.get_result(), StorageResolveResponse_Result::CDN) {
|
||||
return Err(CdnUrlError::Parsing);
|
||||
return Err(CdnUrlError::Storage.into());
|
||||
}
|
||||
|
||||
let is_expiring = !msg.get_fileid().is_empty();
|
||||
|
@ -114,7 +105,7 @@ impl TryFrom<CdnUrlMessage> for MaybeExpiringUrls {
|
|||
.get_cdnurl()
|
||||
.iter()
|
||||
.map(|cdn_url| {
|
||||
let url = Url::parse(cdn_url).map_err(|_| CdnUrlError::Parsing)?;
|
||||
let url = Url::parse(cdn_url)?;
|
||||
|
||||
if is_expiring {
|
||||
let expiry_str = if let Some(token) = url
|
||||
|
@ -122,29 +113,47 @@ impl TryFrom<CdnUrlMessage> for MaybeExpiringUrls {
|
|||
.into_iter()
|
||||
.find(|(key, _value)| key == "__token__")
|
||||
{
|
||||
let start = token.1.find("exp=").ok_or(CdnUrlError::Parsing)?;
|
||||
let slice = &token.1[start + 4..];
|
||||
let end = slice.find('~').ok_or(CdnUrlError::Parsing)?;
|
||||
String::from(&slice[..end])
|
||||
if let Some(mut start) = token.1.find("exp=") {
|
||||
start += 4;
|
||||
if token.1.len() >= start {
|
||||
let slice = &token.1[start..];
|
||||
if let Some(end) = slice.find('~') {
|
||||
// this is the only valid invariant for akamaized.net
|
||||
String::from(&slice[..end])
|
||||
} else {
|
||||
String::from(slice)
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else if let Some(query) = url.query() {
|
||||
let mut items = query.split('_');
|
||||
String::from(items.next().ok_or(CdnUrlError::Parsing)?)
|
||||
if let Some(first) = items.next() {
|
||||
// this is the only valid invariant for scdn.co
|
||||
String::from(first)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
return Err(CdnUrlError::Parsing);
|
||||
String::new()
|
||||
};
|
||||
|
||||
let mut expiry: i64 = expiry_str.parse().map_err(|_| CdnUrlError::Parsing)?;
|
||||
let mut expiry: i64 = expiry_str.parse()?;
|
||||
|
||||
expiry -= 5 * 60; // seconds
|
||||
|
||||
Ok(MaybeExpiringUrl(
|
||||
cdn_url.to_owned(),
|
||||
Some(expiry.try_into().map_err(|_| CdnUrlError::Parsing)?),
|
||||
Some(expiry.try_into()?),
|
||||
))
|
||||
} else {
|
||||
Ok(MaybeExpiringUrl(cdn_url.to_owned(), None))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<MaybeExpiringUrl>, CdnUrlError>>()?;
|
||||
.collect::<Result<Vec<MaybeExpiringUrl>, Error>>()?;
|
||||
|
||||
Ok(Self(result))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue