use crate::{
app::clips::query::ClipsQuery,
models::{
clips::{
games::ClipsGames,
hosted::{ClipsStatusType, HostedClip},
local::LocalClip,
unified::{SelectedClip, UnifiedClip, UnifiedClipType},
},
error::WayclipError,
},
settings::UserSettings,
};
use chrono::{DateTime, Local};
use colored::ColoredString;
use std::fmt::Display;
#[derive(Debug, Clone, Default)]
pub struct LocalClipInfo {
name: String,
tags: Vec<ColoredString>,
game: Option<ClipsGames>,
created: DateTime<Local>,
uploaded: Option<DateTime<Local>>,
duration: f32,
file_size_mb: u64,
file_name: String,
}
#[derive(Debug, Clone, Default)]
pub struct HostedClipInfo {
hosted_id: String,
hosted_status: String,
hosted_link: Option<String>,
hosted_usage: i32,
}
#[derive(Debug, Clone, Default)]
pub struct SelectedClipInfo {
local: LocalClipInfo,
hosted: HostedClipInfo,
}
impl Display for SelectedClipInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.local.file_name.is_empty() {
writeln!(f, "Name: {}", self.local.name)?;
if !self.local.tags.is_empty() {
let tags_str = self
.local
.tags
.iter()
.map(|t| format!("[{}]", t))
.collect::<Vec<_>>()
.join(", ");
writeln!(f, "Tags: {}", tags_str)?;
}
if let Some(ref game) = self.local.game {
writeln!(f, "Game: {:?}", game.display_name())?;
}
writeln!(
f,
"Created: {}",
self.local.created.format("%Y-%m-%d %H:%M:%S")
)?;
if let Some(u) = self.local.uploaded {
writeln!(f, "Uploaded: {}", u.format("%Y-%m-%d %H:%M:%S"))?;
}
writeln!(f, "Duration: {:.2}s", self.local.duration)?;
writeln!(f, "Local file size: {}MB", self.local.file_size_mb)?;
writeln!(f, "File name: {}", self.local.file_name)?;
}
if !self.hosted.hosted_id.is_empty() {
writeln!(f, "Hosted ID: {}", self.hosted.hosted_id)?;
if let Some(ref link) = self.hosted.hosted_link {
writeln!(f, "Hosted link: {}", link)?;
}
writeln!(f, "Status: {}", self.hosted.hosted_status)?;
writeln!(f, "Hosted storage usage: {}MB", self.hosted.hosted_usage)?;
}
Ok(())
}
}
impl From<&HostedClip> for HostedClipInfo {
fn from(hosted: &HostedClip) -> Self {
let mut info = HostedClipInfo {
hosted_id: hosted.clip_id.clone(),
hosted_usage: hosted.file_size_mb,
..Default::default()
};
match (
&hosted.preview_status,
&hosted.clip_status,
&hosted.thumbnail_status,
) {
(ClipsStatusType::Ready, ClipsStatusType::Ready, ClipsStatusType::Ready) => {
info.hosted_link = Some(
format!("https://wayclip.com/clips/{}", hosted.clip_id),
);
info.hosted_status = hosted.clip_visibility.to_string();
}
_ => {
info.hosted_status = "Pending".to_string();
}
}
info
}
}
impl From<&LocalClip> for LocalClipInfo {
fn from(local: &LocalClip) -> Self {
let mut info = LocalClipInfo {
name: local.name.clone(),
tags: local
.clone()
.clip_tags
.iter()
.map(|t| t.get_colored_string())
.collect::<Vec<_>>(),
game: local.detected_game,
created: DateTime::from(local.created_at),
duration: local.file_duration_ms as f32 / 1000.0,
file_size_mb: local.file_size_mb,
file_name: local
.video_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string(),
..Default::default()
};
if local.uploaded_id.is_some()
&& let Some(time) = local.uploaded_at
{
let local_time: DateTime<Local> = DateTime::from(time);
info.uploaded = Some(local_time);
}
info
}
}
impl From<&UnifiedClip> for SelectedClipInfo {
fn from(unified_clip: &UnifiedClip) -> Self {
let mut info = SelectedClipInfo::default();
if let Some(ref local) = unified_clip.local {
info.local = local.into();
}
if let Some(ref hosted) = unified_clip.hosted {
info.hosted = hosted.into()
}
info
}
}
impl From<&SelectedClip> for SelectedClipInfo {
fn from(selected_clip: &SelectedClip) -> Self {
let (local, hosted): (Option<&LocalClip>, Option<&HostedClip>) = match selected_clip {
SelectedClip::Both(local, hosted) => (Some(local), Some(hosted)),
SelectedClip::Local(local) => (Some(local), None),
SelectedClip::Hosted(hosted) => (None, Some(hosted)),
};
let mut info = SelectedClipInfo::default();
if let Some(local) = local {
info.local = local.into();
}
if let Some(hosted) = hosted {
info.hosted = hosted.into()
}
info
}
}
impl UnifiedClip {
pub async fn get_all_clips() -> Result<Vec<Self>, WayclipError> {
let settings = UserSettings::load()?;
let mut unified_clips: Vec<Self> = Vec::new();
let mut hosted_clips: Vec<HostedClip> = match settings.api.enabled {
true => ClipsQuery::get_all_hosted_clips().await.unwrap_or_default(),
false => Vec::new(),
};
let local_clips = ClipsQuery::get_all_local_clips().await?;
for mut local_clip in local_clips {
let mut unified = UnifiedClip {
name: local_clip.name.clone(),
created_at: local_clip.created_at,
local: Some(local_clip.clone()),
hosted: None,
};
if let Some(ref uploaded_id) = local_clip.uploaded_id {
match hosted_clips.iter().position(|c| &c.clip_id == uploaded_id) {
Some(pos) => {
let hosted_clip = hosted_clips.remove(pos);
unified.hosted = Some(hosted_clip);
}
None => {
log::warn!(
"Clip {} is flagged as hosted, but was not found on remote under the ID {}",
local_clip.name,
uploaded_id
);
local_clip.uploaded_id = None;
local_clip.uploaded_at = None;
local_clip.update_metadata()?;
}
}
}
unified_clips.push(unified);
}
for hosted_clip in hosted_clips {
if !unified_clips.iter().any(|c| {
matches!(
&c.local,
Some(local) if local.uploaded_id == Some(hosted_clip.clip_id.clone())
)
}) {
unified_clips.push(UnifiedClip {
name: hosted_clip.clip_name.clone(),
created_at: hosted_clip.uploaded_at.into(),
local: None,
hosted: Some(hosted_clip),
})
}
}
Ok(unified_clips)
}
}
impl From<&UnifiedClip> for UnifiedClipType {
fn from(value: &UnifiedClip) -> Self {
match (&value.local, &value.hosted) {
(Some(_), Some(_)) => UnifiedClipType::Both,
(Some(_), None) => UnifiedClipType::LocalOnly,
(None, Some(_)) => UnifiedClipType::HostedOnly,
(None, None) => UnifiedClipType::None,
}
}
}