1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#[macro_use] extern crate failure; use failure::Error;
#[macro_use] extern crate log;

extern crate zip as zipcrate;
extern crate xz2;
extern crate flate2;
extern crate tar as tarcrate;

#[cfg(feature = "indicate")]
extern crate indicatif;

use std::path::{PathBuf, Path};
use std::fs;

mod formats;

pub fn contains_file<P:AsRef<Path>>(src : P, file : &str) -> Result<bool,Error>
    where std::path::PathBuf: std::convert::From<P>, P : std::fmt::Display + Copy,  
{
    //! Checks if a certain file is in an archive.
        
    let src_path : PathBuf = PathBuf::from(src);
    
    match src_path.extension() {
        None => Err(format_err!("File '{}' has no extension, not a file?",src)),
        Some(ext) => { 
            match ext.to_str().unwrap() {
                "zip" => formats::zip::contains(&src_path,file),
                "tar" => formats::tar::contains(&src_path,file),
                "gz" => { 
                    let buffer = formats::gz::decode(&src_path)?;
                    match get_second_extension(src_path.file_name().unwrap().to_str().unwrap()){
                        Some("tar") => formats::tar::buffer_contains(&buffer,file),
                        Some(other_ext) => Err(format_err!("Unknown format {}",other_ext)),
                        None => Err(format_err!("No nested archive")),
                    }
                },
                "xz" => { 
                    let buffer = formats::xz::decode(&src_path)?;
                    match get_second_extension(src_path.file_name().unwrap().to_str().unwrap()){
                        Some("tar") => formats::tar::buffer_contains(&buffer,file),
                        Some(other_ext) => Err(format_err!("Unknown format {}",other_ext)),
                        None => Err(format_err!("No nested archive")),
                    }
                },
                ext => Err(format_err!("Unknown extension type {}",ext)),
            }
        } 
    }
}

pub fn extract_to<P:AsRef<Path>>(src : P, des : P) -> Result<PathBuf,Error>
    where std::path::PathBuf: std::convert::From<P>, P : std::fmt::Display + Copy, 
{
    //! Extracts the archive to the destination folder.
    //! 
    //! Checks if the archive and folder exists and then
    //! extracts the archive into the folder. If the destination
    //! folder doesn't exist then it attempts to create it.
    
    let src_path : PathBuf = PathBuf::from(src);
    let des_path : PathBuf = PathBuf::from(des);

    if false == src_path.exists() { 
        return Err(format_err!("Source file, '{}' does not exist.",src)); 
    }

    if false == des_path.exists() { 
        warn!("Destination '{}' does not exist.",src);
        fs::create_dir_all(&des_path)?;
    }

    match src_path.extension() {
        None => Err(format_err!("File '{}' has no extension, not a file?",src)),
        Some(ext) => { 
            match ext.to_str().unwrap() {
                "zip" => formats::zip::extract(&src_path,&des_path),
                "tar" => formats::tar::extract(&src_path,&des_path),
                "gz" => { 
                    let buffer = formats::gz::decode(&src_path)?;
                    match get_second_extension(src_path.file_name().unwrap().to_str().unwrap()){
                        Some("tar") => formats::tar::extract_buffer(&buffer,&des_path,false),
                        Some(other_ext) => Err(format_err!("Unknown format {}",other_ext)),
                        None => Err(format_err!("No nested archive")),
                    }
                },
                "xz" => { 
                    let buffer = formats::xz::decode(&src_path)?;
                    match get_second_extension(src_path.file_name().unwrap().to_str().unwrap()){
                        Some("tar") => formats::tar::extract_buffer(&buffer,&des_path,false),
                        Some(other_ext) => Err(format_err!("Unknown format {}",other_ext)),
                        None => Err(format_err!("No nested archive")),
                    }
                },
                ext => Err(format_err!("Unknown extension type {}",ext)),
            }
        } 
    }
}


pub fn extract_root_to<P:AsRef<Path>>(src : P, des : P) -> Result<PathBuf,Error>
    where std::path::PathBuf: std::convert::From<P>, P : std::fmt::Display + Copy, 
{
    //! Extracts the root of the archive to the destination folder.
    //! 
    //! Checks if the archive and folder exists and then
    //! extracts the archive into the folder. If the destination
    //! folder doesn't exist then it attempts to create it.
    //! 
    //! The 'root' of the folder is where all the files are located.
    //! Sometimes the contents of archives are nested in multiple 
    //! folders before the actual data. 
    //! 
    //! This function looks at each actual file in the archive and finds
    //! the file with the shortest absolute path in the archive, and assumes
    //! that is the root of the archive, and then extracts that file to the root
    //! of the destination folder.

    let src_path : PathBuf = PathBuf::from(src);
    let des_path : PathBuf = PathBuf::from(des);

    if false == src_path.exists() { 
        return Err(format_err!("Source file, '{}' does not exist.",src)); 
    }

    if false == des_path.exists() { 
        warn!("Destination '{}' does not exist.",src);
        fs::create_dir_all(&des_path)?;
    }

    match src_path.extension() {
        None => Err(format_err!("File '{}' has no extension, not a file?",src)),
        Some(ext) => { 
            match ext.to_str().unwrap() {
                "zip" => formats::zip::extract_root(&src_path,&des_path),
                "tar" => formats::tar::extract_root(&src_path,&des_path),
                "gz" => { 
                    let buffer = formats::gz::decode(&src_path)?;
                    match get_second_extension(src_path.file_name().unwrap().to_str().unwrap()){
                        Some("tar") => formats::tar::extract_buffer(&buffer,&des_path,true),
                        Some(other_ext) => Err(format_err!("Unknown format {}",other_ext)),
                        None => Err(format_err!("No nested archive")),
                    }
                },
                "xz" => { 
                    let buffer = formats::xz::decode(&src_path)?;
                    match get_second_extension(src_path.file_name().unwrap().to_str().unwrap()){
                        Some("tar") => formats::tar::extract_buffer(&buffer,&des_path,true),
                        Some(other_ext) => Err(format_err!("Unknown format {}",other_ext)),
                        None => Err(format_err!("No nested archive")),
                    }
                },
                ext => Err(format_err!("Unknown extension type {}",ext)),
            }
        } 
    }
}

fn get_second_extension<'a>(file : &'a str) -> Option<&'a str> {
    //! gets the second extenison, so aa.b.c then it returns b
    
    let splits = file.split(".").collect::<Vec<&str>>();

    if splits.len() >= 3 {
        Some(splits[splits.len()-2])
    } else {
        None
    }
}