subcomponent 0.1.0

A components orchestrator
/*
 * Copyright (c) 2017 Jean Guyomarc'h
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

extern crate std;

use std::error::Error as FmtError;
use self::Error::*;
use subprocess;


/*
 * To register a new unpacker, add the name of the unpacker method
 * as a new field of this macro.
 */
macro_rules! foreach_fetch {
    ($mac:ident) => {
        $mac!(tar);
        $mac!(bzip2);
        $mac!(xz);
        $mac!(gzip);
    }
}

macro_rules! declare_mod {
    ($name:ident) => { pub mod $name; }
}
foreach_fetch!(declare_mod);


#[derive(Debug)]
pub enum Error {
   NotAnAbsolutePath,
   NotAFile,
   NotADirectory,
   IOError(std::io::Error),
   ProcessError(subprocess::Error),
   PathError,
   UnknownUnpackingMethod,
}

impl From<std::io::Error> for Error {
   fn from(error: std::io::Error) -> Self {
      IOError(error)
   }
}

impl From<subprocess::Error> for Error {
   fn from(error: subprocess::Error) -> Self {
      ProcessError(error)
   }
}

impl std::error::Error for Error {
   fn description(&self) -> &str {
      match *self {
         NotAnAbsolutePath => "Path must be absolute",
         NotAFile => "Path must be an existing file",
         NotADirectory => "Path must be an existing directory",
         IOError(ref err) => err.description(),
         ProcessError(ref err) => err.description(),
         PathError => "Path failed to be processed",
         UnknownUnpackingMethod => "Unknown unpacking method",
      }
   }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.description())
    }
}

pub trait Unpacker {
   fn unpack(&self, in_file: &std::path::Path, out_dir: &std::path::Path) -> Result<std::path::PathBuf, Error>;
}

pub fn check_unpack_args(in_file: &std::path::Path, out_dir: &std::path::Path) -> Result<(), Error> {
   /*
    * Safety checks on the input parameters of all the unpackers.
    */
   if ! in_file.is_absolute() || ! out_dir.is_absolute() {
      error!("Unpacker paths must be absolute");
      return Err(NotAnAbsolutePath);
   }
   if ! in_file.is_file() {
      error!("Input path is not an existing file archive");
      return Err(NotAFile);
   }
   if out_dir.exists() && ! out_dir.is_dir() {
      error!("Output path exists but is not a directory");
      return Err(NotADirectory);
   }

   Ok(())
}

pub fn get_for_archive(formats: &[String]) -> Result<Vec<Box<Unpacker>>, Error> {

   let mut unpackers = Vec::new();

   for format in formats.iter().rev() {
      macro_rules! match_unpacker {
         ($name:ident) => {
            if format == $name::name_get() {
               unpackers.push($name::new());
               trace!("Using unpack method {}", format);
               continue;
            }
         }
      }
      foreach_fetch!(match_unpacker);

      error!("Unknown unpacking method: \"{}\"", format);
      return Err(UnknownUnpackingMethod);
   }
   Ok(unpackers)
}