chore: refactor into a RecordingDataManager, lay the ground work for a RenderManager
This commit is contained in:
44
src/recording_data/between.rs
Normal file
44
src/recording_data/between.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{ResultExt as _, Snafu};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use time::UtcDateTime;
|
||||
use twilight_model::id::{
|
||||
Id,
|
||||
marker::{ChannelMarker, GuildMarker},
|
||||
};
|
||||
|
||||
use super::{ListError, Recording, RecordingDataManager, recording};
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum RecordingEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a recording
|
||||
ParseError { source: recording::TakeError },
|
||||
}
|
||||
|
||||
// impl RecordingDataManager {
|
||||
// pub async fn between(
|
||||
// &self,
|
||||
// start: UtcDateTime,
|
||||
// end: UtcDateTime,
|
||||
// ) -> Result<impl TryStream<Ok = Recording, Error = RecordingEntryError> + Unpin, ListError>
|
||||
// {
|
||||
// todo!();
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl RecordingDataManager {
|
||||
// pub async fn between_in_vc(
|
||||
// &self,
|
||||
// start: UtcDateTime,
|
||||
// end: UtcDateTime,
|
||||
// guild_id: Id<GuildMarker>,
|
||||
// voice_channel_id: Id<ChannelMarker>,
|
||||
// ) -> Result<impl TryStream<Ok = Recording, Error = RecordingEntryError> + Unpin, ListError>
|
||||
// {
|
||||
// todo!();
|
||||
// Ok(self.between(start, end)?)
|
||||
// }
|
||||
// }
|
||||
111
src/recording_data/clip.rs
Normal file
111
src/recording_data/clip.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{ResultExt as _, Snafu};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use super::{
|
||||
CreateListerSnafu, Day, Guild, Hour, ListError, Microsecond, Minute, Month,
|
||||
RecordingDataManager, Second, User, VoiceChannel, Year, guild, microsecond, second, user,
|
||||
voice_channel,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Clip {
|
||||
pub second: Second,
|
||||
pub microsecond: Microsecond,
|
||||
pub guild: Guild,
|
||||
pub voice_channel: VoiceChannel,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// could not parse the second out of the clip metadata
|
||||
TakeSecondError { source: second::TakeError },
|
||||
|
||||
/// could not parse the microsecond out of the clip metadata
|
||||
TakeMicrosecondError { source: microsecond::TakeError },
|
||||
|
||||
/// could not parse the guild out of the clip metadata
|
||||
TakeGuildError { source: guild::TakeError },
|
||||
|
||||
/// could not parse the voice channel out of the clip metadata
|
||||
TakeVoiceChannelError { source: voice_channel::TakeError },
|
||||
|
||||
/// could not parse the user out of the clip metadata
|
||||
TakeUserError { source: user::TakeError },
|
||||
}
|
||||
|
||||
pub fn take(s: &str) -> Result<Clip, TakeError> {
|
||||
let (second, s) = second::take(s).context(TakeSecondSnafu)?;
|
||||
let (microsecond, s) = microsecond::take(s).context(TakeMicrosecondSnafu)?;
|
||||
let (guild, s) = guild::take(s).context(TakeGuildSnafu)?;
|
||||
let (voice_channel, s) = voice_channel::take(s).context(TakeVoiceChannelSnafu)?;
|
||||
let user = user::take(s).context(TakeUserSnafu)?;
|
||||
|
||||
Ok(Clip {
|
||||
second,
|
||||
microsecond,
|
||||
guild,
|
||||
voice_channel,
|
||||
user,
|
||||
})
|
||||
}
|
||||
|
||||
impl FromStr for Clip {
|
||||
type Err = TakeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
take(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Clip {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self {
|
||||
second,
|
||||
microsecond,
|
||||
guild,
|
||||
voice_channel,
|
||||
user,
|
||||
} = self;
|
||||
|
||||
let user = user
|
||||
.as_ref()
|
||||
.map_or_else(|| "UNKNOWN".into(), ToString::to_string);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"audio-{second}.{microsecond}-{guild}-{voice_channel}-{user}.wav"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum ClipEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a clip
|
||||
ParseError { source: TakeError },
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn clips(
|
||||
&self,
|
||||
year: Year,
|
||||
month: Month,
|
||||
day: Day,
|
||||
hour: Hour,
|
||||
minute: Minute,
|
||||
) -> Result<impl TryStream<Ok = Clip, Error = ClipEntryError> + Unpin, ListError> {
|
||||
let lister = self
|
||||
.operator
|
||||
.lister(&format!("{year}/{month}/{day}/{hour}/{minute}/"))
|
||||
.await
|
||||
.context(CreateListerSnafu)?;
|
||||
|
||||
Ok(lister
|
||||
.map_err(|error| ClipEntryError::ReceiveEntryError { source: error })
|
||||
.and_then(|entry| std::future::ready(entry.name().parse().context(ParseSnafu))))
|
||||
}
|
||||
}
|
||||
57
src/recording_data/day.rs
Normal file
57
src/recording_data/day.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use super::{CreateListerSnafu, ListError, Month, RecordingDataManager, Year};
|
||||
|
||||
pub type Day = u8;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// days are supposed to be directories, but this wasn't (because it didn't end with `/`)
|
||||
NotADirectory,
|
||||
|
||||
/// could not parse the day as an integer
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(s: &str) -> Result<(Day, &str), TakeError> {
|
||||
let (day, rest) = s.split_once('/').context(NotADirectorySnafu)?;
|
||||
|
||||
let day = day.parse().context(ParseIntegerSnafu)?;
|
||||
|
||||
Ok((day, rest))
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum DayEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a day
|
||||
ParseError { source: TakeError },
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn days(
|
||||
&self,
|
||||
year: Year,
|
||||
month: Month,
|
||||
) -> Result<impl TryStream<Ok = Day, Error = DayEntryError> + Unpin, ListError> {
|
||||
let lister = self
|
||||
.operator
|
||||
.lister(&format!("{year}/{month}/"))
|
||||
.await
|
||||
.context(CreateListerSnafu)?;
|
||||
|
||||
Ok(lister
|
||||
.map_err(|error| DayEntryError::ReceiveEntryError { source: error })
|
||||
.and_then(|entry| {
|
||||
std::future::ready(
|
||||
take(entry.name())
|
||||
.map(|(day, rest)| day)
|
||||
.context(ParseSnafu),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
22
src/recording_data/guild.rs
Normal file
22
src/recording_data/guild.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::str::FromStr;
|
||||
use twilight_model::id::{Id, marker::GuildMarker};
|
||||
|
||||
pub type Guild = Id<GuildMarker>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// guilds are supposed to be followed by -
|
||||
Malformed,
|
||||
|
||||
/// could not parse the guild ID
|
||||
ParseIdError { source: <Guild as FromStr>::Err },
|
||||
}
|
||||
|
||||
pub fn take(path: &str) -> Result<(Guild, &str), TakeError> {
|
||||
let (guild, rest) = path.split_once('-').context(MalformedSnafu)?;
|
||||
|
||||
let guild = guild.parse().context(ParseIdSnafu)?;
|
||||
|
||||
Ok((guild, rest))
|
||||
}
|
||||
58
src/recording_data/hour.rs
Normal file
58
src/recording_data/hour.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use super::{CreateListerSnafu, Day, ListError, Month, RecordingDataManager, Year};
|
||||
|
||||
pub type Hour = u8;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// hours are supposed to be directories, but this wasn't (because it didn't end with `/`)
|
||||
NotADirectory,
|
||||
|
||||
/// could not parse the hour as an integer
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(s: &str) -> Result<(Hour, &str), TakeError> {
|
||||
let (hour, rest) = s.split_once('/').context(NotADirectorySnafu)?;
|
||||
|
||||
let hour = hour.parse().context(ParseIntegerSnafu)?;
|
||||
|
||||
Ok((hour, rest))
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum HourEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a hour
|
||||
ParseError { source: TakeError },
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn hours(
|
||||
&self,
|
||||
year: Year,
|
||||
month: Month,
|
||||
day: Day,
|
||||
) -> Result<impl TryStream<Ok = Hour, Error = HourEntryError> + Unpin, ListError> {
|
||||
let lister = self
|
||||
.operator
|
||||
.lister(&format!("{year}/{month}/{day}/"))
|
||||
.await
|
||||
.context(CreateListerSnafu)?;
|
||||
|
||||
Ok(lister
|
||||
.map_err(|error| HourEntryError::ReceiveEntryError { source: error })
|
||||
.and_then(|entry| {
|
||||
std::future::ready(
|
||||
take(entry.name())
|
||||
.map(|(hour, _rest)| hour)
|
||||
.context(ParseSnafu),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
21
src/recording_data/microsecond.rs
Normal file
21
src/recording_data/microsecond.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
pub type Microsecond = u32;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// microseconds are supposed to be followed by -
|
||||
Malformed,
|
||||
|
||||
/// could not parse the microsecond as an integer
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(path: &str) -> Result<(Microsecond, &str), TakeError> {
|
||||
let (microsecond, rest) = path.split_once('-').context(MalformedSnafu)?;
|
||||
|
||||
let microsecond = microsecond.parse().context(ParseIntegerSnafu)?;
|
||||
|
||||
Ok((microsecond, rest))
|
||||
}
|
||||
59
src/recording_data/minute.rs
Normal file
59
src/recording_data/minute.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use super::{CreateListerSnafu, Day, Hour, ListError, Month, RecordingDataManager, Year};
|
||||
|
||||
pub type Minute = u8;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// minutes are supposed to be directories, but this wasn't (because it didn't end with `/`)
|
||||
NotADirectory,
|
||||
|
||||
/// could not parse the minute as an integer
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(s: &str) -> Result<(Minute, &str), TakeError> {
|
||||
let (minute, rest) = s.split_once('/').context(NotADirectorySnafu)?;
|
||||
|
||||
let minute = minute.parse().context(ParseIntegerSnafu)?;
|
||||
|
||||
Ok((minute, rest))
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum MinuteEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a minute
|
||||
ParseError { source: TakeError },
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn minutes(
|
||||
&self,
|
||||
year: Year,
|
||||
month: Month,
|
||||
day: Day,
|
||||
hour: Hour,
|
||||
) -> Result<impl TryStream<Ok = Minute, Error = MinuteEntryError> + Unpin, ListError> {
|
||||
let lister = self
|
||||
.operator
|
||||
.lister(&format!("{year}/{month}/{day}/{hour}/"))
|
||||
.await
|
||||
.context(CreateListerSnafu)?;
|
||||
|
||||
Ok(lister
|
||||
.map_err(|error| MinuteEntryError::ReceiveEntryError { source: error })
|
||||
.and_then(|entry| {
|
||||
std::future::ready(
|
||||
take(entry.name())
|
||||
.map(|(minute, _rest)| minute)
|
||||
.context(ParseSnafu),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
47
src/recording_data/mod.rs
Normal file
47
src/recording_data/mod.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use futures::TryStream;
|
||||
use opendal::Operator;
|
||||
use snafu::Snafu;
|
||||
|
||||
mod between;
|
||||
mod clip;
|
||||
mod day;
|
||||
mod guild;
|
||||
mod hour;
|
||||
mod microsecond;
|
||||
mod minute;
|
||||
mod month;
|
||||
mod recording;
|
||||
mod second;
|
||||
mod user;
|
||||
mod voice_channel;
|
||||
mod year;
|
||||
|
||||
pub use clip::Clip;
|
||||
use day::Day;
|
||||
use guild::Guild;
|
||||
use hour::Hour;
|
||||
use microsecond::Microsecond;
|
||||
use minute::Minute;
|
||||
use month::Month;
|
||||
pub use recording::Recording;
|
||||
use second::Second;
|
||||
use user::User;
|
||||
use voice_channel::VoiceChannel;
|
||||
use year::Year;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecordingDataManager {
|
||||
operator: Operator,
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub fn new(operator: Operator) -> Self {
|
||||
Self { operator }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum ListError {
|
||||
/// error creating a lister through the storage operator
|
||||
CreateListerError { source: opendal::Error },
|
||||
}
|
||||
55
src/recording_data/month.rs
Normal file
55
src/recording_data/month.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
|
||||
use super::{CreateListerSnafu, ListError, RecordingDataManager, Year};
|
||||
|
||||
pub use time::Month;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// months are supposed to be directories, but this wasn't (because it didn't end with `/`)
|
||||
NotADirectory,
|
||||
|
||||
/// could not parse the month as its name
|
||||
ParseMonthNameError { source: time::error::InvalidVariant },
|
||||
}
|
||||
|
||||
pub fn take(s: &str) -> Result<(Month, &str), TakeError> {
|
||||
let (month, rest) = s.split_once('/').context(NotADirectorySnafu)?;
|
||||
|
||||
let month = month.parse().context(ParseMonthNameSnafu)?;
|
||||
|
||||
Ok((month, rest))
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum MonthEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a month
|
||||
ParseError { source: TakeError },
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn months(
|
||||
&self,
|
||||
year: Year,
|
||||
) -> Result<impl TryStream<Ok = Month, Error = MonthEntryError> + Unpin, ListError> {
|
||||
let lister = self
|
||||
.operator
|
||||
.lister(&format!("{year}/"))
|
||||
.await
|
||||
.context(CreateListerSnafu)?;
|
||||
|
||||
Ok(lister
|
||||
.map_err(|error| MonthEntryError::ReceiveEntryError { source: error })
|
||||
.and_then(|entry| {
|
||||
std::future::ready(
|
||||
take(entry.name())
|
||||
.map(|(month, rest)| month)
|
||||
.context(ParseSnafu),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
143
src/recording_data/recording.rs
Normal file
143
src/recording_data/recording.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use hound::{SampleFormat, WavSpec};
|
||||
use snafu::{ResultExt as _, Snafu};
|
||||
use std::{convert::Infallible, fmt::Display, io::Cursor, str::FromStr};
|
||||
use time::UtcDateTime;
|
||||
use twilight_model::id::{
|
||||
Id,
|
||||
marker::{ChannelMarker, GuildMarker},
|
||||
};
|
||||
|
||||
use super::{
|
||||
Clip, Day, Hour, ListError, Minute, Month, RecordingDataManager, Year, clip, day, hour, minute,
|
||||
month, year,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Recording {
|
||||
pub year: Year,
|
||||
pub month: Month,
|
||||
pub day: Day,
|
||||
pub hour: Hour,
|
||||
pub minute: Minute,
|
||||
pub clip: Clip,
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// could not parse the year out of the recording
|
||||
TakeYearError { source: year::TakeError },
|
||||
|
||||
/// could not parse the month out of the recording
|
||||
TakeMonthError { source: month::TakeError },
|
||||
|
||||
/// could not parse the day out of the recording
|
||||
TakeDayError { source: day::TakeError },
|
||||
|
||||
/// could not parse the hour out of the recording
|
||||
TakeHourError { source: hour::TakeError },
|
||||
|
||||
/// could not parse the minute out of the recording
|
||||
TakeMinuteError { source: minute::TakeError },
|
||||
|
||||
/// could not parse the clip out of the recording
|
||||
TakeClipError { source: clip::TakeError },
|
||||
}
|
||||
|
||||
pub fn take(s: &str) -> Result<Recording, TakeError> {
|
||||
let (year, s) = year::take(s).context(TakeYearSnafu)?;
|
||||
let (month, s) = month::take(s).context(TakeMonthSnafu)?;
|
||||
let (day, s) = day::take(s).context(TakeDaySnafu)?;
|
||||
let (hour, s) = hour::take(s).context(TakeHourSnafu)?;
|
||||
let (minute, s) = minute::take(s).context(TakeMinuteSnafu)?;
|
||||
let clip = clip::take(s).context(TakeClipSnafu)?;
|
||||
|
||||
Ok(Recording {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
clip,
|
||||
})
|
||||
}
|
||||
|
||||
impl FromStr for Recording {
|
||||
type Err = TakeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
take(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Recording {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
clip,
|
||||
} = self;
|
||||
|
||||
write!(f, "{year}/{month}/{day}/{hour}/{minute}/{clip}")
|
||||
}
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn write(
|
||||
&self,
|
||||
recording: &Recording,
|
||||
samples: &[i16],
|
||||
sample_rate: u32,
|
||||
channels: u16,
|
||||
) -> Result<
|
||||
(),
|
||||
Infallible, // TODO: a real error type
|
||||
> {
|
||||
let wav_spec = WavSpec {
|
||||
channels,
|
||||
sample_rate,
|
||||
bits_per_sample: 16,
|
||||
sample_format: SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
let writer = Cursor::new(&mut buffer);
|
||||
|
||||
let mut wav_writer = hound::WavWriter::new(writer, wav_spec).expect("TODO");
|
||||
|
||||
let mut sample_writer = wav_writer.get_i16_writer(samples.len() as u32);
|
||||
|
||||
for sample in samples {
|
||||
sample_writer.write_sample(*sample);
|
||||
}
|
||||
sample_writer.flush().expect("TODO");
|
||||
|
||||
wav_writer.finalize().expect("TODO");
|
||||
|
||||
let path = recording.to_string();
|
||||
|
||||
self.operator.write(&path, buffer).await.expect("TODO");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn read(&self, recording: &Recording, sample_rate: u32, channels: u16) -> Vec<i16> {
|
||||
let path = recording.to_string();
|
||||
|
||||
let buffer = self.operator.read(&path).await.expect("TODO");
|
||||
|
||||
let wav_spec = WavSpec {
|
||||
channels,
|
||||
sample_rate,
|
||||
bits_per_sample: 16,
|
||||
sample_format: SampleFormat::Int,
|
||||
};
|
||||
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
25
src/recording_data/second.rs
Normal file
25
src/recording_data/second.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
pub type Second = u8;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// seconds are supposed to be preceded by audio-
|
||||
MalformedPrefix,
|
||||
|
||||
/// seconds are supposed to be followed by .
|
||||
MalformedSuffix,
|
||||
|
||||
/// could not parse the second as an integer
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(path: &str) -> Result<(Second, &str), TakeError> {
|
||||
let (_prefix, path) = path.split_once("audio-").context(MalformedPrefixSnafu)?;
|
||||
let (second, rest) = path.split_once('.').context(MalformedSuffixSnafu)?;
|
||||
|
||||
let second = second.parse().context(ParseIntegerSnafu)?;
|
||||
|
||||
Ok((second, rest))
|
||||
}
|
||||
25
src/recording_data/user.rs
Normal file
25
src/recording_data/user.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
use twilight_model::id::{Id, marker::UserMarker};
|
||||
|
||||
pub type User = Option<Id<UserMarker>>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// users are supposed to be terminated by .wav
|
||||
Malformed,
|
||||
|
||||
/// could not parse the user ID
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(path: &str) -> Result<User, TakeError> {
|
||||
let user = path.strip_suffix(".wav").context(MalformedSnafu)?;
|
||||
|
||||
let user = match user {
|
||||
"UNKNOWN" => None,
|
||||
user => Some(user.parse().context(ParseIntegerSnafu)?),
|
||||
};
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
26
src/recording_data/voice_channel.rs
Normal file
26
src/recording_data/voice_channel.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
use twilight_model::id::Id;
|
||||
use twilight_model::id::marker::ChannelMarker;
|
||||
|
||||
pub type VoiceChannel = Id<ChannelMarker>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// voice channels are supposed to be followed by -
|
||||
Malformed,
|
||||
|
||||
/// could not parse the voice channel ID
|
||||
ParseIdError {
|
||||
source: <VoiceChannel as FromStr>::Err,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn take(path: &str) -> Result<(VoiceChannel, &str), TakeError> {
|
||||
let (voice_channel, rest) = path.split_once('-').context(MalformedSnafu)?;
|
||||
|
||||
let voice_channel = voice_channel.parse().context(ParseIdSnafu)?;
|
||||
|
||||
Ok((voice_channel, rest))
|
||||
}
|
||||
51
src/recording_data/year.rs
Normal file
51
src/recording_data/year.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use futures::{TryStream, TryStreamExt as _};
|
||||
use snafu::{OptionExt as _, ResultExt as _, Snafu};
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use super::{CreateListerSnafu, ListError, RecordingDataManager};
|
||||
|
||||
pub type Year = i32;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum TakeError {
|
||||
/// years are supposed to be directories, but this wasn't (because it didn't end with `/`)
|
||||
NotADirectory,
|
||||
|
||||
/// could not parse the year as an integer
|
||||
ParseIntegerError { source: ParseIntError },
|
||||
}
|
||||
|
||||
pub fn take(path: &str) -> Result<(Year, &str), TakeError> {
|
||||
let (year, rest) = path.split_once('/').context(NotADirectorySnafu)?;
|
||||
|
||||
let year = year.parse().context(ParseIntegerSnafu)?;
|
||||
|
||||
Ok((year, rest))
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum YearEntryError {
|
||||
/// failed to get an entry from the storage operator's lister
|
||||
ReceiveEntryError { source: opendal::Error },
|
||||
|
||||
/// failed to parse the entry as a year
|
||||
ParseError { source: TakeError },
|
||||
}
|
||||
|
||||
impl RecordingDataManager {
|
||||
pub async fn years(
|
||||
&self,
|
||||
) -> Result<impl TryStream<Ok = Year, Error = YearEntryError> + Unpin, ListError> {
|
||||
let lister = self.operator.lister("").await.context(CreateListerSnafu)?;
|
||||
|
||||
Ok(lister
|
||||
.map_err(|error| YearEntryError::ReceiveEntryError { source: error })
|
||||
.and_then(|entry| {
|
||||
std::future::ready(
|
||||
take(entry.name())
|
||||
.map(|(year, _rest)| year)
|
||||
.context(ParseSnafu),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user