use super::*;
use crate::changestore::ChangeStore;
use crate::Conflict;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
pub trait Archive {
type File: std::io::Write;
type Error: std::error::Error;
fn create_file(&mut self, path: &str, mtime: u64, perm: u16) -> Self::File;
fn close_file(&mut self, f: Self::File) -> Result<(), Self::Error>;
}
#[cfg(feature = "tarball")]
pub struct Tarball<W: std::io::Write> {
pub archive: tar::Builder<flate2::write::GzEncoder<W>>,
pub prefix: Option<String>,
pub buffer: Vec<u8>,
}
#[cfg(feature = "tarball")]
pub struct File {
buf: Vec<u8>,
path: String,
permissions: u16,
mtime: u64,
}
#[cfg(feature = "tarball")]
impl std::io::Write for File {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.buf.write(buf)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(())
}
}
#[cfg(feature = "tarball")]
impl<W: std::io::Write> Tarball<W> {
pub fn new(w: W, prefix: Option<String>) -> Self {
let encoder = flate2::write::GzEncoder::new(w, flate2::Compression::best());
Tarball {
archive: tar::Builder::new(encoder),
buffer: Vec::new(),
prefix,
}
}
}
#[cfg(feature = "tarball")]
impl<W: std::io::Write> Archive for Tarball<W> {
type File = File;
type Error = std::io::Error;
fn create_file(&mut self, path: &str, mtime: u64, permissions: u16) -> Self::File {
self.buffer.clear();
File {
buf: std::mem::replace(&mut self.buffer, Vec::new()),
path: if let Some(ref prefix) = self.prefix {
prefix.clone() + path
} else {
path.to_string()
},
mtime,
permissions,
}
}
fn close_file(&mut self, file: Self::File) -> Result<(), Self::Error> {
let mut header = tar::Header::new_gnu();
header.set_size(file.buf.len() as u64);
header.set_mode(file.permissions as u32);
header.set_mtime(file.mtime);
header.set_cksum();
self.archive
.append_data(&mut header, &file.path, &file.buf[..])?;
self.buffer = file.buf;
Ok(())
}
}
#[derive(Debug, Error)]
pub enum ArchiveError<
P: std::error::Error + 'static,
T: std::error::Error + 'static,
A: std::error::Error + 'static,
> {
#[error(transparent)]
A(A),
#[error(transparent)]
P(P),
#[error(transparent)]
Txn(T),
#[error(transparent)]
Unrecord(#[from] crate::unrecord::UnrecordError<P, T>),
#[error(transparent)]
Apply(#[from] crate::apply::ApplyError<P, T>),
#[error("State not found: {:?}", state)]
StateNotFound { state: crate::pristine::Merkle },
#[error(transparent)]
File(#[from] crate::output::FileError<P, T>),
#[error(transparent)]
Output(#[from] crate::output::PristineOutputError<P, T>),
}
impl<
P: std::error::Error + 'static,
T: std::error::Error + 'static,
A: std::error::Error + 'static,
> std::convert::From<TxnErr<T>> for ArchiveError<P, T, A>
{
fn from(e: TxnErr<T>) -> Self {
ArchiveError::Txn(e.0)
}
}
pub(crate) fn archive<
'a,
T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
P: ChangeStore,
I: Iterator<Item = &'a str>,
A: Archive,
>(
changes: &P,
txn: &T,
channel: &ChannelRef<T>,
prefix: &mut I,
arch: &mut A,
) -> Result<Vec<Conflict>, ArchiveError<P::Error, T::GraphError, A::Error>> {
let channel = channel.borrow();
let mut conflicts = Vec::new();
let mut files = HashMap::new();
let mut next_files = HashMap::new();
let mut next_prefix_basename = prefix.next();
collect_children(
txn,
changes,
T::graph(&channel),
Position::ROOT,
Inode::ROOT,
"",
next_prefix_basename,
&mut files,
)?;
let mut done = HashMap::new();
let mut done_inodes = HashSet::new();
while !files.is_empty() {
debug!("files {:?}", files.len());
next_files.clear();
next_prefix_basename = prefix.next();
for (a, mut b) in files.drain() {
debug!("files: {:?} {:?}", a, b);
b.sort_by(|u, v| {
txn.get_changeset(T::changes(&channel), u.0.change)
.unwrap()
.cmp(&txn.get_changeset(T::changes(&channel), v.0.change).unwrap())
});
let mut is_first_name = true;
for (name_key, mut output_item) in b {
match done.entry(output_item.pos) {
Entry::Occupied(e) => {
debug!("pos already visited: {:?} {:?}", a, output_item.pos);
if *e.get() != name_key {
conflicts.push(Conflict::MultipleNames {
pos: output_item.pos,
});
}
continue;
}
Entry::Vacant(e) => {
e.insert(name_key);
}
}
if !done_inodes.insert(output_item.pos) {
debug!("inode already visited: {:?} {:?}", a, output_item.pos);
continue;
}
let name = if !is_first_name {
conflicts.push(Conflict::Name {
path: a.to_string(),
});
break;
} else {
is_first_name = false;
a.clone()
};
let file_name = path::file_name(&name).unwrap();
path::push(&mut output_item.path, file_name);
let path = std::mem::replace(&mut output_item.path, String::new());
let (_, latest_touch) =
crate::fs::get_latest_touch(txn, &channel, output_item.pos)?;
let latest_touch = {
let ext = txn.get_external(latest_touch)?.unwrap();
let c = changes.get_header(&ext).map_err(ArchiveError::P)?;
c.timestamp.timestamp() as u64
};
if output_item.meta.is_dir() {
collect_children(
txn,
changes,
T::graph(&channel),
output_item.pos,
Inode::ROOT, &path,
next_prefix_basename,
&mut next_files,
)?;
} else {
debug!("latest_touch: {:?}", latest_touch);
let mut l = crate::alive::retrieve(txn, T::graph(&channel), output_item.pos)?;
let mut f =
arch.create_file(&path, latest_touch, output_item.meta.permissions());
{
let mut f = crate::vertex_buffer::ConflictsWriter::new(
&mut f,
&output_item.path,
&mut conflicts,
);
crate::alive::output_graph(
changes,
txn,
&channel,
&mut f,
&mut l,
&mut Vec::new(),
)?;
}
arch.close_file(f).map_err(ArchiveError::A)?;
}
if output_item.is_zombie {
conflicts.push(Conflict::ZombieFile {
path: name.to_string(),
})
}
}
}
std::mem::swap(&mut files, &mut next_files);
}
Ok(conflicts)
}