Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

lib.rs
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


//! LibRsync
//! # Examples
//! In the following example, the signature file should be computed on the local file, sent to the remote machine where the delta is computed. Then, `delta_file` must be run on the remote machine, creating a file /tmp/delta. Then, /tmp/delta should be copied to the local machine, and finally `patch_file` must be applied to write a copy of the remote file in /tmp/new.
//!
//! ```
//! extern crate rsync;
//! use rsync::*;
//!
//! fn main(){
//!     sig_file("/tmp/old","/tmp/old.sig",2048,8,None).unwrap();
//!     let sig=loadsig_file("/tmp/old.sig",None).unwrap();
//!     delta_file(&sig, "/tmp/new", "/tmp/delta",None).unwrap();
//!     patch_file("/tmp/old","/tmp/delta","/tmp/new.new",None).unwrap();
//! }
//! ```


extern crate libc;
use libc::{c_int,c_char,size_t,c_longlong,FILE,fopen,fclose,strlen};
use std::ptr;

use std::path::{Path};

use std::{error,fmt};
#[allow(missing_copy_implementations)]
enum CRsSignature {}
type CRsResult=c_int;

// This needs to be generated from the C headers.
type RsLongT=c_longlong;

#[repr(C)]
pub struct rs_stats_t {
    op:*const c_char,
    lit_cmds:c_int,
    lit_bytes:RsLongT,
    lit_cmdbytes:RsLongT,
    copy_cmds:RsLongT,
    copy_bytes:RsLongT,
    copy_cmdbytes:RsLongT,
    sig_cmds:RsLongT,
    sig_bytes:RsLongT,
    false_matches:c_int,
    sig_blocks:RsLongT,
    block_len:size_t,
    in_bytes:RsLongT,
    out_bytes:RsLongT
}

#[derive(Debug)]
pub enum Error {
    Error(c_int),
    PathEncoding,
    FileMissing,
    IO(std::io::Error)
}


extern "C" {
    fn rs_loadsig_file(old_file:*const FILE, sig_out:*mut*mut CRsSignature,stats:*mut rs_stats_t)->CRsResult;
    fn rs_sig_file(old_file:*const FILE,sig_file:*const FILE,
                    block_len:size_t, strong_len:size_t,
                    stats:*mut rs_stats_t)->CRsResult;
    fn rs_delta_file(sig:*const CRsSignature,input_file:*const FILE,output_file:*const FILE,stats:*mut rs_stats_t)->CRsResult;
    fn rs_patch_file(basis:*const FILE, delta:*const FILE, new_file:*const FILE, stats:*mut rs_stats_t)->CRsResult;
    fn rs_strerror(err:CRsResult)->*const c_char;
    fn rs_free_sumset(sig:*const CRsSignature);
    fn rs_build_hash_table(sig:*mut CRsSignature)->c_int;
    fn rs_sumset_dump(sig:*const CRsSignature);
}

pub fn sumset_dump(sig:&Signature) {
    unsafe { rs_sumset_dump(sig.sig) }
}

pub struct Signature {
    sig:*mut CRsSignature
}


impl Drop for Signature {
    fn drop(&mut self){
        unsafe { rs_free_sumset(self.sig) }
    }
}


