From 8dfa036fa7411793931f496ce4ec8a24fbf548d7 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+kimono-koans@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:45:42 -0500 Subject: [PATCH 1/6] Initial commit --- src/config.rs | 108 ++++++++++++++++++++++++++++++++----------------- src/main.rs | 29 ++++++++++++- src/utility.rs | 14 +++++-- 3 files changed, 108 insertions(+), 43 deletions(-) diff --git a/src/config.rs b/src/config.rs index 99d11f3..4aa7791 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,6 @@ // that was distributed with this source code. use std::{ - borrow::Cow, collections::HashSet, ffi::OsStr, path::{Path, PathBuf}, @@ -75,6 +74,7 @@ fn parse_args() -> ArgMatches { .help("write the new input files' hash information. If no other flags are specified, dano will ignore files which already have file hashes.") .short('w') .long("write") + .conflicts_with_all(&["PRINT", "DUMP", "DUPLICATES", "CLEAN", "TEST"]) .display_order(4)) .arg( Arg::new("TEST") @@ -83,30 +83,39 @@ fn parse_args() -> ArgMatches { .long("test") .alias("compare") .short_alias('c') + .conflicts_with_all(&["PRINT", "DUMP", "DUPLICATES", "CLEAN", "WRITE"]) .display_order(5)) .arg( Arg::new("PRINT") .help("pretty print all recorded file information (discovered within both the hash file and any xattrs).") .short('p') .long("print") + .conflicts_with_all(&["DUMP", "DUPLICATES", "CLEAN", "WRITE", "TEST"]) .display_order(6)) .arg( Arg::new("DUMP") .help("dump the recorded file information (in hash file and xattrs) to the output file (don't test/compare).") .long("dump") + .conflicts_with_all(&["DUPLICATES", "CLEAN", "WRITE", "PRINT", "TEST"]) .display_order(7)) .arg( Arg::new("DUPLICATES") .help("show any hash value duplicates discovered when reading back recorded file information (in hash file and xattrs).") .long("duplicates") .aliases(&["dupes"]) + .conflicts_with_all(&["DUMP", "CLEAN", "WRITE", "PRINT", "TEST"]) .display_order(8)) + .arg( + Arg::new("CLEAN") + .help("remove any hash files, given as input files, and remove any extended attributes, given as input files.") + .long("clean") + .display_order(9)) .arg( Arg::new("IMPORT_FLAC") .help("import flac checksums and write such information as dano recorded file information.") .long("import-flac") .conflicts_with_all(&["TEST", "PRINT", "DUMP", "DUPLICATES"]) - .display_order(9)) + .display_order(10)) .arg( Arg::new("NUM_THREADS") .help("requested number of threads to use for file processing. Default is the number of logical cores.") @@ -116,13 +125,13 @@ fn parse_args() -> ArgMatches { .min_values(1) .require_equals(true) .value_parser(clap::builder::ValueParser::os_string()) - .display_order(10)) + .display_order(11)) .arg( Arg::new("SILENT") .help("quiet many informational messages (such as \"OK\").") .short('s') .long("silent") - .display_order(11), + .display_order(12), ) .arg( Arg::new("WRITE_NEW") @@ -130,7 +139,7 @@ fn parse_args() -> ArgMatches { .long("write-new") .requires("TEST") .conflicts_with_all(&["PRINT", "DUMP", "DUPLICATES", "WRITE"]) - .display_order(12), + .display_order(13), ) .arg( Arg::new("OVERWRITE_OLD") @@ -139,19 +148,19 @@ fn parse_args() -> ArgMatches { .long("overwrite") .requires("TEST") .conflicts_with_all(&["PRINT", "DUMP", "DUPLICATES", "WRITE"]) - .display_order(13), + .display_order(14), ) .arg( Arg::new("DISABLE_FILTER") .help("disable the default filtering of file extensions which ffmpeg lists as \"common\" extensions for supported file formats.") .long("disable-filter") - .display_order(14), + .display_order(15), ) .arg( Arg::new("CANONICAL_PATHS") .help("use canonical paths (paths from the root directory) instead of potentially relative paths.") .long("canonical-paths") - .display_order(15), + .display_order(16), ) .arg( Arg::new("XATTR") @@ -160,7 +169,7 @@ fn parse_args() -> ArgMatches { When XATTR is enabled, if a write is requested, dano will always overwrite extended attributes previously written.") .short('x') .long("xattr") - .display_order(16), + .display_order(17), ) .arg( Arg::new("HASH_ALGO") @@ -171,13 +180,13 @@ fn parse_args() -> ArgMatches { .require_equals(true) .possible_values(["murmur3", "md5", "crc32", "adler32", "sha1", "sha160", "sha256", "sha384", "sha512"]) .value_parser(clap::builder::ValueParser::os_string()) - .display_order(17)) + .display_order(18)) .arg( Arg::new("DECODE") .help("decode internal bitstream before hashing. This option makes testing and writes much slower, but this option is potentially useful for lossless formats.") .long("decode") .conflicts_with_all(&["PRINT", "DUMP", "DUPLICATES"]) - .display_order(18)) + .display_order(19)) .arg( Arg::new("REWRITE_ALL") .help("rewrite all recorded hashes to the latest and greatest format version. \ @@ -185,7 +194,7 @@ fn parse_args() -> ArgMatches { .long("rewrite") .requires("WRITE") .conflicts_with_all(&["PRINT", "DUMP", "DUPLICATES", "TEST"]) - .display_order(19)) + .display_order(20)) .arg( Arg::new("ONLY") .help("hash the an input file container's first audio or video stream only, if available. \ @@ -196,13 +205,13 @@ fn parse_args() -> ArgMatches { .possible_values(["audio", "video"]) .value_parser(clap::builder::ValueParser::os_string()) .requires("WRITE") - .display_order(20)) + .display_order(21)) .arg( Arg::new("DRY_RUN") .help("print the information to stdout that would be written to disk.") .long("dry-run") .conflicts_with_all(&["PRINT", "DUPLICATES"]) - .display_order(21)) + .display_order(22)) .get_matches() } @@ -225,6 +234,7 @@ pub enum ExecMode { Print, Dump, Duplicates, + Clean, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -291,7 +301,9 @@ impl Config { let opt_overwrite_old = matches.is_present("OVERWRITE_OLD"); let opt_write_new = matches.is_present("WRITE_NEW"); - let exec_mode = if matches.is_present("TEST") { + let exec_mode = if matches.is_present("CLEAN") { + ExecMode::Clean + } else if matches.is_present("TEST") { let test_mode_config = TestModeConfig { opt_overwrite_old, opt_write_new, @@ -311,7 +323,7 @@ impl Config { ExecMode::Duplicates } else { return Err(DanoError::new( - "You must specify an execution mode: TEST, WRITE, PRINT or DUMP", + "You must specify an execution mode: TEST, WRITE, DUPLICATES, CLEAN, PRINT or DUMP", ) .into()); }; @@ -361,6 +373,7 @@ impl Config { }; Self::parse_paths( &res, + &exec_mode, opt_disable_filter, opt_canonical_paths, opt_silent, @@ -391,6 +404,7 @@ impl Config { fn parse_paths( raw_paths: &[PathBuf], + exec_mode: &ExecMode, opt_disable_filter: bool, opt_canonical_paths: bool, opt_silent: bool, @@ -424,12 +438,44 @@ impl Config { eprintln!("ERROR: Path cannot be serialized to string: {:?}", path); false }) + .map(|path| { + if opt_canonical_paths { + if let Ok(canonical) = path.canonicalize() { + return canonical; + } + + eprintln!( + "WARN: Unable convert relative path to canonical path: {:?}", + path + ); + } + + path.to_owned() + }) .filter(|path| { - if path.file_name() == Some(OsStr::new(hash_file)) { + if let &ExecMode::Clean = exec_mode { + if path.file_name() == Some(OsStr::new(DANO_DEFAULT_HASH_FILE_NAME)) { + match std::fs::remove_file(&path) { + Ok(_) => { + let msg = format!("dano hash file successfully removed: {:?}", path); + eprintln!("{}", &msg); + }, + Err(err) => { + let msg = format!("ERROR: Removal of dano hash file failed: {:?}: {:?}", path, err); + eprintln!("{}", &msg); + } + } + } + + return false; + } + + if path.file_name() == Some(hash_file.as_os_str()) { eprintln!( "ERROR: File name is the name of a dano hash file: {:?}", path ); + return false; } @@ -437,17 +483,19 @@ impl Config { }) .filter_map(|path| { if !opt_disable_filter { - let opt_extension = path.extension(); + let path_ref = &path; + + let opt_extension = path_ref.extension(); if auto_extension_filter .lines() .any(|extension| opt_extension == Some(OsStr::new(extension))) { - return Some(Either::Right(path.as_path())); + return Some(Either::Right(path)); } if let Some(ext) = opt_extension { - return Some(Either::Left(ext.to_string_lossy())); + return Some(Either::Left(ext.to_string_lossy().to_string())); } // what are these None cases: hidden files (dot files), @@ -455,12 +503,12 @@ impl Config { return None; } - Some(Either::Right(path.as_path())) + Some(Either::Right(path)) }) .partition_map(|item| item); if !opt_silent && !bad_extensions.is_empty() { - let unique: HashSet> = bad_extensions.into_iter().collect(); + let unique: HashSet = bad_extensions.into_iter().collect(); let buffer: String = unique.iter().map(|ext| format!("{} ", ext)).collect(); @@ -468,21 +516,5 @@ impl Config { } valid_paths - .iter() - .map(|path| { - if opt_canonical_paths { - if let Ok(canonical) = path.canonicalize() { - return canonical; - } - - eprintln!( - "WARN: Unable convert relative path to canonical path: {:?}", - path - ); - } - - path.to_path_buf() - }) - .collect() } } diff --git a/src/main.rs b/src/main.rs index aa66cb1..e640ab9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,9 @@ mod utility; mod versions; use std::collections::BTreeMap; +use std::path::PathBuf; -use itertools::Itertools; +use itertools::{Itertools}; use crate::lookup::FileInfo; use config::{Config, ExecMode}; @@ -36,7 +37,7 @@ use lookup::FileInfoLookup; use output::WriteableFileInfo; use process::{ProcessedFiles, RemainderBundle}; use requests::{FileInfoRequest, RequestBundle}; -use utility::{prepare_thread_pool, print_err_buf, print_file_info, DanoError, DanoResult}; +use utility::{prepare_thread_pool, print_err_buf, print_file_info, DanoError, DanoResult, remove_dano_xattr}; const DANO_FILE_INFO_VERSION: usize = 4; const HEXADECIMAL_RADIX: u32 = 16; @@ -65,6 +66,30 @@ fn exec() -> DanoResult { let recorded_file_info = RecordedFileInfo::new(&config)?; let exit_code = match &config.exec_mode { + ExecMode::Clean => { + // dano_hashes.txt is removed during recorded_file_info ingest + let errors: Vec<&PathBuf> = config.paths + .iter().filter_map(|path| { + match remove_dano_xattr(&path) { + Ok(_) => { + eprintln!("dano successfully removed extended attribute from: {:?}", path); + None + }, + Err(err) => { + eprintln!("ERROR: {}", err); + Some(path) + } + } + }).collect(); + + if errors.is_empty() { + eprintln!("All dano extended attributes successfully cleaned."); + DANO_CLEAN_EXIT_CODE + } else { + eprintln!("ERROR: Could not clean extended attributes form the following paths: {:?}", errors); + DANO_ERROR_EXIT_CODE + } + } ExecMode::Write(write_config) if write_config.opt_rewrite || write_config.opt_import_flac => { diff --git a/src/utility.rs b/src/utility.rs index 64b31c4..cfd8b1c 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -53,7 +53,7 @@ pub fn prepare_thread_pool(config: &Config) -> DanoResult { Ok(thread_pool) } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct DanoError { pub details: String, } @@ -109,8 +109,16 @@ pub fn write_non_file(file_info: &FileInfo) -> DanoResult<()> { write_out_xattr(&serialized, file_info) } +pub fn remove_dano_xattr(path: &Path) -> DanoResult<()> { + match xattr::get(path, DANO_XATTR_KEY_NAME).ok().flatten() { + Some(_) => xattr::remove(path, DANO_XATTR_KEY_NAME).map_err(|err| err.into()), + None => { + Err(DanoError::new(&format!("dano extended attribute for following path does not exist: {:?}.", path)).into()) + } + } +} + fn write_out_xattr(out_string: &str, file_info: &FileInfo) -> DanoResult<()> { - // always remove and reset when writing xattrs let _ = xattr::remove(&file_info.path, DANO_XATTR_KEY_NAME); xattr::set(&file_info.path, DANO_XATTR_KEY_NAME, out_string.as_bytes()) .map_err(|err| err.into()) @@ -155,7 +163,7 @@ pub fn print_file_info(config: &Config, file_info: &FileInfo) -> DanoResult<()> // this fn used then is just to print info about the hash. we may wish to send to dev null match config.exec_mode { ExecMode::Print | ExecMode::Duplicates | ExecMode::Test(_) => print_out_buf(&buffer), - ExecMode::Write(_) | ExecMode::Dump => print_err_buf(&buffer), + ExecMode::Write(_) | ExecMode::Dump | ExecMode::Clean => print_err_buf(&buffer), } } From 23828990b38ab07e280dc4aaa3bb6e21a132ccb9 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+kimono-koans@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:50:15 -0500 Subject: [PATCH 2/6] Fix paths not found error --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4aa7791..f5d019b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -371,6 +371,7 @@ impl Config { _ => read_stdin()?, } }; + Self::parse_paths( &res, &exec_mode, @@ -465,9 +466,8 @@ impl Config { eprintln!("{}", &msg); } } + return false; } - - return false; } if path.file_name() == Some(hash_file.as_os_str()) { From 32dd700ec82c71781215ed1743eb2daff12aa250 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+kimono-koans@users.noreply.github.com> Date: Sun, 4 Jun 2023 18:47:37 -0500 Subject: [PATCH 3/6] Quite errors when no xattr is found --- src/main.rs | 3 +++ src/utility.rs | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index e640ab9..e3e59f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,6 +75,9 @@ fn exec() -> DanoResult { eprintln!("dano successfully removed extended attribute from: {:?}", path); None }, + Err(err) if err.to_string().contains("No data available") => { + None + } Err(err) => { eprintln!("ERROR: {}", err); Some(path) diff --git a/src/utility.rs b/src/utility.rs index cfd8b1c..1def91e 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -110,12 +110,7 @@ pub fn write_non_file(file_info: &FileInfo) -> DanoResult<()> { } pub fn remove_dano_xattr(path: &Path) -> DanoResult<()> { - match xattr::get(path, DANO_XATTR_KEY_NAME).ok().flatten() { - Some(_) => xattr::remove(path, DANO_XATTR_KEY_NAME).map_err(|err| err.into()), - None => { - Err(DanoError::new(&format!("dano extended attribute for following path does not exist: {:?}.", path)).into()) - } - } + xattr::remove(path, DANO_XATTR_KEY_NAME).map_err(|err| err.into()) } fn write_out_xattr(out_string: &str, file_info: &FileInfo) -> DanoResult<()> { From 882695eb04cdcbdc24ed7e5477e93d7c9cddef9d Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+kimono-koans@users.noreply.github.com> Date: Sun, 4 Jun 2023 18:48:59 -0500 Subject: [PATCH 4/6] Print clean successful actions to stdout --- src/config.rs | 2 +- src/main.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index f5d019b..4a0c935 100644 --- a/src/config.rs +++ b/src/config.rs @@ -459,7 +459,7 @@ impl Config { match std::fs::remove_file(&path) { Ok(_) => { let msg = format!("dano hash file successfully removed: {:?}", path); - eprintln!("{}", &msg); + println!("{}", &msg); }, Err(err) => { let msg = format!("ERROR: Removal of dano hash file failed: {:?}: {:?}", path, err); diff --git a/src/main.rs b/src/main.rs index e3e59f7..fcfd280 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,7 +72,7 @@ fn exec() -> DanoResult { .iter().filter_map(|path| { match remove_dano_xattr(&path) { Ok(_) => { - eprintln!("dano successfully removed extended attribute from: {:?}", path); + println!("dano successfully removed extended attribute from: {:?}", path); None }, Err(err) if err.to_string().contains("No data available") => { @@ -86,10 +86,10 @@ fn exec() -> DanoResult { }).collect(); if errors.is_empty() { - eprintln!("All dano extended attributes successfully cleaned."); + println!("All dano extended attributes successfully cleaned."); DANO_CLEAN_EXIT_CODE } else { - eprintln!("ERROR: Could not clean extended attributes form the following paths: {:?}", errors); + println!("ERROR: Could not clean extended attributes form the following paths: {:?}", errors); DANO_ERROR_EXIT_CODE } } From 0107b9a5c8af17e33076de8cf6ff845cae1d78d1 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+kimono-koans@users.noreply.github.com> Date: Sun, 4 Jun 2023 18:50:49 -0500 Subject: [PATCH 5/6] Cargo fmt --- src/config.rs | 23 +++++++++++++---------- src/main.rs | 45 ++++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4a0c935..b3d972c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -285,8 +285,7 @@ impl Config { .into()); }; - let opt_xattr = - matches.is_present("XATTR") || std::env::var_os(XATTR_ENV_KEY).is_some(); + let opt_xattr = matches.is_present("XATTR") || std::env::var_os(XATTR_ENV_KEY).is_some(); let opt_dry_run = matches.is_present("DRY_RUN") || (matches.is_present("PRINT") && matches.is_present("WRITE")); let opt_num_threads = matches @@ -374,7 +373,7 @@ impl Config { Self::parse_paths( &res, - &exec_mode, + &exec_mode, opt_disable_filter, opt_canonical_paths, opt_silent, @@ -456,13 +455,17 @@ impl Config { .filter(|path| { if let &ExecMode::Clean = exec_mode { if path.file_name() == Some(OsStr::new(DANO_DEFAULT_HASH_FILE_NAME)) { - match std::fs::remove_file(&path) { + match std::fs::remove_file(path) { Ok(_) => { - let msg = format!("dano hash file successfully removed: {:?}", path); + let msg = + format!("dano hash file successfully removed: {:?}", path); println!("{}", &msg); - }, + } Err(err) => { - let msg = format!("ERROR: Removal of dano hash file failed: {:?}: {:?}", path, err); + let msg = format!( + "ERROR: Removal of dano hash file failed: {:?}: {:?}", + path, err + ); eprintln!("{}", &msg); } } @@ -483,9 +486,9 @@ impl Config { }) .filter_map(|path| { if !opt_disable_filter { - let path_ref = &path; - - let opt_extension = path_ref.extension(); + let path_ref = &path; + + let opt_extension = path_ref.extension(); if auto_extension_filter .lines() diff --git a/src/main.rs b/src/main.rs index fcfd280..1dae312 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ mod versions; use std::collections::BTreeMap; use std::path::PathBuf; -use itertools::{Itertools}; +use itertools::Itertools; use crate::lookup::FileInfo; use config::{Config, ExecMode}; @@ -37,7 +37,9 @@ use lookup::FileInfoLookup; use output::WriteableFileInfo; use process::{ProcessedFiles, RemainderBundle}; use requests::{FileInfoRequest, RequestBundle}; -use utility::{prepare_thread_pool, print_err_buf, print_file_info, DanoError, DanoResult, remove_dano_xattr}; +use utility::{ + prepare_thread_pool, print_err_buf, print_file_info, remove_dano_xattr, DanoError, DanoResult, +}; const DANO_FILE_INFO_VERSION: usize = 4; const HEXADECIMAL_RADIX: u32 = 16; @@ -67,29 +69,34 @@ fn exec() -> DanoResult { let exit_code = match &config.exec_mode { ExecMode::Clean => { - // dano_hashes.txt is removed during recorded_file_info ingest - let errors: Vec<&PathBuf> = config.paths - .iter().filter_map(|path| { - match remove_dano_xattr(&path) { - Ok(_) => { - println!("dano successfully removed extended attribute from: {:?}", path); - None - }, - Err(err) if err.to_string().contains("No data available") => { - None - } - Err(err) => { - eprintln!("ERROR: {}", err); - Some(path) - } + // dano_hashes.txt is removed during recorded_file_info ingest + let errors: Vec<&PathBuf> = config + .paths + .iter() + .filter(|path| match remove_dano_xattr(path) { + Ok(_) => { + println!( + "dano successfully removed extended attribute from: {:?}", + path + ); + false + } + Err(err) if err.to_string().contains("No data available") => false, + Err(err) => { + eprintln!("ERROR: {}", err); + true } - }).collect(); + }) + .collect(); if errors.is_empty() { println!("All dano extended attributes successfully cleaned."); DANO_CLEAN_EXIT_CODE } else { - println!("ERROR: Could not clean extended attributes form the following paths: {:?}", errors); + println!( + "ERROR: Could not clean extended attributes form the following paths: {:?}", + errors + ); DANO_ERROR_EXIT_CODE } } From d973d9d5adbd2b955f4665844cea7048ca070fd6 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+kimono-koans@users.noreply.github.com> Date: Sun, 4 Jun 2023 18:52:33 -0500 Subject: [PATCH 6/6] Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- dano.1 | 6 +++--- third_party/LICENSES_THIRD_PARTY.html | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2062316..4424a80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,7 +115,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "dano" -version = "0.8.0" +version = "0.8.1" dependencies = [ "clap", "crossbeam-channel", diff --git a/Cargo.toml b/Cargo.toml index f28a9f4..6949ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dano" -version = "0.8.0" +version = "0.8.1" edition = "2021" keywords = ["checksum", "verify", "media", "cli-utility", "storage"] description = "A CLI tool for generating checksums of media bitstreams" diff --git a/dano.1 b/dano.1 index 398619e..95e04dc 100644 --- a/dano.1 +++ b/dano.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DANO "1" "May 2023" "dano 0.8.0" "User Commands" +.TH DANO "1" "May 2023" "dano 0.8.1" "User Commands" .SH NAME -dano \- manual page for dano 0.8.0 +dano \- manual page for dano 0.8.1 .SH DESCRIPTION -dano 0.8.0 +dano 0.8.1 dano is a wrapper for ffmpeg that checksums the internal file streams of certain media files, and stores them in a format which can be used to verify such checksums later. This is handy, because, should you choose to change metadata tags, or change file names, the media checksums should remain diff --git a/third_party/LICENSES_THIRD_PARTY.html b/third_party/LICENSES_THIRD_PARTY.html index f1cbe36..504745b 100644 --- a/third_party/LICENSES_THIRD_PARTY.html +++ b/third_party/LICENSES_THIRD_PARTY.html @@ -1129,7 +1129,7 @@

Used by:

Apache License 2.0

Used by:

@@ -1412,7 +1412,7 @@

Used by:

Mozilla Public License 2.0

Used by:

Mozilla Public License Version 2.0
 ==================================