use std::collections::HashMap;
use std::path::PathBuf;
use std::time::SystemTime;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum SyncState {
Clean,
Dirty,
LocalSaved,
ValidationError(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FileType {
PackageDeclaration { schema: String, package: String },
PackageBody { schema: String, package: String },
Function { schema: String, name: String },
Procedure { schema: String, name: String },
}
#[allow(dead_code)]
impl FileType {
pub fn cache_filename(&self) -> String {
match self {
FileType::PackageDeclaration { schema, package } => {
format!("{schema}_{package}_DECLARATION.sql")
}
FileType::PackageBody { schema, package } => {
format!("{schema}_{package}_BODY.sql")
}
FileType::Function { schema, name } => {
format!("{schema}_FUNCTION_{name}.sql")
}
FileType::Procedure { schema, name } => {
format!("{schema}_PROCEDURE_{name}.sql")
}
}
}
pub fn vfs_path(&self) -> String {
match self {
FileType::PackageDeclaration { schema, package } => {
format!("{schema}.{package}.DECLARATION")
}
FileType::PackageBody { schema, package } => {
format!("{schema}.{package}.BODY")
}
FileType::Function { schema, name } => {
format!("{schema}.{name}")
}
FileType::Procedure { schema, name } => {
format!("{schema}.{name}")
}
}
}
pub fn paired_path(&self) -> Option<String> {
match self {
FileType::PackageDeclaration { schema, package } => {
Some(format!("{schema}.{package}.BODY"))
}
FileType::PackageBody { schema, package } => {
Some(format!("{schema}.{package}.DECLARATION"))
}
_ => None,
}
}
pub fn schema(&self) -> &str {
match self {
FileType::PackageDeclaration { schema, .. }
| FileType::PackageBody { schema, .. }
| FileType::Function { schema, .. }
| FileType::Procedure { schema, .. } => schema,
}
}
}
#[allow(dead_code)]
pub struct VirtualFile {
pub path: String,
pub content: String,
pub local_saved: String,
pub db_content: String,
pub sync_state: SyncState,
pub file_type: FileType,
pub cache_path: Option<PathBuf>,
pub last_accessed: SystemTime,
}
#[allow(dead_code)]
impl VirtualFile {
pub fn new(file_type: FileType, db_content: String, cache_dir: Option<&PathBuf>) -> Self {
let path = file_type.vfs_path();
let cache_path = cache_dir.map(|dir| dir.join(file_type.cache_filename()));
Self {
path,
content: db_content.clone(),
local_saved: String::new(),
db_content,
sync_state: SyncState::Clean,
file_type,
cache_path,
last_accessed: SystemTime::now(),
}
}
pub fn update_content(&mut self, content: String) {
self.content = content;
self.last_accessed = SystemTime::now();
let reference = if self.local_saved.is_empty() {
&self.db_content
} else {
&self.local_saved
};
if self.content != *reference {
self.sync_state = SyncState::Dirty;
} else if !self.local_saved.is_empty() && self.local_saved != self.db_content {
self.sync_state = SyncState::LocalSaved;
} else {
self.sync_state = SyncState::Clean;
}
}
pub fn mark_local_saved(&mut self) {
self.local_saved = self.content.clone();
self.sync_state = SyncState::LocalSaved;
}
pub fn mark_compiled(&mut self) {
self.db_content = self.local_saved.clone();
self.sync_state = SyncState::Clean;
}
pub fn mark_error(&mut self, msg: String) {
self.sync_state = SyncState::ValidationError(msg);
}
pub fn touch(&mut self) {
self.last_accessed = SystemTime::now();
}
pub fn is_modified(&self) -> bool {
self.content != self.db_content
}
}
#[allow(dead_code)]
pub struct VirtualFileSystem {
pub files: HashMap<String, VirtualFile>,
pub connection_id: String,
pub cache_dir: Option<PathBuf>,
pub max_cache_files: usize,
}
#[allow(dead_code)]
impl VirtualFileSystem {
pub fn new(connection_id: String, cache_dir: Option<PathBuf>) -> Self {
Self {
files: HashMap::new(),
connection_id,
cache_dir,
max_cache_files: 50,
}
}
pub fn get_or_create(&mut self, file_type: FileType, db_content: String) -> &mut VirtualFile {
let path = file_type.vfs_path();
self.files
.entry(path.clone())
.or_insert_with(|| VirtualFile::new(file_type, db_content, self.cache_dir.as_ref()))
}
pub fn get(&self, path: &str) -> Option<&VirtualFile> {
self.files.get(path)
}
pub fn get_mut(&mut self, path: &str) -> Option<&mut VirtualFile> {
self.files.get_mut(path)
}
pub fn sync_state(&self, path: &str) -> Option<&SyncState> {
self.files.get(path).map(|f| &f.sync_state)
}
pub fn remove(&mut self, path: &str) -> Option<VirtualFile> {
self.files.remove(path)
}
pub fn evict_lru(&mut self) -> Vec<String> {
let mut evicted = Vec::new();
while self.files.len() > self.max_cache_files {
let oldest = self
.files
.iter()
.min_by_key(|(_, f)| f.last_accessed)
.map(|(k, _)| k.clone());
if let Some(key) = oldest {
evicted.push(key.clone());
self.files.remove(&key);
} else {
break;
}
}
evicted
}
pub fn path_for_package_decl(schema: &str, name: &str) -> String {
format!("{schema}.{name}.DECLARATION")
}
pub fn path_for_package_body(schema: &str, name: &str) -> String {
format!("{schema}.{name}.BODY")
}
pub fn path_for_function(schema: &str, name: &str) -> String {
format!("{schema}.{name}")
}
pub fn path_for_procedure(schema: &str, name: &str) -> String {
format!("{schema}.{name}")
}
}