Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ This project is currently in alpha stage. Progress and next steps:
- AirDrop: Shows as "AirDrop" without exposing internal URL
- Recents: Shows as "Recents" without exposing internal URL
- Applications: Shows as "Applications" without exposing internal URL
- Desktop: Shows as "~/Desktop" for user's desktop folder
- Downloads: Shows as "~/Downloads" for user's downloads folder
- User-friendly path formatting (show regular paths instead of raw URLs)

🚧 **In Progress**:
- User-friendly path formatting (show regular paths instead of raw URLs)
- Support for custom folder locations

🔜 **Planned**:
- Handle special locations:
- User Desktop (`file:///Users/<user>/Desktop/`)
- User Downloads (`file:///Users/<user>/Downloads/`)
- Add/remove favorites
- Command-line interface improvements

- Configuration options

## Documentation

- [Architecture Overview](docs/architecture.md)
Expand Down
24 changes: 11 additions & 13 deletions src/finder/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ pub enum Target {
AirDrop,
Recents,
Applications,
Downloads,
Custom { label: String, path: String },
}

impl Target {
pub fn custom(label: impl Into<String>, path: impl Into<String>) -> Self {
Self::Custom {
label: label.into(),
path: path.into(),
}
}
}

#[derive(Debug, PartialEq)]
pub struct SidebarItem {
target: Target,
Expand All @@ -26,7 +34,6 @@ impl fmt::Display for SidebarItem {
Target::AirDrop => write!(f, "AirDrop"),
Target::Recents => write!(f, "Recents"),
Target::Applications => write!(f, "Applications"),
Target::Downloads => write!(f, "~/Downloads"),
Target::Custom { label, path } => write!(f, "{} -> {}", label, path),
}
}
Expand All @@ -40,11 +47,8 @@ mod tests {

#[test]
fn should_create_sidebar_item_with_custom_target() {
let item = SidebarItem::new(Target::Custom {
label: "Documents".to_string(),
path: "/Users/user/Documents".to_string(),
});
assert_eq!(format!("{}", item), "Documents -> /Users/user/Documents");
let item = SidebarItem::new(Target::custom("Projects", "/Users/user/Projects"));
assert_eq!(format!("{}", item), "Projects -> /Users/user/Projects");
}

#[test]
Expand All @@ -64,10 +68,4 @@ mod tests {
let item = SidebarItem::new(Target::Applications);
assert_eq!(format!("{}", item), "Applications");
}

#[test]
fn should_create_sidebar_item_with_downloads() {
let item = SidebarItem::new(Target::Downloads);
assert_eq!(format!("{}", item), "~/Downloads");
}
}
145 changes: 145 additions & 0 deletions src/system/favorites/item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use std::fmt;

use crate::{
finder::Target,
system::favorites::{DisplayName, Url},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacOsUrl {
AirDrop,
Recents,
Applications,
Custom(String),
}

impl MacOsUrl {
const AIRDROP: &'static str = "nwnode://domain-AirDrop";
const RECENTS: &'static str = "file:///System/Library/CoreServices/Finder.app/Contents/Resources/MyLibraries/myDocuments.cannedSearch/";
const APPLICATIONS: &'static str = "file:///Applications/";

fn clean_path(url: impl AsRef<str>) -> String {
url.as_ref()
.strip_prefix("file://")
.and_then(|p| p.strip_suffix('/'))
.unwrap_or(url.as_ref())
.to_string()
}
}

impl From<Url> for MacOsUrl {
fn from(url: Url) -> Self {
let url_str = url.to_string();

match url_str.as_str() {
Self::AIRDROP => Self::AirDrop,
Self::RECENTS => Self::Recents,
Self::APPLICATIONS => Self::Applications,
url => Self::Custom(Self::clean_path(url)),
}
}
}

#[derive(Debug, Clone)]
pub struct FavoriteItem {
url: Url,
name: DisplayName,
}

impl FavoriteItem {
pub fn new(url: Url, name: DisplayName) -> Self {
Self { url, name }
}
}

impl fmt::Display for FavoriteItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} -> {}", self.name, self.url)
}
}

