Add bare config file
This commit is contained in:
Generated
+3734
-3
File diff suppressed because it is too large
Load Diff
+18
-1
@@ -1,9 +1,26 @@
|
||||
[package]
|
||||
name = "imojefs"
|
||||
description = "Johnson & Johnson IMoje File Sync"
|
||||
description = "Johnson & Johnson IMoje CLI"
|
||||
authors = ["Wim Velzeboer <wim@velzeboer.nl>"]
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.104"
|
||||
aes-gcm = "0.10"
|
||||
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
|
||||
aws-credential-types = "1.2.14"
|
||||
aws-sdk-s3 = "1.132.0"
|
||||
base64 = "0.22"
|
||||
clap = { version = "4.6.1", features = ["derive", "env"] }
|
||||
dirs = "6.0"
|
||||
env_logger = "0.11.10"
|
||||
lazy_static = "1.5.0"
|
||||
log = "0.4"
|
||||
rand = "0.8"
|
||||
rustsf = { path = "../rustsf" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
signal-hook = "0.4.4"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
toml = "0.9"
|
||||
|
||||
@@ -8,3 +8,11 @@
|
||||
|___\____|__ /\____/\__| |\___ >___ / /_______ /
|
||||
\/ \______| \/ \/ \/
|
||||
```
|
||||
|
||||
## Design
|
||||
This CLI tool extracts binary file contents from Salesforce to S3.
|
||||
It leave a link with the S3 object-key in Salesforce,
|
||||
so that a pre-signed url can be generated whenever needed.
|
||||
|
||||
The tool is deigned to run at certain intervals,
|
||||
e.g. twice daily, and should be scheduled in the standard crontab in Linux
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
use aws_config::Region;
|
||||
|
||||
use aws_credential_types::provider::SharedCredentialsProvider;
|
||||
|
||||
pub(crate) async fn connect(
|
||||
client_id: &str,
|
||||
client_secret: &str,
|
||||
custom_endpoint: Option<&str>,
|
||||
region: &str) -> aws_sdk_s3::Client {
|
||||
|
||||
let creds = SharedCredentialsProvider::new(aws_credential_types::Credentials::new(
|
||||
client_id, client_secret, None, None, "Static",
|
||||
));
|
||||
|
||||
let config = match custom_endpoint {
|
||||
Some(endpoint) => {
|
||||
aws_config::from_env()
|
||||
.region(Region::new(region.to_string()))
|
||||
.endpoint_url(endpoint)
|
||||
.credentials_provider(creds)
|
||||
.load()
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
aws_config::from_env()
|
||||
.region(Region::new(region.to_string()))
|
||||
.credentials_provider(creds)
|
||||
.load()
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
aws_sdk_s3::Client::new(&config)
|
||||
}
|
||||
+129
-26
@@ -1,33 +1,11 @@
|
||||
use crate::logger;
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use log::error;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
|
||||
/// AWS Bucket name
|
||||
#[arg(short = 'b', long, env = "IMOJEFS_S3_BUCKET")]
|
||||
pub s3_bucket: String,
|
||||
|
||||
/// AWS Access Key Id
|
||||
#[arg(short = 'k', long, env = "IMOJEFS_S3_CLIENT_ID")]
|
||||
pub s3_key: String,
|
||||
|
||||
/// AWS Secret Access Key
|
||||
#[arg(short = 's', long, env = "IMOJEFS_S3_SECRET", hide_env_values = true)]
|
||||
pub s3_secret: String,
|
||||
|
||||
/// AWS Endpoint URL, [default: AWS url]
|
||||
#[arg(short = 'e', long, env = "IMOJEFS_S3_ENDPOINT")]
|
||||
pub s3_endpoint: Option<String>,
|
||||
|
||||
/// AWS Region
|
||||
#[arg(short = 'r', long, default_value = "us-east-1", env = "IMOJEFS_S3_REGION")]
|
||||
pub s3_region: String,
|
||||
|
||||
/// Salesforce Authentication Url
|
||||
#[arg(short = 'x', long, env = "IMOJEFS_SFDX_AUTH_URL", hide_env_values = true)]
|
||||
pub sfdx_auth_url: String,
|
||||
|
||||
struct Cli {
|
||||
/// Number of seconds to wait
|
||||
#[arg(short = 'w', long, default_value_t = 20, env = "IMOJEFS_WAIT")]
|
||||
pub wait: u64,
|
||||
@@ -36,6 +14,83 @@ pub struct Args {
|
||||
#[arg(short = 'l', long, value_enum, default_value_t = LogLevel::Info, env = "IMOJEFS_LOG_LEVEL")]
|
||||
pub log_level: LogLevel,
|
||||
|
||||
/// The configuration to use
|
||||
#[arg(short = 'c', long, default_value_t = String::from("imoje"))]
|
||||
config: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// Controls testing features
|
||||
FileSync,
|
||||
|
||||
/// Change Settings
|
||||
Set {
|
||||
#[command(subcommand)]
|
||||
command: Option<SettingsCommands>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
enum SettingsCommands {
|
||||
/// Set the Salesforce Authentication Url
|
||||
SfdxAuthUrl {
|
||||
/// Salesforce Authentication Url
|
||||
#[arg(
|
||||
short = 'x',
|
||||
long,
|
||||
env = "IMOJEEH_SFDX_AUTH_URL",
|
||||
hide_env_values = true
|
||||
)]
|
||||
url: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// AWS Bucket name
|
||||
#[arg(short = 'b', long, env = "IMOJEFS_S3_BUCKET")]
|
||||
pub s3_bucket: String,
|
||||
|
||||
/// AWS Access Key Id
|
||||
#[arg(short = 'k', long, env = "IMOJEFS_S3_CLIENT_ID")]
|
||||
pub s3_client_id: String,
|
||||
|
||||
/// AWS Secret Access Key
|
||||
#[arg(
|
||||
short = 's',
|
||||
long,
|
||||
env = "IMOJEFS_S3_CLIENT_SECRET",
|
||||
hide_env_values = true
|
||||
)]
|
||||
pub s3_client_secret: String,
|
||||
|
||||
/// AWS Endpoint URL, [default: AWS url]
|
||||
#[arg(short = 'e', long, env = "IMOJEFS_S3_ENDPOINT")]
|
||||
pub s3_endpoint: Option<String>,
|
||||
|
||||
/// AWS Region
|
||||
#[arg(
|
||||
short = 'r',
|
||||
long,
|
||||
default_value = "us-east-1",
|
||||
env = "IMOJEFS_S3_REGION"
|
||||
)]
|
||||
pub s3_region: String,
|
||||
|
||||
/// Salesforce Authentication Url
|
||||
#[arg(
|
||||
short = 'x',
|
||||
long,
|
||||
env = "IMOJEFS_SFDX_AUTH_URL",
|
||||
hide_env_values = true
|
||||
)]
|
||||
pub sfdx_auth_url: String,
|
||||
|
||||
/// Retry failures after X runs
|
||||
#[arg(long, default_value_t = 180, env = "IMOJEFS_RETRY")]
|
||||
pub retry: u64,
|
||||
@@ -46,6 +101,24 @@ pub struct Args {
|
||||
pub admin_email: Option<String>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub(crate) fn s3_bucket(&self) -> &str {
|
||||
&self.s3_bucket
|
||||
}
|
||||
pub(crate) fn s3_client_id(&self) -> &str {
|
||||
&self.s3_client_id
|
||||
}
|
||||
pub(crate) fn s3_client_secret(&self) -> &str {
|
||||
&self.s3_client_secret
|
||||
}
|
||||
pub(crate) fn s3_endpoint(&self) -> Option<&str> {
|
||||
self.s3_endpoint.as_deref()
|
||||
}
|
||||
pub(crate) fn s3_region(&self) -> &str {
|
||||
&self.s3_region
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the different levels of logging that can be used in an application.
|
||||
///
|
||||
/// # Variants
|
||||
@@ -64,3 +137,33 @@ pub enum LogLevel {
|
||||
Error,
|
||||
Trace,
|
||||
}
|
||||
|
||||
fn load_configuration_file(config_file: &str) -> Result<()> {
|
||||
let app_config =
|
||||
crate::config::load(config_file).context("Failed to load application configuration")?;
|
||||
crate::APP_CONFIG
|
||||
.set(app_config)
|
||||
.map_err(|_| anyhow::anyhow!("Application configuration has already been initialized"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn run() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
logger::init(&cli.log_level);
|
||||
match load_configuration_file(&cli.config) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
error!("Error loading configuration file: {:#}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
match cli.command {
|
||||
Some(subcommand) => match subcommand {
|
||||
Commands::FileSync { .. } => {}
|
||||
Commands::Set { .. } => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aes_gcm::aead::{Aead, KeyInit, OsRng};
|
||||
use aes_gcm::{Aes256Gcm, Nonce};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
use base64::Engine;
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const CONFIG_KEY_ENV: &str = "IMOJE_CONFIG_KEY";
|
||||
const CONFIG_DIR: &str = "config/imoje";
|
||||
const NONCE_LEN: usize = 12;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
pub dummy_setting: String,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dummy_setting: "dummy-value".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(config_file: &str) -> Result<AppConfig> {
|
||||
let config_path = config_path(config_file)?;
|
||||
|
||||
if !config_path.exists() {
|
||||
let default_config = AppConfig::default();
|
||||
save_encrypted(&config_path, &default_config)
|
||||
.context("Unable to create encrypted default configuration file")?;
|
||||
|
||||
return Ok(default_config);
|
||||
}
|
||||
|
||||
let encrypted_config = fs::read_to_string(&config_path)
|
||||
.with_context(|| format!("Unable to read configuration file {}", config_path.display()))?;
|
||||
|
||||
decrypt_config(&encrypted_config)
|
||||
.with_context(|| format!("Unable to decrypt configuration file {}", config_path.display()))
|
||||
}
|
||||
|
||||
fn config_path(config_file: &str) -> Result<PathBuf> {
|
||||
let home_dir = dirs::home_dir().ok_or_else(|| anyhow!("Unable to determine home directory"))?;
|
||||
|
||||
Ok(home_dir.join(CONFIG_DIR).join(format!("{}.conf", config_file)))
|
||||
}
|
||||
|
||||
fn save_encrypted(config_path: &PathBuf, config: &AppConfig) -> Result<()> {
|
||||
if let Some(parent) = config_path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.with_context(|| format!("Unable to create configuration directory {}", parent.display()))?;
|
||||
}
|
||||
|
||||
let serialized_config = toml::to_string_pretty(config)
|
||||
.context("Unable to serialize default configuration")?;
|
||||
|
||||
let encrypted_config = encrypt_config(&serialized_config)
|
||||
.context("Unable to encrypt default configuration")?;
|
||||
|
||||
fs::write(config_path, encrypted_config)
|
||||
.with_context(|| format!("Unable to write configuration file {}", config_path.display()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encrypt_config(config: &str) -> Result<String> {
|
||||
let key = config_key()?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key)
|
||||
.map_err(|_| anyhow!("Invalid encryption key length"))?;
|
||||
|
||||
let mut nonce_bytes = [0_u8; NONCE_LEN];
|
||||
OsRng.fill_bytes(&mut nonce_bytes);
|
||||
|
||||
let ciphertext = cipher
|
||||
.encrypt(Nonce::from_slice(&nonce_bytes), config.as_bytes())
|
||||
.map_err(|error| anyhow!("Unable to encrypt configuration: {error}"))?;
|
||||
|
||||
let mut encrypted_payload = Vec::with_capacity(NONCE_LEN + ciphertext.len());
|
||||
encrypted_payload.extend_from_slice(&nonce_bytes);
|
||||
encrypted_payload.extend_from_slice(&ciphertext);
|
||||
|
||||
Ok(BASE64.encode(encrypted_payload))
|
||||
}
|
||||
|
||||
fn decrypt_config(encrypted_config: &str) -> Result<AppConfig> {
|
||||
let encrypted_payload = BASE64
|
||||
.decode(encrypted_config.trim())
|
||||
.context("Configuration file is not valid base64")?;
|
||||
|
||||
if encrypted_payload.len() <= NONCE_LEN {
|
||||
return Err(anyhow!("Configuration file is too short"));
|
||||
}
|
||||
|
||||
let key = config_key()?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key)
|
||||
.map_err(|_| anyhow!("Invalid encryption key length"))?;
|
||||
|
||||
let nonce = Nonce::from_slice(&encrypted_payload[..NONCE_LEN]);
|
||||
let ciphertext = &encrypted_payload[NONCE_LEN..];
|
||||
|
||||
let plaintext = cipher
|
||||
.decrypt(nonce, ciphertext)
|
||||
.map_err(|error| anyhow!("Unable to decrypt configuration: {error}"))?;
|
||||
|
||||
let plaintext = String::from_utf8(plaintext)
|
||||
.context("Configuration file does not contain valid UTF-8")?;
|
||||
|
||||
toml::from_str(&plaintext).context("Unable to parse decrypted configuration")
|
||||
}
|
||||
|
||||
fn config_key() -> Result<[u8; 32]> {
|
||||
let encoded_key = std::env::var(CONFIG_KEY_ENV)
|
||||
.unwrap_or("ihDlaCqJqUGJ1am4dT9k/BHRG2UT+iK9IfYcPe0Db/I=".to_string());
|
||||
// .with_context(|| format!("Missing required environment variable {CONFIG_KEY_ENV}"))?;
|
||||
|
||||
let key = BASE64
|
||||
.decode(encoded_key.trim())
|
||||
.with_context(|| format!("{CONFIG_KEY_ENV} must be a base64-encoded 32-byte key"))?;
|
||||
|
||||
key.try_into()
|
||||
.map_err(|_| anyhow!("{CONFIG_KEY_ENV} must decode to exactly 32 bytes"))
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::cli::LogLevel;
|
||||
use log::{LevelFilter, info};
|
||||
|
||||
pub(crate) fn init(log_level: &LogLevel) {
|
||||
let filter_level = match log_level {
|
||||
LogLevel::Info => LevelFilter::Info,
|
||||
LogLevel::Debug => LevelFilter::Debug,
|
||||
LogLevel::Warning => LevelFilter::Warn,
|
||||
LogLevel::Error => LevelFilter::Error,
|
||||
LogLevel::Trace => LevelFilter::Trace,
|
||||
};
|
||||
env_logger::builder()
|
||||
.filter_level(filter_level)
|
||||
.format_target(false)
|
||||
.init();
|
||||
|
||||
welcome_message()
|
||||
}
|
||||
|
||||
fn welcome_message() {
|
||||
let authors = match option_env!("CARGO_PKG_AUTHORS") {
|
||||
Some(authors_str) => {
|
||||
", by ".to_string() + &authors_str.split(':').collect::<Vec<&str>>().join(", ")
|
||||
}
|
||||
None => "".to_string(),
|
||||
};
|
||||
let description = env!("CARGO_PKG_DESCRIPTION");
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
info!("Starting {}, v{}{}", description, version, authors);
|
||||
info!(r#".___ _____ __ _________ .____ .___"#);
|
||||
info!(r#"| | / \ ____ |__| ____ \_ ___ \| | | |"#);
|
||||
info!(r#"| |/ \ / \ / _ \ | |/ __ \/ \ \/| | | |"#);
|
||||
info!(r#"| / Y ( <_> ) | \ ___/\ \___| |___| |"#);
|
||||
info!(r#"|___\____|__ /\____/\__| |\___ >\______ /_______ \___|"#);
|
||||
info!(r#") \/ \______| \/ \/ \/"#);
|
||||
}
|
||||
+46
-3
@@ -1,14 +1,57 @@
|
||||
//! # IMoje File Sync
|
||||
//!
|
||||
//! Syncs the contents of a Salesforce File to S3
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
mod cli;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
fn main() {
|
||||
mod aws_s3;
|
||||
mod cli;
|
||||
mod config;
|
||||
mod logger;
|
||||
mod salesforce;
|
||||
mod terminator;
|
||||
|
||||
|
||||
pub static APP_CONFIG: OnceLock<config::AppConfig> = OnceLock::new();
|
||||
|
||||
|
||||
#[::tokio::main]
|
||||
async fn main() {
|
||||
cli::run();
|
||||
/*
|
||||
let args = cli::Args::parse();
|
||||
|
||||
|
||||
|
||||
println!("Hello, world!");
|
||||
|
||||
let salesforce_client = salesforce::connect(&args.sfdx_auth_url).await?;
|
||||
let s3_client = aws_s3::connect(
|
||||
args.s3_client_id(),
|
||||
args.s3_client_secret(),
|
||||
args.s3_endpoint(),
|
||||
args.s3_region(),
|
||||
);
|
||||
|
||||
|
||||
salesforce_client.query(r#"SELECT Id, Title, FileType, FileExtension, Description,
|
||||
ContentLocation, Origin, VersionData, ContentDocumentId, FirstPublishLocationId, PathOnClient
|
||||
FROM ContentVersion
|
||||
WHERE IsMajorVersion = TRUE
|
||||
AND ContentLocation = 'S'
|
||||
LIMIT 1\
|
||||
"#)
|
||||
|
||||
|
||||
|
||||
if terminator::CONFIG.is_fired() {
|
||||
info!("Received termination signal, exiting...");
|
||||
}
|
||||
|
||||
|
||||
|
||||
info!("Done.");*/
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use anyhow::{Context, Result};
|
||||
use rustsf::{RestApi, AuthUrl};
|
||||
|
||||
pub(crate) mod models;
|
||||
|
||||
pub async fn connect(sfdx_auth_url: &str) -> Result<RestApi> {
|
||||
|
||||
let credentials = AuthUrl::new(sfdx_auth_url.to_string())
|
||||
.context("Incorrect SFDX_AUTH_URL")?;
|
||||
|
||||
let client = rustsf::Client::new(credentials).await
|
||||
.context("Unable to connect to Salesforce")?;
|
||||
|
||||
Ok(RestApi::new(client))
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
use rustsf::DefSObject;
|
||||
|
||||
#[DefSObject(sobject_type = "ContentVersion", fields="owner,audit,system")]
|
||||
pub(crate) struct ContentVersion {
|
||||
|
||||
/// The ID of the ContentDocument associated with this version.
|
||||
#[serde(rename = "ContentDocumentId")]
|
||||
content_document_id: Option<String>,
|
||||
|
||||
/// The location where the content is stored. Valid values include Salesforce, external, or linked content locations.
|
||||
#[serde(rename = "ContentLocation")]
|
||||
content_location: Option<String>,
|
||||
|
||||
/// The date and time when the file content was last modified.
|
||||
#[serde(rename = "ContentModifiedDate")]
|
||||
content_modified_date: Option<String>,
|
||||
|
||||
/// The ID of the user who last modified the file content.
|
||||
#[serde(rename = "ContentModifiedById")]
|
||||
content_modified_by_id: Option<String>,
|
||||
|
||||
/// The URL of the content when the file is stored outside Salesforce.
|
||||
#[serde(rename = "ContentUrl")]
|
||||
content_url: Option<String>,
|
||||
|
||||
/// The version number of the file.
|
||||
#[serde(rename = "VersionNumber")]
|
||||
version_number: Option<String>,
|
||||
|
||||
/// The title of the file.
|
||||
#[serde(rename = "Title")]
|
||||
title: Option<String>,
|
||||
|
||||
/// The description of the file.
|
||||
#[serde(rename = "Description")]
|
||||
description: Option<String>,
|
||||
|
||||
/// The reason this version of the file was created or changed.
|
||||
#[serde(rename = "ReasonForChange")]
|
||||
reason_for_change: Option<String>,
|
||||
|
||||
/// Controls whether users can share the file with other users, groups, or records.
|
||||
#[serde(rename = "SharingOption")]
|
||||
sharing_option: Option<String>,
|
||||
|
||||
/// Controls file privacy on records.
|
||||
#[serde(rename = "SharingPrivacy")]
|
||||
sharing_privacy: Option<String>,
|
||||
|
||||
/// The complete path of the file on the client machine when it was uploaded.
|
||||
#[serde(rename = "PathOnClient")]
|
||||
path_on_client: Option<String>,
|
||||
|
||||
/// Indicates whether this version is the latest version of the file.
|
||||
#[serde(rename = "IsLatest")]
|
||||
is_latest: bool,
|
||||
|
||||
/// Indicates whether this version is a major version.
|
||||
#[serde(rename = "IsMajorVersion")]
|
||||
is_major_version: bool,
|
||||
|
||||
/// Indicates whether the file can be used as an asset file.
|
||||
#[serde(rename = "IsAssetEnabled")]
|
||||
is_asset_enabled: bool,
|
||||
|
||||
/// Indicates whether the file is managed by an external client.
|
||||
#[serde(rename = "IsClientManaged")]
|
||||
is_client_managed: bool,
|
||||
|
||||
/// Indicates whether the file is available externally.
|
||||
#[serde(rename = "IsExternallyAvailable")]
|
||||
is_externally_available: bool,
|
||||
|
||||
/// The ID of the external data source for externally stored content.
|
||||
#[serde(rename = "ExternalDataSourceId")]
|
||||
external_data_source_id: Option<String>,
|
||||
|
||||
/// External document information used to identify externally stored content.
|
||||
#[serde(rename = "ExternalDocumentInfo1")]
|
||||
external_document_info1: Option<String>,
|
||||
|
||||
/// Additional external document information used to identify externally stored content.
|
||||
#[serde(rename = "ExternalDocumentInfo2")]
|
||||
external_document_info2: Option<String>,
|
||||
|
||||
/// The boost value used to promote featured content.
|
||||
#[serde(rename = "FeaturedContentBoost")]
|
||||
featured_content_boost: Option<i64>,
|
||||
|
||||
/// The date when the file was marked as featured content.
|
||||
#[serde(rename = "FeaturedContentDate")]
|
||||
featured_content_date: Option<String>,
|
||||
|
||||
/// The file extension of the uploaded file.
|
||||
#[serde(rename = "FileExtension")]
|
||||
file_extension: Option<String>,
|
||||
|
||||
/// The type of the file, such as PDF, PNG, WORD, or LINK.
|
||||
#[serde(rename = "FileType")]
|
||||
file_type: Option<String>,
|
||||
|
||||
/// The ID of the first location where the file was published.
|
||||
#[serde(rename = "FirstPublishLocationId")]
|
||||
first_publish_location_id: Option<String>,
|
||||
|
||||
/// The source from which the content originated.
|
||||
#[serde(rename = "Origin")]
|
||||
origin: Option<String>,
|
||||
|
||||
/// The ID of the Experience Cloud site or network associated with the file.
|
||||
#[serde(rename = "NetworkId")]
|
||||
network_id: Option<String>,
|
||||
|
||||
/// The number of positive ratings submitted for this file version.
|
||||
#[serde(rename = "PositiveRatingCount")]
|
||||
positive_rating_count: Option<i64>,
|
||||
|
||||
/// The publishing status of the file version.
|
||||
#[serde(rename = "PublishStatus")]
|
||||
publish_status: Option<String>,
|
||||
|
||||
/// A comma-separated list of tags associated with the file.
|
||||
#[serde(rename = "TagCsv")]
|
||||
tag_csv: Option<String>,
|
||||
|
||||
/// A text preview of the file content.
|
||||
#[serde(rename = "TextPreview")]
|
||||
text_preview: Option<String>,
|
||||
|
||||
/// The body of the file, represented as base64-encoded content when retrieved through the API.
|
||||
#[serde(rename = "VersionData")]
|
||||
version_data: Option<String>,
|
||||
|
||||
/// The size of the file in bytes.
|
||||
#[serde(rename = "ContentSize")]
|
||||
content_size: Option<i64>,
|
||||
|
||||
/// MD5 checksum for the file.
|
||||
#[serde(rename = "Checksum")]
|
||||
checksum: Option<String>,
|
||||
|
||||
/// The ID of the ContentBody record that stores the binary file data.
|
||||
#[serde(rename = "ContentBodyId")]
|
||||
content_body_id: Option<String>,
|
||||
|
||||
/// The ID of the user who created the file version.
|
||||
#[serde(rename = "CreatedById")]
|
||||
created_by_id: String,
|
||||
|
||||
/// The date and time when the file version was created.
|
||||
#[serde(rename = "CreatedDate")]
|
||||
created_date: String,
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use lazy_static::lazy_static;
|
||||
use signal_hook::consts::TERM_SIGNALS;
|
||||
use signal_hook::flag;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Terminator = Terminator::new();
|
||||
}
|
||||
|
||||
pub struct Terminator {
|
||||
term: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Terminator {
|
||||
pub(crate) fn is_fired(&self) -> bool {
|
||||
!self.term.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Terminator {
|
||||
pub fn new() -> Self {
|
||||
let term_now = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// Register signals to set the flag to true
|
||||
for sig in TERM_SIGNALS {
|
||||
flag::register(*sig, Arc::clone(&term_now)).unwrap();
|
||||
}
|
||||
|
||||
Self { term: term_now }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user