diff --git a/assets/app.js b/assets/app.js
new file mode 100644
index 0000000..8841630
--- /dev/null
+++ b/assets/app.js
@@ -0,0 +1,232 @@
+// QuickSearch Application JavaScript
+
+// Enhanced UI interactions
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('QuickSearch UI loaded');
+
+ // Add loading states to buttons
+ function addLoadingState(button, originalText) {
+ button.disabled = true;
+ button.innerHTML = '' + originalText;
+ }
+
+ function removeLoadingState(button, originalText) {
+ button.disabled = false;
+ button.innerHTML = originalText;
+ }
+
+ // Enhanced form interactions
+ const forms = document.querySelectorAll('form');
+ forms.forEach(form => {
+ form.addEventListener('submit', function(e) {
+ const submitButton = form.querySelector('button[type="submit"]');
+ if (submitButton) {
+ addLoadingState(submitButton, submitButton.textContent);
+ }
+ });
+ });
+
+ // Keyboard shortcuts
+ document.addEventListener('keydown', function(e) {
+ // Ctrl+F to focus search
+ if (e.ctrlKey && e.key === 'f') {
+ e.preventDefault();
+ const searchInput = document.querySelector('input[type="text"]');
+ if (searchInput) {
+ searchInput.focus();
+ searchInput.select();
+ }
+ }
+
+ // Escape to clear search
+ if (e.key === 'Escape') {
+ const searchInput = document.querySelector('input[type="text"]');
+ if (searchInput && searchInput === document.activeElement) {
+ searchInput.value = '';
+ searchInput.blur();
+ }
+ }
+ });
+
+ // Enhanced table interactions
+ function enhanceTable(table) {
+ // Add click-to-copy functionality for table cells
+ const cells = table.querySelectorAll('td');
+ cells.forEach(cell => {
+ cell.addEventListener('click', function() {
+ const text = cell.textContent.trim();
+ if (text && navigator.clipboard) {
+ navigator.clipboard.writeText(text).then(() => {
+ // Visual feedback
+ cell.style.backgroundColor = '#4CAF50';
+ cell.style.color = 'white';
+ setTimeout(() => {
+ cell.style.backgroundColor = '';
+ cell.style.color = '';
+ }, 200);
+ });
+ }
+ });
+ });
+
+ // Add sortable columns (basic implementation)
+ const headers = table.querySelectorAll('th');
+ headers.forEach((header, index) => {
+ header.style.cursor = 'pointer';
+ header.addEventListener('click', () => sortTable(table, index));
+ });
+ }
+
+ // Simple table sorting
+ function sortTable(table, columnIndex) {
+ const tbody = table.querySelector('tbody');
+ const rows = Array.from(tbody.querySelectorAll('tr'));
+
+ rows.sort((a, b) => {
+ const aVal = a.cells[columnIndex]?.textContent.trim() || '';
+ const bVal = b.cells[columnIndex]?.textContent.trim() || '';
+
+ // Try numeric sort first
+ const aNum = parseFloat(aVal);
+ const bNum = parseFloat(bVal);
+
+ if (!isNaN(aNum) && !isNaN(bNum)) {
+ return aNum - bNum;
+ }
+
+ // Fall back to string sort
+ return aVal.localeCompare(bVal);
+ });
+
+ // Clear tbody and re-append sorted rows
+ tbody.innerHTML = '';
+ rows.forEach(row => tbody.appendChild(row));
+ }
+
+ // Auto-enhance any tables that appear
+ const observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ mutation.addedNodes.forEach(function(node) {
+ if (node.nodeType === 1) { // Element node
+ const tables = node.querySelectorAll ? node.querySelectorAll('table') : [];
+ tables.forEach(enhanceTable);
+
+ if (node.tagName === 'TABLE') {
+ enhanceTable(node);
+ }
+ }
+ });
+ });
+ });
+
+ observer.observe(document.body, { childList: true, subtree: true });
+
+ // Enhance existing tables
+ document.querySelectorAll('table').forEach(enhanceTable);
+});
+
+// Utility functions for Rust integration
+window.QuickSearch = {
+ // Function to show toast notifications
+ showToast: function(message, type = 'info') {
+ const toast = document.createElement('div');
+ toast.className = `toast toast-${type}`;
+ toast.textContent = message;
+ toast.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ padding: 12px 20px;
+ border-radius: 6px;
+ color: white;
+ font-weight: 600;
+ z-index: 2000;
+ animation: slideIn 0.3s ease;
+ `;
+
+ // Set background based on type
+ const colors = {
+ info: '#2196F3',
+ success: '#4CAF50',
+ warning: '#FF9800',
+ error: '#f44336'
+ };
+ toast.style.backgroundColor = colors[type] || colors.info;
+
+ document.body.appendChild(toast);
+
+ setTimeout(() => {
+ toast.style.animation = 'slideOut 0.3s ease';
+ setTimeout(() => {
+ document.body.removeChild(toast);
+ }, 300);
+ }, 3000);
+ },
+
+ // Function to update status display
+ updateStatus: function(status) {
+ const statusDisplay = document.querySelector('.status-display');
+ if (statusDisplay) {
+ statusDisplay.textContent = status;
+ }
+ },
+
+ // Function to highlight search terms in results
+ highlightSearchTerms: function(searchTerm, container) {
+ if (!searchTerm || !container) return;
+
+ const walker = document.createTreeWalker(
+ container,
+ NodeFilter.SHOW_TEXT,
+ null,
+ false
+ );
+
+ const textNodes = [];
+ let node;
+ while (node = walker.nextNode()) {
+ textNodes.push(node);
+ }
+
+ textNodes.forEach(textNode => {
+ const parent = textNode.parentNode;
+ if (parent.tagName === 'B') return; // Skip already highlighted
+
+ const text = textNode.textContent;
+ const regex = new RegExp(`(${searchTerm})`, 'gi');
+
+ if (regex.test(text)) {
+ const highlightedHTML = text.replace(regex, '$1');
+ const wrapper = document.createElement('span');
+ wrapper.innerHTML = highlightedHTML;
+ parent.replaceChild(wrapper, textNode);
+ }
+ });
+ }
+};
+
+// Add custom CSS for toasts and animations
+const style = document.createElement('style');
+style.textContent = `
+ @keyframes slideIn {
+ from { transform: translateX(100%); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+ }
+
+ @keyframes slideOut {
+ from { transform: translateX(0); opacity: 1; }
+ to { transform: translateX(100%); opacity: 0; }
+ }
+
+ mark {
+ background: #ffeb3b;
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-weight: bold;
+ }
+
+ .toast {
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
+ }
+`;
+document.head.appendChild(style);
diff --git a/assets/index.html b/assets/index.html
new file mode 100644
index 0000000..5bbae84
--- /dev/null
+++ b/assets/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ QuickSearch - File Indexer & Search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/styles.css b/assets/styles.css
new file mode 100644
index 0000000..3a68b19
--- /dev/null
+++ b/assets/styles.css
@@ -0,0 +1,299 @@
+/* QuickSearch Application Styles */
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ margin: 0;
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+}
+
+.app-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
+ overflow: hidden;
+}
+
+.app-header {
+ background: linear-gradient(90deg, #4CAF50 0%, #45a049 100%);
+ color: white;
+ padding: 20px;
+ text-align: center;
+}
+
+.app-content {
+ padding: 20px;
+}
+
+.section {
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+ border-left: 4px solid #4CAF50;
+}
+
+.form-control {
+ width: 100%;
+ padding: 12px;
+ border: 2px solid #e0e0e0;
+ border-radius: 6px;
+ font-size: 14px;
+ transition: border-color 0.3s ease;
+ box-sizing: border-box;
+}
+
+.form-control:focus {
+ outline: none;
+ border-color: #4CAF50;
+ box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
+}
+
+.btn {
+ padding: 12px 24px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ transition: all 0.3s ease;
+ margin-right: 10px;
+ display: inline-block;
+}
+
+.btn-primary {
+ background: linear-gradient(90deg, #4CAF50 0%, #45a049 100%);
+ color: white;
+}
+
+.btn-primary:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
+}
+
+.btn-danger {
+ background: linear-gradient(90deg, #f44336 0%, #d32f2f 100%);
+ color: white;
+}
+
+.btn-danger:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(244, 67, 54, 0.3);
+}
+
+.btn-info {
+ background: linear-gradient(90deg, #2196F3 0%, #1976D2 100%);
+ color: white;
+}
+
+.btn-info:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);
+}
+
+.btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none !important;
+ box-shadow: none !important;
+}
+
+.status-display {
+ background: #1a1a1a;
+ color: #00ff00;
+ padding: 15px;
+ border-radius: 6px;
+ font-family: 'Courier New', monospace;
+ white-space: pre-wrap;
+ font-size: 13px;
+ border: 1px solid #333;
+ overflow-x: auto;
+}
+
+.search-results {
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ margin-top: 20px;
+}
+
+.results-table {
+ max-height: 400px;
+ overflow: auto;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+}
+
+.results-table table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.results-table th {
+ background: #4CAF50;
+ color: white;
+ padding: 12px;
+ text-align: left;
+ font-weight: 600;
+ position: sticky;
+ top: 0;
+ border-bottom: 2px solid #45a049;
+}
+
+.results-table td {
+ padding: 10px 12px;
+ border-bottom: 1px solid #e0e0e0;
+ word-break: break-all;
+}
+
+.results-table tbody tr:hover {
+ background: #f5f5f5;
+}
+
+.results-table tbody tr:nth-child(even) {
+ background: #fafafa;
+}
+
+.results-table tbody tr:nth-child(even):hover {
+ background: #f0f0f0;
+}
+
+/* Clickable path cells */
+.path-cell.clickable {
+ cursor: pointer;
+ color: #1976d2;
+ font-weight: 500;
+ transition: all 0.2s ease;
+ position: relative;
+}
+
+.path-cell.clickable:hover {
+ background: #e3f2fd !important;
+ color: #0d47a1;
+ text-decoration: underline;
+}
+
+.path-cell.clickable:active {
+ background: #bbdefb !important;
+ transform: scale(0.98);
+}
+
+
+.error-message {
+ background: #ffebee;
+ color: #c62828;
+ padding: 12px;
+ border-radius: 6px;
+ border-left: 4px solid #f44336;
+ margin: 10px 0;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.5);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal-dialog {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ max-width: 600px;
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
+ animation: modalSlideIn 0.3s ease;
+}
+
+@keyframes modalSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+h1, h2, h3 {
+ margin-top: 0;
+ color: #333;
+}
+
+label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+ color: #555;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+/* Highlight matched text in search results */
+.results-table b {
+ background: #ffeb3b;
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-weight: bold;
+ color: #333;
+}
+
+/* Loading spinner */
+.loading {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 3px solid #f3f3f3;
+ border-top: 3px solid #4CAF50;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-right: 10px;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ body {
+ padding: 10px;
+ }
+
+ .app-container {
+ border-radius: 8px;
+ }
+
+ .app-content {
+ padding: 15px;
+ }
+
+ .form-control {
+ font-size: 16px; /* Prevents zoom on iOS */
+ }
+
+ .results-table {
+ font-size: 12px;
+ }
+
+ .btn {
+ width: 100%;
+ margin-bottom: 10px;
+ margin-right: 0;
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d914d42
--- /dev/null
+++ b/index.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+ QuickSearch
+
+
+
+
+
+
diff --git a/src/file_handling.rs b/src/file_handling.rs
index dfdcde5..fb54444 100644
--- a/src/file_handling.rs
+++ b/src/file_handling.rs
@@ -85,7 +85,15 @@ pub fn analyze_files_for_batch_update(
};
let fpath = match entry.path().canonicalize() {
- Ok(fp) => fp.to_string_lossy().to_string(),
+ Ok(fp) => {
+ let path_str = fp.to_string_lossy().to_string();
+ // Remove Windows UNC prefix \\?\
+ if path_str.starts_with("\\\\?\\") {
+ path_str[4..].to_string()
+ } else {
+ path_str
+ }
+ },
Err(_) => continue,
};
@@ -205,7 +213,15 @@ pub fn process_batch_updates_files_only(
}
let fpath = match entry.path().canonicalize() {
- Ok(fp) => fp.into_os_string(),
+ Ok(fp) => {
+ let path_str = fp.to_string_lossy().to_string();
+ // Remove Windows UNC prefix \\?\
+ if path_str.starts_with("\\\\?\\") {
+ std::ffi::OsString::from(&path_str[4..])
+ } else {
+ fp.into_os_string()
+ }
+ },
Err(_) => continue,
};
@@ -299,7 +315,15 @@ pub fn process_batch_inserts_files_only(
}
let fpath = match entry.path().canonicalize() {
- Ok(fp) => fp.into_os_string(),
+ Ok(fp) => {
+ let path_str = fp.to_string_lossy().to_string();
+ // Remove Windows UNC prefix \\?\
+ if path_str.starts_with("\\\\?\\") {
+ std::ffi::OsString::from(&path_str[4..])
+ } else {
+ fp.into_os_string()
+ }
+ },
Err(_) => continue,
};
@@ -377,7 +401,8 @@ pub fn process_text_indexing(
// Check stop flag
if *stop_flag.lock().unwrap() {
- drop(tx);
+ // Commit current transaction before stopping
+ let _ = tx.commit();
drop(conn);
return Ok(());
}
@@ -405,7 +430,7 @@ pub fn process_text_indexing(
let trimmed_file_string = safe_truncate_string(&file_string, config.processing.maximum_text_size);
Some(trimmed_file_string)
}
- Err(e) => {
+ Err(_e) => {
// eprintln!("Warning: Failed to read plaintext file {}: {}", fpath, e);
None
}
diff --git a/src/frontend.rs b/src/frontend.rs
index 15d9e4f..780c800 100644
--- a/src/frontend.rs
+++ b/src/frontend.rs
@@ -201,22 +201,25 @@ pub fn App(props: AppProps) -> Element {
rsx! {
div {
- style: "padding: 20px; font-family: Arial, sans-serif;",
+ class: "app-container",
- h1 { "QuickSearch File Indexer" }
+ div {
+ class: "app-header",
+ h1 { "QuickSearch File Indexer" }
+ }
+
+ div {
+ class: "app-content",
div {
- style: "margin-bottom: 20px;",
+ class: "section",
h2 { "Indexing Controls" }
div {
- style: "margin-bottom: 10px;",
- label {
- style: "display: block; margin-bottom: 5px;",
- "Path to index:"
- }
+ class: "form-group",
+ label { "Path to index:" }
input {
- style: "width: 400px; padding: 5px;",
+ class: "form-control",
r#type: "text",
value: "{indexing_path}",
oninput: move |evt| indexing_path.set(evt.value())
@@ -224,13 +227,10 @@ pub fn App(props: AppProps) -> Element {
}
div {
- style: "margin-bottom: 10px;",
- label {
- style: "display: block; margin-bottom: 5px;",
- "Database path:"
- }
+ class: "form-group",
+ label { "Database path:" }
input {
- style: "width: 400px; padding: 5px;",
+ class: "form-control",
r#type: "text",
value: "{db_path}",
oninput: move |evt| db_path.set(evt.value())
@@ -238,9 +238,9 @@ pub fn App(props: AppProps) -> Element {
}
div {
- style: "margin-bottom: 20px;",
+ class: "form-group",
button {
- style: "margin-right: 10px; padding: 10px 20px; background-color: #4CAF50; color: white; border: none; cursor: pointer;",
+ class: "btn btn-primary",
onclick: move |_| {
let service = indexing_service_for_start.clone();
let config = config_for_start.clone();
@@ -266,7 +266,7 @@ pub fn App(props: AppProps) -> Element {
"Start Indexing"
}
button {
- style: "padding: 10px 20px; background-color: #f44336; color: white; border: none; cursor: pointer;",
+ class: "btn btn-danger",
onclick: move |_| {
let _ = indexing_service_for_stop.stop_indexing();
},
@@ -276,9 +276,10 @@ pub fn App(props: AppProps) -> Element {
}
div {
+ class: "section",
h2 { "Status" }
pre {
- style: "background-color: #f5f5f5; padding: 10px; border-radius: 5px; font-family: monospace;",
+ class: "status-display",
"{status_text}"
}
}
@@ -287,14 +288,16 @@ pub fn App(props: AppProps) -> Element {
indexing_service: props.indexing_service.clone(),
db_path: db_path().clone()
}
+
+ } // Close app-content
}
// Configuration validation dialog
if show_config_dialog() {
div {
- style: "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000;",
+ class: "modal-backdrop",
div {
- style: "background-color: white; padding: 30px; border-radius: 10px; max-width: 600px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);",
+ class: "modal-dialog",
h3 {
style: "margin-top: 0; color: #d32f2f;",
"⚠️ Configuration Changes Detected"
diff --git a/src/indexing.rs b/src/indexing.rs
index b003cbb..cb99fc2 100644
--- a/src/indexing.rs
+++ b/src/indexing.rs
@@ -1,6 +1,7 @@
use std::sync::{Arc, Mutex, mpsc};
use std::thread;
use std::time::Instant;
+use std::process::Command;
use walkdir::WalkDir;
use rusqlite::{Connection, params};
@@ -163,10 +164,25 @@ impl IndexingService {
/// Execute a search query against the database
pub fn execute_search(&self, db_path: &str, query: &str) -> Result, String> {
let conn = Connection::open(db_path)
- .map_err(|e| format!("Failed to open database: {}", e))?;
+ .map_err(|e| {
+ if e.to_string().contains("corrupt") || e.to_string().contains("malformed") {
+ format!("DATABASE_CORRUPTED: {}", e)
+ } else {
+ format!("Failed to open database: {}", e)
+ }
+ })?;
let mut stmt = conn.prepare(query)
- .map_err(|e| format!("Failed to prepare query: {}", e))?;
+ .map_err(|e| {
+ let error_msg = e.to_string();
+ if error_msg.contains("malformed") || error_msg.contains("corrupt") || error_msg.contains("database disk image is malformed") {
+ format!("DATABASE_CORRUPTED: {}", error_msg)
+ } else if error_msg.contains("fts5: syntax error") {
+ format!("Search syntax error: The search term contains characters that cannot be processed. Please try a simpler search term.")
+ } else {
+ format!("Failed to prepare query: {}", error_msg)
+ }
+ })?;
let column_count = stmt.column_count();
let column_names: Vec = (0..column_count)
@@ -187,13 +203,31 @@ impl IndexingService {
}
Ok(SearchResultRow { values })
})
- .map_err(|e| format!("Failed to execute query: {}", e))?;
+ .map_err(|e| {
+ let error_msg = e.to_string();
+ if error_msg.contains("malformed") || error_msg.contains("corrupt") || error_msg.contains("database disk image is malformed") {
+ format!("DATABASE_CORRUPTED: {}", error_msg)
+ } else if error_msg.contains("fts5: syntax error") {
+ format!("Search syntax error: The search term contains characters that cannot be processed. Please try a simpler search term.")
+ } else {
+ format!("Failed to execute query: {}", error_msg)
+ }
+ })?;
let mut results = Vec::new();
for row in rows {
match row {
Ok(search_row) => results.push(search_row),
- Err(e) => return Err(format!("Error reading row: {}", e)),
+ Err(e) => {
+ let error_msg = e.to_string();
+ if error_msg.contains("malformed") || error_msg.contains("corrupt") || error_msg.contains("database disk image is malformed") {
+ return Err(format!("DATABASE_CORRUPTED: {}", error_msg));
+ } else if error_msg.contains("fts5: syntax error") {
+ return Err(format!("Search syntax error: The search term contains characters that cannot be processed. Please try a simpler search term."));
+ } else {
+ return Err(format!("Error reading row: {}", error_msg));
+ }
+ }
}
}
@@ -203,6 +237,98 @@ impl IndexingService {
}])
}
+ /// Open file explorer to the directory containing the specified file path
+ pub fn open_file_explorer(&self, file_path: &str) -> Result<(), String> {
+ #[cfg(windows)]
+ {
+ Command::new("explorer")
+ .arg("/select,")
+ .arg(file_path)
+ .spawn()
+ .map_err(|e| format!("Failed to open file explorer: {}", e))?;
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ Command::new("open")
+ .arg("-R")
+ .arg(file_path)
+ .spawn()
+ .map_err(|e| format!("Failed to open file explorer: {}", e))?;
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ let path = std::path::Path::new(file_path);
+ let dir_path = if path.is_file() {
+ path.parent().unwrap_or(path)
+ } else {
+ path
+ };
+
+ // Try different file managers
+ let managers = ["xdg-open", "nautilus", "dolphin", "thunar", "pcmanfm"];
+ let mut success = false;
+
+ for manager in &managers {
+ if let Ok(_) = Command::new(manager)
+ .arg(dir_path)
+ .spawn() {
+ success = true;
+ break;
+ }
+ }
+
+ if !success {
+ return Err("No suitable file manager found".to_string());
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Clean up UNC prefixes from existing database entries
+ pub fn clean_unc_prefixes(&self, db_path: &str) -> Result<(), String> {
+ let conn = Connection::open(db_path)
+ .map_err(|e| format!("Failed to open database: {}", e))?;
+
+ // Clean UNC prefixes from files table
+ conn.execute(
+ "UPDATE files SET path = SUBSTR(path, 5) WHERE path LIKE '\\\\?\\%'",
+ (),
+ ).map_err(|e| format!("Failed to update files table: {}", e))?;
+
+ // Clean UNC prefixes from searchabletext table
+ conn.execute(
+ "UPDATE searchabletext SET path = SUBSTR(path, 5) WHERE path LIKE '\\\\?\\%'",
+ (),
+ ).map_err(|e| format!("Failed to update searchabletext table: {}", e))?;
+
+ Ok(())
+ }
+
+ /// Check if the database is corrupted or malformed
+ pub fn check_database_health(&self, db_path: &str) -> Result {
+ match Connection::open(db_path) {
+ Ok(conn) => {
+ // Try to run integrity check
+ match conn.prepare("PRAGMA integrity_check") {
+ Ok(mut stmt) => {
+ match stmt.query_row([], |row| {
+ let result: String = row.get(0)?;
+ Ok(result == "ok")
+ }) {
+ Ok(is_ok) => Ok(is_ok),
+ Err(_) => Ok(false)
+ }
+ },
+ Err(_) => Ok(false)
+ }
+ },
+ Err(_) => Ok(false)
+ }
+ }
+
/// Check if configuration changes require index recreation
pub fn check_config_validation(&self, db_path: &str, config: &Config, indexing_path: &str) -> Result