feat: Enhance user settings commands and data path management

- Added `fs_extra` dependency for improved file operations.
- Updated `arboard` dependency to version 3.5.0.
- Introduced `cmd_check_custom_data_path` to verify the status of a given path.
- Implemented `cmd_validate_custom_db_path` to check if a directory is writable.
- Replaced `cmd_set_custom_db_path` with `cmd_set_and_relocate_data` for setting a new data path and relocating existing data.
- Replaced `cmd_remove_custom_db_path` with `cmd_revert_to_default_data_location` to revert to the default data location with optional file movement.
- Refactored database path retrieval functions for clarity and efficiency.
- Updated clipboard history and item services to utilize new data directory functions.
This commit is contained in:
Sergey Kurdin 2025-06-10 14:12:41 -04:00
parent 98d9673e30
commit d92008c474
14 changed files with 2183 additions and 1120 deletions

135
custom_db_location_plan.md Normal file
View File

@ -0,0 +1,135 @@
# Plan: Implement Custom Data Location Feature
This document outlines the plan to implement the feature allowing users to specify a custom location for the PasteBar application's data.
## 1. Goals
* Allow users to specify a custom parent directory for application data via the settings UI.
* The application will create and manage a `pastebar-data` subdirectory within the user-specified location.
* This `pastebar-data` directory will contain the database file (`pastebar-db.data`), the `clip-images` folder, and the `clipboard-images` folder.
* Provide options to either **move** the existing data, **copy** it, or **use the new location without moving/copying**.
* Ensure the application uses the data from the new location after a restart.
* Handle potential errors gracefully and inform the user.
* Update the application state and backend configuration accordingly.
## 2. Backend (Rust - `src-tauri`)
### 2.1. Configuration (`user_settings_service.rs`)
* The `UserConfig` struct's `custom_db_path: Option<String>` will now be repurposed to store the path to the **user-selected parent directory**. The application logic will handle appending the `/pastebar-data/` segment. This requires no change to the struct itself, only to how the path is interpreted.
### 2.2. Path Logic (`db.rs` and new helpers)
* We will introduce new helper functions to consistently resolve data paths, whether default or custom.
* `get_data_dir() -> PathBuf`: This will be the core helper. It checks for a `custom_db_path` in the settings.
* If present, it returns `PathBuf::from(custom_path)`.
* If `None`, it returns the default application data directory.
* `get_db_path()`: This function will be refactored to use `get_data_dir().join("pastebar-db.data")`.
* `get_clip_images_dir()`: A new helper that returns `get_data_dir().join("clip-images")`.
* `get_clipboard_images_dir()`: A new helper that returns `get_data_dir().join("clipboard-images")`.
### 2.3. New & Updated Tauri Commands (`user_settings_command.rs`)
* **`cmd_validate_custom_db_path(path: String) -> Result<bool, String>`**
* **No change in purpose.** This command will still check if the user-selected directory is valid and writable.
* **`cmd_check_custom_data_path(path: String) -> Result<PathStatus, String>`**
* A new command to check the status of a selected directory. It returns one of the following statuses: `Empty`, `NotEmpty`, `IsPastebarDataAndNotEmpty`.
* **`cmd_set_and_relocate_data(new_parent_dir_path: String, operation: String) -> Result<String, String>`** (renamed from `set_and_relocate_db`)
* `new_parent_dir_path`: The new directory path selected by the user.
* `operation`: Either "move", "copy", or "none".
* **Updated Steps:**
1. Get the source paths:
* Current DB file path.
* Current `clip-images` directory path.
* Current `clipboard-images` directory path.
2. Define the new data directory: `let new_data_dir = Path::new(&new_parent_dir_path);`
3. Create the new data directory: `fs::create_dir_all(&new_data_dir)`.
4. Perform file/directory operations for each item (DB file, `clip-images` dir, `clipboard-images` dir):
* If "move": `fs::rename(source, destination)`.
* If "copy": `fs::copy` for the file, and a recursive copy function for the directories.
* If "none", do nothing.
* Handle cases where source items might not exist (e.g., `clip-images` folder hasn't been created yet) by skipping them gracefully.
5. If successful, call `user_settings_service::set_custom_db_path(&new_parent_dir_path)`.
6. Return a success or error message.
* **`cmd_revert_to_default_data_location(move_files_back: bool, overwrite_default: bool) -> Result<String, String>`** (renamed and signature updated)
* **Updated Steps:**
1. Get the current custom data directory.
2. Get the default application data directory.
3. If `move_files_back` is `true`:
* Move/copy the DB file, `clip-images` dir, and `clipboard-images` dir from the custom location back to the default location.
* Handle the `overwrite_default` flag for each item.
4. Call `user_settings_service::remove_custom_db_path()` to clear the setting.
## 3. Frontend (React)
* The UI has been updated to refer to "Custom Application Data Location" instead of "Custom Database Location".
* A third radio button option, "Use new location", has been added.
* The `handleBrowse` function now calls the `cmd_check_custom_data_path` command to analyze the selected directory and prompts the user accordingly.
* The `settingsStore.ts` has been updated to support the "none" operation.
## 4. User Interaction Flow (Mermaid Diagram)
```mermaid
graph TD
subgraph User Flow
A[User navigates to User Preferences] --> B{Custom Data Path Set?};
B -- Yes --> C[Display Current Custom Path];
B -- No --> D[Display Current Path: Default];
C --> E[Show "Revert to Default" Button];
D --> F[User Selects New Parent Directory];
F --> G{Path Status?};
G -- Empty --> H[Set Path];
G -- Not Empty --> I{Confirm "pastebar-data" subfolder};
I -- Yes --> J[Append "pastebar-data" to path];
J --> H;
I -- No --> H;
G -- Is 'pastebar-data' and Not Empty --> K[Alert user existing data will be used];
K --> H;
H --> L[User Selects Operation: Move/Copy/None];
L --> M[User Clicks "Apply and Restart"];
end
subgraph Backend Logic
M --> N[Frontend calls `cmd_set_and_relocate_data`];
N -- Success --> O[1. Create new data dir if needed];
O --> P[2. Move/Copy/Skip data];
P --> Q[3. Update `custom_db_path` in settings];
Q --> R[Show Success Toast & Relaunch App];
N -- Error --> S[Show Error Toast];
E --> T[User Clicks "Revert"];
T --> U[Frontend calls `cmd_revert_to_default_data_location`];
U -- Success --> V[Move/Copy data back to default app dir & clear setting];
V --> W[Show Success Toast & Relaunch App];
U -- Error --> X[Show Error Toast];
end
D -- "Browse..." --> F;
```
## 5. Implementation Summary
The following changes have been implemented:
* **`packages/pastebar-app-ui/src/pages/settings/UserPreferences.tsx`**:
* Renamed "Custom Database Location" to "Custom Application Data Location".
* Added a third radio button for the "Use new location" option.
* Updated the `handleBrowse` function to call the new `cmd_check_custom_data_path` command and handle the different path statuses with user prompts.
* **`packages/pastebar-app-ui/src/store/settingsStore.ts`**:
* Updated the `applyCustomDbPath` function to accept the "none" operation.
* Updated the `revertToDefaultDbPath` function to call the renamed backend command.
* **`src-tauri/src/commands/user_settings_command.rs`**:
* Added the `cmd_check_custom_data_path` command.
* Renamed `cmd_set_and_relocate_db` to `cmd_set_and_relocate_data` and updated its logic to handle the "none" operation and the new data directory structure.
* Renamed `cmd_revert_to_default_db_location` to `cmd_revert_to_default_data_location` and updated its logic.
* **`src-tauri/src/db.rs`**:
* Refactored the `get_data_dir` function to no longer automatically append `pastebar-data`.
* Added `get_clip_images_dir` and `get_clipboard_images_dir` helper functions.
* **`src-tauri/src/main.rs`**:
* Registered the new and renamed commands in the `invoke_handler`.
* **`src-tauri/Cargo.toml`**:
* Added the `fs_extra` dependency for recursive directory copying.
* **`src-tauri/src/services/items_service.rs`** and **`src-tauri/src/services/history_service.rs`**:
* Updated to use the new `get_clip_images_dir` and `get_clipboard_images_dir` helper functions.

View File

@ -107,6 +107,11 @@ function App() {
settingsStore.initSettings({
appDataDir: import.meta.env.TAURI_DEBUG ? appDevDataDir : appDataDir,
// Initialize new DB path settings for type conformity; actual value loaded by loadInitialCustomDbPath
customDbPath: null,
isCustomDbPathValid: null,
customDbPathError: null,
dbRelocationInProgress: false,
appLastUpdateVersion: settings.appLastUpdateVersion?.valueText,
appLastUpdateDate: settings.appLastUpdateDate?.valueText,
isHideMacOSDockIcon: settings.isHideMacOSDockIcon?.valueBool,
@ -187,6 +192,8 @@ function App() {
settingsStore.initConstants({
APP_DETECT_LANGUAGES_SUPPORTED: appDetectLanguageSupport,
})
// Load the actual custom DB path after basic settings are initialized
settingsStore.loadInitialCustomDbPath()
type().then(osType => {
if (osType === 'Windows_NT' && settings.copyPasteDelay?.valueInt === 0) {
settingsStore.updateSetting('copyPasteDelay', 2)

View File

@ -24,6 +24,7 @@ Are you sure you want to delete?: Are you sure you want to delete?
Are you sure?: Are you sure?
Attach History Window: Attach History Window
Back: Back
Browse...: Browse...
Build on {{buildDate}}: Build on {{buildDate}}
Cancel: Cancel
Cancel Reset: Cancel Reset

View File

@ -12,6 +12,8 @@ Application UI Color Theme: Application UI Color Theme
Application UI Fonts Scale: Application UI Fonts Scale
Application UI Language: Application UI Language
Applications listed below will not have their copy to clipboard action captured in clipboard history. Case insensitive.: Applications listed below will not have their copy to clipboard action captured in clipboard history. Case insensitive.
Apply and Restart: Apply and Restart
Are you sure you want to revert to the default database location? The application will restart.: Are you sure you want to revert to the default database location? The application will restart.
Auto Disable History Capture when Screen Unlocked: Auto Disable History Capture when Screen Unlocked
Auto Lock Application Screen on User Inactivity: Auto Lock Application Screen on User Inactivity
Auto Lock Screen on User Inactivity: Auto Lock Screen on User Inactivity
@ -35,20 +37,28 @@ Change the application UI language: Change the application UI language
Change the application user interface color theme: Change the application user interface color theme
Change the application user interface font size scale: Change the application user interface font size scale
Change the application user interface language: Change the application user interface language
Changing the database location requires an application restart to take effect.: Changing the database location requires an application restart to take effect.
Clip Notes Popup Maximum Dimensions: Clip Notes Popup Maximum Dimensions
Clipboard History Settings: Clipboard History Settings
'Complete details:': 'Complete details:'
Configure settings to automatically delete clipboard history items after a specified duration.: Configure settings to automatically delete clipboard history items after a specified duration.
Copy data: Copy data
Copy database file: Copy database file
Create a preview card on link hover in the clipboard history. This allows you to preview the link before opening or pasting it.: Create a preview card on link hover in the clipboard history. This allows you to preview the link before opening or pasting it.
Create an unlimited number of collections to organize your clips and menus.: Create an unlimited number of collections to organize your clips and menus.
Current database location: Current database location
Custom: Custom
Custom Application Data Location: Custom Application Data Location
Custom Database Location: Custom Database Location
Custom themes: Custom themes
Decrease UI Font Size: Decrease UI Font Size
Default: Default
? Display clipboard history capture toggle on the locked application screen. This allows you to control history capture settings directly from the lock screen.
: Display clipboard history capture toggle on the locked application screen. This allows you to control history capture settings directly from the lock screen.
Display disabled collections name on the navigation bar collections menu: Display disabled collections name on the navigation bar collections menu
Display disabled collections name on the navigation bar under collections menu: Display disabled collections name on the navigation bar under collections menu
Display full name of selected collection on the navigation bar: Display full name of selected collection on the navigation bar
Do you want to attempt to move the database file from "{{customPath}}" back to the default location?: Do you want to attempt to move the database file from "{{customPath}}" back to the default location?
Drag and drop to prioritize languages for detection. The higher a language is in the list, the higher its detection priority.: Drag and drop to prioritize languages for detection. The higher a language is in the list, the higher its detection priority.
Email: Email
Email is not valid: Email is not valid
@ -66,6 +76,7 @@ Enable programming language detection: Enable programming language detection
Enable screen unlock requirement on app launch for enhanced security, safeguarding data from unauthorized access.: Enable screen unlock requirement on app launch for enhanced security, safeguarding data from unauthorized access.
Enhance security by automatically locking the application screen after a set period of user inactivity.: Enhance security by automatically locking the application screen after a set period of user inactivity.
Enter Passcode length: Enter Passcode length
Enter new directory path or leave empty for default on next revert: Enter new directory path or leave empty for default on next revert
Enter recovery password to reset passcode.: Enter recovery password to reset passcode.
Enter your <strong>{{screenLockPassCodeLength}} digits</strong> passcode: Enter your <strong>{{screenLockPassCodeLength}} digits</strong> passcode
Entered Passcode is invalid: Entered Passcode is invalid
@ -73,12 +84,15 @@ Excluded Apps List: Excluded Apps List
Execute Web Requests: Execute Web Requests
Execute terminal or shell commands directly from PasteBar clip and copy the results to the clipboard.: Execute terminal or shell commands directly from PasteBar clip and copy the results to the clipboard.
'Expires:': 'Expires:'
Failed to revert to default database location.: Failed to revert to default database location.
Forgot Passcode ? Enter your recovery password to reset the passcode.: Forgot Passcode ? Enter your recovery password to reset the passcode.
Forgot passcode ?: Forgot passcode ?
Forgot?: Forgot?
Forgot? Reset using Password: Forgot? Reset using Password
Get priority email support from us to resolve any issues or questions you may have about PasteBar.: Get priority email support from us to resolve any issues or questions you may have about PasteBar.
'Hint: {{screenLockRecoveryPasswordMasked}}': 'Hint: {{screenLockRecoveryPasswordMasked}}'
? If a database file already exists at the default location, do you want to overwrite it? Choosing "Cancel" will skip moving the file if an existing file is found.
: If a database file already exists at the default location, do you want to overwrite it? Choosing "Cancel" will skip moving the file if an existing file is found.
Incorrect passcode.: Incorrect passcode.
Increase UI Font Size: Increase UI Font Size
Issued: Issued
@ -95,8 +109,13 @@ Medium: Medium
Minimal 4 digits: Minimal 4 digits
Minimize Window: Minimize Window
Minimum number of lines to trigger detection: Minimum number of lines to trigger detection
Move data: Move data
Move database file: Move database file
Name: Name
New Data Directory Path: New Data Directory Path
New Database Directory Path: New Database Directory Path
Open Security Settings: Open Security Settings
Operation when applying new path: Operation when applying new path
Passcode digits remaining: Passcode digits remaining
Passcode is locked.: Passcode is locked.
Passcode is not set: Passcode is not set
@ -132,12 +151,15 @@ Require Screen Unlock at Application Start: Require Screen Unlock at Application
? Require screen unlock at application launch to enhance security. This setting ensures that only authorized users can access the application, protecting your data from unauthorized access right from the start.
: Require screen unlock at application launch to enhance security. This setting ensures that only authorized users can access the application, protecting your data from unauthorized access right from the start.
Reset Font Size: Reset Font Size
Revert to Default and Restart: Revert to Default and Restart
Run Terminal or Shell Commands: Run Terminal or Shell Commands
Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard.: Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard.
? 'Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard. '
: 'Scrape and parse websites or API responses using built-in web scraping tools and copy the extracted data to the clipboard. '
Security: Security
Security Settings: Security Settings
Select Data Directory: Select Data Directory
Select Database Directory: Select Database Directory
Send HTTP requests to web APIs or services and copy the response data to the clipboard.: Send HTTP requests to web APIs or services and copy the response data to the clipboard.
Sensitive words or sentences listed below will automatically be masked if found in the copied text. Case insensitive.: Sensitive words or sentences listed below will automatically be masked if found in the copied text. Case insensitive.
Set a passcode to unlock the locked screen and protect your data from unauthorized access.: Set a passcode to unlock the locked screen and protect your data from unauthorized access.
@ -158,6 +180,8 @@ Swap Panels Layout: Swap Panels Layout
Switch the layout position of panels in Clipboard History and Paste Menu views: Switch the layout position of panels in Clipboard History and Paste Menu views
Thank you again for using PasteBar.: Thank you again for using PasteBar.
Thank you for testing! 🙌: Thank you for testing! 🙌
The selected folder is not empty. Do you want to create a "pastebar-data" subfolder to store the data?: The selected folder is not empty. Do you want to create a "pastebar-data" subfolder to store the data?
This folder already contains PasteBar data. The application will use this existing data after restart.: This folder already contains PasteBar data. The application will use this existing data after restart.
? This option lets you control the display and timing of hover notes on clips. You can choose to show notes instantly or with a delay to prevent unintended popups.
: This option lets you control the display and timing of hover notes on clips. You can choose to show notes instantly or with a delay to prevent unintended popups.
? This option lets you customize the maximum width and height of the popup that displays clip notes, ensuring it fits comfortably within your desired size.
@ -177,6 +201,7 @@ Unlimited Collections: Unlimited Collections
Unlimited Tabs per Collection: Unlimited Tabs per Collection
Unlimited paste history: Unlimited paste history
Use Password: Use Password
Use new location: Use new location
User Preferences: User Preferences
Web Scraping and Parsing: Web Scraping and Parsing
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.

View File

@ -1,11 +1,13 @@
import { useEffect, useState } from 'react'
import { invoke } from '@tauri-apps/api'
import { dialog, invoke } from '@tauri-apps/api'
import { join } from '@tauri-apps/api/path'
import i18n from '~/locales'
import { LANGUAGES } from '~/locales/languges'
import {
clipNotesDelays,
clipNotesSizes,
fontSizeIncrements,
settingsStore, // Import the Zustand store instance for getState()
settingsStoreAtom,
themeStoreAtom,
uiStoreAtom,
@ -39,6 +41,335 @@ import {
import md from '~/store/example.md?raw'
// Helper component for Custom Database Location settings
function CustomDatabaseLocationSettings() {
const { t } = useTranslation()
const {
customDbPath,
// isCustomDbPathValid is checked via settingsStore.getState() after validation
customDbPathError: storeCustomDbPathError, // Renamed to avoid conflict with local error state
dbRelocationInProgress,
validateCustomDbPath,
applyCustomDbPath,
revertToDefaultDbPath,
relaunchApp,
} = useAtomValue(settingsStoreAtom)
const [newDbPathInput, setNewDbPathInput] = useState(customDbPath || '')
const [dbOperation, setDbOperation] = useState<'move' | 'copy' | 'none'>('copy')
const [isApplying, setIsApplying] = useState(false)
const [isReverting, setIsReverting] = useState(false)
const [operationError, setOperationError] = useState<string | null>(null)
const [validationError, setValidationError] = useState<string | null>(null)
useEffect(() => {
setNewDbPathInput(customDbPath || '')
setValidationError(null) // Clear local validation error when customDbPath changes
}, [customDbPath])
// Effect to react to validation errors from the store
useEffect(() => {
const state = settingsStore.getState()
if (newDbPathInput && storeCustomDbPathError && !state.isCustomDbPathValid) {
setValidationError(storeCustomDbPathError)
} else if (state.isCustomDbPathValid) {
setValidationError(null)
}
// Intentionally not depending on state.isCustomDbPathValid directly to avoid loop with onBlur validation
}, [storeCustomDbPathError, newDbPathInput])
const handleBrowse = async () => {
setOperationError(null)
setValidationError(null)
try {
const selected = await dialog.open({
directory: true,
multiple: false,
title: t('Select Data Directory', { ns: 'settings' }),
})
if (typeof selected === 'string') {
const status: any = await invoke('cmd_check_custom_data_path', {
pathStr: selected,
})
let finalPath = selected
if (status === 'NotEmpty') {
const confirmSubfolder = window.confirm(
t(
'The selected folder is not empty. Do you want to create a "pastebar-data" subfolder to store the data?',
{ ns: 'settings' }
)
)
if (confirmSubfolder) {
finalPath = await join(selected, 'pastebar-data')
}
} else if (status === 'IsPastebarDataAndNotEmpty') {
window.alert(
t(
'This folder already contains PasteBar data. The application will use this existing data after restart.',
{ ns: 'settings' }
)
)
}
setNewDbPathInput(finalPath)
await validateCustomDbPath(finalPath)
}
} catch (error) {
console.error('Error handling directory selection:', error)
setOperationError(
t('An error occurred during directory processing.', { ns: 'settings' })
)
}
}
const handleApply = async () => {
if (!newDbPathInput || newDbPathInput === customDbPath) {
setOperationError(
t('Please select a new directory different from the current one.', {
ns: 'settings',
})
)
return
}
setIsApplying(true)
setOperationError(null)
setValidationError(null)
await validateCustomDbPath(newDbPathInput)
const currentStoreState = settingsStore.getState()
if (!currentStoreState.isCustomDbPathValid) {
setValidationError(
currentStoreState.customDbPathError ||
t('Invalid directory selected.', { ns: 'settings' })
)
setIsApplying(false)
return
}
const confirmed = window.confirm(
t(
'Are you sure you want to {{operation}} the database to "{{path}}"? The application will restart.',
{
ns: 'settings',
operation: t(dbOperation, { ns: 'settings' }),
path: newDbPathInput,
}
)
)
if (confirmed) {
try {
await applyCustomDbPath(newDbPathInput, dbOperation)
// Consider using a toast notification here
relaunchApp()
} catch (error: any) {
setOperationError(
error.message ||
t('Failed to apply custom database location.', { ns: 'settings' })
)
} finally {
setIsApplying(false)
}
} else {
setIsApplying(false)
}
}
const handleRevert = async () => {
setOperationError(null)
setValidationError(null)
const confirmedInitial = window.confirm(
t(
'Are you sure you want to revert to the default database location? The application will restart.',
{ ns: 'settings' }
)
)
if (!confirmedInitial) return
let moveFileConfirmed = false
let overwriteConfirmed = false // Default to false
if (customDbPath) {
// Only ask about moving if there IS a custom path currently set
moveFileConfirmed = window.confirm(
t(
'Do you want to attempt to move the database file from "{{customPath}}" back to the default location?',
{ ns: 'settings', customPath: customDbPath }
)
)
if (moveFileConfirmed) {
// Only ask for overwrite if they chose to move the file
overwriteConfirmed = window.confirm(
t(
'If a database file already exists at the default location, do you want to overwrite it? Choosing "Cancel" will skip moving the file if an existing file is found.',
{ ns: 'settings' }
)
)
}
}
setIsReverting(true)
try {
await revertToDefaultDbPath(moveFileConfirmed, overwriteConfirmed)
// Consider using a toast notification here
relaunchApp()
} catch (error: any) {
setOperationError(
error.message ||
t('Failed to revert to default database location.', { ns: 'settings' })
)
} finally {
setIsReverting(false)
}
}
const isLoading = dbRelocationInProgress || isApplying || isReverting
const currentPathDisplay = customDbPath || t('Default', { ns: 'settings' })
const isPathUnchanged = newDbPathInput === customDbPath
return (
<Box className="animate-in fade-in max-w-xl mt-4">
<Card>
<CardHeader>
<CardTitle>
{t('Custom Application Data Location', { ns: 'settings' })}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Text className="text-sm text-muted-foreground">
{t('Current database location', { ns: 'settings' })}:{' '}
<span className="font-semibold">{currentPathDisplay}</span>
</Text>
<InputField
label={t('New Data Directory Path', { ns: 'settings' })}
value={newDbPathInput}
onChange={e => {
setNewDbPathInput(e.target.value)
setOperationError(null)
setValidationError(null) // Clear validation error on input change
}}
onBlur={async () => {
if (newDbPathInput && newDbPathInput !== customDbPath) {
await validateCustomDbPath(newDbPathInput)
} else if (!newDbPathInput && customDbPath) {
// if input is cleared but a custom path was set
settingsStore.setState({
customDbPathError: null,
isCustomDbPathValid: null,
})
setValidationError(null)
} else if (newDbPathInput === customDbPath) {
settingsStore.setState({
customDbPathError: null,
isCustomDbPathValid: null,
})
setValidationError(null)
}
}}
disabled={isLoading}
placeholder={t(
'Enter new directory path or leave empty for default on next revert',
{ ns: 'settings' }
)}
/>
{validationError && (
<Text className="text-sm text-red-500">{validationError}</Text>
)}
<Button onClick={handleBrowse} disabled={isLoading} variant="outline">
{dbRelocationInProgress && !isApplying && !isReverting ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : null}
{t('Browse...', { ns: 'common' })}
</Button>
<Flex className="items-center space-x-4">
<Text className="text-sm">
{t('Operation when applying new path', { ns: 'settings' })}:
</Text>
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="radio"
name="dbOperation"
value="copy"
checked={dbOperation === 'copy'}
onChange={() => setDbOperation('copy')}
disabled={isLoading}
className="form-radio accent-primary"
/>
<TextNormal size="sm">{t('Copy data', { ns: 'settings' })}</TextNormal>
</label>
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="radio"
name="dbOperation"
value="move"
checked={dbOperation === 'move'}
onChange={() => setDbOperation('move')}
disabled={isLoading}
className="form-radio accent-primary"
/>
<TextNormal size="sm">{t('Move data', { ns: 'settings' })}</TextNormal>
</label>
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="radio"
name="dbOperation"
value="none"
checked={dbOperation === 'none'}
onChange={() => setDbOperation('none')}
disabled={isLoading}
className="form-radio accent-primary"
/>
<TextNormal size="sm">
{t('Use new location', { ns: 'settings' })}
</TextNormal>
</label>
</Flex>
{operationError && (
<Text className="text-sm text-red-500">{operationError}</Text>
)}
<Flex className="space-x-2">
<Button
onClick={handleApply}
disabled={
isLoading || !newDbPathInput || isPathUnchanged || !!validationError
}
>
{isApplying ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : null}
{t('Apply and Restart', { ns: 'settings' })}
</Button>
{customDbPath && ( // Only show revert button if a custom path is currently set
<Button
onClick={handleRevert}
disabled={isLoading}
variant="secondary" // Base variant
className="bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white dark:text-slate-100" // Destructive-like styling
>
{isReverting ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : null}
{t('Revert to Default and Restart', { ns: 'settings' })}
</Button>
)}
</Flex>
<Text className="text-xs text-muted-foreground pt-2">
{t(
'Changing the database location requires an application restart to take effect.',
{ ns: 'settings' }
)}
</Text>
</CardContent>
</Card>
</Box>
)
}
export default function UserPreferences() {
const { t } = useTranslation()
@ -69,12 +400,16 @@ export default function UserPreferences() {
hotKeysShowHideQuickPasteWindow,
setHotKeysShowHideMainAppWindow,
setHotKeysShowHideQuickPasteWindow,
// Custom DB Path states and actions (customDbPath, isCustomDbPathValid, etc.)
// are now handled in the CustomDatabaseLocationSettings component.
// relaunchApp is also used there.
} = useAtomValue(settingsStoreAtom)
const { setFontSize, fontSize, setIsSwapPanels, isSwapPanels, returnRoute, isMacOSX } =
useAtomValue(uiStoreAtom)
const [isAutoStartEnabled, setIsAutoStartEnabled] = useState(false)
// Local states for DB path input, operation, and confirmations have been moved to CustomDatabaseLocationSettings
const { setTheme, theme } = useTheme()
const { mode, setMode, themeDark } = useAtomValue(themeStoreAtom)
@ -83,7 +418,7 @@ export default function UserPreferences() {
if (theme !== mode) {
setMode(theme)
}
}, [theme])
}, [theme, mode, setMode]) // Added mode and setMode to dependency array
useEffect(() => {
invoke('is_autostart_enabled').then(isEnabled => {
@ -107,6 +442,8 @@ export default function UserPreferences() {
setQuickPasteHotkey(hotKeysShowHideQuickPasteWindow)
}
}, [hotKeysShowHideMainAppWindow, hotKeysShowHideQuickPasteWindow])
// Removed mainAppHotkey, quickPasteHotkey from local state dependencies in the original thought process,
// as they are set inside this effect. The effect correctly depends on props.
const handleKeyDown = (
event: KeyboardEvent | React.KeyboardEvent<HTMLInputElement>,
@ -274,6 +611,10 @@ export default function UserPreferences() {
</Card>
</Box>
{/* ------------- Custom Database Location Settings Card ------------- */}
<CustomDatabaseLocationSettings />
{/* ------------------------------------------------------------------ */}
<Box className="animate-in fade-in max-w-xl mt-4">
<Card>
<CardHeader className="flex flex-col items-start justify-between space-y-0 pb-1">

View File

@ -26,7 +26,11 @@ import {
type Settings = {
appLastUpdateVersion: string
appLastUpdateDate: string
appDataDir: string
appDataDir: string // This might be the old customDbPath or a general app data dir. We'll add a specific one.
customDbPath: string | null // Path to the custom database directory
isCustomDbPathValid: boolean | null // Validation status of the entered customDbPath
customDbPathError: string | null // Error message if validation fails or operation fails
dbRelocationInProgress: boolean // True if a DB move/copy/revert operation is ongoing
isAppReady: boolean
isClipNotesHoverCardsEnabled: boolean
clipNotesHoverCardsDelayMS: number
@ -88,6 +92,14 @@ type Constants = {
}
export interface SettingsStoreState {
setCustomDbPath: (path: string | null) => void
validateCustomDbPath: (path: string) => Promise<void>
applyCustomDbPath: (
newPath: string,
operation: 'move' | 'copy' | 'none'
) => Promise<string>
revertToDefaultDbPath: (moveFile: boolean, overwrite: boolean) => Promise<string>
loadInitialCustomDbPath: () => Promise<void>
setIsHistoryEnabled: (isHistoryEnabled: boolean) => void
setIsHistoryAutoUpdateOnCaputureEnabled: (
isHistoryAutoUpdateOnCaputureEnabled: boolean
@ -120,7 +132,7 @@ export interface SettingsStoreState {
setAppToursCompletedList: (words: string[]) => void
setAppToursSkippedList: (words: string[]) => void
setHistoryDetectLanguagesPrioritizedList: (words: string[]) => void
setAppDataDir: (appDataDir: string) => void
setAppDataDir: (appDataDir: string) => void // Keep if used for other general app data
setIsAutoCloseOnCopyPaste: (isEnabled: boolean) => void
setClipNotesHoverCardsDelayMS: (delay: number) => void
setClipNotesMaxWidth: (width: number) => void
@ -175,7 +187,11 @@ const initialState: SettingsStoreState & Settings = {
appLastUpdateVersion: '0.0.1',
appLastUpdateDate: '',
isAppReady: false,
appDataDir: '',
appDataDir: '', // Default app data dir if needed for other things
customDbPath: null,
isCustomDbPathValid: null,
customDbPathError: null,
dbRelocationInProgress: false,
isHistoryEnabled: true,
isFirstRun: true,
historyDetectLanguagesEnabledList: [],
@ -287,7 +303,12 @@ const initialState: SettingsStoreState & Settings = {
setClipTextMinLength: () => {},
setClipTextMaxLength: () => {},
initConstants: () => {},
setAppDataDir: () => {},
setAppDataDir: () => {}, // Keep if used for other general app data
setCustomDbPath: () => {},
validateCustomDbPath: async () => {},
applyCustomDbPath: async () => '',
revertToDefaultDbPath: async () => '',
loadInitialCustomDbPath: async () => {},
updateSetting: () => {},
setIsFirstRun: () => {},
setAppLastUpdateVersion: () => {},
@ -738,10 +759,80 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
availableVersionDateISO.value = null
},
initConstants: (CONST: Constants) => set(() => ({ CONST })),
setAppDataDir: (appDataDir: string) =>
setAppDataDir: (
appDataDir: string // Keep if used for other general app data
) =>
set(() => ({
appDataDir,
})),
// Actions for custom DB path
setCustomDbPath: (path: string | null) =>
set({ customDbPath: path, isCustomDbPathValid: null, customDbPathError: null }),
loadInitialCustomDbPath: async () => {
try {
const path = await invoke('cmd_get_custom_db_path')
set({ customDbPath: path as string | null })
} catch (error) {
console.error('Failed to load initial custom DB path:', error)
set({ customDbPathError: 'Failed to load custom DB path setting.' })
}
},
validateCustomDbPath: async (path: string) => {
set({
dbRelocationInProgress: true,
customDbPathError: null,
isCustomDbPathValid: null,
})
try {
await invoke('cmd_validate_custom_db_path', { pathStr: path })
set({ isCustomDbPathValid: true, dbRelocationInProgress: false })
} catch (error) {
console.error('Custom DB path validation failed:', error)
set({
isCustomDbPathValid: false,
customDbPathError: error as string,
dbRelocationInProgress: false,
})
}
},
applyCustomDbPath: async (newPath: string, operation: 'move' | 'copy' | 'none') => {
set({ dbRelocationInProgress: true, customDbPathError: null })
try {
const message = await invoke('cmd_set_and_relocate_data', {
newParentDirPath: newPath,
operation,
})
set({
customDbPath: newPath,
isCustomDbPathValid: true,
dbRelocationInProgress: false,
})
return message as string
} catch (error) {
console.error('Failed to apply custom DB path:', error)
set({ customDbPathError: error as string, dbRelocationInProgress: false })
throw error
}
},
revertToDefaultDbPath: async (moveFile: boolean, overwrite: boolean) => {
set({ dbRelocationInProgress: true, customDbPathError: null })
try {
const message = await invoke('cmd_revert_to_default_data_location', {
moveFilesBack: moveFile,
overwriteDefault: overwrite,
})
set({
customDbPath: null,
isCustomDbPathValid: null,
dbRelocationInProgress: false,
})
return message as string
} catch (error) {
console.error('Failed to revert to default DB path:', error)
set({ customDbPathError: error as string, dbRelocationInProgress: false })
throw error
}
},
setAppLastUpdateVersion: (appLastUpdateVersion: string) => {
return get().updateSetting('appLastUpdateVersion', appLastUpdateVersion)
},

View File

@ -1 +1,2 @@
custom_db_path: "/Users/kurdin/"
custom_db_path: V:\iCloudDrive\AppSyncData
data: {}

2296
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ winapi = { version = "0.3", features = ["winuser", "windef"] }
winreg = "0.52.0"
[dependencies]
fs_extra = "1.3.0"
fns = "0"
mouse_position = "0.1.4"
keyring = "2.3.2"
@ -65,7 +66,7 @@ chrono = { version = "0.4.24", features = ["serde"] }
uuid = "1.3.1"
once_cell = "1.7.0"
thiserror = "1.0"
arboard = "3.2.1"
arboard = "3.5.0"
image = "0.24.9"
tempfile = "3"
base64 = "0.22.0"

View File

@ -1,11 +1,47 @@
use serde_yaml::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use tauri::command;
use crate::services::user_settings_service::{
get_all_settings, get_custom_db_path, get_setting, remove_custom_db_path, remove_setting,
set_custom_db_path, set_setting,
use crate::db::{
get_clip_images_dir, get_clipboard_images_dir, get_data_dir, get_db_path, get_default_data_dir,
get_default_db_path_string,
};
use crate::services::user_settings_service::{
self as user_settings_service, get_all_settings, get_custom_db_path, get_setting,
remove_custom_db_path, remove_setting, set_custom_db_path, set_setting,
};
use fs_extra::dir::{copy, CopyOptions};
use std::path::PathBuf;
#[derive(serde::Serialize)]
pub enum PathStatus {
Empty,
NotEmpty,
IsPastebarDataAndNotEmpty,
}
/// Checks the status of a given path.
#[command]
pub fn cmd_check_custom_data_path(path_str: String) -> Result<PathStatus, String> {
let path = Path::new(&path_str);
if !path.exists() || !path.is_dir() {
return Ok(PathStatus::Empty); // Treat non-existent paths as empty for this purpose
}
if path.file_name().and_then(|n| n.to_str()) == Some("pastebar-data") {
if path.read_dir().map_err(|e| e.to_string())?.next().is_some() {
return Ok(PathStatus::IsPastebarDataAndNotEmpty);
}
}
if path.read_dir().map_err(|e| e.to_string())?.next().is_some() {
return Ok(PathStatus::NotEmpty);
}
Ok(PathStatus::Empty)
}
/// Returns the current `custom_db_path` (if any).
#[command]
@ -13,16 +49,183 @@ pub fn cmd_get_custom_db_path() -> Option<String> {
get_custom_db_path()
}
/// Insert or update a new `custom_db_path`.
// cmd_set_custom_db_path is now part of cmd_set_and_relocate_db
// cmd_remove_custom_db_path is now part of cmd_revert_to_default_db_location
/// Validates if the provided path is a writable directory.
#[command]
pub fn cmd_set_custom_db_path(new_path: String) -> Result<(), String> {
set_custom_db_path(&new_path)
pub fn cmd_validate_custom_db_path(path_str: String) -> Result<bool, String> {
let path = Path::new(&path_str);
if !path.exists() {
// Attempt to create it if it doesn't exist, to check writability of parent
if let Some(parent) = path.parent() {
if !parent.exists() {
fs::create_dir_all(parent).map_err(|e| {
format!(
"Failed to create parent directory {}: {}",
parent.display(),
e
)
})?;
}
}
// Check if we can create the directory itself (simulates future db file creation in this dir)
fs::create_dir_all(&path).map_err(|e| {
format!(
"Path {} is not a valid directory or cannot be created: {}",
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
let temp_file_path = path.join(".tmp_pastebar_writable_check");
match fs::File::create(&temp_file_path) {
Ok(_) => {
fs::remove_file(&temp_file_path)
.map_err(|e| format!("Failed to remove temporary check file: {}", e))?;
Ok(true)
}
Err(e) => Err(format!("Directory {} is not writable: {}", path_str, e)),
}
}
/// Remove (clear) the `custom_db_path`.
/// Sets the custom data path, and moves/copies the data directory.
#[command]
pub fn cmd_remove_custom_db_path() -> Result<(), String> {
remove_custom_db_path()
pub fn cmd_set_and_relocate_data(
new_parent_dir_path: String,
operation: String,
) -> Result<String, String> {
let current_data_dir = get_data_dir();
let new_data_dir = PathBuf::from(&new_parent_dir_path);
fs::create_dir_all(&new_data_dir)
.map_err(|e| format!("Failed to create new data directory: {}", e))?;
let items_to_relocate = vec!["pastebar-db.data", "clip-images", "clipboard-images"];
for item_name in items_to_relocate {
let source_path = current_data_dir.join(item_name);
let dest_path = new_data_dir.join(item_name);
if !source_path.exists() {
println!(
"Source item {} does not exist, skipping.",
source_path.display()
);
continue;
}
match operation.as_str() {
"move" => {
if source_path.is_dir() {
fs::rename(&source_path, &dest_path).map_err(|e| {
format!(
"Failed to move directory {} to {}: {}",
source_path.display(),
dest_path.display(),
e
)
})?;
} else {
fs::rename(&source_path, &dest_path).map_err(|e| {
format!(
"Failed to move file {} to {}: {}",
source_path.display(),
dest_path.display(),
e
)
})?;
}
}
"copy" => {
if source_path.is_dir() {
let mut options = CopyOptions::new();
options.overwrite = true;
copy(&source_path, &dest_path, &options)
.map_err(|e| format!("Failed to copy directory: {}", e))?;
} else {
fs::copy(&source_path, &dest_path).map_err(|e| format!("Failed to copy file: {}", e))?;
}
}
"none" => {
// Do nothing, just switch to the new location
}
_ => return Err("Invalid operation specified. Use 'move', 'copy', or 'none'.".to_string()),
}
}
user_settings_service::set_custom_db_path(&new_parent_dir_path)?;
Ok(format!(
"Data successfully {} to {}. Please restart the application.",
operation,
new_data_dir.display()
))
}
/// Clears the custom data path and optionally moves the data back to default.
#[command]
pub fn cmd_revert_to_default_data_location(
move_files_back: bool,
overwrite_default: bool,
) -> Result<String, String> {
let current_custom_data_dir = get_data_dir();
let default_data_dir = get_default_data_dir();
if move_files_back && current_custom_data_dir != default_data_dir {
let items_to_relocate = vec!["pastebar-db.data", "clip-images", "clipboard-images"];
for item_name in items_to_relocate {
let source_path = current_custom_data_dir.join(item_name);
let dest_path = default_data_dir.join(item_name);
if !source_path.exists() {
continue;
}
if dest_path.exists() && !overwrite_default {
return Err(format!(
"Default item at {} already exists. Cannot overwrite without explicit confirmation.",
dest_path.display()
));
}
if source_path.is_dir() {
fs::rename(&source_path, &dest_path).map_err(|e| {
format!(
"Failed to move directory {} to {}: {}",
source_path.display(),
dest_path.display(),
e
)
})?;
} else {
fs::rename(&source_path, &dest_path).map_err(|e| {
format!(
"Failed to move file {} to {}: {}",
source_path.display(),
dest_path.display(),
e
)
})?;
}
}
}
user_settings_service::remove_custom_db_path()?;
Ok("Data location reverted to default. Please restart the application.".to_string())
}
/// Return all key-value pairs from the `data` map.

View File

@ -217,59 +217,47 @@ fn db_file_exists() -> bool {
Path::new(&db_path).exists()
}
fn get_db_path() -> String {
/// Returns the base directory for application data.
/// This will be a `pastebar-data` subdirectory if a custom path is set.
pub fn get_data_dir() -> PathBuf {
let user_config = load_user_config();
if let Some(custom_path) = user_config.custom_db_path {
let final_path = adjust_custom_db_path(&custom_path);
// Check if it's valid/writable
if can_access_or_create(&final_path) {
return final_path;
} else {
eprintln!(
"Warning: custom_db_path=\"{}\" is invalid or not writable. Falling back to default...",
custom_path
);
}
}
if cfg!(debug_assertions) {
let app_dir = APP_CONSTANTS.get().unwrap().app_dev_data_dir.clone();
let path = if cfg!(target_os = "macos") {
format!(
"{}/local.pastebar-db.data",
adjust_canonicalization(app_dir)
)
} else if cfg!(target_os = "windows") {
format!(
"{}\\local.pastebar-db.data",
adjust_canonicalization(app_dir)
)
} else {
format!(
"{}/local.pastebar-db.data",
adjust_canonicalization(app_dir)
)
};
path
if let Some(custom_path_str) = user_config.custom_db_path {
PathBuf::from(custom_path_str)
} else {
let app_data_dir = APP_CONSTANTS.get().unwrap().app_data_dir.clone();
let data_dir = app_data_dir.as_path();
let path = if cfg!(target_os = "macos") {
format!("{}/pastebar-db.data", adjust_canonicalization(data_dir))
} else if cfg!(target_os = "windows") {
format!("{}\\pastebar-db.data", adjust_canonicalization(data_dir))
} else {
format!("{}/pastebar-db.data", adjust_canonicalization(data_dir))
};
path
get_default_data_dir()
}
}
/// Returns the default application data directory.
pub fn get_default_data_dir() -> PathBuf {
if cfg!(debug_assertions) {
APP_CONSTANTS.get().unwrap().app_dev_data_dir.clone()
} else {
APP_CONSTANTS.get().unwrap().app_data_dir.clone()
}
}
pub fn get_db_path() -> String {
let db_path = get_data_dir().join("pastebar-db.data");
db_path.to_string_lossy().into_owned()
}
/// Returns the path to the `clip-images` directory.
pub fn get_clip_images_dir() -> PathBuf {
get_data_dir().join("clip-images")
}
/// Returns the path to the `clipboard-images` directory.
pub fn get_clipboard_images_dir() -> PathBuf {
get_data_dir().join("clipboard-images")
}
/// Returns the default database file path as a string.
pub fn get_default_db_path_string() -> String {
let db_path = get_default_data_dir().join("pastebar-db.data");
db_path.to_string_lossy().into_owned()
}
fn can_access_or_create(db_path: &str) -> bool {
let path = std::path::Path::new(db_path);
@ -298,33 +286,13 @@ fn can_access_or_create(db_path: &str) -> bool {
}
}
fn adjust_custom_db_path(custom_path: &str) -> String {
use std::path::PathBuf;
let path = PathBuf::from(custom_path);
match fs::metadata(&path) {
Ok(metadata) => {
if metadata.is_dir() {
// It's a directory, so append "pastebar-db.data" to it
let mut dir_path = path.clone();
dir_path.push("pastebar-db.data");
dir_path.to_string_lossy().into_owned()
} else {
// It's a file or symlink, so leave it as is
custom_path.to_string()
}
}
Err(_) => {
// If we cant read metadata (e.g. it doesn't exist yet),
// we treat `custom_path` as a file path already.
custom_path.to_string()
}
}
}
pub fn get_config_file_path() -> PathBuf {
if cfg!(debug_assertions) {
let app_dir = APP_CONSTANTS.get().unwrap().app_dev_data_dir.clone();
let app_dir = APP_CONSTANTS
.get()
.expect("APP_CONSTANTS not initialized")
.app_dev_data_dir
.clone();
if cfg!(target_os = "macos") {
PathBuf::from(format!(
"{}/pastebar_settings.yaml",

View File

@ -1185,8 +1185,12 @@ async fn main() {
security_commands::delete_os_password,
security_commands::get_stored_os_password,
user_settings_command::cmd_get_custom_db_path,
user_settings_command::cmd_set_custom_db_path,
user_settings_command::cmd_remove_custom_db_path,
// user_settings_command::cmd_set_custom_db_path, // Replaced by cmd_set_and_relocate_db
// user_settings_command::cmd_remove_custom_db_path, // Replaced by cmd_revert_to_default_db_location
user_settings_command::cmd_validate_custom_db_path,
user_settings_command::cmd_check_custom_data_path,
user_settings_command::cmd_set_and_relocate_data,
user_settings_command::cmd_revert_to_default_data_location,
user_settings_command::cmd_get_all_settings,
user_settings_command::cmd_get_setting,
user_settings_command::cmd_set_setting,

View File

@ -32,7 +32,7 @@ use std::path::{Path, PathBuf};
use std::io::Cursor;
use crate::db::APP_CONSTANTS;
use crate::db::{self, APP_CONSTANTS};
use crate::schema::clipboard_history;
use crate::schema::clipboard_history::dsl::*;
use crate::schema::link_metadata;
@ -226,11 +226,7 @@ pub fn add_clipboard_history_from_image(
let _history_id = nanoid!().to_string();
let folder_name = &_history_id[..3];
let base_dir = if cfg!(debug_assertions) {
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
} else {
&APP_CONSTANTS.get().unwrap().app_data_dir
};
let base_dir = db::get_clipboard_images_dir();
let (_image_width, _image_height) = image.dimensions();
@ -279,7 +275,7 @@ pub fn add_clipboard_history_from_image(
))
.execute(connection);
} else {
let folder_path = base_dir.join("clipboard-images").join(folder_name);
let folder_path = base_dir.join(folder_name);
ensure_dir_exists(&folder_path);
let image_file_name = folder_path.join(format!("{}.png", &_history_id));
@ -712,13 +708,7 @@ pub fn delete_recent_clipboard_history(
pub fn delete_all_clipboard_histories() -> String {
let connection = &mut establish_pool_db_connection();
let base_dir = if cfg!(debug_assertions) {
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
} else {
&APP_CONSTANTS.get().unwrap().app_data_dir
};
let folder_path = base_dir.join("clipboard-images");
let folder_path = db::get_clipboard_images_dir();
let _ = remove_dir_if_exists(&folder_path);

View File

@ -1,7 +1,7 @@
use std::fs;
use std::path::Path;
use crate::db::APP_CONSTANTS;
use crate::db::{self, APP_CONSTANTS};
use crate::models::models::UpdatedItemData;
use crate::models::Item;
use crate::services::utils::debug_output;
@ -509,13 +509,7 @@ pub fn add_image_to_item(item_id: &str, image_full_path: &str) -> Result<String,
let is_svg = extension == "svg";
let base_dir = if cfg!(debug_assertions) {
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
} else {
&APP_CONSTANTS.get().unwrap().app_data_dir
};
let folder_path = base_dir.join("clip-images").join(&item_id[..3]);
let folder_path = db::get_clip_images_dir().join(&item_id[..3]);
ensure_dir_exists(&folder_path);
let new_image_path = folder_path.join(format!("{}.{}", item_id, extension));
@ -636,13 +630,7 @@ pub fn save_item_image_from_history_item(
) -> Result<String, String> {
let folder_name = &item_id[..3];
let base_dir = if cfg!(debug_assertions) {
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
} else {
&APP_CONSTANTS.get().unwrap().app_data_dir
};
let folder_path = base_dir.join("clip-images").join(folder_name);
let folder_path = db::get_clip_images_dir().join(folder_name);
ensure_dir_exists(&folder_path);
let clip_image_file_name = folder_path.join(format!("{}.png", &item_id));
@ -684,13 +672,7 @@ pub fn upload_image_file_to_item_id(
let file_name = format!("{}.{}", item_id, extension);
let base_dir = if cfg!(debug_assertions) {
&APP_CONSTANTS.get().unwrap().app_dev_data_dir
} else {
&APP_CONSTANTS.get().unwrap().app_data_dir
};
let folder_path = base_dir.join("clip-images").join(&item_id[..3]);
let folder_path = db::get_clip_images_dir().join(&item_id[..3]);
ensure_dir_exists(&folder_path);
let image_path = folder_path.join(&file_name);