veridian_kernel/pkg/
statistics.rs1#[cfg(feature = "alloc")]
14extern crate alloc;
15
16#[cfg(feature = "alloc")]
17use alloc::{collections::BTreeMap, string::String, vec::Vec};
18
19#[cfg(feature = "alloc")]
20use super::{PackageMetadata, Version};
21
22#[cfg(feature = "alloc")]
28#[derive(Debug, Clone)]
29pub struct PackageStats {
30 pub install_count: u64,
32 pub last_installed: u64,
34 pub last_updated: u64,
36 pub total_downloads: u64,
38}
39
40#[cfg(feature = "alloc")]
41impl PackageStats {
42 pub fn new() -> Self {
44 Self {
45 install_count: 0,
46 last_installed: 0,
47 last_updated: 0,
48 total_downloads: 0,
49 }
50 }
51}
52
53#[cfg(feature = "alloc")]
54impl Default for PackageStats {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60#[cfg(feature = "alloc")]
62pub struct StatsCollector {
63 stats: BTreeMap<String, PackageStats>,
65}
66
67#[cfg(feature = "alloc")]
68impl StatsCollector {
69 pub fn new() -> Self {
71 Self {
72 stats: BTreeMap::new(),
73 }
74 }
75
76 pub fn record_install(&mut self, package_name: &str, timestamp: u64) {
78 let entry = self
79 .stats
80 .entry(String::from(package_name))
81 .or_insert_with(PackageStats::new);
82 entry.install_count += 1;
83 entry.last_installed = timestamp;
84 }
85
86 pub fn record_update(&mut self, package_name: &str, timestamp: u64) {
88 let entry = self
89 .stats
90 .entry(String::from(package_name))
91 .or_insert_with(PackageStats::new);
92 entry.last_updated = timestamp;
93 }
94
95 pub fn record_download(&mut self, package_name: &str) {
97 let entry = self
98 .stats
99 .entry(String::from(package_name))
100 .or_insert_with(PackageStats::new);
101 entry.total_downloads += 1;
102 }
103
104 pub fn get_stats(&self, package_name: &str) -> Option<&PackageStats> {
106 self.stats.get(package_name)
107 }
108
109 pub fn get_most_installed(&self, n: usize) -> Vec<(&str, u64)> {
112 let mut entries: Vec<(&str, u64)> = self
113 .stats
114 .iter()
115 .map(|(name, s)| (name.as_str(), s.install_count))
116 .collect();
117 entries.sort_by(|a, b| b.1.cmp(&a.1));
118 entries.truncate(n);
119 entries
120 }
121
122 pub fn total_packages(&self) -> usize {
124 self.stats.len()
125 }
126}
127
128#[cfg(feature = "alloc")]
129impl Default for StatsCollector {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135#[cfg(feature = "alloc")]
141#[derive(Debug, Clone)]
142pub struct UpdateNotification {
143 pub package: String,
145 pub current_version: Version,
147 pub available_version: Version,
149 pub is_security: bool,
151 pub changelog: String,
153}
154
155#[cfg(feature = "alloc")]
161pub fn check_for_updates(
162 installed: &[PackageMetadata],
163 available: &[PackageMetadata],
164) -> Vec<UpdateNotification> {
165 let mut notifications = Vec::new();
166
167 let mut available_map: BTreeMap<&str, &PackageMetadata> = BTreeMap::new();
170 for pkg in available {
171 let entry = available_map.entry(pkg.name.as_str()).or_insert(pkg);
172 if pkg.version > entry.version {
173 *entry = pkg;
174 }
175 }
176
177 for inst in installed {
178 if let Some(avail) = available_map.get(inst.name.as_str()) {
179 if avail.version > inst.version {
180 let desc_lower = avail.description.as_bytes();
182 let is_security = contains_ignore_case(desc_lower, b"security");
183
184 notifications.push(UpdateNotification {
185 package: inst.name.clone(),
186 current_version: inst.version.clone(),
187 available_version: avail.version.clone(),
188 is_security,
189 changelog: avail.description.clone(),
190 });
191 }
192 }
193 }
194
195 notifications
196}
197
198#[cfg(feature = "alloc")]
200fn contains_ignore_case(haystack: &[u8], needle: &[u8]) -> bool {
201 if needle.is_empty() || needle.len() > haystack.len() {
202 return needle.is_empty();
203 }
204 for i in 0..=(haystack.len() - needle.len()) {
205 let mut matched = true;
206 for j in 0..needle.len() {
207 if to_ascii_lower(haystack[i + j]) != to_ascii_lower(needle[j]) {
208 matched = false;
209 break;
210 }
211 }
212 if matched {
213 return true;
214 }
215 }
216 false
217}
218
219#[cfg(feature = "alloc")]
221fn to_ascii_lower(b: u8) -> u8 {
222 if b.is_ascii_uppercase() {
223 b + 32
224 } else {
225 b
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
235pub enum AdvisorySeverity {
236 Low,
238 Medium,
240 High,
242 Critical,
244}
245
246#[cfg(feature = "alloc")]
249#[derive(Debug, Clone)]
250pub struct SecurityAdvisory {
251 pub id: String,
253 pub affected_packages: Vec<String>,
255 pub severity: AdvisorySeverity,
257 pub description: String,
259 pub fixed_version: Option<Version>,
261}
262
263#[cfg(feature = "alloc")]
268pub fn check_advisories(
269 installed: &[PackageMetadata],
270 advisories: &[SecurityAdvisory],
271) -> Vec<SecurityAdvisory> {
272 let installed_names: Vec<&str> = installed.iter().map(|p| p.name.as_str()).collect();
273
274 advisories
275 .iter()
276 .filter(|adv| {
277 adv.affected_packages
278 .iter()
279 .any(|name| installed_names.contains(&name.as_str()))
280 })
281 .cloned()
282 .collect()
283}