tagctl 1.2.1

Adds or removes tags to given paths inside square brackets or to extended attributes
Documentation
/*
Copyright (C) 2022  Kody VB
This file is part of Tagctl.

Tagctl 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.

Tagctl 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
Tagctl. If not, see <https://www.gnu.org/licenses/>.
*/

use crate::cli;
use crate::shared_logic;
use std::path::Path;

pub fn x_add_tag(path: &Path, data: &cli::Args) {
    // Adds a tag to a path's 'user.xdg.tags' xattr
    if !path.exists() {
        eprintln!(
            "Error: Couldn't find expected file: \"{}\"",
            path.to_str().unwrap()
        );
        return;
    }

    let tag: String = if data.tag_varies {
        shared_logic::convert_tag_characters(path, &data.tag)
    } else {
        data.tag.clone()
    };

    let mut path_xattrs = get_xattrs(path);
    if path_xattrs.is_empty() {
        if data.verbose {
            println!("Adding \"{}\" to: \"{}\"...", tag, path.to_str().unwrap());
        }
        xattr::set(path, "user.xdg.tags", tag.as_bytes())
            .map_err(|err| {
                eprintln!(
                    "Couldn't set xattr for: \"{}\"\nerror: {}",
                    path.to_str().unwrap(),
                    err
                );
            })
            .ok();
        return;
    }

    if path_xattrs.contains(&tag) {
        if data.verbose {
            println!("Ignoring \"{}\"...", path.to_str().unwrap());
        }
        return;
    }

    if data.verbose {
        println!("Adding \"{}\" to: \"{}\"...", tag, path.to_str().unwrap());
    }
    path_xattrs.push(tag);
    let string_xattrs = path_xattrs.join(",");
    xattr::set(path, "user.xdg.tags", string_xattrs.as_bytes())
        .map_err(|err| {
            eprintln!(
                "Couldn't set xattr for: \"{}\"\nerror: {}",
                path.to_str().unwrap(),
                err
            );
        })
        .ok();
}

pub fn x_remove_tag(path: &Path, data: &cli::Args) {
    // Removes a tag from a path's 'user.xdg.tags' xattr
    if !path.exists() {
        eprintln!(
            "Error: Couldn't find expected file: \"{}\"",
            path.to_str().unwrap()
        );
        return;
    }

    let tag: String = if data.tag_varies {
        shared_logic::convert_tag_characters(path, &data.tag)
    } else {
        data.tag.clone()
    };

    let mut path_xattrs = get_xattrs(path);
    if path_xattrs.is_empty() || !path_xattrs.contains(&tag) {
        if data.verbose {
            println!("Ignoring: \"{}\"", path.to_str().unwrap());
        }
        return;
    }

    let index = path_xattrs.iter().position(|r| r == &tag).unwrap();
    path_xattrs.remove(index);
    if path_xattrs.is_empty() {
        if data.verbose {
            println!(
                "Removing tag \"{}\" from path \"{}\"...",
                tag,
                path.to_str().unwrap()
            );
        }
        xattr::remove(path, "user.xdg.tags")
            .map_err(|err| {
                eprintln!(
                    "Couldn't remove tag \"{}\" from path \"{}\"\nerror: {}",
                    tag,
                    path.to_str().unwrap(),
                    err
                );
            })
            .ok();
    } else {
        let string_xattrs = path_xattrs.join(",");
        if data.verbose {
            println!(
                "Removing tag \"{}\" from path \"{}\"...",
                tag,
                path.to_str().unwrap()
            );
        }
        xattr::set(path, "user.xdg.tags", string_xattrs.as_bytes())
            .map_err(|err| {
                eprintln!(
                    "Couldn't remove tag \"{}\" from path \"{}\"\nerror: {}",
                    tag,
                    path.to_str().unwrap(),
                    err
                );
            })
            .ok();
    }
}

pub fn x_remove_all_tags(path: &Path, data: &cli::Args) {
    // Removes all tags from a path's 'user.xdg.tags' xattr
    if !path.exists() {
        eprintln!(
            "Error: Couldn't find expected file: \"{}\"",
            path.to_str().unwrap()
        );
        return;
    }

    let path_xattrs = get_xattrs(path);
    if path_xattrs.is_empty() {
        if data.verbose {
            println!("Ignoring: \"{}\"", path.to_str().unwrap());
        }
        return;
    }

    if data.verbose {
        println!(
            "Removing tag(s) {:?} from path \"{}\"...",
            path_xattrs,
            path.to_str().unwrap()
        );
    }

    xattr::remove(path, "user.xdg.tags")
        .map_err(|err| {
            eprintln!(
                "Couldn't remove tag {:?} from path \"{}\"\nerror: {}",
                path_xattrs,
                path.to_str().unwrap(),
                err
            );
        })
        .ok();
}

pub fn get_xattrs(path: &Path) -> Vec<String> {
    // Returns a vector of all the values associated with "user.xdg.tags" as strings
    let xattrs = xattr::list(path).unwrap();
    let mut vector_of_xattr_values = vec![];
    for attr in xattrs {
        if attr.to_str().unwrap() == "user.xdg.tags" {
            let byte_vector: &[u8] = &xattr::get(path, "user.xdg.tags").unwrap().unwrap();
            let string = std::str::from_utf8(byte_vector).unwrap();
            let split_string: Vec<&str> = string.split(',').collect();
            for each_part in split_string {
                vector_of_xattr_values.push(each_part.to_owned());
            }
            break;
        }
    }
    vector_of_xattr_values
}