fix: address review issues for custom db path
This commit is contained in:
parent
e5e812310c
commit
88cbd4458a
@ -117,7 +117,9 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create pastebar-data directory:', error)
|
console.error('Failed to create pastebar-data directory:', error)
|
||||||
setOperationError(
|
setOperationError(
|
||||||
t('Failed to create directory. Please check permissions and try again.', { ns: 'settings' })
|
t('Failed to create directory. Please check permissions and try again.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -201,6 +203,12 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
await applyCustomDbPath(selectedPathForChangeDialog, dbOperationForChangeDialog)
|
await applyCustomDbPath(selectedPathForChangeDialog, dbOperationForChangeDialog)
|
||||||
relaunchApp()
|
relaunchApp()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
// Attempt rollback on failure
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore rollback errors
|
||||||
|
}
|
||||||
setOperationError(
|
setOperationError(
|
||||||
error.message ||
|
error.message ||
|
||||||
t('Failed to apply custom database location.', { ns: 'settings' })
|
t('Failed to apply custom database location.', { ns: 'settings' })
|
||||||
@ -287,7 +295,9 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create pastebar-data directory:', error)
|
console.error('Failed to create pastebar-data directory:', error)
|
||||||
setOperationError(
|
setOperationError(
|
||||||
t('Failed to create directory. Please check permissions and try again.', { ns: 'settings' })
|
t('Failed to create directory. Please check permissions and try again.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -317,9 +327,7 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
// Handle applying the initial setup
|
// Handle applying the initial setup
|
||||||
const handleApplySetup = async () => {
|
const handleApplySetup = async () => {
|
||||||
if (!selectedPathForChangeDialog) {
|
if (!selectedPathForChangeDialog) {
|
||||||
setOperationError(
|
setOperationError(t('Please select a directory first.', { ns: 'settings' }))
|
||||||
t('Please select a directory first.', { ns: 'settings' })
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIsApplyingChange(true)
|
setIsApplyingChange(true)
|
||||||
@ -367,6 +375,9 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
await applyCustomDbPath(selectedPathForChangeDialog, dbOperationForChangeDialog)
|
await applyCustomDbPath(selectedPathForChangeDialog, dbOperationForChangeDialog)
|
||||||
relaunchApp()
|
relaunchApp()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
} catch (_) {}
|
||||||
setOperationError(
|
setOperationError(
|
||||||
error.message ||
|
error.message ||
|
||||||
t('Failed to apply custom database location.', { ns: 'settings' })
|
t('Failed to apply custom database location.', { ns: 'settings' })
|
||||||
@ -424,7 +435,9 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create pastebar-data directory:', error)
|
console.error('Failed to create pastebar-data directory:', error)
|
||||||
setOperationError(
|
setOperationError(
|
||||||
t('Failed to create directory. Please check permissions and try again.', { ns: 'settings' })
|
t('Failed to create directory. Please check permissions and try again.', {
|
||||||
|
ns: 'settings',
|
||||||
|
})
|
||||||
)
|
)
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
return false
|
return false
|
||||||
@ -462,9 +475,19 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
await applyCustomDbPath(finalPath, 'none') // 'none' for initial setup
|
try {
|
||||||
relaunchApp()
|
await applyCustomDbPath(finalPath, 'none') // 'none' for initial setup
|
||||||
pathSuccessfullySet = true // Path will be set by store, app restarts
|
relaunchApp()
|
||||||
|
pathSuccessfullySet = true // Path will be set by store, app restarts
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
await revertToDefaultDbPath()
|
||||||
|
} catch (_) {}
|
||||||
|
setOperationError(
|
||||||
|
(error as any).message ||
|
||||||
|
t('Failed to apply custom database location.', { ns: 'settings' })
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -506,7 +529,11 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
return (
|
return (
|
||||||
<Box className="animate-in fade-in max-w-xl mt-4">
|
<Box className="animate-in fade-in max-w-xl mt-4">
|
||||||
<Card
|
<Card
|
||||||
className={`${!customDbPath && !isSetupSectionExpanded ? 'opacity-80 bg-gray-100 dark:bg-gray-900/80' : ''}`}
|
className={`${
|
||||||
|
!customDbPath && !isSetupSectionExpanded
|
||||||
|
? 'opacity-80 bg-gray-100 dark:bg-gray-900/80'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
||||||
<CardTitle className="animate-in fade-in text-md font-medium">
|
<CardTitle className="animate-in fade-in text-md font-medium">
|
||||||
@ -569,7 +596,7 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
) : null}
|
) : null}
|
||||||
{t('Change Custom Data Folder...', { ns: 'settings' })}
|
{t('Change Custom Data Folder...', { ns: 'settings' })}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleRevertFromContent}
|
onClick={handleRevertFromContent}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
@ -685,10 +712,9 @@ export default function CustomDatabaseLocationSettings() {
|
|||||||
{isProcessing && !isApplyingChange ? (
|
{isProcessing && !isApplyingChange ? (
|
||||||
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||||
) : null}
|
) : null}
|
||||||
{selectedPathForChangeDialog
|
{selectedPathForChangeDialog
|
||||||
? t('Change Selected Folder...', { ns: 'settings' })
|
? t('Change Selected Folder...', { ns: 'settings' })
|
||||||
: t('Select Data Folder...', { ns: 'settings' })
|
: t('Select Data Folder...', { ns: 'settings' })}
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{selectedPathForChangeDialog && (
|
{selectedPathForChangeDialog && (
|
||||||
|
@ -15,6 +15,22 @@ use crate::services::user_settings_service::{
|
|||||||
use fs_extra::dir::{copy, CopyOptions};
|
use fs_extra::dir::{copy, CopyOptions};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn rollback_moves(items: &[(PathBuf, PathBuf)]) {
|
||||||
|
for (src, dest) in items.iter().rev() {
|
||||||
|
if dest.exists() {
|
||||||
|
if dest.is_dir() {
|
||||||
|
let mut options = CopyOptions::new();
|
||||||
|
options.overwrite = true;
|
||||||
|
let _ = copy(dest, src, &options);
|
||||||
|
let _ = fs::remove_dir_all(dest);
|
||||||
|
} else {
|
||||||
|
let _ = fs::copy(dest, src);
|
||||||
|
let _ = fs::remove_file(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub enum PathStatus {
|
pub enum PathStatus {
|
||||||
Empty,
|
Empty,
|
||||||
@ -50,9 +66,11 @@ pub fn cmd_check_custom_data_path(path_str: String) -> Result<PathStatus, String
|
|||||||
let pastebar_data_subfolder = path.join("pastebar-data");
|
let pastebar_data_subfolder = path.join("pastebar-data");
|
||||||
if pastebar_data_subfolder.exists() && pastebar_data_subfolder.is_dir() {
|
if pastebar_data_subfolder.exists() && pastebar_data_subfolder.is_dir() {
|
||||||
// Check if the pastebar-data subfolder contains database files
|
// Check if the pastebar-data subfolder contains database files
|
||||||
let has_dev_db_in_subfolder = pastebar_data_subfolder.join("local.pastebar-db.data").exists();
|
let has_dev_db_in_subfolder = pastebar_data_subfolder
|
||||||
|
.join("local.pastebar-db.data")
|
||||||
|
.exists();
|
||||||
let has_prod_db_in_subfolder = pastebar_data_subfolder.join("pastebar-db.data").exists();
|
let has_prod_db_in_subfolder = pastebar_data_subfolder.join("pastebar-db.data").exists();
|
||||||
|
|
||||||
if has_dev_db_in_subfolder || has_prod_db_in_subfolder {
|
if has_dev_db_in_subfolder || has_prod_db_in_subfolder {
|
||||||
return Ok(PathStatus::IsPastebarDataAndNotEmpty);
|
return Ok(PathStatus::IsPastebarDataAndNotEmpty);
|
||||||
} else {
|
} else {
|
||||||
@ -80,51 +98,33 @@ pub fn cmd_get_custom_db_path() -> Option<String> {
|
|||||||
#[command]
|
#[command]
|
||||||
pub fn cmd_create_directory(path_str: String) -> Result<(), String> {
|
pub fn cmd_create_directory(path_str: String) -> Result<(), String> {
|
||||||
let path = Path::new(&path_str);
|
let path = Path::new(&path_str);
|
||||||
fs::create_dir_all(&path).map_err(|e| {
|
fs::create_dir_all(&path)
|
||||||
format!(
|
.map_err(|e| format!("Failed to create directory {}: {}", path.display(), e))?;
|
||||||
"Failed to create directory {}: {}",
|
|
||||||
path.display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates if the provided path is a writable directory.
|
/// Validates if the provided path is a writable directory.
|
||||||
#[command]
|
#[command]
|
||||||
pub fn cmd_validate_custom_db_path(path_str: String) -> Result<bool, String> {
|
pub fn cmd_validate_custom_db_path(path_str: String) -> Result<bool, String> {
|
||||||
let path = Path::new(&path_str);
|
let input_path = PathBuf::from(&path_str);
|
||||||
if !path.exists() {
|
|
||||||
// Attempt to create it if it doesn't exist, to check writability of parent
|
if input_path
|
||||||
if let Some(parent) = path.parent() {
|
.components()
|
||||||
if !parent.exists() {
|
.any(|c| matches!(c, std::path::Component::ParentDir))
|
||||||
fs::create_dir_all(parent).map_err(|e| {
|
{
|
||||||
format!(
|
return Err("Path traversal not allowed".to_string());
|
||||||
"Failed to create parent directory {}: {}",
|
}
|
||||||
parent.display(),
|
|
||||||
e
|
let path = if input_path.exists() {
|
||||||
)
|
input_path
|
||||||
})?;
|
.canonicalize()
|
||||||
}
|
.map_err(|e| format!("Invalid path: {}", e))?
|
||||||
}
|
} else {
|
||||||
// Check if we can create the directory itself (simulates future db file creation in this dir)
|
input_path
|
||||||
fs::create_dir_all(&path).map_err(|e| {
|
};
|
||||||
format!(
|
|
||||||
"Path {} is not a valid directory or cannot be created: {}",
|
if path.exists() && !path.is_dir() {
|
||||||
path.display(),
|
return Err(format!("Path {} is not a directory.", path.display()));
|
||||||
e
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
// Clean up by removing the directory if we created it for validation
|
|
||||||
fs::remove_dir(&path).map_err(|e| {
|
|
||||||
format!(
|
|
||||||
"Failed to clean up validation directory {}: {}",
|
|
||||||
path.display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
} else if !path.is_dir() {
|
|
||||||
return Err(format!("Path {} is not a directory.", path_str));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check writability by trying to create a temporary file
|
// Check writability by trying to create a temporary file
|
||||||
@ -153,6 +153,8 @@ pub fn cmd_set_and_relocate_data(
|
|||||||
|
|
||||||
let items_to_relocate = vec!["pastebar-db.data", "clip-images", "clipboard-images"];
|
let items_to_relocate = vec!["pastebar-db.data", "clip-images", "clipboard-images"];
|
||||||
|
|
||||||
|
let mut moved_items: Vec<(PathBuf, PathBuf)> = Vec::new();
|
||||||
|
|
||||||
for item_name in items_to_relocate {
|
for item_name in items_to_relocate {
|
||||||
let source_path = current_data_dir.join(item_name);
|
let source_path = current_data_dir.join(item_name);
|
||||||
let dest_path = new_data_dir.join(item_name);
|
let dest_path = new_data_dir.join(item_name);
|
||||||
@ -168,24 +170,24 @@ pub fn cmd_set_and_relocate_data(
|
|||||||
match operation.as_str() {
|
match operation.as_str() {
|
||||||
"move" => {
|
"move" => {
|
||||||
if source_path.is_dir() {
|
if source_path.is_dir() {
|
||||||
fs::rename(&source_path, &dest_path).map_err(|e| {
|
let mut options = CopyOptions::new();
|
||||||
format!(
|
options.overwrite = true;
|
||||||
"Failed to move directory {} to {}: {}",
|
copy(&source_path, &dest_path, &options)
|
||||||
source_path.display(),
|
.map_err(|e| format!("Failed to copy directory: {}", e))?;
|
||||||
dest_path.display(),
|
if let Err(e) = fs::remove_dir_all(&source_path) {
|
||||||
e
|
let _ = fs_extra::dir::remove(&dest_path);
|
||||||
)
|
rollback_moves(&moved_items);
|
||||||
})?;
|
return Err(format!("Failed to remove original directory: {}", e));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fs::rename(&source_path, &dest_path).map_err(|e| {
|
fs::copy(&source_path, &dest_path).map_err(|e| format!("Failed to copy file: {}", e))?;
|
||||||
format!(
|
if let Err(e) = fs::remove_file(&source_path) {
|
||||||
"Failed to move file {} to {}: {}",
|
let _ = fs::remove_file(&dest_path);
|
||||||
source_path.display(),
|
rollback_moves(&moved_items);
|
||||||
dest_path.display(),
|
return Err(format!("Failed to remove original file: {}", e));
|
||||||
e
|
}
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
moved_items.push((source_path.clone(), dest_path.clone()));
|
||||||
}
|
}
|
||||||
"copy" => {
|
"copy" => {
|
||||||
if source_path.is_dir() {
|
if source_path.is_dir() {
|
||||||
@ -205,6 +207,7 @@ pub fn cmd_set_and_relocate_data(
|
|||||||
}
|
}
|
||||||
|
|
||||||
user_settings_service::set_custom_db_path(&new_parent_dir_path)?;
|
user_settings_service::set_custom_db_path(&new_parent_dir_path)?;
|
||||||
|
crate::db::reinitialize_connection_pool();
|
||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"Data successfully {} to {}. Please restart the application.",
|
"Data successfully {} to {}. Please restart the application.",
|
||||||
@ -218,6 +221,7 @@ pub fn cmd_set_and_relocate_data(
|
|||||||
pub fn cmd_revert_to_default_data_location() -> Result<String, String> {
|
pub fn cmd_revert_to_default_data_location() -> Result<String, String> {
|
||||||
// Simply remove the custom database path setting
|
// Simply remove the custom database path setting
|
||||||
remove_custom_db_path()?;
|
remove_custom_db_path()?;
|
||||||
|
crate::db::reinitialize_connection_pool();
|
||||||
|
|
||||||
Ok("Custom database location setting removed successfully.".to_string())
|
Ok("Custom database location setting removed successfully.".to_string())
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use serde::Serialize;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::RwLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use diesel::connection::SimpleConnection;
|
use diesel::connection::SimpleConnection;
|
||||||
@ -39,7 +40,7 @@ pub struct ConnectionOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref DB_POOL_CONNECTION: Pool = init_connection_pool();
|
pub static ref DB_POOL_CONNECTION: RwLock<Pool> = RwLock::new(init_connection_pool());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
||||||
@ -92,6 +93,12 @@ fn init_connection_pool() -> Pool {
|
|||||||
.expect("Failed to create db pool.")
|
.expect("Failed to create db pool.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reinitialize_connection_pool() {
|
||||||
|
let new_pool = init_connection_pool();
|
||||||
|
let mut pool_lock = DB_POOL_CONNECTION.write().unwrap();
|
||||||
|
*pool_lock = new_pool;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(app: &mut tauri::App) {
|
pub fn init(app: &mut tauri::App) {
|
||||||
let config = app.config().clone();
|
let config = app.config().clone();
|
||||||
|
|
||||||
@ -182,6 +189,7 @@ pub fn establish_pool_db_connection(
|
|||||||
});
|
});
|
||||||
|
|
||||||
DB_POOL_CONNECTION
|
DB_POOL_CONNECTION
|
||||||
|
.read()
|
||||||
.get()
|
.get()
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to db pool"))
|
.unwrap_or_else(|_| panic!("Error connecting to db pool"))
|
||||||
}
|
}
|
||||||
@ -268,10 +276,11 @@ pub fn get_default_db_path_string() -> String {
|
|||||||
pub fn to_relative_image_path(absolute_path: &str) -> String {
|
pub fn to_relative_image_path(absolute_path: &str) -> String {
|
||||||
let data_dir = get_data_dir();
|
let data_dir = get_data_dir();
|
||||||
let data_dir_str = data_dir.to_string_lossy();
|
let data_dir_str = data_dir.to_string_lossy();
|
||||||
|
|
||||||
if absolute_path.starts_with(&data_dir_str.as_ref()) {
|
if absolute_path.starts_with(&data_dir_str.as_ref()) {
|
||||||
// Remove the data directory prefix and replace with placeholder
|
// Remove the data directory prefix and replace with placeholder
|
||||||
let relative_path = absolute_path.strip_prefix(&data_dir_str.as_ref())
|
let relative_path = absolute_path
|
||||||
|
.strip_prefix(&data_dir_str.as_ref())
|
||||||
.unwrap_or(absolute_path)
|
.unwrap_or(absolute_path)
|
||||||
.trim_start_matches('/')
|
.trim_start_matches('/')
|
||||||
.trim_start_matches('\\');
|
.trim_start_matches('\\');
|
||||||
@ -291,7 +300,10 @@ pub fn to_absolute_image_path(relative_path: &str) -> String {
|
|||||||
.unwrap_or(relative_path)
|
.unwrap_or(relative_path)
|
||||||
.trim_start_matches('/')
|
.trim_start_matches('/')
|
||||||
.trim_start_matches('\\');
|
.trim_start_matches('\\');
|
||||||
data_dir.join(path_without_placeholder).to_string_lossy().into_owned()
|
data_dir
|
||||||
|
.join(path_without_placeholder)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
} else {
|
} else {
|
||||||
// If path doesn't have placeholder, return as is
|
// If path doesn't have placeholder, return as is
|
||||||
relative_path.to_string()
|
relative_path.to_string()
|
||||||
|
@ -39,7 +39,7 @@ pub fn insert_or_update_setting_by_name(
|
|||||||
setting: &Setting,
|
setting: &Setting,
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let connection = &mut DB_POOL_CONNECTION.get().unwrap();
|
let connection = &mut DB_POOL_CONNECTION.read().unwrap().get().unwrap();
|
||||||
|
|
||||||
match settings
|
match settings
|
||||||
.filter(name.eq(&setting.name))
|
.filter(name.eq(&setting.name))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user