diff --git a/Cargo.lock b/Cargo.lock index 90d4cfa1..98542dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time", + "time 0.1.43", "winapi", ] @@ -1369,7 +1369,6 @@ dependencies = [ "base64", "byteorder", "bytes", - "chrono", "dns-sd", "env_logger", "form_urlencoded", @@ -1400,6 +1399,7 @@ dependencies = [ "sha-1 0.10.0", "shannon", "thiserror", + "time 0.3.5", "tokio", "tokio-stream", "tokio-tungstenite", @@ -1441,7 +1441,6 @@ dependencies = [ "async-trait", "byteorder", "bytes", - "chrono", "librespot-core", "librespot-protocol", "log", @@ -2737,6 +2736,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +dependencies = [ + "libc", +] + [[package]] name = "tinyvec" version = "1.5.1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 8ae38091..ab3be7a7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -17,7 +17,6 @@ aes = "0.7" base64 = "0.13" byteorder = "1.4" bytes = "1" -chrono = "0.4" dns-sd = { version = "0.1", optional = true } form_urlencoded = "1.0" futures-core = "0.3" @@ -46,6 +45,7 @@ serde_json = "1.0" sha-1 = "0.10" shannon = "0.2" thiserror = "1.0" +time = "0.3" tokio = { version = "1", features = ["io-util", "macros", "net", "parking_lot", "rt", "sync", "time"] } tokio-stream = "0.1" tokio-tungstenite = { version = "*", default-features = false, features = ["rustls-tls-native-roots"] } diff --git a/core/src/cdn_url.rs b/core/src/cdn_url.rs index befdefd6..7257a9a5 100644 --- a/core/src/cdn_url.rs +++ b/core/src/cdn_url.rs @@ -3,7 +3,6 @@ use std::{ ops::{Deref, DerefMut}, }; -use chrono::Local; use protobuf::Message; use thiserror::Error; use url::Url; @@ -84,9 +83,9 @@ impl CdnUrl { return Err(CdnUrlError::Unresolved.into()); } - let now = Local::now(); + let now = Date::now_utc(); let url = self.urls.iter().find(|url| match url.1 { - Some(expiry) => now < expiry.as_utc(), + Some(expiry) => now < expiry, None => true, }); diff --git a/core/src/date.rs b/core/src/date.rs index fe052299..d7cf09ef 100644 --- a/core/src/date.rs +++ b/core/src/date.rs @@ -1,30 +1,27 @@ -use std::{convert::TryFrom, fmt::Debug, ops::Deref}; +use std::{ + convert::{TryFrom, TryInto}, + fmt::Debug, + ops::Deref, +}; -use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}; -use thiserror::Error; +use time::{error::ComponentRange, Date as _Date, OffsetDateTime, PrimitiveDateTime, Time}; use crate::Error; use librespot_protocol as protocol; use protocol::metadata::Date as DateMessage; -#[derive(Debug, Error)] -pub enum DateError { - #[error("item has invalid timestamp {0}")] - Timestamp(i64), -} - -impl From for Error { - fn from(err: DateError) -> Self { - Error::invalid_argument(err) +impl From for Error { + fn from(err: ComponentRange) -> Self { + Error::out_of_range(err) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Date(pub DateTime); +pub struct Date(pub OffsetDateTime); impl Deref for Date { - type Target = DateTime; + type Target = OffsetDateTime; fn deref(&self) -> &Self::Target { &self.0 } @@ -32,42 +29,43 @@ impl Deref for Date { impl Date { pub fn as_timestamp(&self) -> i64 { - self.0.timestamp() + self.0.unix_timestamp() } pub fn from_timestamp(timestamp: i64) -> Result { - if let Some(date_time) = NaiveDateTime::from_timestamp_opt(timestamp, 0) { - Ok(Self::from_utc(date_time)) - } else { - Err(DateError::Timestamp(timestamp).into()) - } + let date_time = OffsetDateTime::from_unix_timestamp(timestamp)?; + Ok(Self(date_time)) } - pub fn as_utc(&self) -> DateTime { + pub fn as_utc(&self) -> OffsetDateTime { self.0 } - pub fn from_utc(date_time: NaiveDateTime) -> Self { - Self(DateTime::::from_utc(date_time, Utc)) + pub fn from_utc(date_time: PrimitiveDateTime) -> Self { + Self(date_time.assume_utc()) + } + + pub fn now_utc() -> Self { + Self(OffsetDateTime::now_utc()) } } -impl From<&DateMessage> for Date { - fn from(date: &DateMessage) -> Self { - let naive_date = NaiveDate::from_ymd( - date.get_year() as i32, - date.get_month() as u32, - date.get_day() as u32, - ); - let naive_time = NaiveTime::from_hms(date.get_hour() as u32, date.get_minute() as u32, 0); - let naive_datetime = NaiveDateTime::new(naive_date, naive_time); - Self(DateTime::::from_utc(naive_datetime, Utc)) +impl TryFrom<&DateMessage> for Date { + type Error = crate::Error; + fn try_from(msg: &DateMessage) -> Result { + let date = _Date::from_calendar_date( + msg.get_year(), + (msg.get_month() as u8).try_into()?, + msg.get_day() as u8, + )?; + let time = Time::from_hms(msg.get_hour() as u8, msg.get_minute() as u8, 0)?; + Ok(Self::from_utc(PrimitiveDateTime::new(date, time))) } } -impl From> for Date { - fn from(date: DateTime) -> Self { - Self(date) +impl From for Date { + fn from(datetime: OffsetDateTime) -> Self { + Self(datetime) } } diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index ee5b18e8..76635219 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -11,7 +11,6 @@ edition = "2018" async-trait = "0.1" byteorder = "1" bytes = "1" -chrono = "0.4" log = "0.4" protobuf = "2" thiserror = "1" diff --git a/metadata/src/album.rs b/metadata/src/album.rs index 6e07ed7e..8a372245 100644 --- a/metadata/src/album.rs +++ b/metadata/src/album.rs @@ -101,7 +101,7 @@ impl TryFrom<&::Message> for Album { artists: album.get_artist().try_into()?, album_type: album.get_field_type(), label: album.get_label().to_owned(), - date: album.get_date().into(), + date: album.get_date().try_into()?, popularity: album.get_popularity(), genres: album.get_genre().to_vec(), covers: album.get_cover().into(), @@ -111,12 +111,12 @@ impl TryFrom<&::Message> for Album { copyrights: album.get_copyright().into(), restrictions: album.get_restriction().into(), related: album.get_related().try_into()?, - sale_periods: album.get_sale_period().into(), + sale_periods: album.get_sale_period().try_into()?, cover_group: album.get_cover_group().get_image().into(), original_title: album.get_original_title().to_owned(), version_title: album.get_version_title().to_owned(), type_str: album.get_type_str().to_owned(), - availability: album.get_availability().into(), + availability: album.get_availability().try_into()?, }) } } diff --git a/metadata/src/audio/item.rs b/metadata/src/audio/item.rs index 89860c04..d2304810 100644 --- a/metadata/src/audio/item.rs +++ b/metadata/src/audio/item.rs @@ -1,7 +1,5 @@ use std::fmt::Debug; -use chrono::Local; - use crate::{ availability::{AudioItemAvailability, Availabilities, UnavailabilityReason}, episode::Episode, @@ -12,7 +10,7 @@ use crate::{ use super::file::AudioFiles; -use librespot_core::{session::UserData, spotify_id::SpotifyItemType, Error, Session, SpotifyId}; +use librespot_core::{session::UserData, date::Date, spotify_id::SpotifyItemType, Error, Session, SpotifyId}; pub type AudioItemResult = Result; @@ -93,7 +91,7 @@ pub trait InnerAudioItem { if !(availability .iter() - .any(|availability| Local::now() >= availability.start.as_utc())) + .any(|availability| Date::now_utc() >= availability.start)) { return Err(UnavailabilityReason::Embargo); } diff --git a/metadata/src/availability.rs b/metadata/src/availability.rs index d4681c28..d3c4615b 100644 --- a/metadata/src/availability.rs +++ b/metadata/src/availability.rs @@ -1,8 +1,8 @@ -use std::{fmt::Debug, ops::Deref}; +use std::{convert::{TryFrom, TryInto}, fmt::Debug, ops::Deref}; use thiserror::Error; -use crate::util::from_repeated_message; +use crate::util::try_from_repeated_message; use librespot_core::date::Date; @@ -39,13 +39,14 @@ pub enum UnavailabilityReason { NotWhitelisted, } -impl From<&AvailabilityMessage> for Availability { - fn from(availability: &AvailabilityMessage) -> Self { - Self { +impl TryFrom<&AvailabilityMessage> for Availability { + type Error = librespot_core::Error; + fn try_from(availability: &AvailabilityMessage) -> Result { + Ok(Self { catalogue_strs: availability.get_catalogue_str().to_vec(), - start: availability.get_start().into(), - } + start: availability.get_start().try_into()?, + }) } } -from_repeated_message!(AvailabilityMessage, Availabilities); +try_from_repeated_message!(AvailabilityMessage, Availabilities); diff --git a/metadata/src/episode.rs b/metadata/src/episode.rs index 0eda76ff..d04282ec 100644 --- a/metadata/src/episode.rs +++ b/metadata/src/episode.rs @@ -108,7 +108,7 @@ impl TryFrom<&::Message> for Episode { audio: episode.get_audio().into(), description: episode.get_description().to_owned(), number: episode.get_number(), - publish_time: episode.get_publish_time().into(), + publish_time: episode.get_publish_time().try_into()?, covers: episode.get_cover_image().get_image().into(), language: episode.get_language().to_owned(), is_explicit: episode.get_explicit().to_owned(), @@ -120,7 +120,7 @@ impl TryFrom<&::Message> for Episode { freeze_frames: episode.get_freeze_frame().get_image().into(), keywords: episode.get_keyword().to_vec(), allow_background_playback: episode.get_allow_background_playback(), - availability: episode.get_availability().into(), + availability: episode.get_availability().try_into()?, external_url: episode.get_external_url().to_owned(), episode_type: episode.get_field_type(), has_music_and_talk: episode.get_music_and_talk(), diff --git a/metadata/src/sale_period.rs b/metadata/src/sale_period.rs index af6b58ac..053d5e1c 100644 --- a/metadata/src/sale_period.rs +++ b/metadata/src/sale_period.rs @@ -1,6 +1,6 @@ -use std::{fmt::Debug, ops::Deref}; +use std::{convert::{TryFrom, TryInto}, fmt::Debug, ops::Deref}; -use crate::{restriction::Restrictions, util::from_repeated_message}; +use crate::{restriction::Restrictions, util::try_from_repeated_message}; use librespot_core::date::Date; @@ -24,14 +24,15 @@ impl Deref for SalePeriods { } } -impl From<&SalePeriodMessage> for SalePeriod { - fn from(sale_period: &SalePeriodMessage) -> Self { - Self { +impl TryFrom<&SalePeriodMessage> for SalePeriod { + type Error = librespot_core::Error; + fn try_from(sale_period: &SalePeriodMessage) -> Result { + Ok(Self { restrictions: sale_period.get_restriction().into(), - start: sale_period.get_start().into(), - end: sale_period.get_end().into(), - } + start: sale_period.get_start().try_into()?, + end: sale_period.get_end().try_into()?, + }) } } -from_repeated_message!(SalePeriodMessage, SalePeriods); +try_from_repeated_message!(SalePeriodMessage, SalePeriods); diff --git a/metadata/src/show.rs b/metadata/src/show.rs index 9f84ba21..19e910d8 100644 --- a/metadata/src/show.rs +++ b/metadata/src/show.rs @@ -65,7 +65,7 @@ impl TryFrom<&::Message> for Show { keywords: show.get_keyword().to_vec(), media_type: show.get_media_type(), consumption_order: show.get_consumption_order(), - availability: show.get_availability().into(), + availability: show.get_availability().try_into()?, trailer_uri: SpotifyId::from_uri(show.get_trailer_uri())?, has_music_and_talk: show.get_music_and_talk(), is_audiobook: show.get_is_audiobook(), diff --git a/metadata/src/track.rs b/metadata/src/track.rs index df1db8d1..4808b3f1 100644 --- a/metadata/src/track.rs +++ b/metadata/src/track.rs @@ -4,7 +4,6 @@ use std::{ ops::Deref, }; -use chrono::Local; use uuid::Uuid; use crate::{ @@ -77,7 +76,7 @@ impl InnerAudioItem for Track { }; // TODO: check meaning of earliest_live_timestamp in - let availability = if Local::now() < track.earliest_live_timestamp.as_utc() { + let availability = if Date::now_utc() < track.earliest_live_timestamp { Err(UnavailabilityReason::Embargo) } else { Self::available_for_user( @@ -130,12 +129,12 @@ impl TryFrom<&::Message> for Track { restrictions: track.get_restriction().into(), files: track.get_file().into(), alternatives: track.get_alternative().try_into()?, - sale_periods: track.get_sale_period().into(), + sale_periods: track.get_sale_period().try_into()?, previews: track.get_preview().into(), tags: track.get_tags().to_vec(), earliest_live_timestamp: track.get_earliest_live_timestamp().try_into()?, has_lyrics: track.get_has_lyrics(), - availability: track.get_availability().into(), + availability: track.get_availability().try_into()?, licensor: Uuid::from_slice(track.get_licensor().get_uuid()) .unwrap_or_else(|_| Uuid::nil()), language_of_performance: track.get_language_of_performance().to_vec(),