133 lines
4.4 KiB
Rust
133 lines
4.4 KiB
Rust
// fif - File Info Fixer
|
|
// Copyright (C) 2021 Lynnesbian
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
mod parameters;
|
|
|
|
use std::path::{Path};
|
|
use walkdir::{WalkDir, DirEntry};
|
|
use smartstring::alias::String;
|
|
use structopt::StructOpt;
|
|
use std::fs::File;
|
|
use std::io;
|
|
use std::io::Read;
|
|
use mime_guess;
|
|
use xdg_mime::SharedMimeInfo;
|
|
use mime_guess::Mime;
|
|
use log::{warn, error};
|
|
|
|
// TODO: test if this actually works on a windows machine
|
|
#[cfg(windows)]
|
|
fn is_hidden(entry: &DirEntry) -> bool {
|
|
use std::os::windows::prelude::*;
|
|
use std::fs;
|
|
fs::metadata(entry) // try to get metadata for file
|
|
.map_or(
|
|
false, // if getting metadata/attributes fails, assume it's not hidden
|
|
|f| f.file_attributes() & 0x2 // flag for hidden - https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
|
)
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
fn is_hidden(entry: &DirEntry) -> bool {
|
|
entry.file_name().to_str().map_or(false, |f| f.starts_with('.') && f != ".")
|
|
}
|
|
|
|
fn ext_match(args: ¶meters::Parameters, entry: &DirEntry) -> bool {
|
|
if !args.scan_hidden && is_hidden(entry) {
|
|
// skip hidden files and directories. this check is performed first because it's very lightweight.
|
|
return false;
|
|
}
|
|
|
|
if entry.file_type().is_dir() {
|
|
// always allow directories - there's no point doing file extension matching on something that isn't a file.
|
|
return true;
|
|
}
|
|
|
|
let ext = Path::new(entry.file_name()) // create a Path from the entry...
|
|
.extension() // get its extension...
|
|
.map(|e| String::from(e.to_string_lossy())); // and convert it from an OsStr to a String.
|
|
|
|
if ext.is_none() { return false } // don't scan files without extensions. TODO - this should be configurable
|
|
|
|
if let Some(extensions) = &args.extensions {
|
|
// if the user has specified a list of extensions to check against, make sure this file ends in one of them.
|
|
// TODO - maybe use ascii_lowercase instead?
|
|
return extensions.contains(&ext.unwrap().to_ascii_lowercase().into())
|
|
}
|
|
true
|
|
}
|
|
|
|
fn mime_type(db: &SharedMimeInfo, filepath: &Path) -> io::Result<Option<Mime>, > {
|
|
// attempt to read up to the 256 bytes of the file
|
|
let mut buffer = [0; 256];
|
|
let mut file = File::open(filepath)?;
|
|
|
|
file.read(&mut buffer)?;
|
|
|
|
Ok(db.get_mime_type_for_data(&buffer).map(|m| m.0))
|
|
}
|
|
|
|
fn get_ext_from_mime(mime: &Mime) -> Option<String> {
|
|
match mime_guess::get_mime_extensions(mime) // get a list of possible extensions for this mime type
|
|
.map(|g| g[0]) { // take the first option in the list and return it as a string
|
|
// jpeg files are given the primary extension "jpe", due to the extension list being stored in alphabetical order.
|
|
// to handle this particular case, swap "jpe" out for "jpg", and leave everything else the same, making sure we
|
|
// convert the &strs to Strings.
|
|
Some("jpe") => Some(String::from("jpg")),
|
|
Some(ext) => Some(String::from(ext)),
|
|
None => None
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let args = parameters::Parameters::from_args();
|
|
let db = xdg_mime::SharedMimeInfo::new();
|
|
println!("{:#?}", args);
|
|
// println!("{:#?}", args.dirs);
|
|
println!("=====\nIterating directory: {:?}\n=====", args.dirs);
|
|
|
|
let stepper = WalkDir::new(&args.dirs).into_iter();
|
|
for entry in stepper.filter_entry(|e| ext_match(&args, e)) {
|
|
let entry = entry.unwrap();
|
|
if !entry.file_type().is_file() {
|
|
// println!("{} is not a file", entry.path().display());
|
|
continue
|
|
}
|
|
|
|
// let result = tree_magic_mini::from_filepath(entry.path()).unwrap_or("???");
|
|
let result = mime_type(&db, entry.path());
|
|
if let Err(error) = result {
|
|
error!("{}", error);
|
|
continue
|
|
}
|
|
|
|
let result = result.unwrap();
|
|
if result.is_none() {
|
|
warn!("Couldn't determine mimetype for {}", entry.path().display());
|
|
continue
|
|
}
|
|
|
|
let result = result.unwrap();
|
|
|
|
println!(
|
|
"{} has type {}, extension should be {:?}",
|
|
entry.path().display(),
|
|
result,
|
|
get_ext_from_mime(&result)
|
|
);
|
|
}
|
|
}
|