/// Generates a signature of the "old_file" input file and writes it to "signature_file".
pub fn sig_file<P:AsRef<Path>>(old_file:P,signature_file:P,
                               block_len:usize,strong_len:usize,stats:Option<&mut rs_stats_t>)->Result<(),Error>{
    let old=old_file.as_ref().as_os_str().to_os_string();
    let sig=signature_file.as_ref().as_os_str().to_os_string();
    match (old.to_str(),sig.to_str()) {
        (Some(old),Some(sig))=>{
            unsafe {
                let old=std::ffi::CString::new(old).unwrap();
                let fi=fopen(old.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
                if !fi.is_null() {
                    let sig=std::ffi::CString::new(sig).unwrap();
                    let fo=fopen(sig.as_ptr() as *const c_char,"wb".as_ptr() as *const c_char);
                    let e=rs_sig_file(fi,fo,
                                      block_len as size_t,
                                      strong_len as size_t,
                                      match stats {
                                          None => std::ptr::null_mut(),
                                          Some(x)=>x
                                      });
                    fclose(fi);
                    fclose(fo);
                    if e==0 { Ok(()) } else { Err(Error::Error(e)) }
                } else {
                    println!("old: {:?}",old);
                    Err(Error::FileMissing)
                }
            }
        },
        _=>Err(Error::PathEncoding)
    }
}

/// Loads a signature from "sig_file".
pub fn loadsig_file<P:AsRef<Path>>(sig_file:P,stats:Option<&mut rs_stats_t>)->Result<Signature,Error>{
    let sig_file=sig_file.as_ref().as_os_str().to_os_string();
    match sig_file.to_str() {
        Some(s)=>
            unsafe {
                let mut sig_out=ptr::null_mut();
                let s=std::ffi::CString::new(s).unwrap();
                let fi=fopen(s.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
                if !fi.is_null(){
                    let e=rs_loadsig_file(fi, &mut sig_out,
                                          match stats {
                                              None => std::ptr::null_mut(),
                                              Some(x)=>x
                                          });
                    fclose(fi);
                    if e==0 { Ok(Signature{sig:sig_out}) } else { Err(Error::Error(e)) }
                } else {
                    Err(Error::FileMissing)
                }
            },
        None =>{
            Err(Error::PathEncoding)
        }
    }
}

/// Produce a delta from a signature and a "new" file.
pub fn delta_file<P:AsRef<Path>>(sig:&Signature,input_file:P,output_file:P,stats:Option<&mut rs_stats_t>)->Result<(),Error>{
    let input_file=input_file.as_ref().as_os_str().to_os_string();
    let output_file=output_file.as_ref().as_os_str().to_os_string();
    match (input_file.to_str(),output_file.to_str()) {
        (Some(i),Some(o))=>{
            unsafe {
                let i=std::ffi::CString::new(i).unwrap();
                let fi=fopen(i.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
                if !fi.is_null() {
                    let o=std::ffi::CString::new(o).unwrap();
                    let fo=fopen(o.as_ptr() as *const c_char,"wb".as_ptr() as *const c_char);
                    rs_build_hash_table(sig.sig);
                    let e=rs_delta_file(sig.sig,fi,fo,
                                        match stats {
                                            None => std::ptr::null_mut(),
                                            Some(x)=>x
                                        });
                    fclose(fi);
                    fclose(fo);
                    if e==0 { Ok(()) } else { Err(Error::Error(e)) }
                } else {
                    Err(Error::IO(std::io::Error::last_os_error()))
                }
            }
        },
        _=>Err(Error::PathEncoding)
    }
}


/// Applies a delta file (or a "patch") to "old_file", and write the result to "new_file".
pub fn patch_file<P:AsRef<Path>>(old_file:P,delta:P,new_file:P,stats:Option<&mut rs_stats_t>)->Result<(),Error>{
    let old_file=old_file.as_ref().as_os_str().to_os_string();
    let delta=delta.as_ref().as_os_str().to_os_string();
    let new_file=new_file.as_ref().as_os_str().to_os_string();

    match (old_file.to_str(),delta.to_str(),new_file.to_str()) {
        (Some(old),Some(delta),Some(new))=>{
            unsafe {
                let old=std::ffi::CString::new(old).unwrap();
                let fa=fopen(old.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
                let delta=std::ffi::CString::new(delta).unwrap();
                let fb=fopen(delta.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
                let new=std::ffi::CString::new(new).unwrap();
                let fc=fopen(new.as_ptr() as *const c_char,"wb".as_ptr() as *const c_char);
                if !fa.is_null() && !fb.is_null() {
                    let e=rs_patch_file(fa,fb,fc,match stats { Some(st)=>st, None => std::ptr::null_mut() });
                    fclose(fa);
                    fclose(fb);
                    fclose(fc);
                    if e==0 { Ok(()) } else { Err(Error::Error(e)) }
                } else {
                    Err(Error::FileMissing)
                }
            }
        },
        _=>Err(Error::PathEncoding)
    }
}

impl error::Error for Error {
    fn cause(&self) -> Option<&dyn error::Error> {
        match *self {
            Error::IO(ref e)=>Some(e),
            _ => None
        }
    }
}


impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::PathEncoding => write!(f,"Error in the encoding of a path"),
            Error::FileMissing => write!(f,"File missing"),
            Error::Error(e)=>{
                write!(f,"{}",
                       unsafe {
                           let c=rs_strerror(e);
                           std::str::from_utf8_unchecked(
                               std::slice::from_raw_parts(c as *const u8,strlen(c))
                                   )
                       })
            },
            Error::IO(ref e)=>e.fmt(f)
        }
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::prelude::*;
    extern crate rand;
    #[test]
    fn test() {
        let mut local:Vec<u8>=vec![0;10000];
        let mut remote:Vec<u8>=vec![0;10000];
        for x in local.iter_mut() { *x = rand::random() }
        for x in remote.iter_mut() { *x = rand::random() }

        let local_file="local";
        let remote_file="remote";

        {
            let mut f = File::create(local_file).unwrap();
            f.write_all(&local[..]).unwrap();
            let mut f = File::create(remote_file).unwrap();
            f.write_all(&remote[..]).unwrap();
        }
        let signature_file="local.sig";
        sig_file(local_file,signature_file,2048,8,None).unwrap();

        let signature=loadsig_file(signature_file,None).unwrap();

        let delta="delta";
        delta_file(&signature,remote_file,delta,None).unwrap();
        let copy_file="remote.copy";
        patch_file(local_file,delta,copy_file,None).unwrap();

        let mut f = File::open(copy_file).unwrap();
        let mut contents: Vec<u8> = Vec::new();
        let _ = f.read_to_end(&mut contents).unwrap();

        if contents!=remote { panic!("test failled") }
    }
}