sugar_cli/
cache.rs

1use std::{
2    ops::{Deref, DerefMut},
3    path::Path,
4};
5
6use anchor_client::solana_sdk::pubkey::Pubkey;
7use anyhow::Result;
8use mpl_candy_machine_core::ConfigLine;
9use serde::{Deserialize, Serialize};
10
11use crate::{common::*, pdas::find_candy_machine_creator_pda};
12
13#[derive(Debug, Deserialize, Serialize)]
14pub struct Cache {
15    pub program: CacheProgram,
16    pub items: CacheItems,
17    #[serde(skip_deserializing, skip_serializing)]
18    pub file_path: String,
19}
20
21impl Cache {
22    pub fn new() -> Self {
23        Cache {
24            program: CacheProgram::new(),
25            items: CacheItems::new(),
26            file_path: String::new(),
27        }
28    }
29
30    pub fn write_to_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
31        let f = File::create(path)?;
32        serde_json::to_writer_pretty(f, &self)?;
33
34        Ok(())
35    }
36
37    pub fn sync_file(&mut self) -> Result<()> {
38        let file_path = self.file_path.clone();
39        self.write_to_file(Path::new(&file_path))
40    }
41}
42
43impl Default for Cache {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49#[derive(Debug, Deserialize, Serialize)]
50pub struct CacheProgram {
51    #[serde(rename = "candyMachine")]
52    pub candy_machine: String,
53    #[serde(rename = "candyGuard")]
54    pub candy_guard: String,
55    #[serde(rename = "candyMachineCreator")]
56    pub candy_machine_creator: String,
57    #[serde(rename = "collectionMint")]
58    pub collection_mint: String,
59}
60
61impl CacheProgram {
62    pub fn new() -> Self {
63        CacheProgram {
64            candy_machine: String::new(),
65            candy_guard: String::new(),
66            candy_machine_creator: String::new(),
67            collection_mint: String::new(),
68        }
69    }
70
71    pub fn new_from_cm(candy_machine: &Pubkey) -> Self {
72        let (candy_machine_creator_pda, _creator_bump) =
73            find_candy_machine_creator_pda(candy_machine);
74        CacheProgram {
75            candy_machine: candy_machine.to_string(),
76            candy_guard: String::new(),
77            candy_machine_creator: candy_machine_creator_pda.to_string(),
78            collection_mint: String::new(),
79        }
80    }
81}
82
83impl Default for CacheProgram {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89#[derive(Debug, Deserialize, Serialize)]
90pub struct CacheItems(pub IndexMap<String, CacheItem>);
91
92impl Deref for CacheItems {
93    type Target = IndexMap<String, CacheItem>; // Our wrapper struct will coerce into Option
94    fn deref(&self) -> &IndexMap<String, CacheItem> {
95        &self.0 // We just extract the inner element
96    }
97}
98
99impl DerefMut for CacheItems {
100    fn deref_mut(&mut self) -> &mut IndexMap<String, CacheItem> {
101        &mut self.0
102    }
103}
104
105impl CacheItems {
106    pub fn new() -> Self {
107        CacheItems(IndexMap::new())
108    }
109}
110impl Default for CacheItems {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116#[derive(Clone, Debug, Deserialize, Serialize)]
117pub struct CacheItem {
118    pub name: String,
119    #[serde(default = "String::default")]
120    pub image_hash: String,
121    pub image_link: String,
122    #[serde(default = "String::default")]
123    pub metadata_hash: String,
124    pub metadata_link: String,
125    #[serde(rename = "onChain")]
126    pub on_chain: bool,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub animation_hash: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub animation_link: Option<String>,
131}
132
133impl CacheItem {
134    pub fn to_config_line(&self) -> Option<ConfigLine> {
135        if !self.on_chain {
136            Some(ConfigLine {
137                name: self.name.clone(),
138                uri: self.metadata_link.clone(),
139            })
140        } else {
141            None
142        }
143    }
144}
145
146pub fn load_cache(cache_file_path: &str, create: bool) -> Result<Cache> {
147    let cache_file_path = Path::new(cache_file_path);
148    if !cache_file_path.exists() {
149        if create {
150            // if the cache file does not exist, creates a new Cache object
151            let mut cache = Cache::new();
152            cache.file_path = path_to_string(cache_file_path)?;
153            Ok(cache)
154        } else {
155            let cache_file_string = path_to_string(cache_file_path)?;
156            let error = CacheError::CacheFileNotFound(cache_file_string).into();
157            error!("{:?}", error);
158            Err(error)
159        }
160    } else {
161        info!("Cache exists, loading...");
162        let file = match File::open(cache_file_path) {
163            Ok(file) => file,
164            Err(err) => {
165                let cache_file_string = path_to_string(cache_file_path)?;
166                let error =
167                    CacheError::FailedToOpenCacheFile(cache_file_string, err.to_string()).into();
168                error!("{:?}", error);
169                return Err(error);
170            }
171        };
172
173        let mut cache: Cache = match serde_json::from_reader(file) {
174            Ok(cache) => cache,
175            Err(err) => {
176                let error = CacheError::CacheFileWrongFormat(err.to_string()).into();
177                error!("{:?}", error);
178                return Err(error);
179            }
180        };
181        cache.file_path = path_to_string(cache_file_path)?;
182
183        Ok(cache)
184    }
185}