nft_server/generators/
disk.rs1use std::{collections::HashMap, path::PathBuf};
2
3use async_trait::async_trait;
4use ethers::types::U256;
5use eyre::Result;
6use serde::de::DeserializeOwned;
7use tokio::sync::RwLock;
8
9use crate::{open_sea::ContractMetadata, MetadataGenerator, NftMetadata};
10
11#[derive(Debug)]
25pub struct LocalJson {
26 location: PathBuf,
27 cache: RwLock<HashMap<U256, NftMetadata>>,
28 contract_cache: RwLock<Option<ContractMetadata>>,
29}
30
31#[derive(thiserror::Error, Debug)]
33pub enum LocalJsonError {
34 #[error("{0}")]
36 Serde(#[from] serde_json::Error),
37 #[error("{0}")]
39 Filesystem(#[from] std::io::Error),
40}
41
42impl LocalJson {
43 pub fn new(location: PathBuf) -> Result<Self> {
51 eyre::ensure!(
52 !location.exists() || location.is_dir(),
53 "location exists and is not a directory"
54 );
55 std::fs::create_dir_all(&location)?;
56 Ok(Self {
57 location,
58 cache: Default::default(),
59 contract_cache: Default::default(),
60 })
61 }
62
63 async fn load_json<T, S>(&self, file_name: S) -> Result<Option<T>, LocalJsonError>
65 where
66 T: DeserializeOwned,
67 S: AsRef<str>,
68 {
69 let path = self.location.with_file_name(file_name.as_ref());
70 let raw = tokio::fs::read(path).await;
71 match raw {
72 Ok(raw) => Ok(serde_json::from_slice(&raw)?),
73 Err(e) => {
74 if e.kind() == tokio::io::ErrorKind::NotFound {
75 Ok(None)
76 } else {
77 Err(e.into())
78 }
79 }
80 }
81 }
82
83 async fn load_metadata(&self, token_id: U256) -> Result<Option<NftMetadata>, LocalJsonError> {
84 if let Some(metadata) = self.cache.read().await.get(&token_id).cloned() {
85 return Ok(Some(metadata));
86 } else if let Some(metadata) = self
87 .load_json::<NftMetadata, _>(format!("{}.json", token_id))
88 .await?
89 {
90 self.cache.write().await.insert(token_id, metadata.clone());
91 return Ok(Some(metadata));
92 }
93 Ok(None)
94 }
95
96 async fn load_contract_metadata(&self) -> Option<ContractMetadata> {
97 match *(self.contract_cache.read().await) {
98 Some(ref metadata) => Some(metadata.clone()),
99 None => match self.load_json::<ContractMetadata, _>("contract.json").await {
100 Ok(Some(metadata)) => {
101 self.contract_cache.write().await.replace(metadata.clone());
102 Some(metadata)
103 }
104 _ => None,
105 },
106 }
107 }
108}
109
110#[async_trait]
111impl MetadataGenerator for LocalJson {
112 type Error = LocalJsonError;
113
114 async fn metadata_for(&self, token_id: U256) -> Result<Option<NftMetadata>, Self::Error> {
115 self.load_metadata(token_id).await
116 }
117
118 async fn contract_metadata(&self) -> Option<ContractMetadata> {
119 self.load_contract_metadata().await
120 }
121}