orphanage

Check-in Differences
Login

Check-in Differences

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From orphanage-0.1.4 To orphanage-0.2.0

2025-04-03
00:38
Lossy cast. check-in: de2563a827 user: jan tags: trunk
2025-04-02
23:43
Add change log section. check-in: e41a571187 user: jan tags: orphanage-0.2.0, trunk
23:40
Release maintenance. check-in: 9678aea121 user: jan tags: trunk
2025-02-21
04:00
Update rusqlite and rand. check-in: d9acfcb640 user: jan tags: trunk
2024-11-22
14:23
Release maintenance. check-in: 2b23697533 user: jan tags: orphanage-0.1.4, trunk
14:21
Add 'outdated' functions to fs module, useful for driving dependency rules. check-in: 89ac2f5207 user: jan tags: trunk

Changes to .efiles.

1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17

18
19
20
21

Cargo.toml
README.md
www/index.md
www/changelog.md
src/err.rs
src/lib.rs
src/path.rs
src/fs.rs
src/strx.rs

src/buf.rs
src/sqlfuncs.rs
src/futures.rs
src/iox.rs
src/tokiox.rs
src/tokiox/tcpconn.rs
src/serde_parsers.rs
src/setops.rs

tests/setops.rs
examples/fut_if_some.rs
examples/deser.rs
examples/slasher.rs










>

<



<


>




>
1
2
3
4
5
6
7
8
9
10
11

12
13
14

15
16
17
18
19
20
21
22
Cargo.toml
README.md
www/index.md
www/changelog.md
src/err.rs
src/lib.rs
src/path.rs
src/fs.rs
src/strx.rs
src/numx.rs
src/buf.rs

src/futures.rs
src/iox.rs
src/tokiox.rs

src/serde_parsers.rs
src/setops.rs
src/ffi.rs
tests/setops.rs
examples/fut_if_some.rs
examples/deser.rs
examples/slasher.rs
examples/diskfree.rs

Changes to Cargo.toml.

1
2
3
4
5
6
7
8
9
10
[package]
name = "orphanage"
version = "0.1.4"
edition = "2021"
license = "0BSD"
# https://crates.io/category_slugs
categories = [ "network-programming" ]
keywords = [ "sqlite", "fs", "path" ]
repository = "https://repos.qrnch.tech/pub/orphanage"
description = "Random collection of stuff that is still searching for a home."


|







1
2
3
4
5
6
7
8
9
10
[package]
name = "orphanage"
version = "0.2.0"
edition = "2021"
license = "0BSD"
# https://crates.io/category_slugs
categories = [ "network-programming" ]
keywords = [ "sqlite", "fs", "path" ]
repository = "https://repos.qrnch.tech/pub/orphanage"
description = "Random collection of stuff that is still searching for a home."
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
]

# https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section
[badges]
maintenance = { status = "experimental" }

[features]
tokio = ["dep:tokio", "dep:async-trait", "dep:killswitch"]
rusqlite = ["dep:rusqlite", "dep:sha2"]
serde = ["dep:serde", "dep:parse-size"]

[dependencies]
async-trait = { version = "0.1.82", optional = true }
hashbrown = { version = "0.15.1" }
killswitch = { version = "0.4.2", optional = true }
parse-size = { version = "1.1.0", optional = true }

rand = { version = "0.8.5" }
rusqlite = { version = "0.32.1", optional = true, features = ["functions"] }
serde = { version = "1.0.214", optional = true, features = ["derive"] }
sha2 = { version = "0.10.7", optional = true }

shellexpand = { version = "3.1.0" }
tokio = { version = "1.41.0", optional = true, features = [
  "macros", "net", "time"
] }











[dev-dependencies]

killswitch = { version = "0.4.2" }
tokio = { version = "1.40.0", features = ["full"] }
tokio-test = { version = "0.4.3" }
toml = { version = "0.8.18" }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]

[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }

multiple_crate_versions = "allow"








|
<



|
|
<

>
|
<
|
|
>





>
>
>
>
>
>
>
>
>
>

>

|

|













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
]

# https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section
[badges]
maintenance = { status = "experimental" }

[features]
tokio = ["dep:tokio"]

serde = ["dep:serde", "dep:parse-size"]

[dependencies]
#async-trait = { version = "0.1.86", optional = true }
hashbrown = { version = "0.15.2" }

parse-size = { version = "1.1.0", optional = true }
paste = { version = "1.0.15" }
rand = { version = "0.9.0" }

serde = { version = "1.0.218", optional = true, features = ["derive"] }
sha2 = { version = "0.10.8", optional = true }
sha3 = { version = "0.10.8", optional = true }
shellexpand = { version = "3.1.0" }
tokio = { version = "1.41.0", optional = true, features = [
  "macros", "net", "time"
] }

[target.'cfg(unix)'.dependencies]
libc = { version = "0.2.171" }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59.0", features = [
  "Win32_Foundation",
  "Win32_Storage",
  "Win32_Storage_FileSystem"
] }

[dev-dependencies]
humansize = { version = "2.1.3" }
killswitch = { version = "0.4.2" }
tokio = { version = "1.43.0", features = ["full"] }
tokio-test = { version = "0.4.3" }
toml = { version = "0.8.20" }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]

