use mlua::AsChunk; use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; use std::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; use std::sync::Arc; use crate::models::recipe::*; use crate::recipe_functions::common::{self}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RecipeListChange { pub new_value: Value, pub raw_old: Vec, pub raw_new: Vec, /// for sub menu pub sub_changes: BTreeMap, } impl RecipeListChange { pub fn get_changes(&mut self) { let old_length = self.raw_old.len(); let new_length = self.raw_new.len(); let is_match_length = old_length == new_length; let length_for_checking = if is_match_length { old_length } else { if old_length < new_length { new_length } else { old_length } }; let mut checked: Vec> = vec![Vec::new(); length_for_checking]; for ci in 0..length_for_checking { let current_repl = (self.raw_old.get(ci), self.raw_new.get(ci)); if current_repl.0.is_some() && current_repl.1.is_some() { let old_repl = current_repl.0.unwrap(); let new_repl = current_repl.1.unwrap(); let diff = old_repl.compare(new_repl); checked[ci] = diff; } else if current_repl.0.is_none() && current_repl.1.is_some() { // new value checked[ci] = current_repl .1 .unwrap() .to_map() .as_object() .unwrap() .keys() .map(|k| k.to_string()) .collect(); } else if current_repl.0.is_some() && current_repl.1.is_none() { // new value does not exist checked[ci] = current_repl .0 .unwrap() .to_map() .as_object() .unwrap() .keys() .map(|k| format!("old.{}", k).to_string()) .collect(); } } self.new_value = serde_json::to_value(checked).unwrap(); } } pub struct ShardingRecipe { pub shard_name: String, pub category_map: BTreeMap, } impl ShardingRecipe { pub fn build(name: String, data: BTreeMap) -> Self { let keylist: Vec = data.keys().map(|k| k.to_string()).collect(); let mut category_map = BTreeMap::new(); for key in keylist { let kspl: Vec<&str> = key.split("-").collect(); if kspl.len() > 3 { let category = kspl[1].to_string(); if !category_map.contains_key(&category.clone()) { category_map.insert(category.clone(), serde_json::json!(Vec::::new())); } category_map .get_mut(&category.clone()) .unwrap() .as_array_mut() .unwrap() .push(serde_json::to_value(data.get(&key).unwrap()).unwrap()); } } Self { shard_name: name, category_map, } } pub fn save_shard(&mut self) -> Result<(), Box> { for cat_key in self.category_map.keys() { let shard_out_name = format!("{}_{}.shard", self.shard_name, cat_key); let shard: &Value = self.category_map.get(cat_key).unwrap(); let shard_bytes = serde_json::to_vec(shard)?; let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); encoder.write_all(&shard_bytes)?; let compressed_data = encoder.finish()?; let mut file = File::create(shard_out_name)?; file.write_all(&compressed_data)?; file.flush()?; } Ok(()) } } /// break down recipe(s) in the folder into mapping, /// tracking diff between fields. (This does not compare other fields than Recipe01) pub fn build_recipe_shardings(name: String, source: PathBuf) -> ShardingRecipe { let files = common::get_all_files_in_directory(source.as_path().to_str().unwrap()); let known_latest_version = format!("{}/version", source.as_path().to_str().unwrap()); let mut latest_version_string = String::new(); std::fs::File::open(known_latest_version) .unwrap() .read_to_string(&mut latest_version_string) .unwrap(); let latest_version = latest_version_string.parse::().unwrap(); let mut recipes: Vec = files .par_iter() .filter(|file| file.ends_with("json") && file.contains("coffeethai02")) .map(|file| common::create_recipe_model_from_file(file.to_string())) .collect(); // ascending order a.compare(b) recipes.sort_by(|a, b| { let version_a = a.MachineSetting.get_config_number().as_i64().unwrap(); let version_b = b.MachineSetting.get_config_number().as_i64().unwrap(); version_a.cmp(&version_b) }); // Structure // { productCode: { version: [ Option ] } } let mut shards = BTreeMap::new(); if recipes.is_empty() { eprintln!("recipes get empty!!!"); } else { println!("recipes cnt = {}", recipes.len()); } let base_version: i64 = latest_version; let base_recipe: Vec = recipes .iter() .filter(|r| { r.MachineSetting .get_config_number() .as_i64() .unwrap() .eq(&base_version) }) .cloned() .collect(); shards = base_recipe .first() .unwrap() .Recipe01 .par_iter() .map(|rp1| { let mut sub_changes = BTreeMap::new(); if rp1.clone().SubMenu.is_some() { let subs = rp1.clone().SubMenu.unwrap().clone(); sub_changes = subs .par_iter() .map(|sub1| { let change_sub = RecipeListChange { new_value: Value::Null, raw_new: sub1.recipes.clone(), raw_old: sub1.recipes.clone(), sub_changes: BTreeMap::new(), }; let mut change_sub_map = BTreeMap::new(); change_sub_map.insert(base_version, change_sub.clone()); recipes.iter().for_each(|rc| { // clone change let mut change_sub_clone = change_sub.clone(); match rc.search_pd_by_no_country_code(sub1.clone().productCode) { Some(spd) => { change_sub_clone.raw_new = spd.clone().recipes; } None => {} }; change_sub_clone.get_changes(); change_sub_map.insert( rc.MachineSetting .get_config_number() .as_i64() .unwrap() .clone(), change_sub_clone.clone(), ); }); ( sub1.clone().productCode, serde_json::to_value(change_sub_map).unwrap(), ) }) .collect(); } let change = RecipeListChange { new_value: Value::Null, raw_old: rp1.recipes.clone(), raw_new: rp1.recipes.clone(), sub_changes, }; let mut change_map = BTreeMap::new(); change_map.insert(base_version, change.clone()); recipes.iter().for_each(|rc| { // clone change let mut change_clone = change.clone(); match rc.search_pd_by_no_country_code(rp1.clone().productCode) { Some(pdr) => { change_clone.raw_new = pdr.clone().recipes; } None => {} } change_clone.get_changes(); change_map.insert( rc.MachineSetting .get_config_number() .as_i64() .unwrap() .clone(), change_clone, ); }); ( rp1.clone().productCode, serde_json::to_value(change_map).unwrap(), ) }) .collect(); ShardingRecipe::build(name.to_string(), shards) }