impl From<FavoriteItem> for Target {
fn from(item: FavoriteItem) -> Self {
match MacOsUrl::from(item.url) {
MacOsUrl::AirDrop => Target::AirDrop,
MacOsUrl::Recents => Target::Recents,
MacOsUrl::Applications => Target::Applications,
MacOsUrl::Custom(path) => Target::custom(item.name.to_string(), path),
}
}
}

#[cfg(test)]
mod tests {
use core_foundation::{
base::TCFType,
string::CFString,
url::{CFURL, kCFURLPOSIXPathStyle},
};
use pretty_assertions::assert_eq;

use super::*;

fn create_url(path: &str) -> Url {
let cf_string = CFString::new(path);
let is_dir = path.ends_with('/');
let cf_url = CFURL::from_file_system_path(cf_string, kCFURLPOSIXPathStyle, is_dir);
Url::try_from(cf_url.as_concrete_TypeRef()).unwrap()
}

fn create_display_name(name: &str) -> DisplayName {
let cf_string = CFString::new(name);
DisplayName::try_from(cf_string.as_concrete_TypeRef()).unwrap()
}

#[test]
fn should_convert_airdrop_url() {
let target = Target::from(FavoriteItem::new(
create_url(MacOsUrl::AIRDROP),
create_display_name("AirDrop"),
));
assert_eq!(target, Target::AirDrop);
}

#[test]
fn should_convert_recents_url() {
let target = Target::from(FavoriteItem::new(
create_url(MacOsUrl::RECENTS),
create_display_name("Recents"),
));
assert_eq!(target, Target::Recents);
}

#[test]
fn should_convert_applications_url() {
let target = Target::from(FavoriteItem::new(
create_url(MacOsUrl::APPLICATIONS),
create_display_name("Applications"),
));
assert_eq!(target, Target::Applications);
}

#[test]
fn should_convert_custom_url() {
let target = Target::from(FavoriteItem::new(
create_url("file:///Users/user/Projects/"),
create_display_name("Projects"),
));
assert_eq!(target, Target::Custom {
label: "Projects".to_string(),
path: "/Users/user/Projects".to_string(),
});
}

#[test]
fn should_format_favorite_item() {
let item = FavoriteItem::new(
create_url("file:///Users/user/Projects/"),
create_display_name("Projects"),
);
assert_eq!(
format!("{}", item),
"Projects -> file:///Users/user/Projects/"
);
}
}
6 changes: 3 additions & 3 deletions src/system/favorites/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
mod display_name;
mod errors;
mod handle;
mod item;
mod snapshot;
mod snapshot_item;
mod url;
mod url_mapper;

use core_foundation::base::kCFAllocatorDefault;
use core_services::{LSSharedFileListResolutionFlags, kLSSharedFileListFavoriteItems};
pub use display_name::DisplayName;
pub use errors::FavoritesError;
pub use handle::FavoritesHandle;
pub use item::FavoriteItem;
pub use snapshot::Snapshot;
pub use snapshot_item::SnapshotItem;
pub use url::Url;
pub use url_mapper::TargetUrl;

use crate::{
finder::{Result, SidebarItem, Target, favorites::FavoritesApi},
Expand Down Expand Up @@ -72,7 +72,7 @@ impl Favorites {
unsafe fn convert_item(&self, item: SnapshotItem) -> Result<SidebarItem> {
let url = unsafe { self.copy_resolved_url(&item) }?;
let name = unsafe { self.copy_display_name(&item) }?;
let target = Target::from(TargetUrl(url, name));
let target = Target::from(FavoriteItem::new(url, name));
Ok(SidebarItem::new(target))
}
}
Expand Down
106 changes: 0 additions & 106 deletions src/system/favorites/url_mapper.rs

This file was deleted.

Loading