implement connections count with template macro, add --show-hidden option, fix configuration variable names

This commit is contained in:
yggverse 2025-06-28 15:03:40 +03:00
parent 8cdd7ea8ae
commit c484a59579
8 changed files with 119 additions and 19 deletions

View file

@ -51,6 +51,11 @@ nexy -p /path/to/public_dir
-p, --public <PUBLIC>
Absolute path to the public files directory
--show-hidden
Show hidden entries (in the directory listing)
* Important: this option does not prevent access to hidden files!
--template-access-denied <TEMPLATE_ACCESS_DENIED>
Absolute path to the `Access denied` template file
@ -71,7 +76,7 @@ nexy -p /path/to/public_dir
* this template file expects pattern and cannot be in binary format
**Patterns** * `{list}` - entries list for the `public` directory
**Patterns** * `{list}` - entries list for the `public` directory * `{hosts}` - unique visitors count * `{hits}` - requests count
--template-index <TEMPLATE_INDEX>
Absolute path to the `Index` template file for each directory

View file

@ -31,6 +31,12 @@ pub struct Config {
#[arg(short, long)]
pub public: String,
/// Show hidden entries (in the directory listing)
///
/// * Important: this option does not prevent access to hidden files!
#[arg(long, default_value_t = false)]
pub show_hidden: bool,
/// Absolute path to the `Access denied` template file
///
/// * this template file can be in binary format (e.g. image)
@ -56,6 +62,8 @@ pub struct Config {
///
/// **Patterns**
/// * `{list}` - entries list for the `public` directory
/// * `{hosts}` - unique visitors count
/// * `{hits}` - requests count
#[arg(long)]
pub template_welcome: Option<String>,

View file

@ -32,6 +32,7 @@ impl Connection {
}
pub fn handle(mut self) {
self.session.event.connection.update(&self.address.client);
let mut t = 0; // total bytes
match self.request() {
Ok(q) => {
@ -71,7 +72,11 @@ impl Connection {
Response::File(b) => b,
Response::Directory(ref s, is_root) => {
&if is_root {
self.session.template.welcome(Some(s))
self.session.template.welcome(
Some(s),
Some(self.session.event.connection.hosts()),
Some(self.session.event.connection.hits()),
)
} else {
self.session.template.index(Some(s))
}

View file

@ -1,25 +1,32 @@
mod access_log;
mod debug;
mod event;
mod storage;
mod template;
use {access_log::AccessLog, debug::Debug, storage::Storage, template::Template};
use {access_log::AccessLog, debug::Debug, event::Event, storage::Storage, template::Template};
/// Shared, multi-thread features for the current server session
pub struct Session {
pub debug: Debug,
pub access_log: AccessLog,
pub debug: Debug,
pub event: Event,
pub storage: Storage,
pub template: Template,
}
impl Session {
pub fn init(config: &crate::config::Config) -> anyhow::Result<Self> {
let template = Template::init(config)?;
Ok(Self {
debug: Debug::init(config)?,
access_log: AccessLog::init(config)?,
debug: Debug::init(config)?,
event: Event::init(
// do not init `Connection` event if its features not in use
template.welcome.contains("{hosts}") || template.welcome.contains("{hits}"),
)?,
storage: Storage::init(config)?,
template: Template::init(config)?,
template,
})
}
}

15
src/session/event.rs Normal file
View file

@ -0,0 +1,15 @@
mod connection;
use connection::Connection;
pub struct Event {
pub connection: Connection,
// another features...
}
impl Event {
pub fn init(is_connection_enabled: bool) -> anyhow::Result<Self> {
Ok(Self {
connection: Connection::init(is_connection_enabled),
})
}
}

View file

@ -0,0 +1,44 @@
use std::{
collections::HashMap,
net::{IpAddr, SocketAddr},
sync::RwLock,
};
/// Count peer connections (for the current server session)
pub struct Connection(Option<RwLock<HashMap<IpAddr, usize>>>);
impl Connection {
pub fn init(is_enabled: bool) -> Self {
if is_enabled {
Self(Some(RwLock::new(HashMap::with_capacity(100))))
} else {
Self(None)
}
}
pub fn update(&self, peer: &SocketAddr) {
if let Some(ref this) = self.0 {
this.write()
.unwrap()
.entry(peer.ip())
.and_modify(|c| *c += 1)
.or_insert(1);
}
}
pub fn hosts(&self) -> usize {
if let Some(ref this) = self.0 {
this.read().unwrap().len()
} else {
0
}
}
pub fn hits(&self) -> usize {
if let Some(ref this) = self.0 {
this.read().unwrap().values().sum()
} else {
0
}
}
}

View file

@ -18,6 +18,10 @@ pub struct Storage {
public_dir: PathBuf,
/// Streaming buffer options
read_chunk: usize,
/// Show hidden entries (in the directory listing)
///
/// * important: this option does not prevent access to hidden files!
show_hidden: bool,
}
impl Storage {
@ -34,6 +38,7 @@ impl Storage {
list_config: ListConfig::init(config)?,
public_dir,
read_chunk: config.read_chunk,
show_hidden: config.show_hidden,
})
}
@ -124,18 +129,19 @@ impl Storage {
let mut files = Vec::with_capacity(C);
for entry in fs::read_dir(path)? {
let e = entry?;
let name = e.file_name().to_string_lossy().to_string();
if !self.show_hidden && name.starts_with('.') {
continue;
}
let meta = fs::metadata(e.path())?;
match (meta.is_dir(), meta.is_file()) {
(true, _) => dirs.push(Dir {
meta,
name: e.file_name().to_string_lossy().to_string(),
name,
count: fs::read_dir(e.path()).map_or(0, |i| i.count()),
}),
(_, true) => files.push(File {
meta,
name: e.file_name().to_string_lossy().to_string(),
}),
_ => {} // @TODO symlinks support?
(_, true) => files.push(File { meta, name }),
_ => continue, // @TODO symlinks support?
}
}
// build resulting list

View file

@ -3,7 +3,7 @@ pub struct Template {
index: String,
internal_server_error: Vec<u8>,
not_found: Vec<u8>,
welcome: String,
pub welcome: String,
}
impl Template {
@ -14,21 +14,21 @@ impl Template {
Some(ref p) => read(p)?,
None => "Access denied".into(),
},
index: match config.template_access_denied {
index: match config.template_index {
Some(ref p) => read_to_string(p)?,
None => "{list}".into(),
},
internal_server_error: match config.template_access_denied {
internal_server_error: match config.template_internal_server_error {
Some(ref p) => read(p)?,
None => "Internal server error".into(),
},
not_found: match config.template_access_denied {
not_found: match config.template_not_found {
Some(ref p) => read(p)?,
None => "Not found".into(),
},
welcome: match config.template_access_denied {
welcome: match config.template_welcome {
Some(ref p) => read_to_string(p)?,
None => "Welcome to Nexy!\n\n{list}".into(),
None => "Welcome to Nexy!\n\n{list}\n\n👁 {hosts} / {hits}".into(),
},
})
}
@ -51,9 +51,19 @@ impl Template {
&self.not_found
}
pub fn welcome(&self, list: Option<&str>) -> Vec<u8> {
pub fn welcome(
&self,
list: Option<&str>,
hosts: Option<usize>,
hits: Option<usize>,
) -> Vec<u8> {
fn format_count(v: Option<usize>) -> String {
v.map_or(String::new(), |c| c.to_string())
}
self.welcome
.replace("{list}", list.unwrap_or_default())
.replace("{hosts}", &format_count(hosts))
.replace("{hits}", &format_count(hits))
.into()
}
}