|
|
|
@ -14,6 +14,10 @@ GNU Affero General Public License for more details.
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
use crate::util::error::Error; |
|
|
|
|
use std::io::SeekFrom; |
|
|
|
|
use std::io::Seek; |
|
|
|
|
use std::io::Write; |
|
|
|
|
use std::io::Read; |
|
|
|
|
|
|
|
|
|
pub struct ChunkedFile { |
|
|
|
|
pub size: u128, |
|
|
|
@ -23,19 +27,66 @@ pub struct ChunkedFile {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl ChunkedFile { |
|
|
|
|
pub fn new(path: &std::path::Path, chunksize: u128) -> Result<ChunkedFile, Error> { |
|
|
|
|
fn empty() -> ChunkedFile { |
|
|
|
|
return ChunkedFile{ |
|
|
|
|
size: 0, |
|
|
|
|
chunk_size: 0, |
|
|
|
|
chunks_amount: 0, |
|
|
|
|
path: std::path::PathBuf::new(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Create a new, ready-to-write chunked regular file
|
|
|
|
|
pub fn new(path: &std::path::Path, size: u128, chunk_size: u128) -> Result<ChunkedFile, Error> { |
|
|
|
|
let file_handle: std::fs::File; |
|
|
|
|
match std::fs::File::create(path) { |
|
|
|
|
Ok(fh) => { |
|
|
|
|
file_handle = fh; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Err(error) => { |
|
|
|
|
return Err( |
|
|
|
|
Error::new(format!("{}", error).as_str()) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let mut new_chunked_file: ChunkedFile = ChunkedFile::empty(); |
|
|
|
|
|
|
|
|
|
// path
|
|
|
|
|
new_chunked_file.path = path.to_path_buf(); |
|
|
|
|
|
|
|
|
|
// resize file
|
|
|
|
|
match file_handle.set_len(size as u64) { |
|
|
|
|
Ok(()) => {} |
|
|
|
|
Err(error) => { |
|
|
|
|
return Err( |
|
|
|
|
Error::new(format!("could not resize \'{}\': {}", path.display(), error).as_str()) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
new_chunked_file.size = size; |
|
|
|
|
|
|
|
|
|
// chunks
|
|
|
|
|
new_chunked_file.chunk_size = chunk_size; |
|
|
|
|
if new_chunked_file.size % chunk_size != 0 { |
|
|
|
|
new_chunked_file.chunks_amount = (new_chunked_file.size / chunk_size) + 1; |
|
|
|
|
} else { |
|
|
|
|
new_chunked_file.chunks_amount = new_chunked_file.size / chunk_size; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return Ok(new_chunked_file); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Create a new, ready-to-read chunked file from already existing regular file
|
|
|
|
|
pub fn from(path: &std::path::Path, chunk_size: u128) -> Result<ChunkedFile, Error> { |
|
|
|
|
if !path.is_file() { |
|
|
|
|
return Err( |
|
|
|
|
Error::new(format!("\'{}\' does not exist or not a regular file", path.display()).as_str()) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let mut new_chunked_file: ChunkedFile = ChunkedFile{ |
|
|
|
|
size: 0, |
|
|
|
|
chunk_size: 0, |
|
|
|
|
chunks_amount: 0, |
|
|
|
|
path: std::path::Path::new("").to_path_buf(), |
|
|
|
|
}; |
|
|
|
|
let mut new_chunked_file: ChunkedFile = ChunkedFile::empty(); |
|
|
|
|
|
|
|
|
|
// path
|
|
|
|
|
new_chunked_file.path = path.to_path_buf(); |
|
|
|
@ -53,17 +104,98 @@ impl ChunkedFile {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// chunks
|
|
|
|
|
new_chunked_file.chunk_size = chunksize; |
|
|
|
|
new_chunked_file.chunks_amount = (new_chunked_file.size as f32 / chunksize as f32).ceil() as u128; |
|
|
|
|
new_chunked_file.chunk_size = chunk_size; |
|
|
|
|
if new_chunked_file.size % chunk_size != 0 { |
|
|
|
|
new_chunked_file.chunks_amount = (new_chunked_file.size / chunk_size) + 1; |
|
|
|
|
} else { |
|
|
|
|
new_chunked_file.chunks_amount = new_chunked_file.size / chunk_size; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return Ok(new_chunked_file); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn read_chunk(&self, buf: &[u8], chunk_no: u128) -> Option<Error> { |
|
|
|
|
pub fn read_chunk(&self, buf: &mut [u8], chunk_no: u128) -> Option<Error> { |
|
|
|
|
if chunk_no > self.chunks_amount { |
|
|
|
|
return Some(Error::new("chunk number is bigger than there are chunks")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (buf.len() as u128) < self.chunk_size { |
|
|
|
|
return Some(Error::new("buffer is too small to fit a chunk")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// open underlying file
|
|
|
|
|
let mut file_handle: std::fs::File; |
|
|
|
|
match std::fs::File::open(&self.path) { |
|
|
|
|
Ok(fh) => { |
|
|
|
|
file_handle = fh; |
|
|
|
|
} |
|
|
|
|
Err(error) => { |
|
|
|
|
return Some(Error::new(format!("{}", error).as_str())); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// set position and read
|
|
|
|
|
let chunk_start_pos: u128 = self.chunk_size * chunk_no; |
|
|
|
|
match file_handle.seek(SeekFrom::Start(chunk_start_pos as u64)) { |
|
|
|
|
Ok(_) => {} |
|
|
|
|
Err(error) => { |
|
|
|
|
return Some(Error::new(format!("seek error: {}", error).as_str())) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match file_handle.read(buf) { |
|
|
|
|
Ok(_) => {} |
|
|
|
|
Err(error) => { |
|
|
|
|
return Some(Error::new(format!("read error: {}", error).as_str())) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return None; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn write_chunk(&mut self, chunk: &[u8], chunk_no: u128) -> Option<Error> { |
|
|
|
|
if chunk_no > self.chunks_amount { |
|
|
|
|
// can't write outsize of pre-determined boundaries of a file !
|
|
|
|
|
return Some(Error::new("chunk number is bigger than there are chunks"));
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (chunk.len() as u128) < self.chunk_size && chunk_no != self.chunks_amount { |
|
|
|
|
// unfull chunk in the middle of a file
|
|
|
|
|
return Some(Error::new("cannot write unfull chunk in a not last position")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (chunk.len() as u128) > self.chunk_size { |
|
|
|
|
// too big chunk
|
|
|
|
|
return Some(Error::new("chunk is too big")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// open file and write a new chunk
|
|
|
|
|
let mut file_handle: std::fs::File; |
|
|
|
|
match std::fs::File::options().truncate(false).write(true).create(true).open(&self.path) { |
|
|
|
|
Ok(file) => { |
|
|
|
|
file_handle = file; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Err(error) => { |
|
|
|
|
return Some(Error::new(format!("{}", error).as_str())); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let chunk_start_pos: u128 = self.chunk_size * chunk_no; |
|
|
|
|
match file_handle.seek(SeekFrom::Start(chunk_start_pos as u64)) { |
|
|
|
|
Ok(_) => {} |
|
|
|
|
Err(error) => { |
|
|
|
|
return Some(Error::new(format!("seek error: {}", error).as_str())); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match file_handle.write_all(chunk) { |
|
|
|
|
Ok(()) => {} |
|
|
|
|
Err(error) => { |
|
|
|
|
return Some(Error::new(format!("write error: {}", error).as_str())) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return None; |
|
|
|
|
} |
|
|
|
|
} |