Skip to main content

usenet_dl/db/
duplicates.rs

1//! Duplicate detection queries.
2
3use crate::error::DatabaseError;
4use crate::{Error, Result};
5
6use super::{Database, Download};
7
8impl Database {
9    /// Find a download by NZB hash
10    ///
11    /// This is the most reliable duplicate detection method as it compares
12    /// the actual NZB file content (via SHA-256 hash).
13    pub async fn find_by_nzb_hash(&self, nzb_hash: &str) -> Result<Option<Download>> {
14        let row = sqlx::query_as::<_, Download>(
15            r#"
16            SELECT
17                id, name, nzb_path, nzb_meta_name, nzb_hash, job_name,
18                category, destination, post_process, priority, status,
19                progress, speed_bps, size_bytes, downloaded_bytes,
20                error_message, created_at, started_at, completed_at,
21                direct_unpack_state, direct_unpack_extracted_count
22            FROM downloads
23            WHERE nzb_hash = ?
24            LIMIT 1
25            "#,
26        )
27        .bind(nzb_hash)
28        .fetch_optional(&self.pool)
29        .await
30        .map_err(|e| {
31            Error::Database(DatabaseError::QueryFailed(format!(
32                "Failed to find download by nzb_hash: {}",
33                e
34            )))
35        })?;
36
37        Ok(row)
38    }
39
40    /// Find a download by exact name match
41    ///
42    /// This is useful for detecting duplicates when the NZB filename is used
43    /// as the download name. Case-sensitive match.
44    pub async fn find_by_name(&self, name: &str) -> Result<Option<Download>> {
45        let row = sqlx::query_as::<_, Download>(
46            r#"
47            SELECT
48                id, name, nzb_path, nzb_meta_name, nzb_hash, job_name,
49                category, destination, post_process, priority, status,
50                progress, speed_bps, size_bytes, downloaded_bytes,
51                error_message, created_at, started_at, completed_at,
52                direct_unpack_state, direct_unpack_extracted_count
53            FROM downloads
54            WHERE name = ?
55            LIMIT 1
56            "#,
57        )
58        .bind(name)
59        .fetch_optional(&self.pool)
60        .await
61        .map_err(|e| {
62            Error::Database(DatabaseError::QueryFailed(format!(
63                "Failed to find download by name: {}",
64                e
65            )))
66        })?;
67
68        Ok(row)
69    }
70
71    /// Find a download by job name
72    ///
73    /// This detects duplicates using the deobfuscated job name, which catches
74    /// cases where the same content is uploaded with different NZB filenames.
75    pub async fn find_by_job_name(&self, job_name: &str) -> Result<Option<Download>> {
76        let row = sqlx::query_as::<_, Download>(
77            r#"
78            SELECT
79                id, name, nzb_path, nzb_meta_name, nzb_hash, job_name,
80                category, destination, post_process, priority, status,
81                progress, speed_bps, size_bytes, downloaded_bytes,
82                error_message, created_at, started_at, completed_at,
83                direct_unpack_state, direct_unpack_extracted_count
84            FROM downloads
85            WHERE job_name = ?
86            LIMIT 1
87            "#,
88        )
89        .bind(job_name)
90        .fetch_optional(&self.pool)
91        .await
92        .map_err(|e| {
93            Error::Database(DatabaseError::QueryFailed(format!(
94                "Failed to find download by job_name: {}",
95                e
96            )))
97        })?;
98
99        Ok(row)
100    }
101}