free_launch/
launch_entry.rs1use chrono::Local;
2use color_eyre::eyre::{OptionExt, Result};
3use egui::Image;
4use icon::IconFile;
5use std::fs::create_dir_all;
6use std::path::Path;
7use std::time::Instant;
8
9use crate::free_launch::PROJECT_DIRS;
10use crate::invocation::Invocation;
11
12pub trait LaunchId {
15 fn id(&self) -> &str;
16 fn file_path(&self) -> &Path;
17 fn icon_file(&self) -> Option<&IconFile>;
18 fn icon(&self) -> Option<Image>;
19 fn comment(&self) -> Option<&str>;
20}
21
22pub trait LaunchAction {
23 fn launch(&self);
24 fn open_directory(&self);
25}
26
27pub trait Launchable: LaunchId + LaunchAction {}
28
29pub struct LaunchEntry {
31 pub id: String,
32 pub name: String,
33 pub selected: bool,
34 pub items: Vec<Box<dyn Launchable>>,
35 pub invocations: Vec<Invocation>,
37}
38
39impl LaunchEntry {
40 pub fn new(id: String, name: String) -> Self {
42 Self {
43 id,
44 name,
45 selected: false,
46 items: Vec::new(),
47 invocations: Vec::new(),
48 }
49 }
50
51 pub fn name(&self) -> &str {
52 &self.name
53 }
54
55 pub fn add_item(&mut self, item: Box<dyn Launchable>) {
57 self.items.push(item);
58 }
59
60 pub fn add_invocation(&mut self, invocation: Invocation) {
62 self.invocations.push(invocation);
63 }
64
65 pub fn sort_key(&self) -> u64 {
70 let now = Local::now().timestamp() as u64;
71 let invocations = self.invocations.len().max(1);
72 self.invocations
73 .iter()
74 .fold(0, |acc, i| acc + (i.timestamp / (now - i.timestamp).max(1)))
75 }
76
77 pub(crate) fn toggle_selected(&mut self) {
78 self.selected = !self.selected
79 }
80
81 pub(crate) fn preferred_item(&self) -> Option<&Box<dyn Launchable>> {
83 self.items.first()
84 }
85
86 pub(crate) fn id(&self) -> Option<&str> {
87 self.preferred_item().map(|i| i.id())
88 }
89
90 pub(crate) fn file_path(&self) -> Option<&Path> {
91 self.preferred_item().map(|i| i.file_path())
92 }
93
94 pub(crate) fn icon(&self) -> Option<Image> {
95 self.preferred_item().map(|i| i.icon()).flatten()
96 }
97
98 pub(crate) fn comment(&self) -> Option<&str> {
99 self.preferred_item().map(|i| i.comment()).flatten()
100 }
101
102 pub(crate) fn launch(&self, search_query: &str) -> Result<()> {
103 self.log_invocation("launch", search_query);
104
105 self.preferred_item()
106 .map(|i| i.launch())
107 .ok_or_eyre("could not launch item")
108 }
109
110 pub(crate) fn open_directory(&self, search_query: &str) -> Result<()> {
111 self.log_invocation("reveal", search_query);
112
113 self.preferred_item()
114 .map(|i| i.open_directory())
115 .ok_or_eyre("could not reveal item")
116 }
117
118 fn log_invocation(&self, action: &str, search_query: &str) {
119 let cache_dir = PROJECT_DIRS.cache_dir();
121
122 if let Err(e) = create_dir_all(&cache_dir) {
124 eprintln!(
125 "Failed to create cache directory {}: {}",
126 cache_dir.display(),
127 e
128 );
129 return;
130 }
131
132 Invocation::log_entry(search_query, action.to_owned(), self);
136 }
137}