1use std::fmt;
4use std::io;
5use std::path::PathBuf;
6
7#[derive(Debug)]
9pub enum AppError {
10 Database(rusqlite::Error),
11 Io(io::Error),
12 Image(image::ImageError),
13 Exif(String),
14 Ml(String),
15 FileNotFound(PathBuf),
16 DriveNotMounted(PathBuf),
17 Config(String),
18 Cancelled,
19 Other(String),
20}
21
22impl fmt::Display for AppError {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 match self {
25 AppError::Database(e) => write!(f, "Database error: {}", e),
26 AppError::Io(e) => write!(f, "I/O error: {}", e),
27 AppError::Image(e) => write!(f, "Image error: {}", e),
28 AppError::Exif(msg) => write!(f, "EXIF error: {}", msg),
29 AppError::Ml(msg) => write!(f, "ML error: {}", msg),
30 AppError::FileNotFound(path) => write!(f, "File not found: {}", path.display()),
31 AppError::DriveNotMounted(path) => {
32 write!(f, "Drive not mounted: {}", path.display())
33 }
34 AppError::Config(msg) => write!(f, "Configuration error: {}", msg),
35 AppError::Cancelled => write!(f, "Operation cancelled"),
36 AppError::Other(msg) => write!(f, "{}", msg),
37 }
38 }
39}
40
41impl std::error::Error for AppError {}
42
43impl From<rusqlite::Error> for AppError {
44 fn from(e: rusqlite::Error) -> Self {
45 AppError::Database(e)
46 }
47}
48
49impl From<io::Error> for AppError {
50 fn from(e: io::Error) -> Self {
51 AppError::Io(e)
52 }
53}
54
55impl From<image::ImageError> for AppError {
56 fn from(e: image::ImageError) -> Self {
57 AppError::Image(e)
58 }
59}
60
61pub type AppResult<T> = Result<T, AppError>;
62
63impl AppError {
64 pub fn recovery_suggestion(&self) -> &str {
65 match self {
66 AppError::Database(_) => {
67 "Try restarting the app. If it persists, database may be corrupted."
68 }
69 AppError::Io(_) => "Check file permissions and available disk space.",
70 AppError::Image(_) => "This image may be corrupted or unsupported.",
71 AppError::Exif(_) => "Metadata could not be read. Photo can still be indexed.",
72 AppError::Ml(_) => "Face detection failed. Try restarting the app.",
73 AppError::FileNotFound(_) => "The file may have been moved or deleted.",
74 AppError::DriveNotMounted(_) => "Reconnect the drive and try again.",
75 AppError::Config(_) => "Reset settings to defaults and try again.",
76 AppError::Cancelled => "",
77 AppError::Other(_) => "Please try again.",
78 }
79 }
80
81 pub fn should_notify_user(&self) -> bool {
82 !matches!(self, AppError::Cancelled | AppError::Exif(_))
83 }
84
85 pub fn is_recoverable(&self) -> bool {
86 !matches!(self, AppError::Database(_))
87 }
88}
89
90#[derive(Debug, Clone)]
91pub struct ErrorNotification {
92 pub title: String,
93 pub message: String,
94 pub suggestion: String,
95 pub is_critical: bool,
96}
97
98impl From<&AppError> for ErrorNotification {
99 fn from(error: &AppError) -> Self {
100 let title = match error {
101 AppError::Database(_) => "Database Error",
102 AppError::Io(_) => "File Error",
103 AppError::Image(_) => "Image Error",
104 AppError::Exif(_) => "Metadata Error",
105 AppError::Ml(_) => "Face Detection Error",
106 AppError::FileNotFound(_) => "File Not Found",
107 AppError::DriveNotMounted(_) => "Drive Not Connected",
108 AppError::Config(_) => "Settings Error",
109 AppError::Cancelled => "Cancelled",
110 AppError::Other(_) => "Error",
111 };
112
113 Self {
114 title: title.to_string(),
115 message: error.to_string(),
116 suggestion: error.recovery_suggestion().to_string(),
117 is_critical: !error.is_recoverable(),
118 }
119 }
120}