Skip to main content

link_cli/protocol/
archive.rs

1//! Store archive: a whole links store in binary links notation.
2//!
3//! An archive is two [`LinksPacket`]s, one after the other:
4//!
5//! 1. **Links**, without external references: the store link `(a: s t)` is
6//!    the doublet `s t` at address `a`. Addresses the store does not use are
7//!    holes, so every link keeps its address.
8//! 2. **Names**, with external references: one link per named store link,
9//!    in address order, holding the external values
10//!    `address code-point…`.
11//!
12//! Both packets use packed widths, so a store of small addresses costs about
13//! two bytes per link. [`import_store`] writes every link back at its own
14//! address and restores the names, so exporting the imported store gives
15//! the same bytes again.
16
17use super::error::{ProtocolError, ProtocolResult};
18use super::packet::{DecodeLimits, LinksPacket, Reference};
19use crate::lino_database_input::update_link;
20use crate::named_type_links::NamedTypeLinks;
21use anyhow::{Context, Result};
22use std::fs;
23use std::io::{Read, Write};
24use std::path::Path;
25
26/// Writes every link and name of `storage` to `writer`.
27pub fn export_store<S>(storage: &mut S, writer: &mut dyn Write) -> Result<()>
28where
29    S: NamedTypeLinks,
30{
31    let mut links = storage.all_links();
32    links.sort_by_key(|link| link.index);
33    let doublets: Vec<(u64, Vec<Reference>)> = links
34        .iter()
35        .map(|link| {
36            (
37                u64::from(link.index),
38                vec![
39                    Reference::Internal(u64::from(link.source)),
40                    Reference::Internal(u64::from(link.target)),
41                ],
42            )
43        })
44        .collect();
45    let mut names = Vec::new();
46    for link in &links {
47        if let Some(name) = storage.get_name(link.index)? {
48            let mut references = vec![Reference::External(u64::from(link.index))];
49            references.extend(
50                name.chars()
51                    .map(|code_point| Reference::External(u64::from(u32::from(code_point)))),
52            );
53            names.push((names.len() as u64 + 1, references));
54        }
55    }
56    LinksPacket::pack(false, &doublets, true)?.write_to(writer)?;
57    LinksPacket::pack(true, &names, true)?.write_to(writer)?;
58    Ok(())
59}
60
61/// Writes the archive of `storage` to the file at `path`.
62pub fn export_store_file<S, P>(storage: &mut S, path: P) -> Result<()>
63where
64    S: NamedTypeLinks,
65    P: AsRef<Path>,
66{
67    let path = path.as_ref();
68    let mut bytes = Vec::new();
69    export_store(storage, &mut bytes)?;
70    fs::write(path, bytes)
71        .with_context(|| format!("Failed to write the store archive: {}", path.display()))
72}
73
74/// Reads an archive from `reader` into `storage`: each link is written at
75/// its own address, then every name is set.
76pub fn import_store<S>(storage: &mut S, reader: &mut dyn Read) -> Result<()>
77where
78    S: NamedTypeLinks,
79{
80    let limits = DecodeLimits::unlimited();
81    let links = read_packet(reader, &limits, "links")?;
82    let names = read_packet(reader, &limits, "names")?;
83    let mut trailing = [0u8; 1];
84    if reader.read(&mut trailing)? != 0 {
85        return Err(ProtocolError::malformed("trailing bytes after the store archive").into());
86    }
87    let doublets = links
88        .links()
89        .map(|(address, references)| match references {
90            [Reference::Internal(source), Reference::Internal(target)] => Ok((
91                store_address(address)?,
92                store_address(*source)?,
93                store_address(*target)?,
94            )),
95            _ => Err(ProtocolError::malformed(format!(
96                "archive link {address} is not a doublet of link addresses"
97            ))),
98        })
99        .collect::<ProtocolResult<Vec<_>>>()?;
100    let named = names
101        .links()
102        .map(|(_, references)| decode_name(references))
103        .collect::<ProtocolResult<Vec<_>>>()?;
104    // Every address exists before any link refers to it.
105    for &(index, _, _) in &doublets {
106        if !storage.exists(index) {
107            storage.ensure_created(index);
108        }
109    }
110    for (index, source, target) in doublets {
111        update_link(storage, index, source, target)?;
112    }
113    for (index, name) in named {
114        storage.set_name(index, &name)?;
115    }
116    storage.save()
117}
118
119/// Reads the archive in the file at `path` into `storage`.
120pub fn import_store_file<S, P>(storage: &mut S, path: P) -> Result<()>
121where
122    S: NamedTypeLinks,
123    P: AsRef<Path>,
124{
125    let path = path.as_ref();
126    let bytes = fs::read(path)
127        .with_context(|| format!("Failed to read the store archive: {}", path.display()))?;
128    import_store(storage, &mut bytes.as_slice())
129}
130
131fn read_packet(
132    reader: &mut dyn Read,
133    limits: &DecodeLimits,
134    part: &str,
135) -> ProtocolResult<LinksPacket> {
136    LinksPacket::read_from(reader, limits)?.ok_or_else(|| {
137        ProtocolError::malformed(format!("the store archive ends before its {part}"))
138    })
139}
140
141fn store_address(address: u64) -> ProtocolResult<u32> {
142    u32::try_from(address).map_err(|_| {
143        ProtocolError::malformed(format!("address {address} does not fit a 32-bit store"))
144    })
145}
146
147fn decode_name(references: &[Reference]) -> ProtocolResult<(u32, String)> {
148    let values = references
149        .iter()
150        .map(|reference| match reference {
151            Reference::External(value) => Ok(*value),
152            Reference::Internal(_) => Err(ProtocolError::malformed(
153                "an archive name holds only external values",
154            )),
155        })
156        .collect::<ProtocolResult<Vec<_>>>()?;
157    // A packet link holds at least one reference: arity 0 is malformed.
158    let name = values[1..]
159        .iter()
160        .map(|&code_point| {
161            u32::try_from(code_point)
162                .ok()
163                .and_then(char::from_u32)
164                .ok_or_else(|| ProtocolError::malformed(format!("invalid code point {code_point}")))
165        })
166        .collect::<ProtocolResult<String>>()?;
167    Ok((store_address(values[0])?, name))
168}