use crate::domain::{DomainError, InstallationRepository};
use async_trait::async_trait;
use std::fs;
use std::path::{Path, PathBuf};
use zip::ZipArchive;
pub struct FileSystemRepository;
impl Default for FileSystemRepository {
fn default() -> Self {
Self::new()
}
}
impl FileSystemRepository {
#[must_use]
pub fn new() -> Self {
Self
}
fn extract_vsix(vsix_data: &[u8], target_dir: &Path) -> Result<(), DomainError> {
let cursor = std::io::Cursor::new(vsix_data);
let mut archive = ZipArchive::new(cursor)
.map_err(|e| DomainError::InvalidExtensionFormat(e.to_string()))?;
for i in 0..archive.len() {
let mut file = archive
.by_index(i)
.map_err(|e| DomainError::IoError(std::io::Error::other(e)))?;
let outpath = target_dir.join(file.name());
if file.name().ends_with('/') {
fs::create_dir_all(&outpath)?;
} else {
if let Some(parent) = outpath.parent() {
fs::create_dir_all(parent)?;
}
let mut outfile = fs::File::create(&outpath)?;
std::io::copy(&mut file, &mut outfile)?;
}
}
Ok(())
}
}
#[async_trait]
impl InstallationRepository for FileSystemRepository {
async fn install_vscode(
&self,
vsix_data: &[u8],
extension_id: &str,
) -> Result<(), DomainError> {
let extensions_dir = self.get_vscode_extensions_dir()?;
let target_dir = extensions_dir.join(extension_id);
if target_dir.exists() {
fs::remove_dir_all(&target_dir)?;
}
fs::create_dir_all(&target_dir)?;
Self::extract_vsix(vsix_data, &target_dir)?;
Ok(())
}
async fn install_cursor(
&self,
vsix_data: &[u8],
extension_id: &str,
) -> Result<(), DomainError> {
let extensions_dir = self.get_cursor_extensions_dir()?;
let target_dir = extensions_dir.join(extension_id);
if target_dir.exists() {
fs::remove_dir_all(&target_dir)?;
}
fs::create_dir_all(&target_dir)?;
Self::extract_vsix(vsix_data, &target_dir)?;
Ok(())
}
fn get_vscode_extensions_dir(&self) -> Result<PathBuf, DomainError> {
let home = dirs::home_dir().ok_or_else(|| {
DomainError::DirectoryNotFound("Home directory not found".to_string())
})?;
let extensions_dir = home.join(".vscode").join("extensions");
if !extensions_dir.exists() {
fs::create_dir_all(&extensions_dir)?;
}
Ok(extensions_dir)
}
fn get_cursor_extensions_dir(&self) -> Result<PathBuf, DomainError> {
let home = dirs::home_dir().ok_or_else(|| {
DomainError::DirectoryNotFound("Home directory not found".to_string())
})?;
let extensions_dir = if cfg!(target_os = "windows") {
home.join("AppData")
.join("Roaming")
.join("Cursor")
.join("User")
.join("extensions")
} else if cfg!(target_os = "macos") {
home.join("Library")
.join("Application Support")
.join("Cursor")
.join("User")
.join("extensions")
} else {
home.join(".config")
.join("Cursor")
.join("User")
.join("extensions")
};
if !extensions_dir.exists() {
fs::create_dir_all(&extensions_dir)?;
}
Ok(extensions_dir)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extensions_directory_paths() {
let repo = FileSystemRepository::new();
let vscode_dir = repo.get_vscode_extensions_dir();
assert!(vscode_dir.is_ok());
let cursor_dir = repo.get_cursor_extensions_dir();
assert!(cursor_dir.is_ok());
}
}