Skip to content

Commit a7e48ae

Browse files
authored
Merge pull request #12 from NotAShelf/notashelf/push-tnzzwzkuwzpw
treewide: clean up internal code for simplicity
2 parents d6a9d88 + ca1796d commit a7e48ae

File tree

12 files changed

+767
-691
lines changed

12 files changed

+767
-691
lines changed

Cargo.lock

Lines changed: 407 additions & 387 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
2-
edition = "2021"
32
name = "tailray"
4-
version = "0.3.1"
3+
version = "0.3.2"
4+
edition = "2024"
55
authors = ["NotAShelf <raf@notashelf.dev>"]
66
license = "MIT"
77

@@ -11,13 +11,13 @@ ctrlc = "3.4"
1111
env_logger = "0.11"
1212
ksni = {version = "0.3", features = ["blocking"]}
1313
log = "0.4"
14-
notify-rust = {version = "4.11", default_features = false, features = ["d"]}
14+
notify-rust = {version = "4.11", default-features = false, features = ["d"]}
1515
open = "5.3"
1616
resvg = "0.45"
1717
serde = {version = "1.0", features = ["derive"]}
1818
serde_json = "1.0"
1919
signal-hook = "0.3"
2020
thiserror = "2.0"
21-
which = "7.0"
21+
which = "8.0"
2222
whoami = "1.6"
2323
wl-clipboard-rs = "0.9"

