1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
mod icon_info;
mod icon_size;

pub use icon_info::*;
pub use icon_size::*;

use itertools::Itertools;
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{
  cmp::Ordering,
  collections::HashMap,
  convert::TryInto,
  error::Error,
  fmt::{self, Display},
  hash::{Hash, Hasher},
  str::FromStr,
};
use url::Url;

#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, SerializeDisplay, DeserializeFromStr)]
pub enum IconKind {
  AppIcon,
  SiteFavicon,
  SiteLogo,
}

impl Display for IconKind {
  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    f.write_str(match self {
      IconKind::SiteLogo => "site_logo",
      IconKind::AppIcon => "app_icon",
      IconKind::SiteFavicon => "site_favicon",
    })
  }
}

impl FromStr for IconKind {
  type Err = String;

  fn from_str(kind: &str) -> Result<Self, Self::Err> {
    match kind {
      "site_logo" => Ok(IconKind::SiteLogo),
      "app_icon" => Ok(IconKind::AppIcon),
      "site_favicon" => Ok(IconKind::SiteFavicon),
      _ => Err("unknown icon kind!".into()),
    }
  }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Icon {
  pub url: Url,
  pub headers: HashMap<String, String>,
  pub kind: IconKind,
  #[serde(flatten)]
  pub info: IconInfo,
}

impl Hash for Icon {
  fn hash<H: Hasher>(&self, state: &mut H) {
    (
      &self.url,
      self
        .headers
        .iter()
        .sorted_by_key(|(key, _)| *key)
        .collect::<Vec<_>>(),
    )
      .hash(state);
  }
}

impl Icon {
  pub fn new(url: Url, kind: IconKind, info: IconInfo) -> Self {
    Icon::new_with_headers(url, HashMap::new(), kind, info)
  }

  pub fn new_with_headers(
    url: Url,
    headers: HashMap<String, String>,
    kind: IconKind,
    info: IconInfo,
  ) -> Self {
    Self {
      url,
      headers,
      kind,
      info,
    }
  }

  pub async fn load(
    url: Url,
    kind: IconKind,
    sizes: Option<String>,
  ) -> Result<Self, Box<dyn Error>> {
    Icon::load_with_headers(url, HashMap::new(), kind, sizes).await
  }

  pub async fn load_with_headers(
    url: Url,
    headers: HashMap<String, String>,
    kind: IconKind,
    sizes: Option<String>,
  ) -> Result<Self, Box<dyn Error>> {
    let info = IconInfo::load(url.clone(), (&headers).try_into().unwrap(), sizes).await?;

    Ok(Icon::new_with_headers(url, headers, kind, info))
  }
}

impl Ord for Icon {
  fn cmp(&self, other: &Self) -> Ordering {
    self.info.cmp(&other.info)
  }
}

impl PartialOrd for Icon {
  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    Some(self.cmp(other))
  }
}