1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct ProductIdentity<'a> {
7 pub vendor: &'a str,
9 pub product: &'a str,
11 pub model: Option<&'a str>,
13 pub product_suffix: &'a str,
15 pub revision: &'a str,
17}
18
19fn product_prefix(word: &str) -> bool {
20 const PREFIXES: &[&str] = &[
21 "BDRE",
22 "BD-RE",
23 "BDRW",
24 "BD-RW",
25 "BDROM",
26 "BD-ROM",
27 "BDDVDRW",
28 "BDDVDW",
29 "DVDRW",
30 "DVD-RW",
31 "DVDROM",
32 "DVD-ROM",
33 "DVDRAM",
34 "DVD-RAM",
35 "DVD+RW",
36 "CDDVDW",
37 "CDRW",
38 "CD-RW",
39 "BD",
40 "DVD",
41 "CD",
42 "ROM",
43 "RW",
44 "R/RW",
45 "DVDR",
46 "BD-R",
47 "DVD+/-RW",
48 "DVD-R/RW",
49 "DUAL",
50 "MULTI",
51 "COMBO",
52 "BD-MLT",
53 "DVD-MLT",
54 "DVD-MULTI",
55 ];
56 PREFIXES
57 .iter()
58 .any(|prefix| prefix.eq_ignore_ascii_case(word))
59 || (word.bytes().any(|b| b == b'x' || b == b'X')
60 && word.bytes().any(|b| b.is_ascii_digit())
61 && word
62 .bytes()
63 .all(|b| b.is_ascii_digit() || b == b'x' || b == b'X'))
64}
65
66impl<'a> ProductIdentity<'a> {
67 pub fn from_fields(vendor: &'a str, product: &'a str, revision: &'a str) -> Self {
69 let mut rest = product.trim();
70 let model = loop {
71 if rest.is_empty() {
72 break None;
73 }
74 let end = rest.find(char::is_whitespace).unwrap_or(rest.len());
75 let word = &rest[..end];
76 rest = rest[end..].trim_start();
77 if !product_prefix(word) {
78 break Some(word);
79 }
80 };
81 Self {
82 vendor,
83 product,
84 model,
85 product_suffix: rest,
86 revision,
87 }
88 }
89
90 pub fn from_id(id: &'a str, revision: &'a str) -> Option<Self> {
93 let (vendor, product) = id.trim().split_once(char::is_whitespace)?;
94 if product_prefix(vendor) {
95 Some(Self::from_fields("", id.trim(), revision))
96 } else {
97 Some(Self::from_fields(vendor, product.trim_start(), revision))
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 #[test]
106 fn product_context_does_not_replace_the_drive_model() {
107 let id = ProductIdentity::from_id("PIONEER BDR-206PA PP-100", "2.02").unwrap();
108 assert_eq!(id.vendor, "PIONEER");
109 assert_eq!(id.model, Some("BDR-206PA"));
110 assert_eq!(id.product_suffix, "PP-100");
111 assert_eq!(id.product, "BDR-206PA PP-100");
112 assert_eq!(
113 ProductIdentity::from_id("PIONEER BDR-206PB PP-100", "2.02")
114 .unwrap()
115 .model,
116 Some("BDR-206PB")
117 );
118 }
119 #[test]
120 fn archived_oem_product_grammars_keep_the_actual_drive_model() {
121 for (raw, model) in [
122 ("AOPEN 8X8 DVD Dual APP", "APP"),
123 ("Optiarc BD ROM BC-5100S", "BC-5100S"),
124 ("PIONEER DVD RW DVR-106D", "DVR-106D"),
125 ("PLEXTOR DVDR PX-608CU", "PX-608CU"),
126 ("PLEXTOR DVDR PX-810SA", "PX-810SA"),
127 ("PLEXTOR BD-R PX-B940SA", "PX-B940SA"),
128 ("Memorex DVD+/-RW True-8X", "True-8X"),
129 ("PIONEER BDE-PR1EP PP100", "BDE-PR1EP"),
130 ("PIONEER BDR-PR1EPA PP100", "BDR-PR1EPA"),
131 ] {
132 assert_eq!(
133 ProductIdentity::from_id(raw, "").unwrap().model,
134 Some(model),
135 "{raw}"
136 );
137 }
138 let missing_vendor = ProductIdentity::from_id("DVD RW DVR-MCC", "").unwrap();
139 assert_eq!(missing_vendor.vendor, "");
140 assert_eq!(missing_vendor.model, Some("DVR-MCC"));
141 }
142
143 #[test]
144 fn media_prefixes_and_variant_suffixes_are_preserved_separately() {
145 for (raw, model, suffix) in [
146 ("BD-RW BDR-212U", "BDR-212U", ""),
147 ("BDRE BU40N", "BU40N", ""),
148 ("BD-RE BH16NS40", "BH16NS40", ""),
149 ("BC-12B1ST b", "BC-12B1ST", "b"),
150 ] {
151 let id = ProductIdentity::from_fields("vendor", raw, "1.00");
152 assert_eq!(id.model, Some(model));
153 assert_eq!(id.product_suffix, suffix);
154 assert_eq!(id.product, raw);
155 }
156 assert_eq!(
157 ProductIdentity::from_fields("vendor", " BD-RW ", "").model,
158 None
159 );
160 assert_eq!(ProductIdentity::from_fields("vendor", "", "").model, None);
161 assert!(ProductIdentity::from_id("missing-separator", "").is_none());
162 }
163}