use chrono::Local;
use niri_ipc::{Timestamp, Window as NiriWindow};
use std::fmt;
use std::fs::create_dir_all;
use std::path::Path;
use tracing::{error, info, warn};
use crate::free_launch::free_launch::PROJECT_DIRS;
use crate::launch_entries::action_name::ActionName;
use crate::launch_entries::action_result::ActionResult;
use crate::launch_entries::launch_entry_trait::Launchable;
use crate::model::invocation::Invocation;
use crate::model::window::Window;
pub struct LaunchEntry {
pub id: String,
pub name: String,
pub selected: bool,
pub(crate) items: Vec<Box<dyn Launchable>>,
pub invocations: Vec<Invocation>,
pub activation_order: Option<Timestamp>,
}
impl LaunchEntry {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
selected: false,
items: Vec::new(),
invocations: Vec::new(),
activation_order: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub(crate) fn add_item(&mut self, item: Box<dyn Launchable>) {
self.items.push(item);
}
pub(crate) fn add_invocation(&mut self, invocation: Invocation) {
self.invocations.push(invocation);
}
pub fn is_valid(&self) -> bool {
!self.id.is_empty() && !self.items.is_empty()
}
pub fn sort_key(&self) -> u64 {
let now = Local::now().timestamp() as u64;
self.invocations
.iter()
.fold(0, |acc, i| acc + (i.timestamp / (now - i.timestamp).max(1)))
}
pub(crate) fn toggle_selected(&mut self) {
self.selected = !self.selected
}
pub(crate) fn preferred_item(&self) -> Option<&Box<dyn Launchable>> {
self.items.first()
}
pub(crate) fn id(&self) -> Option<String> {
self.preferred_item().map(|i| i.id())
}
pub(crate) fn file_path(&self) -> Option<&Path> {
self.preferred_item().map(|i| i.file_path())
}
pub(crate) fn comment(&self) -> Option<&str> {
self.preferred_item().and_then(|i| i.comment())
}
pub(crate) fn icon_name(&self) -> Option<&str> {
self.preferred_item().and_then(|i| i.icon_name())
}
pub(crate) fn invoke(&self, action: &ActionName, search_query: &str) -> ActionResult {
info!("Launching entry: {}", self.name());
match self.preferred_item().map(|i| i.invoke(action)) {
Some(result) => {
self.log_invocation(action, search_query, &result);
result
}
None => ActionResult::Error("could not find a preferred item".to_owned()),
}
}
pub(crate) fn invoke_default(&self, search_query: &str) -> ActionResult {
self.invoke(&ActionName::Default, search_query)
}
pub(crate) fn invoke_secondary(&self, search_query: &str) -> ActionResult {
match self.preferred_item() {
Some(item) => {
let secondary_action_name = item.secondary_action();
self.invoke(
&ActionName::Action(secondary_action_name.to_owned()),
search_query,
)
}
None => ActionResult::Error("could not find a preferred item".to_owned()),
}
}
fn log_invocation(&self, action: &ActionName, search_query: &str, result: &ActionResult) {
let cache_dir = PROJECT_DIRS.cache_dir();
if let Err(e) = create_dir_all(cache_dir) {
error!(
"Failed to create cache directory {}: {}",
cache_dir.display(),
e
);
return;
}
if let Err(e) = Invocation::log_entry(search_query, action.to_string(), self) {
warn!("Could not log invocation:{}", e)
}
}
}
impl From<Window> for LaunchEntry {
fn from(window: Window) -> Self {
let id = window.id();
let name = window.name().to_owned();
let activation_order = window.focus_timestamp();
let mut launch_entry = LaunchEntry::new(id, name.unwrap_or("UNNAMED".to_owned()));
launch_entry.activation_order = activation_order;
launch_entry.add_item(Box::new(window));
launch_entry
}
}
impl From<NiriWindow> for LaunchEntry {
fn from(niri_window: NiriWindow) -> Self {
let window: Window = niri_window.into();
window.into()
}
}
impl fmt::Display for LaunchEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}