1use std::error::Error;
2use std::fs::{self, File};
3use std::io::{Read, Write};
4use std::path::PathBuf;
5use flate2::write::{GzEncoder, GzDecoder};
6use lz4::block::{compress, CompressionMode};
7use crate::config::OptimizationConfig;
8
9#[derive(Clone)]
11pub struct Optimizer {
12 config: OptimizationConfig,
13 pub speed_limit: Option<u64>,
14}
15
16impl Optimizer {
17 pub fn new(config: OptimizationConfig) -> Self {
19 let speed_limit = config.speed_limit;
20 Self {
21 config,
22 speed_limit,
23 }
24 }
25
26 #[allow(dead_code)]
32 pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
33 if !self.config.compression {
34 return Ok(data.to_vec());
35 }
36 let compressed = match self.config.compression_level {
37 1..=3 => {
38 let mut encoder = GzEncoder::new(Vec::new(), flate2::Compression::fast());
39 encoder.write_all(data)?;
40 encoder.finish()?
41 }
42 4..=6 => {
43 compress(data, Some(CompressionMode::FAST(0)), true)?
44 }
45 7..=9 => {
46 let mut encoder = brotli::CompressorWriter::new(
47 Vec::new(),
48 self.config.compression_level as usize,
49 4096,
50 22,
51 );
52 encoder.write_all(data)?;
53 encoder.into_inner()
54 }
55 _ => return Ok(data.to_vec()),
56 };
57 Ok(compressed)
58 }
59
60 pub fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
64 if !self.config.compression {
65 return Ok(data.to_vec());
66 }
67 let mut decompressed = Vec::new();
68 if data.starts_with(&[0x1f, 0x8b]) {
69 let mut decoder = GzDecoder::new(Vec::new());
70 decoder.write_all(data)?;
71 decompressed = decoder.finish()?;
72 } else if data.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) {
73 let mut decoder = brotli::Decompressor::new(data, 4096);
74 decoder.read_to_end(&mut decompressed)?;
75 } else {
76 let mut decoder = lz4::Decoder::new(data)?;
77 decoder.read_to_end(&mut decompressed)?;
78 }
79 Ok(decompressed)
80 }
81
82 #[allow(dead_code)]
86 pub fn get_cached_file(&self, url: &str) -> Result<Option<Vec<u8>>, Box<dyn Error>> {
87 if !self.config.cache_enabled {
88 return Ok(None);
89 }
90
91 let _cache_dir = self.config.cache_dir.as_str();
92 let cache_path = self.get_cache_path(url)?;
93 if cache_path.exists() {
94 let mut file = File::open(cache_path)?;
95 let mut contents = Vec::new();
96 file.read_to_end(&mut contents)?;
97 return Ok(Some(contents));
98 }
99 Ok(None)
100 }
101
102 #[allow(dead_code)]
106 pub fn cache_file(&self, url: &str, data: &[u8]) -> Result<(), Box<dyn Error>> {
107 if !self.config.cache_enabled {
108 return Ok(());
109 }
110 let cache_path = self.get_cache_path(url)?;
111 if let Some(parent) = cache_path.parent() {
112 fs::create_dir_all(parent)?;
113 }
114 let mut file = File::create(cache_path)?;
115 file.write_all(data)?;
116 Ok(())
117 }
118
119 #[allow(dead_code)]
123 fn get_cache_path(&self, url: &str) -> Result<PathBuf, Box<dyn Error>> {
124 let mut cache_dir = PathBuf::from(
125 if self.config.cache_dir.is_empty() {
126 "~/.cache/kget".to_string()
127 } else {
128 self.config.cache_dir.clone()
129 }
130 );
131
132 if cache_dir.starts_with("~") {
133 if let Some(home) = dirs::home_dir() {
134 cache_dir = home.join(cache_dir.strip_prefix("~").unwrap());
135 }
136 }
137
138 let mut hash = 0u64;
140 for byte in url.bytes() {
141 hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
142 }
143
144 cache_dir.push(format!("{:x}", hash));
145 Ok(cache_dir)
146 }
147
148 pub fn get_peer_limit(&self) -> usize {
149 self.speed_limit.unwrap_or(50) as usize
150 }
151
152 pub fn is_compression_enabled(&self) -> bool {
153 self.config.compression
154 }
155}