Merge "Add ability to manually write to binary files." into main
This commit is contained in:
@@ -14,6 +14,7 @@ rust_defaults {
|
|||||||
"libclap",
|
"libclap",
|
||||||
"libcxx",
|
"libcxx",
|
||||||
"libaconfig_storage_protos",
|
"libaconfig_storage_protos",
|
||||||
|
"libserde",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ rust_binary_host {
|
|||||||
name: "aconfig-storage",
|
name: "aconfig-storage",
|
||||||
defaults: ["aconfig_storage_file.defaults"],
|
defaults: ["aconfig_storage_file.defaults"],
|
||||||
srcs: ["src/main.rs"],
|
srcs: ["src/main.rs"],
|
||||||
rustlibs: ["libaconfig_storage_file"],
|
rustlibs: [
|
||||||
|
"libaconfig_storage_file",
|
||||||
|
"libserde_json",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
rust_test_host {
|
rust_test_host {
|
||||||
|
@@ -14,6 +14,8 @@ tempfile = "3.9.0"
|
|||||||
thiserror = "1.0.56"
|
thiserror = "1.0.56"
|
||||||
clap = { version = "4.1.8", features = ["derive"] }
|
clap = { version = "4.1.8", features = ["derive"] }
|
||||||
cxx = "1.0"
|
cxx = "1.0"
|
||||||
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
|
serde_json = "1.0.93"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "aconfig-storage"
|
name = "aconfig-storage"
|
||||||
|
@@ -20,10 +20,11 @@
|
|||||||
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||||
use crate::{AconfigStorageError, StorageFileType};
|
use crate::{AconfigStorageError, StorageFileType};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Flag info header struct
|
/// Flag info header struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FlagInfoHeader {
|
pub struct FlagInfoHeader {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub container: String,
|
pub container: String,
|
||||||
@@ -89,7 +90,7 @@ impl FlagInfoHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// bit field for flag info
|
/// bit field for flag info
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum FlagInfoBit {
|
pub enum FlagInfoBit {
|
||||||
HasServerOverride = 1 << 0,
|
HasServerOverride = 1 << 0,
|
||||||
IsReadWrite = 1 << 1,
|
IsReadWrite = 1 << 1,
|
||||||
@@ -97,7 +98,7 @@ pub enum FlagInfoBit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Flag info node struct
|
/// Flag info node struct
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct FlagInfoNode {
|
pub struct FlagInfoNode {
|
||||||
pub attributes: u8,
|
pub attributes: u8,
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,7 @@ impl FlagInfoNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Flag info list struct
|
/// Flag info list struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FlagInfoList {
|
pub struct FlagInfoList {
|
||||||
pub header: FlagInfoHeader,
|
pub header: FlagInfoHeader,
|
||||||
pub nodes: Vec<FlagInfoNode>,
|
pub nodes: Vec<FlagInfoNode>,
|
||||||
|
@@ -23,10 +23,11 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
|
use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Flag table header struct
|
/// Flag table header struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FlagTableHeader {
|
pub struct FlagTableHeader {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub container: String,
|
pub container: String,
|
||||||
@@ -95,7 +96,7 @@ impl FlagTableHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Flag table node struct
|
/// Flag table node struct
|
||||||
#[derive(PartialEq, Clone)]
|
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct FlagTableNode {
|
pub struct FlagTableNode {
|
||||||
pub package_id: u32,
|
pub package_id: u32,
|
||||||
pub flag_name: String,
|
pub flag_name: String,
|
||||||
@@ -154,7 +155,7 @@ impl FlagTableNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FlagTable {
|
pub struct FlagTable {
|
||||||
pub header: FlagTableHeader,
|
pub header: FlagTableHeader,
|
||||||
pub buckets: Vec<Option<u32>>,
|
pub buckets: Vec<Option<u32>>,
|
||||||
|
@@ -20,10 +20,11 @@
|
|||||||
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||||
use crate::{AconfigStorageError, StorageFileType};
|
use crate::{AconfigStorageError, StorageFileType};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Flag value header struct
|
/// Flag value header struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FlagValueHeader {
|
pub struct FlagValueHeader {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub container: String,
|
pub container: String,
|
||||||
@@ -89,7 +90,7 @@ impl FlagValueHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Flag value list struct
|
/// Flag value list struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FlagValueList {
|
pub struct FlagValueList {
|
||||||
pub header: FlagValueHeader,
|
pub header: FlagValueHeader,
|
||||||
pub booleans: Vec<bool>,
|
pub booleans: Vec<bool>,
|
||||||
|
@@ -41,6 +41,7 @@ pub mod sip_hasher13;
|
|||||||
pub mod test_utils;
|
pub mod test_utils;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
@@ -107,7 +108,7 @@ impl TryFrom<u8> for StorageFileType {
|
|||||||
|
|
||||||
/// Flag type enum as stored by storage file
|
/// Flag type enum as stored by storage file
|
||||||
/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
|
/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum StoredFlagType {
|
pub enum StoredFlagType {
|
||||||
ReadWriteBoolean = 0,
|
ReadWriteBoolean = 0,
|
||||||
ReadOnlyBoolean = 1,
|
ReadOnlyBoolean = 1,
|
||||||
|
@@ -20,9 +20,29 @@ use aconfig_storage_file::{
|
|||||||
list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList,
|
list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList,
|
||||||
FlagTable, FlagValueList, PackageTable, StorageFileType,
|
FlagTable, FlagValueList, PackageTable, StorageFileType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::{builder::ArgAction, Arg, Command};
|
use clap::{builder::ArgAction, Arg, Command};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage Examples
|
||||||
|
*
|
||||||
|
* Print file:
|
||||||
|
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map
|
||||||
|
*
|
||||||
|
* List flags:
|
||||||
|
* $ aconfig-storage list --flag-map=path/to/flag.map \
|
||||||
|
* --flag-val=path/to/flag.val --package-map=path/to/package.map
|
||||||
|
*
|
||||||
|
* Write binary file for testing:
|
||||||
|
* $ aconfig-storage print --file=path/to/flag.map --type=flag_map --format=json > flag_map.json
|
||||||
|
* $ vim flag_map.json // Manually make updates
|
||||||
|
* $ aconfig-storage write-bytes --input-file=flag_map.json --output-file=path/to/flag.map --type=flag_map
|
||||||
|
*/
|
||||||
fn cli() -> Command {
|
fn cli() -> Command {
|
||||||
Command::new("aconfig-storage")
|
Command::new("aconfig-storage")
|
||||||
.subcommand_required(true)
|
.subcommand_required(true)
|
||||||
@@ -34,7 +54,8 @@ fn cli() -> Command {
|
|||||||
.long("type")
|
.long("type")
|
||||||
.required(true)
|
.required(true)
|
||||||
.value_parser(|s: &str| StorageFileType::try_from(s)),
|
.value_parser(|s: &str| StorageFileType::try_from(s)),
|
||||||
),
|
)
|
||||||
|
.arg(Arg::new("format").long("format").required(false).action(ArgAction::Set)),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("list")
|
Command::new("list")
|
||||||
@@ -50,41 +71,75 @@ fn cli() -> Command {
|
|||||||
Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set),
|
Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("write-bytes")
|
||||||
|
// Where to write the output bytes. Suggest to use the StorageFileType names (e.g. flag.map).
|
||||||
|
.arg(
|
||||||
|
Arg::new("output-file")
|
||||||
|
.long("output-file")
|
||||||
|
.required(true)
|
||||||
|
.action(ArgAction::Set),
|
||||||
|
)
|
||||||
|
// Input file should be json.
|
||||||
|
.arg(
|
||||||
|
Arg::new("input-file").long("input-file").required(true).action(ArgAction::Set),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("type")
|
||||||
|
.long("type")
|
||||||
|
.required(true)
|
||||||
|
.value_parser(|s: &str| StorageFileType::try_from(s)),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_storage_file(
|
fn print_storage_file(
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
file_type: &StorageFileType,
|
file_type: &StorageFileType,
|
||||||
|
as_json: bool,
|
||||||
) -> Result<(), AconfigStorageError> {
|
) -> Result<(), AconfigStorageError> {
|
||||||
let bytes = read_file_to_bytes(file_path)?;
|
let bytes = read_file_to_bytes(file_path)?;
|
||||||
match file_type {
|
match file_type {
|
||||||
StorageFileType::PackageMap => {
|
StorageFileType::PackageMap => {
|
||||||
let package_table = PackageTable::from_bytes(&bytes)?;
|
let package_table = PackageTable::from_bytes(&bytes)?;
|
||||||
println!("{:?}", package_table);
|
println!("{}", to_print_format(package_table, as_json));
|
||||||
}
|
}
|
||||||
StorageFileType::FlagMap => {
|
StorageFileType::FlagMap => {
|
||||||
let flag_table = FlagTable::from_bytes(&bytes)?;
|
let flag_table = FlagTable::from_bytes(&bytes)?;
|
||||||
println!("{:?}", flag_table);
|
println!("{}", to_print_format(flag_table, as_json));
|
||||||
}
|
}
|
||||||
StorageFileType::FlagVal => {
|
StorageFileType::FlagVal => {
|
||||||
let flag_value = FlagValueList::from_bytes(&bytes)?;
|
let flag_value = FlagValueList::from_bytes(&bytes)?;
|
||||||
println!("{:?}", flag_value);
|
println!("{}", to_print_format(flag_value, as_json));
|
||||||
}
|
}
|
||||||
StorageFileType::FlagInfo => {
|
StorageFileType::FlagInfo => {
|
||||||
let flag_info = FlagInfoList::from_bytes(&bytes)?;
|
let flag_info = FlagInfoList::from_bytes(&bytes)?;
|
||||||
println!("{:?}", flag_info);
|
println!("{}", to_print_format(flag_info, as_json));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_print_format<T>(file_contents: T, as_json: bool) -> String
|
||||||
|
where
|
||||||
|
T: Serialize + fmt::Debug,
|
||||||
|
{
|
||||||
|
if as_json {
|
||||||
|
serde_json::to_string(&file_contents).unwrap()
|
||||||
|
} else {
|
||||||
|
format!("{:?}", file_contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), AconfigStorageError> {
|
fn main() -> Result<(), AconfigStorageError> {
|
||||||
let matches = cli().get_matches();
|
let matches = cli().get_matches();
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
Some(("print", sub_matches)) => {
|
Some(("print", sub_matches)) => {
|
||||||
let file_path = sub_matches.get_one::<String>("file").unwrap();
|
let file_path = sub_matches.get_one::<String>("file").unwrap();
|
||||||
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
|
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
|
||||||
print_storage_file(file_path, file_type)?
|
let format = sub_matches.get_one::<String>("format");
|
||||||
|
let as_json: bool = format == Some(&"json".to_string());
|
||||||
|
print_storage_file(file_path, file_type, as_json)?
|
||||||
}
|
}
|
||||||
Some(("list", sub_matches)) => {
|
Some(("list", sub_matches)) => {
|
||||||
let package_map = sub_matches.get_one::<String>("package-map").unwrap();
|
let package_map = sub_matches.get_one::<String>("package-map").unwrap();
|
||||||
@@ -96,10 +151,10 @@ fn main() -> Result<(), AconfigStorageError> {
|
|||||||
let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?;
|
let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?;
|
||||||
for flag in flags.iter() {
|
for flag in flags.iter() {
|
||||||
println!(
|
println!(
|
||||||
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
|
"{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}",
|
||||||
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
|
flag.package_name, flag.flag_name, flag.flag_value, flag.value_type,
|
||||||
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
|
flag.is_readwrite, flag.has_server_override, flag.has_local_override,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -113,6 +168,40 @@ fn main() -> Result<(), AconfigStorageError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Converts JSON of the file into raw bytes (as is used on-device).
|
||||||
|
// Intended to generate/easily update these files for testing.
|
||||||
|
Some(("write-bytes", sub_matches)) => {
|
||||||
|
let input_file_path = sub_matches.get_one::<String>("input-file").unwrap();
|
||||||
|
let input_json = fs::read_to_string(input_file_path).unwrap();
|
||||||
|
|
||||||
|
let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
|
||||||
|
let output_bytes: Vec<u8>;
|
||||||
|
match file_type {
|
||||||
|
StorageFileType::FlagVal => {
|
||||||
|
let list: FlagValueList = serde_json::from_str(&input_json).unwrap();
|
||||||
|
output_bytes = list.into_bytes();
|
||||||
|
}
|
||||||
|
StorageFileType::FlagInfo => {
|
||||||
|
let list: FlagInfoList = serde_json::from_str(&input_json).unwrap();
|
||||||
|
output_bytes = list.into_bytes();
|
||||||
|
}
|
||||||
|
StorageFileType::FlagMap => {
|
||||||
|
let table: FlagTable = serde_json::from_str(&input_json).unwrap();
|
||||||
|
output_bytes = table.into_bytes();
|
||||||
|
}
|
||||||
|
StorageFileType::PackageMap => {
|
||||||
|
let table: PackageTable = serde_json::from_str(&input_json).unwrap();
|
||||||
|
output_bytes = table.into_bytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_file_path = sub_matches.get_one::<String>("output-file").unwrap();
|
||||||
|
let file = File::create(output_file_path);
|
||||||
|
if file.is_err() {
|
||||||
|
panic!("can't make file");
|
||||||
|
}
|
||||||
|
let _ = file.unwrap().write_all(&output_bytes);
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -20,10 +20,11 @@
|
|||||||
use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||||
use crate::{AconfigStorageError, StorageFileType};
|
use crate::{AconfigStorageError, StorageFileType};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Package table header struct
|
/// Package table header struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PackageTableHeader {
|
pub struct PackageTableHeader {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub container: String,
|
pub container: String,
|
||||||
@@ -92,7 +93,7 @@ impl PackageTableHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Package table node struct
|
/// Package table node struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PackageTableNode {
|
pub struct PackageTableNode {
|
||||||
pub package_name: String,
|
pub package_name: String,
|
||||||
pub package_id: u32,
|
pub package_id: u32,
|
||||||
@@ -151,7 +152,7 @@ impl PackageTableNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Package table struct
|
/// Package table struct
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PackageTable {
|
pub struct PackageTable {
|
||||||
pub header: PackageTableHeader,
|
pub header: PackageTableHeader,
|
||||||
pub buckets: Vec<Option<u32>>,
|
pub buckets: Vec<Option<u32>>,
|
||||||
|
Reference in New Issue
Block a user