diff --git a/crates/torii/server/src/artifacts.rs b/crates/torii/server/src/artifacts.rs index dffda63f0b..4539773994 100644 --- a/crates/torii/server/src/artifacts.rs +++ b/crates/torii/server/src/artifacts.rs @@ -57,7 +57,21 @@ async fn serve_static_file( let token_image_dir = artifacts_dir.join(parts[0]).join(parts[1]); let token_id = format!("{}:{}", parts[0], parts[1]); - if !token_image_dir.exists() { + + // Check if image needs to be refetched + let should_fetch = if token_image_dir.exists() { + match check_image_hash(&token_image_dir, &token_id, &pool).await { + Ok(needs_update) => needs_update, + Err(e) => { + error!(error = %e, "Failed to check image hash, will attempt to fetch"); + true + } + } + } else { + true + }; + + if should_fetch { match fetch_and_process_image(&artifacts_dir, &token_id, pool).await { Ok(path) => path, Err(e) => { @@ -145,6 +159,40 @@ pub async fn new( })) } +async fn check_image_hash( + token_image_dir: &Utf8PathBuf, + token_id: &str, + pool: &Pool, +) -> Result { + let hash_file = token_image_dir.join("image.hash"); + + // Get current image URI from metadata + let query = sqlx::query_as::<_, (String,)>(&format!( + "SELECT metadata FROM {TOKENS_TABLE} WHERE id = ?" + )) + .bind(token_id) + .fetch_one(pool) + .await + .context("Failed to fetch metadata from database")?; + + let metadata: serde_json::Value = + serde_json::from_str(&query.0).context("Failed to parse metadata")?; + let current_uri = metadata + .get("image") + .context("Image URL not found in metadata")? + .as_str() + .context("Image field not a string")?; + + // Check if hash file exists and compare + if hash_file.exists() { + let stored_hash = + fs::read_to_string(&hash_file).await.context("Failed to read hash file")?; + Ok(stored_hash != current_uri) + } else { + Ok(true) + } +} + async fn fetch_and_process_image( artifacts_path: &Utf8PathBuf, token_id: &str, @@ -156,25 +204,23 @@ async fn fetch_and_process_image( .bind(token_id) .fetch_one(&pool) .await - .with_context(|| { - format!("Failed to fetch metadata from database for token_id: {}", token_id) - })?; + .context("Failed to fetch metadata from database")?; let metadata: serde_json::Value = serde_json::from_str(&query.0).context("Failed to parse metadata")?; let image_uri = metadata .get("image") - .with_context(|| format!("Image URL not found in metadata for token_id: {}", token_id))? + .context("Image URL not found in metadata")? .as_str() - .with_context(|| format!("Image field not a string for token_id: {}", token_id))? + .context("Image field not a string")? .to_string(); - let image_type = match image_uri { + let image_type = match &image_uri { uri if uri.starts_with("http") || uri.starts_with("https") => { debug!(image_uri = %uri, "Fetching image from http/https URL"); // Fetch image from HTTP/HTTPS URL let response = - fetch_content_from_http(&uri).await.context("Failed to fetch image from URL")?; + fetch_content_from_http(uri).await.context("Failed to fetch image from URL")?; // svg files typically start with {