use crate::settings::ARGS;
use crate::VERSION;
use directories::ProjectDirs;
use lazy_static::lazy_static;
use log::LevelFilter;
#[cfg(not(test))]
use sanitize_filename_reader_friendly::TRIM_LINE_CHARS;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::fs;
#[cfg(not(test))]
use std::fs::File;
#[cfg(not(test))]
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::RwLock;
use tpnote_lib::config::Filename;
use tpnote_lib::config::LocalLinkKind;
use tpnote_lib::config::Tmpl;
use tpnote_lib::config::TmplHtml;
#[cfg(not(test))]
use tpnote_lib::config::FILENAME_DOTFILE_MARKER;
#[cfg(not(test))]
use tpnote_lib::config::LIB_CFG;
use tpnote_lib::error::FileError;
use tpnote_lib::filename::NotePathBuf;
const CARGO_BIN_NAME: &str = env!("CARGO_BIN_NAME");
const CONFIG_FILENAME: &str = concat!(env!("CARGO_BIN_NAME"), ".toml");
const ARG_DEFAULT_DEBUG: LevelFilter = LevelFilter::Error;
const ARG_DEFAULT_EDITOR: bool = false;
const ARG_DEFAULT_NO_FILENAME_SYNC: bool = false;
const ARG_DEFAULT_POPUP: bool = true;
const ARG_DEFAULT_TTY: bool = false;
const ARG_DEFAULT_ADD_HEADER: bool = true;
const CLIPBOARD_READ_ENABLED: bool = true;
const CLIPBOARD_EMPTY_ENABLED: bool = true;
#[cfg(all(target_family = "unix", not(target_vendor = "apple")))]
const APP_ARGS_BROWSER: &[&[&str]] = &[
&[
"flatpak",
"run",
"org.mozilla.firefox",
"--new-window",
"--private-window",
],
&["firefox", "--new-window", "--private-window"],
&["firefox-esr", "--new-window", "--private-window"],
&[
"flatpak",
"run",
"com.github.Eloston.UngoogledChromium",
"--new-window",
"--incognito",
],
&[
"flatpak",
"run",
"org.chromium.Chromium",
"--new-window",
"--incognito",
],
&["chromium-browser", "--new-window", "--incognito"],
&["chrome", "--new-window", "--incognito"],
];
#[cfg(target_family = "windows")]
const APP_ARGS_BROWSER: &[&[&str]] = &[
&[
"C:\\Program Files\\Mozilla Firefox\\firefox.exe",
"--new-window",
"--private-window",
],
&[
"C:\\Program Files\\Google\\Chrome\\Application\\chrome",
"--new-window",
"--incognito",
],
&[
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
"--inprivate",
],
];
#[cfg(all(target_family = "unix", target_vendor = "apple"))]
const APP_ARGS_BROWSER: &[&[&str]] = &[];
#[cfg(all(target_family = "unix", not(target_vendor = "apple")))]
const APP_ARGS_EDITOR: &[&[&str]] = &[
&["code", "-w", "-n"],
&["flatpak", "run", "com.visualstudio.code", "-w", "-n"],
&["atom", "-w"],
&["marktext", "--no-sandbox", "--new-window"],
&[
"flatpak",
"run",
"com.github.marktext.marktext",
"--new-window",
],
&["retext"],
&["geany", "-s", "-i", "-m"],
&["gedit", "-w"],
&["mousepad", "--disable-server"],
&["leafpad"],
&["nvim-qt", "--nofork"],
&["gvim", "--nofork"],
];
#[cfg(target_family = "windows")]
const APP_ARGS_EDITOR: &[&[&str]] = &[
&[
"C:\\Program Files\\Mark Text\\Mark Text.exe",
"--new-window",
],
&[
"C:\\Program Files\\Notepad++\\notepad++.exe",
"-nosession",
"-multiInst",
],
&["C:\\Windows\\notepad.exe"],
];
#[cfg(all(target_family = "unix", target_vendor = "apple"))]
const APP_ARGS_EDITOR: &[&[&str]] = &[
&["code", "-w", "-n"],
&["atom", "-w"],
&["marktext", "--no-sandbox"],
&["typora"],
&["gvim", "--nofork"],
&["mate"],
&["open", "-a", "TextEdit"],
&["open", "-a", "TextMate"],
&["open"],
];
#[cfg(all(target_family = "unix", not(target_vendor = "apple")))]
const APP_ARGS_EDITOR_CONSOLE: &[&[&str]] =
&[&["hx"], &["nvim"], &["nano"], &["vim"], &["emacs"], &["vi"]];
#[cfg(target_family = "windows")]
const APP_ARGS_EDITOR_CONSOLE: &[&[&str]] = &[&["hx"], &["nvim"]];
#[cfg(all(target_family = "unix", target_vendor = "apple"))]
const APP_ARGS_EDITOR_CONSOLE: &[&[&str]] = &[
&["hx"],
&["nvim"],
&["nano"],
&["pico"],
&["vim"],
&["emacs"],
&["vi"],
];
const VIEWER_STARTUP_DELAY: isize = 500;
const VIEWER_MISSING_HEADER_DISABLES: bool = false;
const VIEWER_NOTIFY_PERIOD: u64 = 1000;
const VIEWER_TCP_CONNECTIONS_MAX: usize = 16;
const VIEWER_SERVED_MIME_TYPES: &[&[&str]] = &[
&["md", "text/x-markdown"],
&["txt", "text/plain"],
&["apng", "image/apng"],
&["avif", "image/avif"],
&["bmp", "image/bmp"],
&["gif", "image/gif"],
&["html", "text/html"],
&["htm", "text/html"],
&["ico", "image/vnd.microsoft.icon"],
&["jpeg", "image/jpeg"],
&["jpg", "image/jpeg"],
&["pdf", "application/pdf"],
&["png", "image/png"],
&["svg", "image/svg+xml"],
&["tiff", "image/tiff"],
&["tif", "image/tiff"],
&["webp", "image/webp"],
&["mp3", "audio/mp3"],
&["ogg", "audio/ogg"],
&["oga", "audio/ogg"],
&["weba", "audio/webm"],
&["flac", "audio/flac"],
&["wav", "audio/wav"],
&["opus", "audio/opus"],
&["mp4", "video/mp4"],
&["ogv", "video/ogg"],
&["webm", "video/webm"],
&["ogx", "application/ogg"],
];
const VIEWER_DISPLAYED_TPNOTE_COUNT_MAX: usize = 10;
#[derive(Debug, Serialize, Deserialize)]
pub struct Cfg {
pub version: String,
pub arg_default: ArgDefault,
pub filename: Filename,
pub clipboard: Clipboard,
pub tmpl: Tmpl,
pub app_args: AppArgs,
pub viewer: Viewer,
pub tmpl_html: TmplHtml,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ArgDefault {
pub debug: LevelFilter,
pub edit: bool,
pub no_filename_sync: bool,
pub popup: bool,
pub tty: bool,
pub add_header: bool,
pub export_link_rewriting: LocalLinkKind,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Clipboard {
pub read_enabled: bool,
pub empty_enabled: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AppArgs {
pub browser: Vec<Vec<String>>,
pub editor: Vec<Vec<String>>,
pub editor_console: Vec<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Viewer {
pub startup_delay: isize,
pub missing_header_disables: bool,
pub notify_period: u64,
pub tcp_connections_max: usize,
pub served_mime_types: Vec<Vec<String>>,
pub displayed_tpnote_count_max: usize,
}
impl ::std::default::Default for Cfg {
fn default() -> Self {
let version = match VERSION {
Some(v) => v.to_string(),
None => "".to_string(),
};
Cfg {
version,
arg_default: ArgDefault::default(),
tmpl: Tmpl::default(),
app_args: AppArgs::default(),
clipboard: Clipboard::default(),
filename: Filename::default(),
viewer: Viewer::default(),
tmpl_html: TmplHtml::default(),
}
}
}
impl ::std::default::Default for ArgDefault {
fn default() -> Self {
ArgDefault {
debug: ARG_DEFAULT_DEBUG,
edit: ARG_DEFAULT_EDITOR,
no_filename_sync: ARG_DEFAULT_NO_FILENAME_SYNC,
popup: ARG_DEFAULT_POPUP,
tty: ARG_DEFAULT_TTY,
add_header: ARG_DEFAULT_ADD_HEADER,
export_link_rewriting: LocalLinkKind::Long,
}
}
}
impl ::std::default::Default for AppArgs {
fn default() -> Self {
AppArgs {
editor: APP_ARGS_EDITOR
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
editor_console: APP_ARGS_EDITOR_CONSOLE
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
browser: APP_ARGS_BROWSER
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
}
}
}
impl ::std::default::Default for Clipboard {
fn default() -> Self {
Clipboard {
read_enabled: CLIPBOARD_READ_ENABLED,
empty_enabled: CLIPBOARD_EMPTY_ENABLED,
}
}
}
impl ::std::default::Default for Viewer {
fn default() -> Self {
Viewer {
startup_delay: VIEWER_STARTUP_DELAY,
missing_header_disables: VIEWER_MISSING_HEADER_DISABLES,
notify_period: VIEWER_NOTIFY_PERIOD,
tcp_connections_max: VIEWER_TCP_CONNECTIONS_MAX,
served_mime_types: VIEWER_SERVED_MIME_TYPES
.iter()
.map(|i| i.iter().map(|a| (*a).to_string()).collect())
.collect(),
displayed_tpnote_count_max: VIEWER_DISPLAYED_TPNOTE_COUNT_MAX,
}
}
}
lazy_static! {
pub static ref VIEWER_SERVED_MIME_TYPES_HMAP: HashMap<&'static str, &'static str> = {
let mut hm = HashMap::new();
for l in &CFG.viewer.served_mime_types {
if l.len() >= 2
{
hm.insert(l[0].as_str(), l[1].as_str());
};
};
hm
};
}
lazy_static! {
pub static ref CFG_FILE_LOADING: RwLock<Result<(), FileError>> = RwLock::new(Ok(()));
}
#[cfg(not(test))]
#[inline]
fn config_load(config_path: &Path) -> Result<Cfg, FileError> {
if config_path.exists() {
let config: Cfg = toml::from_str(&fs::read_to_string(config_path)?)?;
if config
.filename
.sort_tag_chars
.find(config.filename.sort_tag_extra_separator)
.is_some()
|| config.filename.sort_tag_extra_separator == FILENAME_DOTFILE_MARKER
{
return Err(FileError::ConfigFileSortTag {
char: FILENAME_DOTFILE_MARKER,
chars: config.filename.sort_tag_chars.escape_default().to_string(),
extra_separator: config
.filename
.sort_tag_extra_separator
.escape_default()
.to_string(),
});
}
if !TRIM_LINE_CHARS.contains(&config.filename.copy_counter_extra_separator) {
return Err(FileError::ConfigFileCopyCounter {
chars: TRIM_LINE_CHARS.escape_default().to_string(),
extra_separator: config
.filename
.copy_counter_extra_separator
.escape_default()
.to_string(),
});
}
{
let mut lib_cfg = LIB_CFG.write().unwrap();
(*lib_cfg).filename = config.filename.clone();
(*lib_cfg).tmpl = config.tmpl.clone();
(*lib_cfg).tmpl_html = config.tmpl_html.clone();
}
Ok(config)
} else {
let cfg = Cfg::default();
config_write(&cfg, config_path)?;
Ok(cfg)
}
}
#[cfg(test)]
#[inline]
fn config_load(_config_path: &Path) -> Result<Cfg, FileError> {
Ok(Cfg::default())
}
#[cfg(not(test))]
fn config_write(config: &Cfg, config_path: &Path) -> Result<(), FileError> {
fs::create_dir_all(config_path.parent().unwrap_or_else(|| Path::new("")))?;
let mut buffer = File::create(config_path)?;
buffer.write_all(toml::to_string_pretty(config)?.as_bytes())?;
Ok(())
}
#[cfg(test)]
fn config_write(_config: &Cfg, _config_path: &Path) -> Result<(), FileError> {
Ok(())
}
lazy_static! {
pub static ref CFG: Cfg = {
let config_path = if let Some(c) = &ARGS.config {
Path::new(c)
} else {
match &*CONFIG_PATH {
Some(p) => p.as_path(),
None => {
let mut cfg_file_loading = CFG_FILE_LOADING.write().unwrap();
*cfg_file_loading = Err(FileError::PathToConfigFileNotFound);
return Cfg::default();
},
}
};
config_load(config_path)
.unwrap_or_else(|e|{
let mut cfg_file_loading = CFG_FILE_LOADING.write().unwrap();
*cfg_file_loading = Err(e);
Cfg::default()
})
};
}
lazy_static! {
pub static ref CONFIG_PATH : Option<PathBuf> = {
if let Some(c) = &ARGS.config {
Some(PathBuf::from(c))
} else {
let config = ProjectDirs::from("rs", "", CARGO_BIN_NAME)?;
let mut config = PathBuf::from(config.config_dir());
config.push(Path::new(CONFIG_FILENAME));
Some(config)
}
};
}
pub fn backup_config_file() -> Result<PathBuf, FileError> {
if let Some(ref config_path) = *CONFIG_PATH {
if config_path.exists() {
let mut config_path_bak = config_path.clone();
config_path_bak.set_next_unused()?;
fs::rename(&config_path.as_path(), &config_path_bak)?;
config_write(&Cfg::default(), config_path)?;
Ok(config_path_bak)
} else {
Err(FileError::ConfigFileNotFound)
}
} else {
Err(FileError::PathToConfigFileNotFound)
}
}