free_launch/
launch_entry.rs1use chrono::Local;
2use color_eyre::eyre::{OptionExt, Result};
3use egui::{Image, Ui};
4use std::fmt;
5use std::fs::create_dir_all;
6use std::path::Path;
7use tracing::{error, info, warn};
8
9use crate::free_launch::PROJECT_DIRS;
10use crate::invocation::Invocation;
11
12pub trait LaunchId {
15 fn id(&self) -> String;
16 fn file_path(&self) -> &Path;
17 fn icon_name(&self) -> Option<&str>;
18 fn comment(&self) -> Option<&str>;
19 fn debug_ui(&self, ui: &mut Ui, count: usize);
20}
21
22pub trait LaunchAction {
23 fn launch(&self);
24 fn open_directory(&self);
25}
26
27pub trait Launchable: LaunchId + LaunchAction + Send + Sync {}
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 is_valid(&self) -> bool {
67 !self.id.is_empty() && !self.items.is_empty()
68 }
69
70 pub fn sort_key(&self) -> u64 {
75 let now = Local::now().timestamp() as u64;
76 self.invocations
77 .iter()
78 .fold(0, |acc, i| acc + (i.timestamp / (now - i.timestamp).max(1)))
79 }
80
81 pub(crate) fn toggle_selected(&mut self) {
82 self.selected = !self.selected
83 }
84
85 pub(crate) fn preferred_item(&self) -> Option<&Box<dyn Launchable>> {
87 self.items.first()
88 }
89
90 pub(crate) fn id(&self) -> Option<String> {
91 self.preferred_item().map(|i| i.id())
92 }
93
94 pub(crate) fn file_path(&self) -> Option<&Path> {
95 self.preferred_item().map(|i| i.file_path())
96 }
97
98 pub(crate) fn comment(&self) -> Option<&str> {
99 self.preferred_item().and_then(|i| i.comment())
100 }
101
102 pub(crate) fn launch(&self, search_query: &str) -> Result<()> {
103 info!("Launching entry: {}", self.name());
104
105 self.log_invocation("launch", search_query);
106
107 self.preferred_item()
108 .map(|i| i.launch())
109 .ok_or_eyre("could not launch item")
110 }
111
112 pub(crate) fn open_directory(&self, search_query: &str) -> Result<()> {
113 self.log_invocation("reveal", search_query);
114
115 self.preferred_item()
116 .map(|i| i.open_directory())
117 .ok_or_eyre("could not reveal item")
118 }
119
120 fn log_invocation(&self, action: &str, search_query: &str) {
121 let cache_dir = PROJECT_DIRS.cache_dir();
123
124 if let Err(e) = create_dir_all(cache_dir) {
126 error!(
127 "Failed to create cache directory {}: {}",
128 cache_dir.display(),
129 e
130 );
131 return;
132 }
133
134 if let Err(e) = Invocation::log_entry(search_query, action.to_owned(), self) {
138 warn!("Could not log invocation:{}", e)
139 }
140 }
141}
142
143impl fmt::Display for LaunchEntry {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 write!(f, "{}", self.name)
146 }
147}