nix/package.nix

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,22 @@
1313
rev ? "dirty",
1414
}: let
1515
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
16-
17-
junkfiles = [
18-
"flake.nix"
19-
"flake.lock"
20-
"LICENSE"
21-
".gitignore"
22-
".envrc"
23-
"README.md"
24-
];
25-
26-
repoDirFilter = name: type:
27-
!((type == "directory") && ((baseNameOf name) == "nix"))
28-
&& !((type == "directory") && ((baseNameOf (dirOf name)) == ".github"))
29-
&& !(builtins.any (r: (builtins.match r (baseNameOf name)) != null) junkfiles);
30-
31-
cleanSource = src:
32-
lib.cleanSourceWith {
33-
filter = repoDirFilter;
34-
src = lib.cleanSource src;
35-
};
3616
in
3717
stdenv.mkDerivation (finalAttrs: {
3818
pname = "tailray";
3919
version = "${cargoToml.package.version}-${rev}";
4020

41-
src = cleanSource ../.;
21+
src = lib.fileset.toSource {
22+
root = ../.;
23+
fileset = lib.fileset.unions [
24+
../icons
25+
../src
26+
../Cargo.lock
27+
../Cargo.toml
28+
29+
../meson.build
30+
];
31+
};
4232

4333
cargoDeps = rustPlatform.importCargoLock {
4434
lockFile = "${finalAttrs.src}/Cargo.lock";

src/error.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use std::error::Error;
2+
use std::fmt;
3+
4+
use crate::pkexec::PkexecError;
5+
use crate::svg::renderer::RenderError;
6+
use crate::tailscale::peer::PeerError;
7+
use crate::tailscale::status::StatusError;
8+
use crate::tray::menu::TrayError;
9+
10+
#[derive(Debug)]
11+
pub enum AppError {
12+
// Subsystem errors
13+
Pkexec(PkexecError),
14+
Render(RenderError),
15+
Peer(PeerError),
16+
Status(StatusError),
17+
Tray(TrayError),
18+
19+
// External library errors
20+
Clipboard(arboard::Error),
21+
Io(std::io::Error),
22+
SerdeJson(serde_json::Error),
23+
Notify(notify_rust::error::Error),
24+
25+
// Generic string error
26+
Message(String),
27+
}
28+
29+
impl fmt::Display for AppError {
30+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31+
match self {
32+
AppError::Pkexec(e) => write!(f, "Pkexec error: {e}"),
33+
AppError::Render(e) => write!(f, "SVG render error: {e}"),
34+
AppError::Peer(e) => write!(f, "Peer error: {e}"),
35+
AppError::Status(e) => write!(f, "Tailscale status error: {e}"),
36+
AppError::Tray(e) => write!(f, "Tray error: {e}"),
37+
AppError::Clipboard(e) => write!(f, "Clipboard error: {e}"),
38+
AppError::Io(e) => write!(f, "IO error: {e}"),
39+
AppError::SerdeJson(e) => write!(f, "JSON error: {e}"),
40+
AppError::Notify(e) => write!(f, "Notification error: {e}"),
41+
AppError::Message(msg) => write!(f, "{msg}"),
42+
}
43+
}
44+
}
45+
46+
impl Error for AppError {
47+
fn source(&self) -> Option<&(dyn Error + 'static)> {
48+
match self {
49+
AppError::Pkexec(e) => Some(e),
50+
AppError::Render(e) => Some(e),
51+
AppError::Peer(e) => Some(e),
52+
AppError::Status(e) => Some(e),
53+
AppError::Tray(e) => Some(e),
54+
AppError::Clipboard(e) => Some(e),
55+
AppError::Io(e) => Some(e),
56+
AppError::SerdeJson(e) => Some(e),
57+
AppError::Notify(e) => Some(e),
58+
AppError::Message(_) => None,
59+
}
60+
}
61+
}
62+
63+
// From conversions for subsystem errors
64+
impl From<PkexecError> for AppError {
65+
fn from(e: PkexecError) -> Self {
66+
AppError::Pkexec(e)
67+
}
68+
}
69+
impl From<RenderError> for AppError {
70+
fn from(e: RenderError) -> Self {
71+
AppError::Render(e)
72+
}
73+
}
74+
impl From<PeerError> for AppError {
75+
fn from(e: PeerError) -> Self {
76+
AppError::Peer(e)
77+
}
78+
}
79+
impl From<StatusError> for AppError {
80+
fn from(e: StatusError) -> Self {
81+
AppError::Status(e)
82+
}
83+
}
84+
impl From<TrayError> for AppError {
85+
fn from(e: TrayError) -> Self {
86+
AppError::Tray(e)
87+
}
88+
}
89+
90+
// From conversions for external errors
91+
impl From<arboard::Error> for AppError {
92+
fn from(e: arboard::Error) -> Self {
93+
AppError::Clipboard(e)
94+
}
95+
}
96+
impl From<std::io::Error> for AppError {
97+
fn from(e: std::io::Error) -> Self {
98+
AppError::Io(e)
99+
}
100+
}
101+
impl From<serde_json::Error> for AppError {
102+
fn from(e: serde_json::Error) -> Self {
103+
AppError::SerdeJson(e)
104+
}
105+
}
106+
impl From<notify_rust::error::Error> for AppError {
107+
fn from(e: notify_rust::error::Error) -> Self {
108+
AppError::Notify(e)
109+
}
110+
}
111+
112+
// From conversion for string messages
113+
impl From<String> for AppError {
114+
fn from(e: String) -> Self {
115+
AppError::Message(e)
116+
}
117+
}
118+
impl From<&str> for AppError {
119+
fn from(e: &str) -> Self {
120+
AppError::Message(e.to_owned())
121+
}
122+
}
123+
124+
// From conversion for Box<dyn Error>
125+
impl From<Box<dyn std::error::Error>> for AppError {
126+
fn from(e: Box<dyn std::error::Error>) -> Self {
127+
AppError::Message(e.to_string())
128+
}
129+
}
130+
131+
// From conversion for ksni::Error
132+
impl From<ksni::Error> for AppError {
133+
fn from(e: ksni::Error) -> Self {
134+
AppError::Message(e.to_string())
135+
}
136+
}

src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod clipboard;
2+
mod error;
23
mod pkexec;
34
mod svg;
45
mod tailscale;
@@ -19,7 +20,7 @@ fn main() {
1920

2021
// Start tray service
2122
if let Err(e) = start_tray_service() {
22-
error!("Tray service error: {}", e);
23+
error!("Tray service error: {e}");
2324
exit(1);
2425
}
2526

src/pkexec.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use thiserror::Error;
44
use which::which;
55
use whoami::username;
66

7+
const FALLBACK_PKEXEC_PATH: &str = "/usr/bin/pkexec";
8+
79
/// Errors that can occur in `PKExec` operations
810
#[derive(Debug, Error)]
911
pub enum PkexecError {
@@ -15,38 +17,28 @@ pub enum PkexecError {
1517
///
1618
/// Returns a Result containing either the path to pkexec or a `PkexecError`
1719
pub fn get_path() -> Result<PathBuf, PkexecError> {
18-
match which("pkexec") {
19-
Ok(path) => {
20-
debug!("pkexec found at: {:?}", path);
21-
Ok(path)
22-
}
23-
Err(e) => {
24-
error!("pkexec not found in PATH: {}", e);
25-
Err(PkexecError::Resolution(e))
26-
}
27-
}
20+
which("pkexec")
21+
.inspect(|path| debug!("pkexec found at: {}", path.display()))
22+
.map_err(PkexecError::Resolution)
2823
}
2924

3025
/// Fallback to get pkexec path or a default path if not found
3126
///
3227
/// This function never fails but logs warnings if pkexec can't be found
3328
pub fn get_path_or_default() -> PathBuf {
3429
get_path().unwrap_or_else(|e| {
35-
error!("Using fallback path for pkexec: {}", e);
36-
PathBuf::from("/usr/bin/pkexec")
30+
error!("Using fallback path for pkexec: {e}");
31+
PathBuf::from(FALLBACK_PKEXEC_PATH)
3732
})
3833
}
3934

4035
/// Determines if privilege elevation is needed
4136
///
4237
/// Returns false if the current user is root, true otherwise
4338
pub fn should_elevate_perms() -> bool {
44-
let current_user = username();
45-
let is_root = current_user == "root";
46-
39+
let is_root = username() == "root";
4740
if is_root {
4841
debug!("Running as root, no need to elevate permissions");
4942
}
50-
5143
!is_root
5244
}

src/svg/renderer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ impl Resvg<'_> {
8282
match renderer.to_icon(SVG_DATA) {
8383
Ok(icon) => vec![icon],
8484
Err(e) => {
85-
error!("Failed to load enabled icon: {}", e);
85+
error!("Failed to load enabled icon: {e}");
8686
Vec::new()
8787
}
8888
}
@@ -93,7 +93,7 @@ impl Resvg<'_> {
9393
match renderer.to_icon(&disabled_svg) {
9494
Ok(icon) => vec![icon],
9595
Err(e) => {
96-
error!("Failed to load disabled icon: {}", e);
96+
error!("Failed to load disabled icon: {e}");
9797
Vec::new()
9898
}
9999
}

src/tailscale/peer.rs

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::clipboard::{copy, get};
2+
use crate::error::AppError;
23
use log::{error, info};
34
use notify_rust::Notification;
45
use std::error::Error;
@@ -39,7 +40,7 @@ pub fn validate_peer_ip(peer_ip: &str) -> Result<(), PeerError> {
3940

4041
// Basic IPv4/IPv6 validation
4142
if !peer_ip.contains('.') && !peer_ip.contains(':') {
42-
error!("Invalid IP address format: {}", peer_ip);
43+
error!("Invalid IP address format: {peer_ip}");
4344
return Err(PeerError::InvalidIP(format!("Invalid format: {peer_ip}")));
4445
}
4546

@@ -55,50 +56,40 @@ pub fn validate_peer_ip(peer_ip: &str) -> Result<(), PeerError> {
5556
///
5657
/// # Returns
5758
/// * `Result<(), Box<dyn Error>>` - Success or error
58-
pub fn copy_peer_ip(peer_ip: &str, notif_body: &str, host: bool) -> Result<(), Box<dyn Error>> {
59-
// Validate IP first
60-
validate_peer_ip(peer_ip)?;
59+
pub fn copy_peer_ip(peer_ip: &str, notif_body: &str, host: bool) -> Result<(), AppError> {
60+
validate_peer_ip(peer_ip).map_err(AppError::Peer)?;
6161

62-
// Copy to clipboard
6362
copy(peer_ip).map_err(|e| {
64-
error!("Failed to copy IP to clipboard: {}", e);
65-
PeerError::ClipboardError(e.to_string())
63+
error!("Failed to copy IP to clipboard: {e}");
64+
AppError::Peer(PeerError::ClipboardError(e.to_string()))
6665
})?;
6766

68-
// Get IP from clipboard to verify
6967
let clip_ip = get().map_err(|e| {
70-
error!("Failed to verify clipboard contents: {}", e);
71-
PeerError::ClipboardError(e.to_string())
68+
error!("Failed to verify clipboard contents: {e}");
69+
AppError::Peer(PeerError::ClipboardError(e.to_string()))
7270
})?;
7371

74-
// Verify the clipboard contents match what we tried to copy
7572
if clip_ip != peer_ip {
7673
error!(
77-
"Clipboard verification failed: expected '{}', got '{}'",
78-
peer_ip, clip_ip
74+
"Clipboard verification failed: expected '{peer_ip}', got '{clip_ip}'"
7975
);
80-
return Err(Box::new(PeerError::VerificationError(
76+
return Err(AppError::Peer(PeerError::VerificationError(
8177
"Clipboard content doesn't match the copied IP".into(),
8278
)));
8379
}
8480

85-
// Create summary for host/peer
86-
let device_type = if host { "host" } else { "peer" };
87-
let summary = format!("Copied {device_type} IP address");
81+
let summary = format!("Copied {} IP address", if host { "host" } else { "peer" });
82+
info!("{summary} {clip_ip} to clipboard");
8883

89-
// Log success
90-
info!("{} {} to clipboard", summary, clip_ip);
91-
92-
// Send notification
9384
Notification::new()
9485
.summary(&summary)
9586
.body(notif_body)
9687
.icon("tailscale")
97-
.timeout(3000) // 3 seconds timeout
88+
.timeout(3000)
9889
.show()
9990
.map_err(|e| {
100-
error!("Failed to show notification: {}", e);
101-
PeerError::NotificationError(e.to_string())
91+
error!("Failed to show notification: {e}");
92+
AppError::Peer(PeerError::NotificationError(e.to_string()))
10293
})?;
10394

10495
Ok(())

0 commit comments

Comments
 (0)