1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
//! Sync conduit handling
//!
//! ## Conduit specification
//!
//! Conduits are implemented as external executables, which are called at sync time to do one of
//! three things:
//!
//! - Replace the device's copy of the data with the local copy of the data
//! - Replace the local copy of the data with the data from the device
//! - Merge the local copy of the data with the device's copy of the data
//!
//! The sync mode, along with other conduit data, is passed in the form of environment variables to
//! the conduit process.
//!
//! ### Environment variables
//!
//! Conduits are always called with the following variables set in their environment:
//!
//! - `PALMRS_SYNC_VERSION` - palmrs-sync version number
//! - `PALMRS_SYNC_MODE` - sync mode (one of `keep-local`, `keep-device`, or `merge`)
//! - `PALMRS_DATA_LOCAL` - path to the local data directory for this conduit
//! - `PALMRS_DATA_DEVICE` - path to a directory containing the entire set of Palm OS database
//! files pulled from the device at the beginning of the sync process
//!
//! In addition, conduits are called with a parsed version of their conduit-specific configuration,
//! pulled from the palm.rs configuration for the specific device that is being synced, in their
//! environment. The names of all of these environment variables are prefixed, and that prefix is
//! passed to the conduit in the `PALMRS_CONFIG_PREFIX` environment variable.
//!
//! For example, a test conduit could be called with the following environment:
//!
//! ```shell
//! PALMRS_SYNC_VERSION="0.1.0-dev.1"
//! PALMRS_SYNC_MODE="keep-device"
//! PALMRS_DATA_LOCAL="/home/user/Documents/palm.rs/conduits/conduit-test"
//! PALMRS_DATA_DEVICE="/tmp/palmrs-device-data.27b862fa.tmp"
//! PALMRS_CONFIG_PREFIX="CONDUIT_TEST__"
//! CONDUIT_TEST__HELLO_WORLD="Hello, world!"
//! ```
//!
//! ### Local data
//!
//! Conduits are free to store whatever data they would like, in whatever format they would like,
//! in their local data directory (the path to which is specified in the `PALMRS_DATA_LOCAL`
//! environment variable passed to the conduit).
//!
//! It is _recommended_, but not _required_, that conduits store their data in a format that is
//! easily editable by a human - either directly with a text editor, or by using already available
//! tools.
//!
//! ### Device data
//!
//! All of the Palm OS database files that have been synced from the device, regardless of whether
//! or not the specific conduit needs them, will be available in the directory specified by the
//! `PALMRS_DATA_DEVICE` environment variable.
//!
//! The data available in that directory may not be the result of a "full" (i.e. "backup") sync
//! with the device - in that case, the partial data from the current sync will be overlaid on top
//! of the data from the last "full" sync.
//!
//! When the conduit is operating in `keep-local` or `merge` modes, any data written back to any of
//! the databases in the `PALMRS_DATA_DEVICE` directory (or, any new database files that are added
//! to that directory) will be synced back to the device. When the conduit is operating in
//! `keep-device` mode, the conduit _MAY NOT_ make changes to the databases in that directory.
use core::{
cmp::PartialEq,
default::Default,
fmt::{self, Debug, Display},
};
use std::{collections::HashMap, env, ffi::OsString, path::PathBuf};
use subprocess::{self, Popen, PopenConfig, Redirection};
use crate::SyncMode;
mod within;
pub use self::within::{WithinConduit, WithinConduitConfig};
/// Container (with builder API) for sync conduit requirements
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ConduitRequirements {
pub databases: Vec<String>,
}
impl ConduitRequirements {
pub fn new() -> Self {
Default::default()
}
pub fn with_databases(mut self, databases: &[&str]) -> Self {
self.databases = databases.into_iter().map(|x| String::from(*x)).collect();
self
}
#[allow(unused_mut)]
pub fn finish(mut self) -> Self {
self
}
}
impl Display for ConduitRequirements {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "[conduit-requirements]")?;
writeln!(f, "databases = {:?}", &self.databases)?;
Ok(())
}
}
/// Conduit call handler
#[derive(Debug, Clone, PartialEq)]
pub struct ConduitHandler {
pub sync_mode: SyncMode,
pub conduit_name: String,
pub conduit_config: HashMap<OsString, OsString>,
pub path_local: PathBuf,
pub path_device: PathBuf,
}
impl ConduitHandler {
pub fn new(conduit_name: &str, sync_mode: SyncMode) -> Self {
Self {
sync_mode,
conduit_name: String::from(conduit_name),
conduit_config: HashMap::new(),
path_local: PathBuf::new(),
path_device: PathBuf::new(),
}
}
pub fn make_config_prefix(&self) -> OsString {
let mut prefix = self
.conduit_name
.replace("palmrs-", "")
.replace("-", "_")
.replace("__", "_")
.to_uppercase();
prefix.push_str("__");
prefix.into()
}
pub fn make_argv(&self) -> Vec<OsString> {
vec![self.conduit_name.clone().into()]
}
pub fn make_environment(&self) -> Vec<(OsString, OsString)> {
let mut env: Vec<(OsString, OsString)> = Vec::new();
let mangled_prefix = self.make_config_prefix();
// Inherit a few things from the current environment
env.push((
"HOME".into(),
env::var_os("HOME").unwrap_or_else(OsString::new),
));
env.push((
"PATH".into(),
env::var_os("PATH").unwrap_or_else(OsString::new),
));
// Inherit Rust logging & backtrace settings, if they're set
if let Some(var_rust_log) = env::var_os("RUST_LOG") {
env.push(("RUST_LOG".into(), var_rust_log.into()));
}
if let Some(var_rust_backtrace) = env::var_os("RUST_BACKTRACE") {
env.push(("RUST_BACKTRACE".into(), var_rust_backtrace.into()));
}
// Generic configuration
env.push((
"PALMRS_SYNC_VERSION".into(),
env!("CARGO_PKG_VERSION").into(),
));
env.push((
"PALMRS_SYNC_MODE".into(),
format!("{}", self.sync_mode).into(),
));
env.push((
"PALMRS_DATA_LOCAL".into(),
self.path_local.as_os_str().into(),
));
env.push((
"PALMRS_DATA_DEVICE".into(),
self.path_device.as_os_str().into(),
));
// Conduit-specific configuration keys
env.push(("PALMRS_CONFIG_PREFIX".into(), mangled_prefix.clone()));
for (cfgkey, cfgval) in &self.conduit_config {
let mut key = mangled_prefix.clone();
key.push(cfgkey.to_ascii_uppercase());
env.push((key, cfgval.clone()));
}
env
}
pub fn popen(&self, stdout: Redirection, stderr: Redirection) -> subprocess::Result<Popen> {
let config = PopenConfig {
stdout,
stderr,
env: Some(self.make_environment()),
..Default::default()
};
Popen::create(&self.make_argv(), config)
}
}