c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

compare_manifests.rs (7615B)


      1 // Copyright 2024 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 /// Compares two manifest stores and prints out the differences.
     15 use std::collections::HashMap;
     16 use std::{fs, path::Path};
     17 
     18 use c2pa::{Error, Reader as ManifestStore, Result};
     19 
     20 /// Compares all the files in two directories and returns a list of issues
     21 pub fn compare_folders<P: AsRef<Path>, Q: AsRef<Path>>(folder1: P, folder2: Q) -> Result<()> {
     22     let folder1 = folder1.as_ref();
     23     let folder2 = folder2.as_ref();
     24 
     25     // handle the case we have files instead of folders
     26     if folder1.is_file() && folder2.is_file() {
     27         let issues = compare_image_manifests(folder1, folder2)?;
     28         if !issues.is_empty() {
     29             eprintln!("Failed {:?}", folder1);
     30             for issue in issues {
     31                 eprintln!("  {}", issue);
     32             }
     33         } else {
     34             println!("Passed {:?}", folder1);
     35         }
     36         return Ok(());
     37     } else if !(folder1.is_dir() && folder2.is_dir()) {
     38         eprintln!("must be two folders or two files");
     39         return Err(Error::BadParam(
     40             "must be two folders or two files".to_string(),
     41         ));
     42     }
     43 
     44     for entry in fs::read_dir(folder1)? {
     45         let entry = entry?;
     46         let path = entry.path();
     47         if path.is_file() {
     48             let relative_path = path.strip_prefix(folder1).unwrap();
     49             let other_path = folder2.join(relative_path);
     50             //println!("Comparing {:?} to {:?}", path, other_path);
     51             let mut issues = Vec::new();
     52             if other_path.exists() {
     53                 let result = compare_image_manifests(&path, &other_path)?;
     54                 issues.extend(result);
     55             } else {
     56                 issues.push(format!(
     57                     "File {} does not exist in {}",
     58                     relative_path.display(),
     59                     folder2.display()
     60                 ));
     61             }
     62             if !issues.is_empty() {
     63                 eprintln!("Failed {:?}", relative_path);
     64                 for issue in issues {
     65                     eprintln!("  {}", issue);
     66                 }
     67             } else {
     68                 println!("Passed {:?}", relative_path);
     69             }
     70         }
     71     }
     72     Ok(())
     73 }
     74 
     75 /// Compares files with manifest stores and returns a list of issues
     76 pub fn compare_image_manifests<P: AsRef<Path>, Q: AsRef<Path>>(
     77     m1: P,
     78     m2: Q,
     79 ) -> Result<Vec<String>> {
     80     let manifest_store1 = match m1.as_ref().extension() {
     81         Some(ext) if ext == "json" => {
     82             ManifestStore::from_json(&fs::read_to_string(m1)?)
     83             //serde_json::from_str(&fs::read_to_string(m1)?).map_err(Error::JsonError)
     84         }
     85         _ => ManifestStore::from_file(m1.as_ref()),
     86     };
     87     let manifest_store2 = match m2.as_ref().extension() {
     88         Some(ext) if ext == "json" => ManifestStore::from_json(&fs::read_to_string(m2)?),
     89         _ => ManifestStore::from_file(m2.as_ref()),
     90     };
     91     // let manifest_store1 = ManifestStore::from_file(m1);
     92     // let manifest_store2 = ManifestStore::from_file(m2);
     93     match (manifest_store1, manifest_store2) {
     94         (Ok(manifest_store1), Ok(manifest_store2)) => {
     95             compare_manifests(&manifest_store1, &manifest_store2)
     96         }
     97         (Err(Error::JumbfNotFound), Err(Error::JumbfNotFound)) => Ok(Vec::new()),
     98         (_, Err(err)) => Err(err),
     99         (Err(err), _) => Err(err),
    100     }
    101 }
    102 
    103 /// Compares two manifest stores and returns a list of issues.
    104 pub fn compare_manifests(
    105     manifest_store1: &ManifestStore,
    106     manifest_store2: &ManifestStore,
    107 ) -> Result<Vec<String>> {
    108     // first we need to gather all the manifests in the order they are first seen recursively
    109     let mut labels1 = Vec::new();
    110     if let Some(label) = manifest_store1.active_label() {
    111         gather_manifests(manifest_store1, label, &mut labels1);
    112     }
    113     let mut labels2 = Vec::new();
    114     if let Some(label) = manifest_store2.active_label() {
    115         gather_manifests(manifest_store2, label, &mut labels2);
    116     }
    117     // now we have two lists of manifests, we need to match them up
    118     let manifest_map: HashMap<_, _> = labels1.into_iter().zip(labels2).collect();
    119 
    120     // now we can compare the manifests
    121     let mut issues = Vec::new();
    122     for (label1, label2) in manifest_map.iter() {
    123         // let foo = serde_json::to_string(&manifest_store1.get(label1))?;
    124         // let foo = serde_json::from_str(&foo)?;
    125         // convert manifests into json values and compare them
    126         let value1 = serde_json::to_value(manifest_store1.get_manifest(label1))?;
    127         let value2 = serde_json::to_value(manifest_store2.get_manifest(label2))?;
    128         compare_json_values(
    129             &format!("manifests.{}", label1),
    130             &value1,
    131             &value2,
    132             &mut issues,
    133         );
    134     }
    135     Ok(issues)
    136 }
    137 
    138 // creates list of manifests in the order they are first seen from the active manifest
    139 fn gather_manifests(
    140     manifest_store: &ManifestStore,
    141     manifest_label: &str,
    142     labels: &mut Vec<String>,
    143 ) {
    144     if !labels.contains(&manifest_label.to_string()) {
    145         labels.push(manifest_label.to_string());
    146     }
    147     if let Some(manifest) = manifest_store.get_manifest(manifest_label) {
    148         for ingredient in manifest.ingredients() {
    149             if let Some(label) = ingredient.active_manifest() {
    150                 gather_manifests(manifest_store, label, labels);
    151             }
    152         }
    153     }
    154 }
    155 
    156 /// Recursively compare two ManifestStore JSON values
    157 fn compare_json_values(
    158     path: &str,
    159     val1: &serde_json::Value,
    160     val2: &serde_json::Value,
    161     issues: &mut Vec<String>,
    162 ) {
    163     match (val1, val2) {
    164         (serde_json::Value::Object(map1), serde_json::Value::Object(map2)) => {
    165             for (key, val1) in map1 {
    166                 let val2 = map2.get(key).unwrap_or(&serde_json::Value::Null);
    167                 compare_json_values(&format!("{}.{}", path, key), val1, val2, issues);
    168             }
    169 
    170             for (key, value) in map2 {
    171                 if map1.get(key).is_none() {
    172                     issues.push(format!("Added {}.{}: {}", path, key, value));
    173                 }
    174             }
    175         }
    176         (serde_json::Value::Array(arr1), serde_json::Value::Array(arr2)) => {
    177             for (i, (val1, val2)) in arr1.iter().zip(arr2.iter()).enumerate() {
    178                 compare_json_values(&format!("{}[{}]", path, i), val1, val2, issues);
    179             }
    180         }
    181         (val1, val2) if val1 != val2 => {
    182             if !(path.ends_with(".instance_id")
    183                 || path.ends_with(".instanceId")
    184                 || path.ends_with(".time")
    185                 || path.contains(".hash")
    186                 || val1.is_string() && val2.is_string() && val1.to_string().contains(":urn:uuid:"))
    187             {
    188                 if val2.is_null() {
    189                     issues.push(format!("Missing {}: {}", path, val1));
    190                 } else if val2.is_null() {
    191                     issues.push(format!("Added {}: {}", path, val2));
    192                 } else {
    193                     issues.push(format!("Changed {}: {} vs {}", path, val1, val2));
    194                 }
    195             }
    196         }
    197         _ => (),
    198     }
    199 }