use std::io::{Seek, Write};
use std::path::{Path, PathBuf};
use std::{fs, io};
use strict_encoding::{StrictDecode, StrictEncode};
use crate::{Wallet, WalletSettings};
const WALLET_DOC_MAGIC: [u8; 4] = [0xa4, 0x54, 0x6a, 0x8e];
pub struct RefWrap<'doc, T>(pub(self) &'doc T)
where T: StrictEncode;
impl<'doc, T> StrictEncode for RefWrap<'doc, T>
where T: StrictEncode
{
fn strict_encode<E: Write>(&self, e: E) -> Result<usize, strict_encoding::Error> {
self.0.strict_encode(e)
}
}
#[derive(StrictDecode)]
pub struct DocReader<T>
where T: StrictDecode
{
pub(self) magic: [u8; 4],
pub(self) data: T,
}
impl<T> DocReader<T>
where T: StrictDecode
{
pub fn magic_u32(&self) -> u32 { u32::from_be_bytes(self.magic) }
}
#[derive(StrictEncode)]
pub struct DocWriter<'doc, T>
where T: StrictEncode
{
pub(self) magic: [u8; 4],
pub(self) data: RefWrap<'doc, T>,
}
impl<'doc, T> DocWriter<'doc, T>
where
T: StrictEncode,
RefWrap<'doc, T>: StrictEncode,
{
pub fn with(magic: [u8; 4], data: &'doc T) -> Self {
DocWriter {
magic,
data: RefWrap(data),
}
}
}
#[derive(Debug, Error, From, Display)]
#[display(inner)]
pub enum Error {
#[from]
File(io::Error),
#[from]
Encoding(strict_encoding::Error),
#[display("incorrect file format or future version (expected {expected:#X}, got {actual:#X})")]
Magic { expected: u32, actual: u32 },
#[display("extra data after the end of file")]
DataNotEntirelyConsumed,
}
pub trait FileDocument
where Self: From<Self::FallbackDocType>
{
const DOC_MAGIC: [u8; 4];
const FILE_EXT: &'static str;
type FallbackDocType: StrictDecode;
fn magic_u32() -> u32 { u32::from_be_bytes(Self::DOC_MAGIC) }
fn file_name(base: &str, order_no: usize) -> String {
let mut path = PathBuf::from(format!("{}-{}", base, order_no));
path.set_extension(Self::FILE_EXT);
path.display().to_string()
}
fn read_file(path: impl AsRef<Path>) -> Result<Self, Error>
where Self: StrictDecode {
let mut file = fs::OpenOptions::new()
.create(false)
.write(false)
.read(true)
.open(&path)?;
let doc = DocReader::<Self>::strict_decode(&mut file)
.map_err(Error::from)
.and_then(|doc| {
if fs::metadata(path)?.len() != file.stream_position()? {
return Err(Error::DataNotEntirelyConsumed);
}
Ok(doc)
})
.or_else(|_| {
file.rewind()?;
DocReader::<Self::FallbackDocType>::strict_decode(&mut file).map(|r| DocReader::<
Self,
> {
magic: r.magic,
data: r.data.into(),
})
})?;
if doc.magic != Self::DOC_MAGIC {
return Err(Error::Magic {
expected: Self::magic_u32(),
actual: doc.magic_u32(),
});
}
Ok(doc.data)
}
fn write_file(&self, path: impl AsRef<Path>) -> Result<usize, Error>
where Self: Sized + StrictEncode {
let doc = DocWriter::with(Self::DOC_MAGIC, self);
let file = fs::File::create(path)?;
doc.strict_encode(file).map_err(Error::Encoding)
}
}
impl FileDocument for Wallet {
const DOC_MAGIC: [u8; 4] = WALLET_DOC_MAGIC;
const FILE_EXT: &'static str = "mcw";
type FallbackDocType = WalletSettings;
}