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
use std::{fs, io::{self, Write, Seek}, path::{Path, PathBuf}, slice::Iter};
use super::bin::DataSource;


#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Dir<D> {
    pub name: String,
    pub files: Vec<File<D>>,
}
impl<D> Dir<D> {
    pub fn new<N: Into<String>, I: IntoIterator<Item = File<D>>>(name: N, files: I) -> Self {
        Self {
            name: name.into(),
            files: files.into_iter().collect()
        }
    }
}
impl<'a, D> IntoIterator for &'a Dir<D> {
    type Item = &'a File<D>;
    type IntoIter = Iter<'a, File<D>>;
    fn into_iter(self) -> Self::IntoIter {
        self.files.iter()
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct File<D> {
    pub name: String,
    pub compressed: Option<bool>,
    pub data: D,
}
impl<D> File<D> {
    pub fn new<N: Into<String>>(name: N, data: D) -> Self {
        Self {
            name: name.into(),
            compressed: None,
            data
        }
    }
}
pub trait Writer {
    type Err = io::Error;

    fn write_bsa<DS, D, W>(&self, dirs: DS, out: W) -> Result<(), Self::Err>
    where
        D: DataSource,
        DS: IntoIterator<Item = Dir<D>>,
        W: Write + Seek;
}

pub fn list_dir<P: AsRef<Path>>(dir: P) -> io::Result<Vec<Dir<PathBuf>>> {
    let mut stack = vec![PathBuf::new()];
    let mut res = vec![];
    while let Some(path) = stack.pop() {
        let mut files = vec![];
        let cwd = dir.as_ref().join(&path);
        for e in fs::read_dir(cwd)? {
            let entry = e?;
            if entry.file_type()?.is_dir() {
                stack.push([&path, &PathBuf::from(entry.file_name())].iter().collect());
            } else {
                files.push(File {
                    name: entry.file_name().into_string().unwrap(),
                    compressed: None,
                    data: entry.path(),
                });
            }
        }
        if !files.is_empty() {
            res.push(Dir {
                name: path.into_os_string().into_string().unwrap(), 
                files
            });
        }
    }
    Ok(res)
}

#[cfg(test)]
pub(crate) mod test {
    use std::{io::Cursor, fmt::Display};

    use super::*;

    pub fn some_bsa_dirs() -> Vec<Dir<Vec<u8>>> {
        vec![
            Dir::new("a", [
                File::new("b", vec![1,2,3,4])
            ])
        ]
    }

    pub fn bsa_bytes<W: Writer, D: DataSource>(writer: W, dirs: Vec<Dir<D>>) -> Cursor<Vec<u8>>
    where
        W::Err: Display,
    {
        let mut out = Cursor::new(Vec::<u8>::new());
        writer.write_bsa(dirs, &mut out)
            .unwrap_or_else(|err| panic!("could not write bsa {}", err));
        Cursor::new(out.into_inner())
    }

    pub fn some_bsa_bytes<W: Writer>() -> Cursor<Vec<u8>>
    where
        W: Default,
        W::Err: Display,
    {
        bsa_bytes(W::default(), some_bsa_dirs())
    }
}