1use std::io::Write;
2
3use diesel::{
4 AsExpression, Associations, FromSqlRow, Identifiable, Insertable, Queryable, Selectable,
5 deserialize::{self, FromSql},
6 pg::{Pg, PgValue},
7 serialize::{self, IsNull, Output, ToSql},
8};
9use serde::{Deserialize, Serialize};
10
11use crate::internals::{
12 context::context_manager::{
13 DownloadedFile, RejectReason as RuntimeRejectReason, RejectedTrack, RetryRequest,
14 },
15 database::schema::{self, sql_types},
16 search::search_manager::{
17 DownloadableFile as RuntimeDownloadableFile, JudgeSubmission as RuntimeJudgeSubmission,
18 SearchItem as RuntimeSearchItem,
19 },
20};
21
22#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
23#[diesel(table_name = schema::search_items)]
24pub struct SearchItemRow {
25 pub id: i32,
26 pub track_id: String,
27 pub track: String,
28 pub artist: String,
29 pub album: String,
30}
31
32#[derive(Debug, Clone, Insertable)]
33#[diesel(table_name = schema::search_items)]
34pub struct NewSearchItemRow {
35 pub track_id: String,
36 pub track: String,
37 pub artist: String,
38 pub album: String,
39}
40
41impl From<&RuntimeSearchItem> for NewSearchItemRow {
42 fn from(value: &RuntimeSearchItem) -> Self {
43 Self {
44 track_id: value.track_id.clone(),
45 track: value.track.clone(),
46 artist: value.artist.clone(),
47 album: value.album.clone(),
48 }
49 }
50}
51
52impl From<RuntimeSearchItem> for NewSearchItemRow {
53 fn from(value: RuntimeSearchItem) -> Self {
54 Self::from(&value)
55 }
56}
57
58impl From<SearchItemRow> for RuntimeSearchItem {
59 fn from(value: SearchItemRow) -> Self {
60 Self {
61 track_id: value.track_id,
62 track: value.track,
63 artist: value.artist,
64 album: value.album,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
70#[diesel(table_name = schema::downloadable_files)]
71pub struct DownloadableFileRow {
72 pub id: i32,
73 pub filename: String,
74 pub username: String,
75 pub size: i64,
76}
77
78#[derive(Debug, Clone, Insertable)]
79#[diesel(table_name = schema::downloadable_files)]
80pub struct NewDownloadableFileRow {
81 pub filename: String,
82 pub username: String,
83 pub size: i64,
84}
85
86impl From<&RuntimeDownloadableFile> for NewDownloadableFileRow {
87 fn from(value: &RuntimeDownloadableFile) -> Self {
88 Self {
89 filename: value.filename.clone(),
90 username: value.username.clone(),
91 size: value.size,
92 }
93 }
94}
95
96impl From<RuntimeDownloadableFile> for NewDownloadableFileRow {
97 fn from(value: RuntimeDownloadableFile) -> Self {
98 Self::from(&value)
99 }
100}
101
102impl From<DownloadableFileRow> for RuntimeDownloadableFile {
103 fn from(value: DownloadableFileRow) -> Self {
104 Self {
105 filename: value.filename,
106 username: value.username,
107 size: value.size,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Queryable, Selectable, Identifiable, Associations)]
113#[diesel(table_name = schema::judge_submissions)]
114#[diesel(belongs_to(SearchItemRow, foreign_key = track))]
115#[diesel(belongs_to(DownloadableFileRow, foreign_key = query))]
116pub struct JudgeSubmissionRow {
117 pub id: i32,
118 pub track: i32,
119 pub query: i32,
120 pub score: Option<f32>,
121}
122
123#[derive(Debug, Clone, Insertable)]
124#[diesel(table_name = schema::judge_submissions)]
125pub struct NewJudgeSubmissionRow {
126 pub track: i32,
127 pub query: i32,
128 pub score: Option<f32>,
129}
130
131#[derive(Debug, Clone)]
132pub struct JudgeSubmissionJoined {
133 pub row: JudgeSubmissionRow,
134 pub track: SearchItemRow,
135 pub query: DownloadableFileRow,
136}
137
138impl From<JudgeSubmissionJoined> for RuntimeJudgeSubmission {
139 fn from(value: JudgeSubmissionJoined) -> Self {
140 Self {
141 track: value.track.into(),
142 query: value.query.into(),
143 score: None,
144 }
145 }
146}
147
148#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
149#[diesel(table_name = schema::downloaded_file)]
150pub struct DownloadedFileRow {
151 pub id: i32,
152 pub filename: String,
153 pub track: Option<i32>,
154}
155
156#[derive(Debug, Clone, Insertable)]
157#[diesel(table_name = schema::downloaded_file)]
158pub struct NewDownloadedFileRow {
159 pub filename: String,
160 pub track: Option<i32>,
161}
162
163impl NewDownloadedFileRow {
164 pub fn from_runtime(value: &DownloadedFile, track: Option<i32>) -> Self {
165 Self {
166 filename: value.filename.clone(),
167 track,
168 }
169 }
170}
171
172#[derive(Debug, Clone, Queryable, Selectable, Identifiable, Associations)]
173#[diesel(table_name = schema::retry_request)]
174#[diesel(belongs_to(JudgeSubmissionRow, foreign_key = request))]
175#[diesel(belongs_to(DownloadableFileRow, foreign_key = failed_download_result))]
176pub struct RetryRequestRow {
177 pub id: i32,
178 pub request: i32,
179 pub retry_attempts: i32,
180 pub failed_download_result: i32,
181}
182
183#[derive(Debug, Clone, Insertable)]
184#[diesel(table_name = schema::retry_request)]
185pub struct NewRetryRequestRow {
186 pub request: i32,
187 pub retry_attempts: i32,
188 pub failed_download_result: i32,
189}
190
191#[derive(Debug, Clone)]
192pub struct RetryRequestJoined {
193 pub row: RetryRequestRow,
194 pub request: JudgeSubmissionJoined,
195 pub failed_download_result: DownloadableFileRow,
196}
197
198impl From<RetryRequestJoined> for RetryRequest {
199 fn from(value: RetryRequestJoined) -> Self {
200 Self {
201 request: value.request.into(),
202 retry_attempts: value.row.retry_attempts as u8,
203 failed_download_result: value.failed_download_result.into(),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsExpression, FromSqlRow)]
209#[diesel(sql_type = sql_types::RejectReason)]
210pub enum RejectReasonRow {
211 AlreadyDownloaded,
212 LowScore,
213 NotMusic,
214 Banned,
215 AbandonedAttemptingSearch,
216}
217
218impl From<&RuntimeRejectReason> for RejectReasonRow {
219 fn from(value: &RuntimeRejectReason) -> Self {
220 match value {
221 RuntimeRejectReason::AlreadyDownloaded => Self::AlreadyDownloaded,
222 RuntimeRejectReason::LowScore(_) => Self::LowScore,
223 RuntimeRejectReason::NotMusic(_) => Self::NotMusic,
224 RuntimeRejectReason::Banned(_) => Self::Banned,
225 RuntimeRejectReason::AbandonedAttemptingSearch => Self::AbandonedAttemptingSearch,
226 }
227 }
228}
229
230impl From<RuntimeRejectReason> for RejectReasonRow {
231 fn from(value: RuntimeRejectReason) -> Self {
232 Self::from(&value)
233 }
234}
235
236impl From<RejectReasonRow> for RuntimeRejectReason {
237 fn from(value: RejectReasonRow) -> Self {
238 match value {
239 RejectReasonRow::AlreadyDownloaded => RuntimeRejectReason::AlreadyDownloaded,
240 RejectReasonRow::LowScore => RuntimeRejectReason::LowScore(0.0),
242 RejectReasonRow::NotMusic => RuntimeRejectReason::NotMusic(String::new()),
243 RejectReasonRow::Banned => RuntimeRejectReason::Banned(String::new()),
244 RejectReasonRow::AbandonedAttemptingSearch => {
245 RuntimeRejectReason::AbandonedAttemptingSearch
246 }
247 }
248 }
249}
250
251impl ToSql<sql_types::RejectReason, Pg> for RejectReasonRow {
252 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
253 let value = match self {
254 RejectReasonRow::AlreadyDownloaded => b"already_downloaded".as_slice(),
255 RejectReasonRow::LowScore => b"low_score".as_slice(),
256 RejectReasonRow::NotMusic => b"not_music".as_slice(),
257 RejectReasonRow::Banned => b"banned".as_slice(),
258 RejectReasonRow::AbandonedAttemptingSearch => b"abandoned_attempting_search".as_slice(),
259 };
260 out.write_all(value)?;
261 Ok(IsNull::No)
262 }
263}
264
265impl FromSql<sql_types::RejectReason, Pg> for RejectReasonRow {
266 fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
267 match bytes.as_bytes() {
268 b"already_downloaded" => Ok(Self::AlreadyDownloaded),
269 b"low_score" => Ok(Self::LowScore),
270 b"not_music" => Ok(Self::NotMusic),
271 b"banned" => Ok(Self::Banned),
272 b"abandoned_attempting_search" => Ok(Self::AbandonedAttemptingSearch),
273 unknown => Err(format!(
274 "Unrecognized reject_reason value: {}",
275 String::from_utf8_lossy(unknown)
276 )
277 .into()),
278 }
279 }
280}
281
282#[derive(Debug, Clone, Queryable, Selectable, Identifiable, Associations)]
283#[diesel(table_name = schema::rejected_track)]
284#[diesel(belongs_to(JudgeSubmissionRow, foreign_key = track))]
285pub struct RejectedTrackRow {
286 pub id: i32,
287 pub track: i32,
288 pub reason: RejectReasonRow,
289}
290
291#[derive(Debug, Clone, Insertable)]
292#[diesel(table_name = schema::rejected_track)]
293pub struct NewRejectedTrackRow {
294 pub track: i32,
295 pub reason: RejectReasonRow,
296 pub value: Option<String>,
297}
298
299impl NewRejectedTrackRow {
300 pub fn from_runtime(track_id: i32, value: &RejectedTrack) -> Self {
301 let (_, reason) = value.parts();
302 Self {
303 track: track_id,
304 reason: reason.into(),
305 value: match reason {
306 RuntimeRejectReason::AlreadyDownloaded
307 | RuntimeRejectReason::AbandonedAttemptingSearch => None,
308 RuntimeRejectReason::LowScore(score) => Some(format!("{score}")),
309 RuntimeRejectReason::NotMusic(filename) => Some(filename.to_owned()),
310 RuntimeRejectReason::Banned(track_id) => Some(track_id.to_owned()),
311 },
312 }
313 }
314}
315
316#[derive(Debug, Clone)]
317pub struct RejectedTrackJoined {
318 pub row: RejectedTrackRow,
319 pub track: JudgeSubmissionJoined,
320}
321
322impl From<RejectedTrackJoined> for RejectedTrack {
323 fn from(value: RejectedTrackJoined) -> Self {
324 RejectedTrack::new(value.track.into(), value.row.reason.into())
325 }
326}