1use std::io::{Seek, Write};
13use std::path::{Path, PathBuf};
14use std::{fs, io};
15
16use strict_encoding::{StrictDecode, StrictEncode};
17
18use crate::{Wallet, WalletSettings};
19
20const WALLET_DOC_MAGIC: [u8; 4] = [0xa4, 0x54, 0x6a, 0x8e];
24
25pub struct RefWrap<'doc, T>(pub(self) &'doc T)
26where T: StrictEncode;
27
28impl<'doc, T> StrictEncode for RefWrap<'doc, T>
29where T: StrictEncode
30{
31 fn strict_encode<E: Write>(&self, e: E) -> Result<usize, strict_encoding::Error> {
32 self.0.strict_encode(e)
33 }
34}
35
36#[derive(StrictDecode)]
37pub struct DocReader<T>
38where T: StrictDecode
39{
40 pub(self) magic: [u8; 4],
41 pub(self) data: T,
42}
43
44impl<T> DocReader<T>
45where T: StrictDecode
46{
47 pub fn magic_u32(&self) -> u32 { u32::from_be_bytes(self.magic) }
48}
49
50#[derive(StrictEncode)]
51pub struct DocWriter<'doc, T>
52where T: StrictEncode
53{
54 pub(self) magic: [u8; 4],
55 pub(self) data: RefWrap<'doc, T>,
56}
57
58impl<'doc, T> DocWriter<'doc, T>
59where
60 T: StrictEncode,
61 RefWrap<'doc, T>: StrictEncode,
62{
63 pub fn with(magic: [u8; 4], data: &'doc T) -> Self {
64 DocWriter {
65 magic,
66 data: RefWrap(data),
67 }
68 }
69}
70
71#[derive(Debug, Error, From, Display)]
72#[display(inner)]
73pub enum Error {
74 #[from]
75 File(io::Error),
76 #[from]
77 Encoding(strict_encoding::Error),
78 #[display("incorrect file format or future version (expected {expected:#X}, got {actual:#X})")]
79 Magic { expected: u32, actual: u32 },
80 #[display("extra data after the end of file")]
81 DataNotEntirelyConsumed,
82}
83
84pub trait FileDocument
85where Self: From<Self::FallbackDocType>
86{
87 const DOC_MAGIC: [u8; 4];
88
89 const FILE_EXT: &'static str;
90
91 type FallbackDocType: StrictDecode;
92
93 fn magic_u32() -> u32 { u32::from_be_bytes(Self::DOC_MAGIC) }
94
95 fn file_name(base: &str, order_no: usize) -> String {
96 let mut path = PathBuf::from(format!("{}-{}", base, order_no));
97 path.set_extension(Self::FILE_EXT);
98 path.display().to_string()
99 }
100
101 fn read_file(path: impl AsRef<Path>) -> Result<Self, Error>
102 where Self: StrictDecode {
103 let mut file = fs::OpenOptions::new()
104 .create(false)
105 .write(false)
106 .read(true)
107 .open(&path)?;
108 let doc = DocReader::<Self>::strict_decode(&mut file)
109 .map_err(Error::from)
110 .and_then(|doc| {
111 if fs::metadata(path)?.len() != file.stream_position()? {
112 return Err(Error::DataNotEntirelyConsumed);
113 }
114 Ok(doc)
115 })
116 .or_else(|_| {
117 file.rewind()?;
118 DocReader::<Self::FallbackDocType>::strict_decode(&mut file).map(|r| DocReader::<
119 Self,
120 > {
121 magic: r.magic,
122 data: r.data.into(),
123 })
124 })?;
125 if doc.magic != Self::DOC_MAGIC {
126 return Err(Error::Magic {
127 expected: Self::magic_u32(),
128 actual: doc.magic_u32(),
129 });
130 }
131 Ok(doc.data)
132 }
133
134 fn write_file(&self, path: impl AsRef<Path>) -> Result<usize, Error>
135 where Self: Sized + StrictEncode {
136 let doc = DocWriter::with(Self::DOC_MAGIC, self);
137 let file = fs::File::create(path)?;
138 doc.strict_encode(file).map_err(Error::Encoding)
139 }
140}
141
142impl FileDocument for Wallet {
143 const DOC_MAGIC: [u8; 4] = WALLET_DOC_MAGIC;
144 const FILE_EXT: &'static str = "mcw";
145 type FallbackDocType = WalletSettings;
146}