[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }

multiple_crate_versions = "allow"

Added examples/diskfree.rs.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
use std::{env, path::Path};

use humansize::{format_size, BINARY, DECIMAL};

use orphanage::fs::get_free_space;


fn main() {
  let pth = env::args_os().nth(1).expect("no pattern given");

  let pth = Path::new(&pth);

  let freespace = get_free_space(pth).unwrap();

  println!("{freespace}");
  println!("{}", freespace / 1024);
  println!("{}", freespace / 1024 / 1024);


  let hs: String = format_size(freespace, DECIMAL);
  println!("{hs}");

  let hs: String = format_size(freespace, BINARY);
  println!("{hs}");
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/buf.rs.

11
12
13
14
15
16
17
18
19
20
21
22
23
  let mut buf = Vec::with_capacity(len);

  // SAFETY: Presumably with_capacity() works as documented.
  unsafe {
    buf.set_len(len);
  }

  rand::thread_rng().fill(&mut buf[..]);

  buf
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







|





11
12
13
14
15
16
17
18
19
20
21
22
23
  let mut buf = Vec::with_capacity(len);

  // SAFETY: Presumably with_capacity() works as documented.
  unsafe {
    buf.set_len(len);
  }

  rand::rng().fill(&mut buf[..]);

  buf
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Added src/ffi.rs.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::{ffi::CString, path::Path};

/// Given a `Path`, return a `CString`, suitable for passing to C functions
/// that require null-terminated paths.
///
/// # Panics
/// Path must not include null's (which they shouldn't be able to).
#[must_use]
#[inline]
pub fn path_to_cstring(path: &Path) -> CString {
  let os_str = path.as_os_str();
  let bytes = os_str.as_encoded_bytes();

  // unwrap() should be okay because embeded null shouldn't be possible in a
  // Path.
  CString::new(bytes).unwrap()
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/fs.rs.

1
2
3
4
5
6


7

8
9
10
11
12
13
14
use std::{
  fs,
  io::ErrorKind,
  path::{Path, PathBuf}
};



use crate::err::Error;


/// Create a random file of a specified size.
///
/// # Errors
/// [`std::io::Error`]
pub fn rndfile(
  fname: impl AsRef<Path>,






>
>
|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::{
  fs,
  io::ErrorKind,
  path::{Path, PathBuf}
};

#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::GetDiskFreeSpaceExA;

use crate::{err::Error, ffi::path_to_cstring};

/// Create a random file of a specified size.
///
/// # Errors
/// [`std::io::Error`]
pub fn rndfile(
  fname: impl AsRef<Path>,
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
pub fn abspath<P>(pth: P) -> Result<PathBuf, Error>
where
  P: AsRef<Path>
{
  Ok(pth.as_ref().canonicalize()?)
}


/// Call a closure if `outfile` is outdated (according to the rules of
/// [`outdated()`] with regards to `infile`.
///
/// Returns `Ok(true)` if the closure was called.
///
/// # Errors
/// Errors mirror the behavior of [`outdated()`]'s errors.







<







65
66
67
68
69
70
71

72
73
74
75
76
77
78
pub fn abspath<P>(pth: P) -> Result<PathBuf, Error>
where
  P: AsRef<Path>
{
  Ok(pth.as_ref().canonicalize()?)
}


/// Call a closure if `outfile` is outdated (according to the rules of
/// [`outdated()`] with regards to `infile`.
///
/// Returns `Ok(true)` if the closure was called.
///
/// # Errors
/// Errors mirror the behavior of [`outdated()`]'s errors.
115
116
117
118
119
120
121











































































122

  let in_mtime = in_md.modified()?;
  let out_mtime = out_md.modified()?;

  Ok(in_mtime > out_mtime)
}












































































// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

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

  let in_mtime = in_md.modified()?;
  let out_mtime = out_md.modified()?;

  Ok(in_mtime > out_mtime)
}


/// Given a directory, return how much disk space is available.
///
/// The directory must exist.
///
/// # Errors
/// [`Error::IO`] is returned if the input path is not a directory or if the
/// free space could not be probed.
pub fn get_free_space(path: &Path) -> Result<u64, Error> {
  #[cfg(unix)]
  let res = get_free_space_unix(path);

  #[cfg(windows)]
  let res = get_free_space_win(path);

  res
}


#[cfg(unix)]
fn get_free_space_unix(path: &Path) -> Result<u64, Error> {
  // Make sure directory exists
  if !path.is_dir() {
    return Err(Error::IO("Not a directory".into()));
  }

  // Must construct a null-terminated C string for libc::statfs()
  let cstr = path_to_cstring(path);
  let cstr_path = cstr.as_bytes_with_nul();

  let mut statfs = unsafe { std::mem::zeroed() };
  let result =
    unsafe { libc::statfs(cstr_path.as_ptr().cast::<i8>(), &mut statfs) };

  if result == 0 {
    Ok(statfs.f_bavail as u64 * u64::from(statfs.f_bsize))
  } else {
    Err(Error::IO("statfs() failed".into()))
  }
}

#[cfg(windows)]
fn get_free_space_win(path: &Path) -> Result<u64, Error> {
  // Make sure directory exists
  if !path.is_dir() {
    return Err(Error::IO("Not a directory".into()));
  }

  // Need a null-terminated string for ffi
  let cstr = path_to_cstring(path);
  let cstr_path = cstr.as_bytes_with_nul();

  let mut free_bytes_available_to_caller: u64 = 0;
  let mut total_bytes: u64 = 0;
  let mut total_free_bytes: u64 = 0;

  let result = unsafe {
    GetDiskFreeSpaceExA(
      cstr_path.as_ptr(),
      &mut free_bytes_available_to_caller,
      &mut total_bytes,
      &mut total_free_bytes
    )
  };

  if result != 0 {
    Ok(free_bytes_available_to_caller)
  } else {
    Err(Error::IO(format!(
      "GetDiskFreeSpaceExW failed with code: {}",
      std::io::Error::last_os_error()
    )))
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/iox.rs.

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    if let Some(ref mut remain) = self.0 {
      if *remain == 0 {
        // signal eof
        Ok(0)
      } else {
        let n = std::cmp::min(*remain, buf.len() as u64);
        let len = usize::try_from(n).unwrap();
        rand::thread_rng().fill(&mut buf[..len]);
        *remain -= n;
        Ok(len)
      }
    } else {
      rand::thread_rng().fill(&mut buf[..]);
      Ok(buf.len())
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







|




|






24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    if let Some(ref mut remain) = self.0 {
      if *remain == 0 {
        // signal eof
        Ok(0)
      } else {
        let n = std::cmp::min(*remain, buf.len() as u64);
        let len = usize::try_from(n).unwrap();
        rand::rng().fill(&mut buf[..len]);
        *remain -= n;
        Ok(len)
      }
    } else {
      rand::rng().fill(&mut buf[..]);
      Ok(buf.len())
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/lib.rs.

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
#![cfg_attr(docsrs, feature(doc_cfg))]

pub mod buf;
mod err;

pub mod fs;
pub mod futures;
pub mod iox;

pub mod path;
pub mod setops;
pub mod strx;

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
pub mod sqlfuncs;

#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub mod tokiox;

#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub use async_trait::async_trait;

#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde_parsers;

pub use err::Error;

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :




>



>




<
<
<
<




<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13




14
15
16
17




18
19
20
21
22
23
24
#![cfg_attr(docsrs, feature(doc_cfg))]

pub mod buf;
mod err;
pub mod ffi;
pub mod fs;
pub mod futures;
pub mod iox;
pub mod numx;
pub mod path;
pub mod setops;
pub mod strx;





#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub mod tokiox;





#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde_parsers;

pub use err::Error;

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Added src/numx.rs.

















































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use paste::paste;

pub trait UnsignedExt {
  type Type;

  /// Round down integer to its closes power-of-two.
  fn round_down_to_pow2(self) -> Self::Type;

  /// Generate a vector of all bit indexes containing a `1`.
  fn bitidx_vec(self) -> Vec<usize>;
}


/// Given an array of (sorted) indexes, generate a vector of contiguous blocks.
///
/// Each block is inclusive-inclusive.
#[must_use]
pub fn usize_list_to_ranges(numbers: &[usize]) -> Vec<(usize, usize)> {
  let mut result = Vec::with_capacity(numbers.len());

  if numbers.is_empty() {
    return result;
  }

  let mut numbers_iter = numbers.iter().peekable();

  while let Some(&num) = numbers_iter.next() {
    let start = num;
    let mut end = num;

    while let Some(&&next_num) = numbers_iter.peek() {
      if next_num == end + 1 {
        end = next_num;
        numbers_iter.next();
      } else {
        break;
      }
    }

    if start == end {
      result.push((start, start));
    } else {
      result.push((start, end));
    }
  }

  result
}

#[must_use]
pub fn ranges_to_str(blocks: &[(usize, usize)]) -> String {
  let mut res = String::new();
  if blocks.is_empty() {
    return res;
  }

  let mut it = blocks.iter().peekable();

  while let Some((start, end)) = it.next() {
    if start == end {
      res.push_str(&start.to_string());
    } else {
      res.push_str(&format!("{start}-{end}"));
    }

    if it.peek().is_some() {
      res.push(',');
    }
  }

  res
}


macro_rules! impl_unsigned_ext {
  ($int_type:ident) => {
    paste! {
      #[must_use]
      pub const fn [<$int_type _round_down_to_pow2>](val: $int_type)
            -> $int_type {
        if val == 0 {
          return 0;
        }
        let n = val.leading_zeros();
        let nshift = $int_type::BITS - n - 1;
        1 << nshift
      }
    }

    paste! {
      #[must_use]
      pub fn [<$int_type _bitidx_vec>](bits: $int_type) -> Vec<usize> {
        let nbits = $int_type::BITS as usize;
        let mut ret = Vec::with_capacity(nbits);
        for idx in 0..nbits {
          if bits & (1 << idx) != 0 {
            ret.push(idx + 1);
          }
        }
        ret
      }
    }

    paste! {
      impl UnsignedExt for $int_type {
        type Type = $int_type;

        #[inline]
        fn round_down_to_pow2(self) -> Self::Type {
          [<$int_type _round_down_to_pow2>](self)
        }

        #[inline]
        fn bitidx_vec(self) -> Vec<usize> {
          [<$int_type _bitidx_vec>](self)
        }
      }
    }
  };
}

impl_unsigned_ext! { u8 }
impl_unsigned_ext! { u16 }
impl_unsigned_ext! { u32 }
impl_unsigned_ext! { u64 }
impl_unsigned_ext! { usize }


#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn bitidxvec() {
    let v = u32_bitidx_vec(0);
    assert_eq!(v.len(), 0);

    let v = u32_bitidx_vec(1);
    assert_eq!(&v, &[1]);

    let v = u32_bitidx_vec(2);
    assert_eq!(&v, &[2]);

    let v = u32_bitidx_vec(3);
    assert_eq!(&v, &[1, 2]);

    let v = u32_bitidx_vec(4);
    assert_eq!(&v, &[3]);
  }

  #[test]
  fn to_range_blocks() {
    let v = u32_bitidx_vec(0);
    let v2 = usize_list_to_ranges(&v);
    assert_eq!(v2.len(), 0);

    let v = u32_bitidx_vec(1);
    let v2 = usize_list_to_ranges(&v);
    assert_eq!(&v2, &[(1, 1)]);

    let v = u32_bitidx_vec(2);
    let v2 = usize_list_to_ranges(&v);
    assert_eq!(&v2, &[(2, 2)]);

    let v = u32_bitidx_vec(3);
    let v2 = usize_list_to_ranges(&v);
    assert_eq!(&v2, &[(1, 2)]);
  }

  #[test]
  fn to_ranges_str() {
    let v = u32_bitidx_vec(0);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert!(s.is_empty());

    let v = u32_bitidx_vec(1);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert_eq!(&s, "1");

    let v = u32_bitidx_vec(2);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert_eq!(&s, "2");

    let v = u32_bitidx_vec(3);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert_eq!(&s, "1-2");

    let v = u32_bitidx_vec(4);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert_eq!(&s, "3");

    let v = u32_bitidx_vec(5);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert_eq!(&s, "1,3");

    let v = u32_bitidx_vec(1 + 2 + 8 + 16);
    let v2 = usize_list_to_ranges(&v);
    let s = ranges_to_str(&v2);
    assert_eq!(&s, "1-2,4-5");
  }

  #[test]
  fn round_down_to_pow2() {
    assert_eq!(u8::round_down_to_pow2(0), 0);
    assert_eq!(u8::round_down_to_pow2(1), 1);
    assert_eq!(u8::round_down_to_pow2(2), 2);
    assert_eq!(u8::round_down_to_pow2(3), 2);

    assert_eq!(u16::round_down_to_pow2(0), 0);
    assert_eq!(u16::round_down_to_pow2(1), 1);
    assert_eq!(u16::round_down_to_pow2(2), 2);
    assert_eq!(u16::round_down_to_pow2(3), 2);

    assert_eq!(u32::round_down_to_pow2(0), 0);
    assert_eq!(u32::round_down_to_pow2(1), 1);
    assert_eq!(u32::round_down_to_pow2(2), 2);
    assert_eq!(u32::round_down_to_pow2(3), 2);

    assert_eq!(u64::round_down_to_pow2(0), 0);
    assert_eq!(u64::round_down_to_pow2(1), 1);
    assert_eq!(u64::round_down_to_pow2(2), 2);
    assert_eq!(u64::round_down_to_pow2(3), 2);
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Deleted src/sqlfuncs.rs.

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
use rusqlite::{functions::FunctionFlags, Connection, Error};

use sha2::{Digest, Sha256};

use crate::strx::{validate_objname, RndStr};

/// Add a `hashstr()` SQL function to the connection object.
///
/// The SQL function `hashstr()` takes two arguments:
/// 1. The algorithm that will be used to hash the input.
/// 2. The input string to hash.
///
/// Currently only the algorithm `sha2/256` is supported.
///
/// ```
/// use rusqlite::Connection;
/// use orphanage::sqlfuncs;
///
/// let conn = Connection::open_in_memory().unwrap();
/// sqlfuncs::hashstr(&conn).unwrap();
/// let instr = "hello, world";
/// let hstr: String = conn.query_row_and_then(
///   "SELECT lower(hex(hashstr('sha2/256', ?)));",
///   [instr],
///   |row| {
///     row.get(0)
///   }
///   ).unwrap();
/// assert_eq!(&hstr[..8], "09ca7e4e");
/// ```
#[allow(clippy::missing_errors_doc)]
pub fn hashstr(conn: &Connection) -> Result<(), Error> {
  conn.create_scalar_function(
    "hashstr",
    2,
    FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
    move |ctx| {
      //assert_eq!(ctx.len(), 2, "called with unexpected number of
      // arguments");
      let algo = ctx.get::<String>(0)?.to_lowercase();
      let s = ctx.get::<String>(1)?;

      let mut hasher = match algo.as_ref() {
        "sha2/256" => Sha256::new(),
        _ => Err(Error::UserFunctionError("Bad hash algo".into()))?
      };
      hasher.update(s.as_bytes());
      let result = hasher.finalize();

      Ok(result[..].to_vec())
    }
  )
}

/// Add a `hashblob()` SQL function to the connection object.
///
/// The SQL function `hashblob()` takes two arguments:
/// 1. The algorithm that will be used to hash the input.
/// 2. The input string to hash.
///
/// Currently only the algorithm `sha2/256` is supported.
///
/// ```
/// use rusqlite::Connection;
/// use orphanage::sqlfuncs;
///
/// let conn = Connection::open_in_memory().unwrap();
/// sqlfuncs::hashblob(&conn).unwrap();
/// let buf = b"hello, world";
/// let hstr: String = conn.query_row_and_then(
///   "SELECT lower(hex(hashblob('sha2/256', ?)));",
///   [&buf],
///   |row| {
///     row.get(0)
///   }
///   ).unwrap();
/// assert_eq!(&hstr[..8], "09ca7e4e");
/// ```
#[allow(clippy::missing_errors_doc)]
pub fn hashblob(conn: &Connection) -> Result<(), Error> {
  conn.create_scalar_function(
    "hashblob",
    2,
    FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
    move |ctx| {
      //assert_eq!(ctx.len(), 2, "called with unexpected number of
      // arguments");
      let algo = ctx.get::<String>(0)?.to_lowercase();
      let blob = ctx.get::<Vec<u8>>(1)?;

      let mut hasher = match algo.as_ref() {
        "sha2/256" => Sha256::new(),
        _ => Err(Error::UserFunctionError("Bad hash algo".into()))?
      };
      hasher.update(&blob);
      let result = hasher.finalize();

      Ok(result[..].to_vec())
    }
  )
}

/// Add a `randomstr()` SQL function to the connection object.
///
/// The SQL function `randomstr()` takes a single argument:
/// 1. The length of the random string, in characters.
///
/// Its output will be a random string of the requested length where each
/// character is a ASCII alphabet or numeric character.
///
/// ```
/// use rusqlite::Connection;
/// use orphanage::sqlfuncs;
///
/// let conn = Connection::open_in_memory().unwrap();
/// sqlfuncs::rndstr_alphanum(&conn).unwrap();
///
/// conn.execute(r#"
/// CREATE TABLE IF NOT EXISTS stuff (
///   id   INTEGER PRIMARY KEY,
///   salt TEXT NOT NULL DEFAULT (randomstr(8))
/// );
/// "#, []);
/// ```
#[allow(clippy::missing_errors_doc)]
pub fn rndstr_alphanum(db: &Connection) -> Result<(), Error> {
  db.create_scalar_function(
    "randomstr",
    1,
    FunctionFlags::SQLITE_UTF8,
    move |ctx| {
      //assert_eq!(ctx.len(), 1, "called with unexpected number of
      // arguments");
      let len = ctx.get::<usize>(0)?;

      Ok(String::rnd_alphanum(len))
    }
  )
}

#[allow(clippy::missing_errors_doc)]
pub fn rndstr(db: &Connection) -> Result<(), Error> {
  db.create_scalar_function(
    "randomstr",
    2,
    FunctionFlags::SQLITE_UTF8,
    move |ctx| {
      //assert_eq!(ctx.len(), 2, "called with unexpected number of
      // arguments");
      let len = ctx.get::<usize>(0)?;
      let alphabet = ctx.get::<String>(1)?;
      let charset = alphabet.as_bytes();

      Ok(String::rnd_from_alphabet(len, charset))
    }
  )
}

/// Add a `isobjname()` SQL function to the connection object.
///
/// The SQL function `isobjname()` takes a signle argument:
/// 1. The input string to check whether it conforms to an object name.
///
/// ```
/// use rusqlite::Connection;
/// use orphanage::sqlfuncs;
///
/// let conn = Connection::open_in_memory().unwrap();
/// sqlfuncs::hashstr(&conn).unwrap();
///
/// conn.execute(r#"
/// CREATE TABLE IF NOT EXISTS stuff (
///   id   INTEGER PRIMARY KEY,
///   name TEXT UNIQUE NOT NULL,
///   CHECK (isobjname(name) == 1)
/// );
/// "#, []);
/// ```
///
/// # Panics
/// The number of input parameters must be exactly 1.
#[allow(clippy::missing_errors_doc)]
pub fn isobjname(conn: &Connection) -> Result<(), Error> {
  conn.create_scalar_function(
    "isobjname",
    1,
    FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
    move |ctx| {
      assert_eq!(ctx.len(), 1, "called with unexpected number of arguments");
      let name = ctx.get::<String>(0)?;
      validate_objname(&name)
        .map_err(|e| Error::UserFunctionError(Box::new(e)))?;
      Ok(1)
    }
  )
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































































































































































































































































































































Changes to src/strx.rs.

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
//! Extended str/string functionality.

use rand::{distributions::Alphanumeric, Rng};

use crate::err::Error;

pub trait RndStr {
  fn rnd_alphanum(len: usize) -> String;
  fn rnd_from_alphabet(len: usize, alpha: &[u8]) -> String;
}

impl RndStr for String {
  /// Generate a random alphanumeric string of a requested length.
  fn rnd_alphanum(len: usize) -> String {
    rand::thread_rng()
      .sample_iter(&Alphanumeric)
      .take(len)
      .map(char::from)
      .collect()
  }

  /// Generate a random string of a requested length, given an alphabet of
  /// acceptable characters.
  ///
  /// ```
  /// use orphanage::strx::RndStr;
  ///
  /// const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
  ///                          abcdefghijklmnopqrstuvwxyz\
  ///                          0123456789-_";
  ///
  /// let s = String::rnd_from_alphabet(16, CHARSET);
  /// assert_eq!(s.len(), 16);
  /// ```
  fn rnd_from_alphabet(len: usize, charset: &[u8]) -> String {
    let mut rng = rand::thread_rng();
    (0..len)
      .map(|_| {
        let idx = rng.gen_range(0..charset.len());
        charset[idx] as char
      })
      .collect()
  }
}




|
<
<









|




















|


|







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
//! Extended str/string functionality.

use rand::{distr::Alphanumeric, Rng};



pub trait RndStr {
  fn rnd_alphanum(len: usize) -> String;
  fn rnd_from_alphabet(len: usize, alpha: &[u8]) -> String;
}

impl RndStr for String {
  /// Generate a random alphanumeric string of a requested length.
  fn rnd_alphanum(len: usize) -> String {
    rand::rng()
      .sample_iter(&Alphanumeric)
      .take(len)
      .map(char::from)
      .collect()
  }

  /// Generate a random string of a requested length, given an alphabet of
  /// acceptable characters.
  ///
  /// ```
  /// use orphanage::strx::RndStr;
  ///
  /// const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
  ///                          abcdefghijklmnopqrstuvwxyz\
  ///                          0123456789-_";
  ///
  /// let s = String::rnd_from_alphabet(16, CHARSET);
  /// assert_eq!(s.len(), 16);
  /// ```
  fn rnd_from_alphabet(len: usize, charset: &[u8]) -> String {
    let mut rng = rand::rng();
    (0..len)
      .map(|_| {
        let idx = rng.random_range(0..charset.len());
        charset[idx] as char
      })
      .collect()
  }
}


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

pub const OBJNAME_SPEC: &str = "must be non-empty, lead with an alphabetic \
                                character, with each following character \
                                being alphanumeric, '_', '-' or '.'";


#[allow(clippy::missing_errors_doc)]
pub fn validate_name<L, R>(s: &str, lead: L, rest: R) -> Result<(), Error>
where
  L: Fn(char) -> bool,
  R: Fn(char) -> bool
{
  let mut chars = s.chars();
  let Some(ch) = chars.next() else {
    return Err(Error::BadFormat(
      "Object name must not be empty".to_string()
    ));
  };
  if !lead(ch) {
    return Err(Error::BadFormat(
      "Invalid leading object name character".to_string()
    ));
  }
  if chars.any(|c| !rest(c)) {
    return Err(Error::BadFormat(
      "Invalid object name character".to_string()
    ));
  }
  Ok(())
}

#[inline]
#[allow(clippy::missing_errors_doc)]
pub fn validate_objname(s: &str) -> Result<(), Error> {
  validate_name(s, is_name_leading_char, is_name_char)
}


#[cfg(test)]
mod tests {
  use super::*;







|






<
|
<


<
|
<


|
<
<

|




|







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

pub const OBJNAME_SPEC: &str = "must be non-empty, lead with an alphabetic \
                                character, with each following character \
                                being alphanumeric, '_', '-' or '.'";


#[allow(clippy::missing_errors_doc)]
pub fn validate_name<L, R>(s: &str, lead: L, rest: R) -> Result<&str, String>
where
  L: Fn(char) -> bool,
  R: Fn(char) -> bool
{
  let mut chars = s.chars();
  let Some(ch) = chars.next() else {

    return Err("must not be empty".into());

  };
  if !lead(ch) {

    return Err("invalid leading character".into());

  }
  if chars.any(|c| !rest(c)) {
    return Err("invalid character".into());


  }
  Ok(s)
}

#[inline]
#[allow(clippy::missing_errors_doc)]
pub fn validate_objname(s: &str) -> Result<&str, String> {
  validate_name(s, is_name_leading_char, is_name_char)
}


#[cfg(test)]
mod tests {
  use super::*;

Changes to src/tokiox.rs.

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
//! tokio extensions.
//!
//! # Connector
//! The _Connector_ subsystem consists of two parts:
//! - The [`Connector`] trait is implemented by applications/libraries that
//!   want to run retryable connection loops.
//! - [`run_connector()`] is a function that takes in a `Connector`
//!   implementation, and attempts to establish a connection and calls the
//!   [`Connector::run()`] once a connection has been successfully been
//!   established.
//!
//! Paradoxically the `run_connector()` function does not itself actually
//! attemtp to establish any connections -- it relies on the `Connector` trait
//! to implement the means to establish connections.
//!
//! The "good path" overall flow of the connector loop is to call the
//! `connect()` method. If it is successful, call the `run()` method, passing
//! along the newly allocated connection. The main application logic relating
//! to the connection should called from this method.
//!
//! The primary purpose of the connector concerns the "failure path":  If the
//! `connect()` method encounters a failure it can choose to signal to back to
//! the connector loop that the error is "retryable", in which case the
//! `retry_delay()` method is called to determine if the connector loop should
//! retry (and implement a delay before returning instructions to do so).
//!
//! Likewise, the `run()` method returns its `Result<(), Self::Error>` wrapped
//! in a `ControlFlow::Continue(_)` to indicate that the connector look should
//! reconnect, while `ControlFlow::Break(_)` signals that a fatal error occured
//! and the connect loop should terminate.

pub mod tcpconn;

use std::ops::ControlFlow;

use async_trait::async_trait;

pub use tcpconn::SimpleTcpConnector;


/// Application callbacks for the [`run_connector()`] function (or equivalent).
#[async_trait]
pub trait Connector {
  /// The connection return type.
  type ConnType: Send;

  /// The application return type.
  type Error;

  /// Establish a connection.
  ///
  /// If a connection was successfully established the implementation should
  /// return `Ok(Self::ConnType)`.
  ///
  /// If an error is returned as `Err(ControlFlow::Continue(Self::Error))` it
  /// signals to the connector loop that the error is non-fatal, and that the
  /// connection should be retried.  Returning an error as
  /// `Err(ControlFlow::Break(Self::Error))` signals that there's no point in
  /// trying to (re)connect (with the same configuration) and the
  /// (re)connection loop is terminated.
  async fn connect(
    &mut self
  ) -> Result<Self::ConnType, ControlFlow<Self::Error, Self::Error>>;

  /// Give application a chance to determine whether or not to attempt a
  /// reconnection, and delay before doing so.
  ///
  /// Implementations return `ControlFlow::Continue(())` to signal to the
  /// connector loop that it should retry the connection.  Returnning
  /// `ControlFlow::Break(Self::Error)` will terminate the connector loop and
  /// cause it to return the error.
  async fn retry_delay(&mut self) -> ControlFlow<Self::Error>;

  /// Run the application's connection handler.
  ///
  /// The application should return `ControlFlow::Continue(_)` to request that
  /// the connector loop delay and reconnect.  Returning
  /// `ControlFlow::Break(_)` will cause the connect loop to terminate and
  /// return the suppied result.
  async fn run(
    &mut self,
    conn: Self::ConnType
  ) -> ControlFlow<Result<(), Self::Error>, Result<(), Self::Error>>;
}


/// Establish a network connection.
///
/// The `run_connector()` function will enter a loop that will attempt to
/// establish a connection and call the `Connector::run()` implementation once
/// succesful.  If the connection fails, `Connector::retry_delay()` will be
/// called to determine whether to retry the connection.
///
/// # Exit conditions
/// The (re)connection loop will keep running until an exit condition has been
/// triggered:
/// - [`Connector::connect()`] returns `Err(ControlFlow::Break(Self::Error))`
/// - [`Connector::retry_delay()`] returns `ControlFlow::Break(Self::Error)`.
/// - [`Connector::run()`] returns `ControlFlow::Break(Result<(),
///   Self::Error>)`
#[deprecated(since = "0.0.3", note = "use `schmoozer` crate instead")]
#[allow(clippy::missing_errors_doc)]
pub async fn run_connector<E>(
  mut connector: impl Connector<Error = E> + Send
) -> Result<(), E>
where
  E: Send
{
  loop {
    // Call the application's connect callback to attempt to establish
    // connection.
    match connector.connect().await {
      Ok(conn) => {
        // A connection was successfully established -- call the run()
        // implementation.
        match connector.run(conn).await {
          ControlFlow::Continue(_res) => {
            // The application has requested a reconnection.
            // Fall through to retry()/delay()
          }
          ControlFlow::Break(res) => {
            // Break out of loop -- passing along the result from the
            // application.
            break res;
          }
        }
      }
      Err(ControlFlow::Continue(_res)) => {
        // The connector returned a retriable error
        // fall through to retry()/delay()
      }
      Err(ControlFlow::Break(res)) => {
        // The connector returned a fatal error
        break Err(res);
      }
    }

    // If this point is reached the application has requested a reconnection.
    // Call `retry_delay()` to allow the application to determine whether to
    // retry or not.

    match connector.retry_delay().await {
      ControlFlow::Continue(()) => {
        // Application wants to reconnect.
        continue;
      }
      ControlFlow::Break(err) => {
        // Application does not want to reconnect
        break Err(err);
      }
    }
  }
}


/// If a timeout has been specified, sleep until then.
///
/// Otherwise wait forever.
///
/// Can be used to timeout select loops.
pub async fn sleep_until_if_some(at: Option<tokio::time::Instant>) {

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1

























































































































































2
3
4
5
6
7
8
//! tokio extensions.


























































































































































/// If a timeout has been specified, sleep until then.
///
/// Otherwise wait forever.
///
/// Can be used to timeout select loops.
pub async fn sleep_until_if_some(at: Option<tokio::time::Instant>) {

Deleted src/tokiox/tcpconn.rs.

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
use std::{future::Future, io::ErrorKind, ops::ControlFlow, time::Duration};

use tokio::net::TcpStream;

use async_trait::async_trait;

use killswitch::KillSwitch;

use super::Connector;

pub struct SimpleTcpConnector<F>
where
  F: Future<
    Output = ControlFlow<
      Result<(), std::io::Error>,
      Result<(), std::io::Error>
    >
  >
{
  addr: String,
  delay: usize,
  ks: KillSwitch,
  cb: Box<dyn Fn(TcpStream, KillSwitch) -> F + Send>
}

impl<F> SimpleTcpConnector<F>
where
  F: Future<
    Output = ControlFlow<
      Result<(), std::io::Error>,
      Result<(), std::io::Error>
    >
  >
{
  #[must_use]
  pub fn new(
    addr: String,
    ks: KillSwitch,
    cb: Box<dyn Fn(TcpStream, KillSwitch) -> F + Send>
  ) -> Self {
    Self {
      addr,
      delay: 1,
      ks,
      cb
    }
  }
}

#[async_trait]
impl<F> Connector for SimpleTcpConnector<F>
where
  F: Future<
      Output = ControlFlow<
        Result<(), std::io::Error>,
        Result<(), std::io::Error>
      >
    > + Send
{
  type Error = std::io::Error;
  type ConnType = TcpStream;

  async fn connect(
    &mut self
  ) -> Result<Self::ConnType, ControlFlow<Self::Error, Self::Error>> {
    tokio::select! {
      res = TcpStream::connect(&self.addr) =>  {
        match res {
          Ok(conn) => Ok(conn),
          Err(e) => match e.kind() {
            ErrorKind::ConnectionAborted | ErrorKind::NotConnected => {
              Err(ControlFlow::Continue(e))
            }
            _ => Err(ControlFlow::Break(e))
          }
        }
      }
      () = self.ks.wait() => {
        // Aborted -- use ErrorKind::Other to signal abortion
        let err = std::io::Error::other(String::from("aborted"));
        Err(ControlFlow::Break(err))
      }
    }
  }

  async fn retry_delay(&mut self) -> ControlFlow<Self::Error> {
    let dur = Duration::from_secs(self.delay.try_into().unwrap());
    tokio::select! {
      () = self.ks.wait() => {
        let err = std::io::Error::other(String::from("aborted"));
        ControlFlow::Break(err)
      }
      () = tokio::time::sleep(dur) => {
        // double sleep duration for each iteration, but cap at 60 seconds
        self.delay = std::cmp::min(self.delay * 2, 60);
        ControlFlow::Continue(())
      }
    }
  }

  async fn run(
    &mut self,
    conn: Self::ConnType
  ) -> ControlFlow<Result<(), Self::Error>, Result<(), Self::Error>> {
    // reset delay
    self.delay = 1;

    let fut = (self.cb)(conn, self.ks.clone());

    fut.await
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




































































































































































































































Changes to www/changelog.md.

1
2


3
4












5
6
7


8
9
10




11
12







13
14
15
16
17
18
19
# Change Log



## [Unreleased]













[Details](/vdiff?from=orphanage-0.1.4&to=trunk)

### Added



### Changed





### Removed








---

## [0.1.4] - 2024-11-22

[Details](/vdiff?from=orphanage-0.1.3&to=orphanage-0.1.4)

### Added


>
>


>
>
>
>
>
>
>
>
>
>
>
>
|


>
>



>
>
>
>


>
>
>
>
>
>
>







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
# Change Log

⚠️  indicates a breaking change.

## [Unreleased]

[Details](/vdiff?from=orphanage-0.2.0&to=trunk)

### Added

### Changed

### Removed

---

## [0.2.0] - 2025-04-03

[Details](/vdiff?from=orphanage-0.1.4&to=orphanage-0.2.0)

### Added

- Add `ffi` module with a `path_to_cstring()` function.

### Changed

- Upgrade `rand` to `0.9.0`.
- ⚠️ Change `validate_name()` and `validate_objname` to return
  `Result<(), String>` with a more constrained message.

### Removed

- ⚠️ Removed `sqlfuncs` module (it lives in
  [sqlfuncs](https://crates.io/crates/sqlfuncs) now)
- Removed rusqlite dependency (as a consequence of sqlfuncs being removed)
- ⚠️ Remove `tokiox::{Connector, run_connector, tcpconn}`.  The connector lives
  in [schmoozer](https://crates.io/crates/schmoozer) now.
- ⚠️ Remove `aync_trait` dependency (it used to be re-exported).

---

## [0.1.4] - 2024-11-22

[Details](/vdiff?from=orphanage-0.1.3&to=orphanage-0.1.4)

### Added

Changes to www/index.md.

15
16
17
18
19
20
21







The crate's documentation uses automatically generated feature labels, which
currently requires nightly featuers.  To build the documentation locally use:

```
RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
```















>
>
>
>
>
>
>
15
16
17
18
19
20
21
22
23
24
25
26
27
28
The crate's documentation uses automatically generated feature labels, which
currently requires nightly featuers.  To build the documentation locally use:

```
RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
```


## Change log

The details of changes can always be found in the timeline, but for a
high-level view of changes between released versions there's a manually
maintained [Change Log](./changelog.md).