summaryrefslogtreecommitdiffstats
path: root/src/shortcut.rs
blob: f7e45060ca063eabc65366e0e46a96a984ed969d (plain)
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
use std::borrow::Borrow;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use crate::constant_strings_paths::{CONFIG_FOLDER, HARDCODED_SHORTCUTS};
use crate::git::git_root;
use crate::impl_selectable_content;
use crate::utils::current_uid;

/// Holds the hardcoded and mountpoints shortcuts the user can jump to.
/// Also know which shortcut is currently selected by the user.
#[derive(Debug, Clone)]
pub struct Shortcut {
    /// The path to the shortcuts. It's a vector since we can't know how much
    /// mount points are defined.
    pub content: Vec<PathBuf>,
    /// The currently selected shortcut
    pub index: usize,
    non_mount_size: usize,
}

impl Shortcut {
    /// Creates the hardcoded shortcuts
    /// Add the config folder and the git root
    ///(no mount point yet).
    pub fn new(start_folder: &Path) -> Self {
        let mut shortcuts = Self::hardcoded_shortcuts();
        shortcuts = Self::with_home_path(shortcuts);
        shortcuts = Self::with_config_folder(shortcuts);
        shortcuts = Self::with_start_folder(shortcuts, start_folder);
        shortcuts = Self::with_git_root(shortcuts);
        let non_mount_size = shortcuts.len();
        Self {
            content: shortcuts,
            index: 0,
            non_mount_size,
        }
    }

    fn hardcoded_shortcuts() -> Vec<PathBuf> {
        HARDCODED_SHORTCUTS
            .iter()
            .map(|s| PathBuf::from_str(s).unwrap())
            .collect()
    }

    /// Insert a shortcut to home directory of the current user.
    fn with_home_path(mut shortcuts: Vec<PathBuf>) -> Vec<PathBuf> {
        if let Ok(home_path) = PathBuf::from_str(shellexpand::tilde("~").borrow()) {
            shortcuts.push(home_path);
        }
        shortcuts
    }

    /// Insert a shortcut to config file directory of the current user.
    fn with_config_folder(mut shortcuts: Vec<PathBuf>) -> Vec<PathBuf> {
        if let Ok(config_folder) = PathBuf::from_str(shellexpand::tilde(CONFIG_FOLDER).borrow()) {
            shortcuts.push(config_folder);
        }
        shortcuts
    }

    fn with_start_folder(mut shortcuts: Vec<PathBuf>, start_folder: &Path) -> Vec<PathBuf> {
        shortcuts.push(start_folder.to_owned());
        shortcuts
    }

    fn git_root_or_cwd() -> PathBuf {
        if let Ok(git_root) = git_root() {
            PathBuf::from(git_root)
        } else {
            std::env::current_dir().unwrap()
        }
    }

    fn with_git_root(mut shortcuts: Vec<PathBuf>) -> Vec<PathBuf> {
        shortcuts.push(Self::git_root_or_cwd());
        shortcuts
    }

    fn clear_doublons(&mut self) {
        self.content.dedup();
        self.content = dedup_slow(self.content.clone())
    }

    pub fn update_git_root(&mut self) {
        self.content[self.non_mount_size - 1] = Self::git_root_or_cwd();
    }

    /// Update the shortcuts with the mount points.
    pub fn extend_with_mount_points(&mut self, mount_points: &[&Path]) {
        self.content
            .extend(mount_points.iter().map(|p| p.to_path_buf()));
        self.extend_with_mtp()
    }

    /// Update the shortcuts with MTP mount points
    fn extend_with_mtp(&mut self) {
        let Ok(uid) = current_uid() else {
            return;
        };
        let mtp_mount_point = PathBuf::from(format!("/run/user/{uid}/gvfs/"));
        if !mtp_mount_point.exists() || !mtp_mount_point.is_dir() {
            return;
        }

        let mount_points: Vec<PathBuf> = match std::fs::read_dir(&mtp_mount_point) {
            Ok(read_dir) => read_dir
                .filter_map(|direntry| direntry.ok())
                .filter(|direntry| direntry.path().is_dir())
                .map(|direntry| direntry.path())
                .collect(),
            Err(error) => {
                log::info!(
                    "unreadable gvfs {mtp_mount_point}: {error:?} ",
                    mtp_mount_point = mtp_mount_point.display(),
                );
                return;
            }
        };
        self.content.extend(mount_points)
    }

    /// Refresh the shortcuts. It drops non "hardcoded" shortcuts and
    /// extend the vector with the mount points.
    pub fn refresh(&mut self, mount_points: &[&Path]) {
        self.content.truncate(self.non_mount_size);
        self.extend_with_mount_points(mount_points);
        self.clear_doublons();
    }
}

/// Remove duplicates from a vector and returns it.
/// Elements should be `PartialEq`.
/// It removes element than are not consecutives and is very slow.
fn dedup_slow<T>(mut elems: Vec<T>) -> Vec<T>
where
    T: PartialEq,
{
    let mut to_remove = vec![];
    for i in 0..elems.len() {
        for j in (i + 1)..elems.len() {
            if elems[i] == elems[j] {
                to_remove.push(j)
            }
        }
    }
    for i in to_remove.iter().rev() {
        elems.remove(*i);
    }
    elems
}

impl_selectable_content!(PathBuf, Shortcut);