1use crate::error::DatabaseError;
4use crate::{Error, Result};
5
6use super::Database;
7
8impl Database {
9 pub async fn was_unclean_shutdown(&self) -> Result<bool> {
16 let value: Option<String> = sqlx::query_scalar(
17 r#"
18 SELECT value FROM runtime_state WHERE key = 'clean_shutdown'
19 "#,
20 )
21 .fetch_optional(&self.pool)
22 .await
23 .map_err(|e| {
24 Error::Database(DatabaseError::QueryFailed(format!(
25 "Failed to check shutdown state: {}",
26 e
27 )))
28 })?;
29
30 Ok(value.is_none_or(|v| v != "true"))
32 }
33
34 pub async fn set_clean_start(&self) -> Result<()> {
40 let now = chrono::Utc::now().timestamp();
41 sqlx::query(
42 r#"
43 INSERT INTO runtime_state (key, value, updated_at)
44 VALUES ('clean_shutdown', 'false', ?)
45 ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = ?
46 "#,
47 )
48 .bind(now)
49 .bind(now)
50 .execute(&self.pool)
51 .await
52 .map_err(|e| {
53 Error::Database(DatabaseError::QueryFailed(format!(
54 "Failed to set clean start: {}",
55 e
56 )))
57 })?;
58
59 Ok(())
60 }
61
62 pub async fn set_clean_shutdown(&self) -> Result<()> {
68 let now = chrono::Utc::now().timestamp();
69 sqlx::query(
70 r#"
71 INSERT INTO runtime_state (key, value, updated_at)
72 VALUES ('clean_shutdown', 'true', ?)
73 ON CONFLICT(key) DO UPDATE SET value = 'true', updated_at = ?
74 "#,
75 )
76 .bind(now)
77 .bind(now)
78 .execute(&self.pool)
79 .await
80 .map_err(|e| {
81 Error::Database(DatabaseError::QueryFailed(format!(
82 "Failed to set clean shutdown: {}",
83 e
84 )))
85 })?;
86
87 Ok(())
88 }
89
90 pub async fn mark_nzb_processed(&self, path: &std::path::Path) -> Result<()> {
95 let path_str = path.to_string_lossy().into_owned();
96 let now = chrono::Utc::now().timestamp();
97
98 sqlx::query(
99 r#"
100 INSERT INTO processed_nzbs (path, processed_at)
101 VALUES (?, ?)
102 ON CONFLICT(path) DO UPDATE SET processed_at = ?
103 "#,
104 )
105 .bind(&path_str)
106 .bind(now)
107 .bind(now)
108 .execute(&self.pool)
109 .await
110 .map_err(|e| {
111 Error::Database(DatabaseError::QueryFailed(format!(
112 "Failed to mark NZB as processed: {}",
113 e
114 )))
115 })?;
116
117 Ok(())
118 }
119
120 pub async fn is_nzb_processed(&self, path: &std::path::Path) -> Result<bool> {
122 let path_str = path.to_string_lossy().into_owned();
123
124 let count: i64 = sqlx::query_scalar(
125 r#"
126 SELECT COUNT(*) FROM processed_nzbs WHERE path = ?
127 "#,
128 )
129 .bind(&path_str)
130 .fetch_one(&self.pool)
131 .await
132 .map_err(|e| {
133 Error::Database(DatabaseError::QueryFailed(format!(
134 "Failed to check if NZB is processed: {}",
135 e
136 )))
137 })?;
138
139 Ok(count > 0)
140 }
141
142 pub async fn is_rss_item_seen(&self, feed_id: i64, guid: &str) -> Result<bool> {
144 let count: i64 = sqlx::query_scalar(
145 r#"
146 SELECT COUNT(*) FROM rss_seen WHERE feed_id = ? AND guid = ?
147 "#,
148 )
149 .bind(feed_id)
150 .bind(guid)
151 .fetch_one(&self.pool)
152 .await
153 .map_err(|e| {
154 Error::Database(DatabaseError::QueryFailed(format!(
155 "Failed to check if RSS item is seen: {}",
156 e
157 )))
158 })?;
159
160 Ok(count > 0)
161 }
162
163 pub async fn mark_rss_item_seen(&self, feed_id: i64, guid: &str) -> Result<()> {
165 let now = chrono::Utc::now().timestamp();
166
167 sqlx::query(
168 r#"
169 INSERT INTO rss_seen (feed_id, guid, seen_at)
170 VALUES (?, ?, ?)
171 ON CONFLICT(feed_id, guid) DO UPDATE SET seen_at = ?
172 "#,
173 )
174 .bind(feed_id)
175 .bind(guid)
176 .bind(now)
177 .bind(now)
178 .execute(&self.pool)
179 .await
180 .map_err(|e| {
181 Error::Database(DatabaseError::QueryFailed(format!(
182 "Failed to mark RSS item as seen: {}",
183 e
184 )))
185 })?;
186
187 Ok(())
188 }
189}