1extern crate clap_port_flag;
2extern crate deflate;
3extern crate fst;
4extern crate futures;
5extern crate hyper;
6extern crate memmap;
7extern crate mime_guess;
8extern crate quicli;
9extern crate tokio;
10extern crate walkdir;
11#[cfg(test)]
12#[macro_use]
13extern crate proptest;
14
15use std::fs::File;
16use std::path::Path;
17use std::result::Result;
18
19use quicli::prelude::*;
20use walkdir::WalkDir;
21
22mod slice;
23
24pub use server::serve;
25mod server;
26
27mod site;
28pub use site::Site;
29use std::path::PathBuf;
30
31pub fn build(src: &Path, target: &Path) -> Result<(), Error> {
32 info!(
33 "trying to build an index and archive from `{}`",
34 src.display()
35 );
36 let src = src
37 .canonicalize()
38 .with_context(|_| format!("Cannot canonicalize path `{}`", src.display()))?;
39
40 let src = Box::new(src.to_path_buf());
41 let src = &*Box::leak(src);
42
43 ensure!(src.is_dir(), "Directory `{}` doesn't exist", src.display());
44
45 use std::io::{BufWriter, Write};
46 let index_path = target.with_extension("index");
47 let index = BufWriter::new(
48 File::create(&index_path)
49 .with_context(|e| format!("couldn't create file `{}`: {}", target.display(), e))?,
50 );
51 let mut index = fst::MapBuilder::new(index)
52 .with_context(|e| format!("couldn't create index file `{}`: {}", target.display(), e))?;
53 info!("will write index to `{}`", index_path.display());
54
55 let archive_path = target.with_extension("archive");
56 let mut archive = BufWriter::new(
57 File::create(&archive_path)
58 .with_context(|e| format!("couldn't create file `{}`: {}", target.display(), e))?,
59 );
60 info!("will write archive to `{}`", archive_path.display());
61
62 let mut archive_index = 0;
63
64 let mut files = WalkDir::new(src)
65 .into_iter()
66 .par_bridge()
67 .flat_map(|entry| entry.map_err(|e| warn!("Couldn't read dir entry {}", e)))
68 .filter(|f| f.path().is_file())
69 .collect::<Vec<_>>();
70
71 ensure!(
72 !files.is_empty(),
73 "Would create empty archive. Is the `{}` directory empty?",
74 src.display()
75 );
76 info!("found {} files", files.len());
77
78 fn rel_as_bytes(path: &Path) -> Vec<u8> {
80 path.to_string_lossy().to_string().into_bytes()
81 }
82 files.par_sort_by(move |a, b| rel_as_bytes(a.path()).cmp(&rel_as_bytes(b.path())));
83 info!("sorted {} files", files.len());
84
85 info!(
86 "now building archive {} as well as index {}",
87 archive_path.display(),
88 index_path.display()
89 );
90
91 let files = files
92 .chunks(2 << 8)
93 .flat_map(|chunk| -> Result<Vec<(PathBuf, Vec<u8>)>, ()> {
94 let files: Result<Vec<(PathBuf, Vec<u8>)>, Error> = chunk
95 .par_iter()
96 .map(|entry| -> Result<(PathBuf, Vec<u8>), Error> {
97 let path = entry.path();
98 let file_content = get_compressed_content(&path).with_context(|_| {
99 format!("Could not read/compress content of {}", path.display())
100 })?;
101 let rel_path = path
102 .strip_prefix(src)
103 .with_context(|_| {
104 format!("Couldn't get relative path for `{:?}`", path.display())
105 })?.to_path_buf();
106 Ok((rel_path, file_content))
107 }).collect();
108 let mut files = files.map_err(|e| warn!("{}", e))?;
109
110 files.par_sort_by(move |a, b| rel_as_bytes(&a.0).cmp(&rel_as_bytes(&b.0)));
111 Ok(files)
112 }).flat_map(|xs| xs);
113
114 for (rel_path, file_content) in files {
115 archive.write_all(&file_content).with_context(|_| {
116 format!(
117 "Could not write compressed content to {}",
118 archive_path.display()
119 )
120 })?;
121
122 index
123 .insert(
124 rel_path.to_string_lossy().as_bytes(),
125 slice::pack_in_u64(archive_index, file_content.len()),
126 ).with_context(|_| format!("Could not insert file {} into index", rel_path.display()))?;
127 archive_index += file_content.len();
128 }
129 info!("wrote all files");
130
131 index
132 .finish()
133 .with_context(|e| format!("Could not finish building index: {}", e))?;
134 info!("finished index");
135
136 Ok(())
137}
138
139fn get_compressed_content(path: &Path) -> Result<Vec<u8>, Error> {
140 use std::fs::read;
141 use std::io::Write;
142
143 use deflate::write::GzEncoder;
144 use deflate::Compression;
145
146 let data =
147 read(path).with_context(|e| format!("Couldn't read file {}: {}", path.display(), e))?;
148
149 let mut encoder = GzEncoder::new(Vec::new(), Compression::Best);
150 encoder.write_all(&data)?;
151 let compressed_data = encoder.finish()?;
152
153 Ok(compressed_data)
154}