use crate::entry::PluginEntryError;
use crate::entry::entry_provider::EntryProvider;
use clack_common::entry::EntryDescriptor;
use libloading::Library;
use std::borrow::Cow;
use std::ffi::{CStr, CString, OsStr};
use std::path::Path;
use std::ptr::NonNull;
pub struct LibraryEntry {
_library: Library,
entry_ptr: NonNull<EntryDescriptor>,
}
const SYMBOL_NAME: &CStr = c"clap_entry";
impl LibraryEntry {
#[cfg(feature = "libloading")]
pub(crate) fn resolve_path(path: &Path) -> Result<(Cow<'_, Path>, CString), PluginEntryError> {
#[cfg(target_os = "macos")]
{
macos_resolve::resolve_path(path)
}
#[cfg(not(target_os = "macos"))]
{
let bundle_path = CString::new(path.to_string_lossy().into_owned())?;
Ok((Cow::Borrowed(path), bundle_path))
}
}
pub unsafe fn load_from_path(path: impl AsRef<OsStr>) -> Result<Self, PluginEntryError> {
let library = Library::new(path).map_err(PluginEntryError::LibraryLoadingError)?;
Self::load_from_library(library)
}
pub unsafe fn load_from_library(library: Library) -> Result<Self, PluginEntryError> {
Self::load_from_symbol_in_library(library, SYMBOL_NAME)
}
pub unsafe fn load_from_symbol_in_library(
library: Library,
symbol_name: &CStr,
) -> Result<Self, PluginEntryError> {
let symbol = library
.get::<*mut EntryDescriptor>(symbol_name.to_bytes_with_nul())
.map_err(PluginEntryError::LibraryLoadingError)?;
let entry_ptr = NonNull::new(*symbol).ok_or(PluginEntryError::NullEntryPointer)?;
Ok(Self {
_library: library,
entry_ptr,
})
}
}
unsafe impl EntryProvider for LibraryEntry {
#[inline]
fn entry_pointer(&self) -> NonNull<EntryDescriptor> {
self.entry_ptr
}
}
unsafe impl Send for LibraryEntry {}
unsafe impl Sync for LibraryEntry {}
#[cfg(target_os = "macos")]
mod macos_resolve {
use super::*;
use std::path::{Path, PathBuf};
pub(super) fn resolve_path(path: &Path) -> Result<(Cow<'_, Path>, CString), PluginEntryError> {
if std::fs::metadata(path)?.is_dir() {
let executable_path =
executable_path_from_bundle(path).ok_or(PluginEntryError::ResolveFailed)?;
let bundle_path = CString::new(path.to_string_lossy().into_owned())?;
Ok((Cow::Owned(executable_path), bundle_path))
} else {
let bundle_path = bundle_path_from_executable(path);
let bundle_path = CString::new(bundle_path.to_string_lossy().into_owned())?;
Ok((Cow::Borrowed(path), bundle_path))
}
}
fn executable_path_from_bundle(path: &Path) -> Option<PathBuf> {
use objc2_foundation::{NSBundle, NSURL};
let url = NSURL::from_directory_path(path)?;
let bundle = NSBundle::bundleWithURL(&url)?;
let executable_url = bundle.executableURL()?.absoluteURL()?;
let executable_path = executable_url.to_file_path()?;
Some(executable_path)
}
fn bundle_path_from_executable(path: &Path) -> &Path {
if let Some(bundle_path) = bundle_path_from_structure(path) {
return bundle_path;
};
if let Some(bundle_path) = bundle_path_from_clap_package_ancestor(path) {
return bundle_path;
};
path
}
fn bundle_path_from_structure(path: &Path) -> Option<&Path> {
let mut ancestors = path.ancestors();
if ancestors.next()? != OsStr::new("MacOS") {
return None;
}
if ancestors.next()? != OsStr::new("Contents") {
return None;
}
ancestors.next()
}
fn bundle_path_from_clap_package_ancestor(path: &Path) -> Option<&Path> {
path.ancestors()
.find(|a| a.extension().is_some_and(|e| e == "clap"))
}
}