diff --git a/BACKUP_RESTORE_IMPLEMENTATION_PLAN.md b/BACKUP_RESTORE_IMPLEMENTATION_PLAN.md
new file mode 100644
index 00000000..257129eb
--- /dev/null
+++ b/BACKUP_RESTORE_IMPLEMENTATION_PLAN.md
@@ -0,0 +1,200 @@
+## Backup and Restore Feature Implementation Plan
+
+Based on my analysis of the PasteBar codebase, here's a comprehensive plan to implement the Backup and Restore functionality:
+
+### 1. **Frontend Implementation**
+
+#### A. Add Navigation Entry (in AppSettings.tsx)
+- Add a new NavLink for "Backup and Restore" between User Preferences and Security sections
+- Route path: `/app-settings/backup-restore`
+
+#### B. Create BackupRestoreSettings.tsx Component
+The component will include:
+- **Backup Section:**
+ - Checkbox options: "Include images" (checked by default)
+ - "Backup Now" button
+ - Progress indicator during backup
+ - **Confirmation dialog before backup:** "Create a backup of your data?"
+ - After backup: Dialog to move file or keep in current location
+
+- **Restore Section:**
+ - **"Restore from File" button** - Opens file picker to select backup from any location
+ - List of existing backup files (parsed from filesystem)
+ - Display: backup filename, date/time, file size
+ - **Total backup space indicator** at the top of the list
+ - "Restore" button for each backup
+ - **"Delete" button for each backup** (trash icon)
+ - **Confirmation dialog before restore:** "This will replace all current data. Are you sure?"
+ - **Confirmation dialog before delete:** "Delete this backup? This action cannot be undone."
+
+#### C. Update Router Configuration (pages/index.tsx)
+- Add route: `{ path: 'backup-restore', element: }`
+
+### 2. **Backend Implementation (Rust/Tauri)**
+
+#### A. Create Backup/Restore Commands Module
+`src-tauri/src/commands/backup_restore_commands.rs`:
+
+- `create_backup(include_images: bool)` - Creates zip file with:
+ - Database file (pastebar-db.data)
+ - clip-images/ folder (if include_images is true)
+ - history-images/ folder (if include_images is true)
+ - Returns: backup file path
+
+- `list_backups()` - Lists all backup files in data directory
+ - Returns: Vec with filename, date, size
+ - **Calculates total size of all backups**
+
+- `restore_backup(backup_path: String)` - Restores from backup:
+ - Validates zip file (works with both local and external paths)
+ - Creates temporary backup of current data
+ - Extracts and replaces database and image folders
+ - Returns: success/error status
+
+- `select_backup_file()` - Opens native file picker
+ - Filters for .zip files
+ - Returns selected file path
+ - Validates that it's a valid PasteBar backup
+
+- `delete_backup(backup_path: String)` - Deletes a backup file
+ - Validates file exists and is a backup
+ - Deletes the file
+ - Returns: success/error status
+
+- `get_data_paths()` - Gets current database and image folder paths
+ - Checks for custom data location setting
+ - Returns default or custom paths
+
+#### B. Update main.rs
+- Register new commands in the Tauri builder
+
+### 3. **File Structure and Naming**
+
+- Backup filename format: `pastebar-data-backup-YYYY-MM-DD-HH-mm.zip`
+- Default location: Same as database location (custom or default)
+- Zip structure:
+ ```
+ pastebar-data-backup-2024-01-06-14-30.zip
+ ├── pastebar-db.data
+ ├── clip-images/
+ │ └── [image files]
+ └── history-images/
+ └── [image files]
+ ```
+
+### 4. **UI/UX Flow**
+
+1. **Creating a Backup:**
+ - User navigates to Settings → Backup and Restore
+ - Selects backup options (include images or not)
+ - Clicks "Backup Now"
+ - **Confirmation dialog:** "Create a backup of your data?"
+ - Progress indicator shows during compression
+ - Dialog appears: "Backup created successfully. Move to another location?"
+ - Options: "Move...", "Keep in current location"
+
+2. **Restoring from List:**
+ - User sees list of available backups with total space used
+ - Clicks "Restore" on desired backup
+ - **Confirmation dialog:** "This will replace all current data. Are you sure?"
+ - Progress indicator during restore
+ - App automatically restarts after successful restore
+
+3. **Restoring from External File:**
+ - User clicks "Restore from File" button
+ - Native file picker opens (filtered for .zip files)
+ - User selects backup file from any location (external drive, cloud folder, etc.)
+ - File is validated as a PasteBar backup
+ - **Confirmation dialog:** "Restore from {{filename}}? This will replace all current data."
+ - Progress indicator during restore
+ - App automatically restarts after successful restore
+
+4. **Deleting a Backup:**
+ - User clicks delete (trash) icon on a backup
+ - **Confirmation dialog:** "Delete this backup? This action cannot be undone."
+ - Backup is deleted and list refreshes
+ - Total backup space updates
+
+### 5. **UI Layout**
+
+```
+Backup and Restore
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+Create Backup
+─────────────
+☑ Include images in backup
+[Backup Now]
+
+Restore Data
+────────────
+[Restore from File...] ← Opens file picker
+
+Available Backups (Total: 152.3 MB)
+───────────────────────────────────
+📦 pastebar-data-backup-2024-01-06-14-30.zip
+ Created: January 6, 2024 at 2:30 PM
+ Size: 25.4 MB
+ [Restore] [🗑️]
+
+📦 pastebar-data-backup-2024-01-05-09-15.zip
+ Created: January 5, 2024 at 9:15 AM
+ Size: 23.1 MB
+ [Restore] [🗑️]
+```
+
+### 6. **Translations to Add**
+
+Settings page titles and descriptions:
+- "Backup and Restore"
+- "Create Backup"
+- "Include images in backup"
+- "Backup Now"
+- "Restore Data"
+- "Restore from File..."
+- "Select backup file"
+- "Available Backups"
+- "Total backup space: {{size}}"
+- "No backups found"
+- "Restore"
+- "Delete"
+- "Create a backup of your data?"
+- "Backup created successfully"
+- "Move to another location?"
+- "This will replace all current data. Are you sure?"
+- "Restore from {{filename}}? This will replace all current data."
+- "Delete this backup? This action cannot be undone."
+- "Restore completed. The application will restart."
+- "Creating backup..."
+- "Restoring backup..."
+- "Backup deleted successfully"
+- "Failed to delete backup"
+- "Invalid backup file"
+- "The selected file is not a valid PasteBar backup"
+
+### 7. **Error Handling**
+
+- Handle insufficient disk space
+- Validate zip file integrity before restore
+- Verify backup contains expected files (pastebar-db.data)
+- Create automatic backup before restore operation
+- Handle file permission errors
+- Rollback on restore failure
+- Prevent deletion of backup that's currently being restored
+- Handle corrupted or incomplete backup files
+- Validate external backup files are from PasteBar
+
+### 8. **Implementation Order**
+
+1. Create backend commands and file operations
+2. Add frontend navigation and basic UI
+3. Implement backup creation flow with confirmation
+4. Implement backup listing with total space calculation
+5. Implement restore from list functionality with confirmation
+6. Implement "Restore from File" with file picker
+7. Implement delete functionality with confirmation
+8. Add all confirmation dialogs
+9. Add translations for all languages
+10. Test complete workflow including external file restore
+
+This implementation provides maximum flexibility for users to manage their backups, whether stored locally or on external drives/cloud storage, while maintaining data safety through multiple confirmation dialogs and validation checks.
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..89f7f962
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,184 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+PasteBar is a cross-platform clipboard manager built with Tauri (Rust + TypeScript/React). It provides unlimited clipboard history, custom clip management, collections, and advanced features like programming language detection and web scraping.
+
+**Technology Stack:**
+- **Backend**: Rust with Tauri framework, Diesel ORM (SQLite), Reqwest, Serde, Tokio
+- **Frontend**: TypeScript, React, React Query, Vite, TailwindCSS, Jotai, Zustand
+- **Platforms**: macOS and Windows (including Apple Silicon M1, Intel, AMD, and ARM)
+
+## Development Commands
+
+### Prerequisites
+First install the Diesel CLI:
+```bash
+cargo install diesel_cli --no-default-features --features sqlite
+```
+
+### Main Development Commands
+```bash
+# Development (starts both frontend and backend in dev mode)
+npm start
+# or
+npm run dev
+
+# Build for production
+npm run build
+
+# Build debug version
+npm run app:build:debug
+
+# Platform-specific builds
+npm run app:build:osx:universal
+npm run app:build:osx:x86_64
+npm run app:build:windows:arm
+
+# Database migrations
+npm run diesel:migration:run
+
+# Code formatting
+npm run format
+
+# Version management
+npm run version:sync
+```
+
+### Frontend Development (packages/pastebar-app-ui/)
+The frontend is a workspace package that builds separately:
+```bash
+cd packages/pastebar-app-ui
+npm run dev # Development server on port 4422
+npm run build # Build to dist-ui/
+```
+
+### Rust/Tauri Development (src-tauri/)
+```bash
+cd src-tauri
+cargo run --no-default-features # Development mode
+cargo build --release # Production build
+```
+
+## Architecture Overview
+
+### High-Level Structure
+
+**Tauri Architecture**: The app uses Tauri's hybrid architecture where:
+- Rust backend handles core functionality (clipboard monitoring, database operations, system integration)
+- TypeScript/React frontend provides the UI
+- Communication happens via Tauri commands and events
+
+**Core Components:**
+
+1. **Clipboard Monitoring** (`src-tauri/src/clipboard/mod.rs`)
+ - Real-time clipboard monitoring using `clipboard-master`
+ - Automatic image capture and text processing
+ - Language detection for code snippets
+ - Configurable exclusion lists and masking
+
+2. **Database Layer** (`src-tauri/src/db.rs` + Diesel)
+ - SQLite database with migrations in `migrations/`
+ - Custom data location support with path transformation
+ - Connection pooling with r2d2
+
+3. **System Integration** (`src-tauri/src/main.rs`)
+ - System tray menu with dynamic content
+ - Global hotkeys and window management
+ - Platform-specific features (macOS accessibility, Windows compatibility)
+
+4. **State Management** (Frontend)
+ - Jotai for atomic state management
+ - Zustand stores for settings, collections, clipboard history
+ - React Query for server state and caching
+
+### Key Patterns
+
+**Path Transformation System**:
+- Images are stored with `{{base_folder}}` placeholders for relative paths
+- `to_relative_image_path()` and `to_absolute_image_path()` handle conversion
+- Enables custom database locations without breaking image references
+
+**Event-Driven Communication**:
+- Tauri events for real-time updates between backend and frontend
+- Settings synchronization across multiple windows
+- Menu rebuilding on state changes
+
+**Multi-Window Architecture**:
+- Main window (primary interface)
+- History window (clipboard history view)
+- QuickPaste window (contextual paste menu)
+
+### Database Schema
+
+Main entities:
+- `items` - Custom clips and menu items
+- `clipboard_history` - Automatic clipboard captures
+- `collections` - Organization containers
+- `tabs` - Sub-organization within collections
+- `link_metadata` - Web scraping and link preview data
+- `settings` - User preferences and configuration
+
+### Frontend Structure
+
+```
+packages/pastebar-app-ui/src/
+├── components/ # Reusable UI components
+├── pages/ # Main application pages
+├── store/ # State management (Jotai + Zustand)
+├── hooks/ # Custom React hooks
+├── lib/ # Utilities and helpers
+├── locales/ # Internationalization
+└── assets/ # Static assets
+```
+
+### Backend Structure
+
+```
+src-tauri/src/
+├── commands/ # Tauri command handlers
+├── services/ # Business logic layer
+├── models/ # Database models
+├── clipboard/ # Clipboard monitoring
+├── menu.rs # System tray menu
+├── db.rs # Database configuration
+└── main.rs # Application entry point
+```
+
+## Important Development Notes
+
+### Settings Management
+- Settings are stored as generic key-value pairs in the database
+- Frontend uses `settingsStore.ts` with automatic synchronization
+- Use `updateSetting()` function and include `invoke('build_system_menu')` for settings that affect the system tray
+
+### Custom Data Locations
+- The app supports custom database locations via user settings
+- All file operations must use `get_data_dir()`, `get_clip_images_dir()`, etc.
+- Path transformation ensures image references work across location changes
+
+### Image Handling
+- Images are stored in both thumbnail and full resolution
+- Use path transformation helpers when storing/retrieving image paths
+- Images support relative paths with `{{base_folder}}` placeholders
+
+### Internationalization
+- Backend translations in `src-tauri/src/services/translations/translations.yaml`
+- Frontend translations in `packages/pastebar-app-ui/src/locales/lang/`
+- Use `t()` function in React components and `Translations::get()` in Rust
+
+### Debug Logging
+- Use `debug_output(|| { println!("message") })` in Rust for debug-only logging
+- Debug messages only appear in debug builds, keeping release builds clean
+
+### System Tray Menu
+- Dynamic menu built from database items and settings
+- Rebuild required when items or relevant settings change
+- Use `invoke('build_system_menu')` after operations that affect menu content
+
+### Database Migrations
+- Use Diesel migrations for schema changes
+- Place migration files in `migrations/` directory
+- Run migrations with `npm run diesel:migration:run`
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/en/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/en/backuprestore.yaml
new file mode 100644
index 00000000..1d7a2b27
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/en/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Backup and Restore
+Create Backup: Create Backup
+Include images in backup: Include images in backup
+Backup Now: Backup Now
+Restore Data: Restore Data
+Restore from File...: Restore from File...
+Select backup file: Select backup file
+Available Backups: Available Backups
+Total backup space: "{{size}}": Total backup space: {{size}}
+No backups found: No backups found
+Restore: Restore
+Delete: Delete
+Create a backup of your data?: Create a backup of your data?
+Backup created successfully: Backup created successfully
+Move to another location?: Move to another location?
+This will replace all current data. Are you sure?: This will replace all current data. Are you sure?
+Restore from "{{filename}}"? This will replace all current data.: Restore from {{filename}}? This will replace all current data.
+Delete this backup? This action cannot be undone.: Delete this backup? This action cannot be undone.
+Restore completed. The application will restart.: Restore completed. The application will restart.
+Creating backup...: Creating backup...
+Restoring backup...: Restoring backup...
+Backup deleted successfully: Backup deleted successfully
+Failed to delete backup: Failed to delete backup
+Invalid backup file: Invalid backup file
+The selected file is not a valid PasteBar backup: The selected file is not a valid PasteBar backup
+Created: Created
+Size: Size
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/esES/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/esES/backuprestore.yaml
new file mode 100644
index 00000000..a9f82d88
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/esES/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Copia de Seguridad y Restauración
+Create Backup: Crear Copia de Seguridad
+Include images in backup: Incluir imágenes en la copia de seguridad
+Backup Now: Crear Copia Ahora
+Restore Data: Restaurar Datos
+Restore from File...: Restaurar desde Archivo...
+Select backup file: Seleccionar archivo de copia de seguridad
+Available Backups: Copias de Seguridad Disponibles
+Total backup space: "{{size}}": Espacio total de copias: {{size}}
+No backups found: No se encontraron copias de seguridad
+Restore: Restaurar
+Delete: Eliminar
+Create a backup of your data?: ¿Crear una copia de seguridad de tus datos?
+Backup created successfully: Copia de seguridad creada exitosamente
+Move to another location?: ¿Mover a otra ubicación?
+This will replace all current data. Are you sure?: Esto reemplazará todos los datos actuales. ¿Estás seguro?
+Restore from "{{filename}}"? This will replace all current data.: ¿Restaurar desde {{filename}}? Esto reemplazará todos los datos actuales.
+Delete this backup? This action cannot be undone.: ¿Eliminar esta copia de seguridad? Esta acción no se puede deshacer.
+Restore completed. The application will restart.: Restauración completada. La aplicación se reiniciará.
+Creating backup...: Creando copia de seguridad...
+Restoring backup...: Restaurando copia de seguridad...
+Backup deleted successfully: Copia de seguridad eliminada exitosamente
+Failed to delete backup: Error al eliminar la copia de seguridad
+Invalid backup file: Archivo de copia de seguridad inválido
+The selected file is not a valid PasteBar backup: El archivo seleccionado no es una copia de seguridad válida de PasteBar
+Created: Creado
+Size: Tamaño
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/fr/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/fr/backuprestore.yaml
new file mode 100644
index 00000000..44f38726
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/fr/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Sauvegarde et Restauration
+Create Backup: Créer une Sauvegarde
+Include images in backup: Inclure les images dans la sauvegarde
+Backup Now: Sauvegarder Maintenant
+Restore Data: Restaurer les Données
+Restore from File...: Restaurer depuis un Fichier...
+Select backup file: Sélectionner le fichier de sauvegarde
+Available Backups: Sauvegardes Disponibles
+Total backup space: "{{size}}": Espace total des sauvegardes: {{size}}
+No backups found: Aucune sauvegarde trouvée
+Restore: Restaurer
+Delete: Supprimer
+Create a backup of your data?: Créer une sauvegarde de vos données?
+Backup created successfully: Sauvegarde créée avec succès
+Move to another location?: Déplacer vers un autre emplacement?
+This will replace all current data. Are you sure?: Ceci remplacera toutes les données actuelles. Êtes-vous sûr?
+Restore from "{{filename}}"? This will replace all current data.: Restaurer depuis {{filename}}? Ceci remplacera toutes les données actuelles.
+Delete this backup? This action cannot be undone.: Supprimer cette sauvegarde? Cette action ne peut pas être annulée.
+Restore completed. The application will restart.: Restauration terminée. L'application va redémarrer.
+Creating backup...: Création de la sauvegarde...
+Restoring backup...: Restauration de la sauvegarde...
+Backup deleted successfully: Sauvegarde supprimée avec succès
+Failed to delete backup: Échec de la suppression de la sauvegarde
+Invalid backup file: Fichier de sauvegarde invalide
+The selected file is not a valid PasteBar backup: Le fichier sélectionné n'est pas une sauvegarde PasteBar valide
+Created: Créé
+Size: Taille
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/fr/settings.yaml b/packages/pastebar-app-ui/src/locales/lang/fr/settings.yaml
index e01c6fa7..199afb67 100644
--- a/packages/pastebar-app-ui/src/locales/lang/fr/settings.yaml
+++ b/packages/pastebar-app-ui/src/locales/lang/fr/settings.yaml
@@ -181,4 +181,4 @@ User Preferences: Préférences utilisateur
Web Scraping and Parsing: Extraction et analyse web
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Les mots ou phrases énumérés ci-dessous ne seront pas capturés dans l'historique du presse-papiers s'ils se trouvent dans le texte copié. Insensible à la casse
passcode reset: réinitialisation code d'accès
-password reset: réinitialisation mot de passe
\ No newline at end of file
+password reset: réinitialisation mot de passe
diff --git a/packages/pastebar-app-ui/src/locales/lang/it/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/it/backuprestore.yaml
new file mode 100644
index 00000000..4d36a22b
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/it/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Backup e Ripristino
+Create Backup: Crea Backup
+Include images in backup: Includi immagini nel backup
+Backup Now: Crea Backup Ora
+Restore Data: Ripristina Dati
+Restore from File...: Ripristina da File...
+Select backup file: Seleziona file di backup
+Available Backups: Backup Disponibili
+Total backup space: "{{size}}": Spazio totale backup: {{size}}
+No backups found: Nessun backup trovato
+Restore: Ripristina
+Delete: Elimina
+Create a backup of your data?: Creare un backup dei tuoi dati?
+Backup created successfully: Backup creato con successo
+Move to another location?: Spostare in un'altra posizione?
+This will replace all current data. Are you sure?: Questo sostituirà tutti i dati attuali. Sei sicuro?
+Restore from "{{filename}}"? This will replace all current data.: Ripristinare da {{filename}}? Questo sostituirà tutti i dati attuali.
+Delete this backup? This action cannot be undone.: Eliminare questo backup? Questa azione non può essere annullata.
+Restore completed. The application will restart.: Ripristino completato. L'applicazione si riavvierà.
+Creating backup...: Creazione backup...
+Restoring backup...: Ripristino backup...
+Backup deleted successfully: Backup eliminato con successo
+Failed to delete backup: Impossibile eliminare il backup
+Invalid backup file: File di backup non valido
+The selected file is not a valid PasteBar backup: Il file selezionato non è un backup PasteBar valido
+Created: Creato
+Size: Dimensione
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/it/settings.yaml b/packages/pastebar-app-ui/src/locales/lang/it/settings.yaml
index 5b11033c..4ca4a35e 100644
--- a/packages/pastebar-app-ui/src/locales/lang/it/settings.yaml
+++ b/packages/pastebar-app-ui/src/locales/lang/it/settings.yaml
@@ -180,4 +180,4 @@ User Preferences: Preferenze Utente
Web Scraping and Parsing: Web Scraping e Parsing
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Le parole o frasi elencate di seguito non verranno catturate nella cronologia degli appunti se trovate nel testo copiato. Non sensibile alle maiuscole/minuscole.
passcode reset: reset del codice di accesso
-password reset: reset della password
\ No newline at end of file
+password reset: reset della password
diff --git a/packages/pastebar-app-ui/src/locales/lang/ru/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/ru/backuprestore.yaml
new file mode 100644
index 00000000..7fcc4e84
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/ru/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Резервное копирование и восстановление
+Create Backup: Создать резервную копию
+Include images in backup: Включить изображения в резервную копию
+Backup Now: Создать резервную копию сейчас
+Restore Data: Восстановить данные
+Restore from File...: Восстановить из файла...
+Select backup file: Выбрать файл резервной копии
+Available Backups: Доступные резервные копии
+Total backup space: "{{size}}": Общий размер резервных копий: {{size}}
+No backups found: Резервные копии не найдены
+Restore: Восстановить
+Delete: Удалить
+Create a backup of your data?: Создать резервную копию ваших данных?
+Backup created successfully: Резервная копия успешно создана
+Move to another location?: Переместить в другое место?
+This will replace all current data. Are you sure?: Это заменит все текущие данные. Вы уверены?
+Restore from "{{filename}}"? This will replace all current data.: Восстановить из {{filename}}? Это заменит все текущие данные.
+Delete this backup? This action cannot be undone.: Удалить эту резервную копию? Это действие нельзя отменить.
+Restore completed. The application will restart.: Восстановление завершено. Приложение перезапустится.
+Creating backup...: Создание резервной копии...
+Restoring backup...: Восстановление резервной копии...
+Backup deleted successfully: Резервная копия успешно удалена
+Failed to delete backup: Не удалось удалить резервную копию
+Invalid backup file: Недействительный файл резервной копии
+The selected file is not a valid PasteBar backup: Выбранный файл не является действительной резервной копией PasteBar
+Created: Создано
+Size: Размер
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/tr/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/tr/backuprestore.yaml
new file mode 100644
index 00000000..e8e6de0b
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/tr/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Yedekleme ve Geri Yükleme
+Create Backup: Yedek Oluştur
+Include images in backup: Yedekte görselleri dahil et
+Backup Now: Şimdi Yedekle
+Restore Data: Verileri Geri Yükle
+Restore from File...: Dosyadan Geri Yükle...
+Select backup file: Yedek dosyası seç
+Available Backups: Mevcut Yedekler
+Total backup space: "{{size}}": Toplam yedek alanı: {{size}}
+No backups found: Yedek bulunamadı
+Restore: Geri Yükle
+Delete: Sil
+Create a backup of your data?: Verilerinizin bir yedeğini oluşturmak istiyor musunuz?
+Backup created successfully: Yedek başarıyla oluşturuldu
+Move to another location?: Başka bir konuma taşı?
+This will replace all current data. Are you sure?: Bu, mevcut tüm verileri değiştirecek. Emin misiniz?
+Restore from "{{filename}}"? This will replace all current data.: {{filename}} dosyasından geri yüklensin mi? Bu, mevcut tüm verileri değiştirecek.
+Delete this backup? This action cannot be undone.: Bu yedeği sil? Bu eylem geri alınamaz.
+Restore completed. The application will restart.: Geri yükleme tamamlandı. Uygulama yeniden başlatılacak.
+Creating backup...: Yedek oluşturuluyor...
+Restoring backup...: Yedek geri yükleniyor...
+Backup deleted successfully: Yedek başarıyla silindi
+Failed to delete backup: Yedek silinemedi
+Invalid backup file: Geçersiz yedek dosyası
+The selected file is not a valid PasteBar backup: Seçilen dosya geçerli bir PasteBar yedeği değil
+Created: Oluşturulma
+Size: Boyut
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/uk/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/uk/backuprestore.yaml
new file mode 100644
index 00000000..7764bab8
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/uk/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: Резервне копіювання та відновлення
+Create Backup: Створити резервну копію
+Include images in backup: Включити зображення в резервну копію
+Backup Now: Створити резервну копію зараз
+Restore Data: Відновити дані
+Restore from File...: Відновити з файлу...
+Select backup file: Вибрати файл резервної копії
+Available Backups: Доступні резервні копії
+Total backup space: "{{size}}": Загальний розмір резервних копій: {{size}}
+No backups found: Резервні копії не знайдені
+Restore: Відновити
+Delete: Видалити
+Create a backup of your data?: Створити резервну копію ваших даних?
+Backup created successfully: Резервну копію успішно створено
+Move to another location?: Перемістити в інше місце?
+This will replace all current data. Are you sure?: Це замінить всі поточні дані. Ви впевнені?
+Restore from "{{filename}}"? This will replace all current data.: Відновити з {{filename}}? Це замінить всі поточні дані.
+Delete this backup? This action cannot be undone.: Видалити цю резервну копію? Цю дію неможливо скасувати.
+Restore completed. The application will restart.: Відновлення завершено. Додаток перезапуститься.
+Creating backup...: Створення резервної копії...
+Restoring backup...: Відновлення резервної копії...
+Backup deleted successfully: Резервну копію успішно видалено
+Failed to delete backup: Не вдалося видалити резервну копію
+Invalid backup file: Недійсний файл резервної копії
+The selected file is not a valid PasteBar backup: Вибраний файл не є дійсною резервною копією PasteBar
+Created: Створено
+Size: Розмір
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/uk/settings.yaml b/packages/pastebar-app-ui/src/locales/lang/uk/settings.yaml
index 1b1abd21..6074d5a6 100644
--- a/packages/pastebar-app-ui/src/locales/lang/uk/settings.yaml
+++ b/packages/pastebar-app-ui/src/locales/lang/uk/settings.yaml
@@ -164,4 +164,29 @@ User Preferences: Користувацькі налаштування
Web Scraping and Parsing: Веб-скрейпінг та аналіз
Words or sentences listed below will not be captured in clipboard history if found in the copied text. Case insensitive.: Слова або речення, перелічені нижче, не будуть захоплюватися в історію буфера обміну, якщо вони будуть знайдені в скопійованому тексті. Без урахування регістру.
passcode reset: скидання коду доступу
-password reset: скидання пароля
\ No newline at end of file
+password reset: скидання пароля
+Backup and Restore: Резервне копіювання та відновлення
+Create Backup: Створити резервну копію
+Include images in backup: Включити зображення в резервну копію
+Backup Now: Створити резервну копію зараз
+Restore Data: Відновити дані
+Restore from File...: Відновити з файлу...
+Select backup file: Вибрати файл резервної копії
+Available Backups: Доступні резервні копії
+Total backup space: "{{size}}": Загальний розмір резервних копій: {{size}}
+No backups found: Резервні копії не знайдені
+Restore: Відновити
+Delete: Видалити
+Create a backup of your data?: Створити резервну копію ваших даних?
+Backup created successfully: Резервну копію успішно створено
+Move to another location?: Перемістити в інше місце?
+This will replace all current data. Are you sure?: Це замінить всі поточні дані. Ви впевнені?
+Restore from "{{filename}}"? This will replace all current data.: Відновити з {{filename}}? Це замінить всі поточні дані.
+Delete this backup? This action cannot be undone.: Видалити цю резервну копію? Цю дію неможливо скасувати.
+Restore completed. The application will restart.: Відновлення завершено. Додаток перезапуститься.
+Creating backup...: Створення резервної копії...
+Restoring backup...: Відновлення резервної копії...
+Backup deleted successfully: Резервну копію успішно видалено
+Failed to delete backup: Не вдалося видалити резервну копію
+Invalid backup file: Недійсний файл резервної копії
+The selected file is not a valid PasteBar backup: Вибраний файл не є дійсною резервною копією PasteBar
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/locales/lang/zhCN/backuprestore.yaml b/packages/pastebar-app-ui/src/locales/lang/zhCN/backuprestore.yaml
new file mode 100644
index 00000000..e723b9d5
--- /dev/null
+++ b/packages/pastebar-app-ui/src/locales/lang/zhCN/backuprestore.yaml
@@ -0,0 +1,27 @@
+Backup and Restore: 备份和恢复
+Create Backup: 创建备份
+Include images in backup: 在备份中包含图片
+Backup Now: 立即备份
+Restore Data: 恢复数据
+Restore from File...: 从文件恢复...
+Select backup file: 选择备份文件
+Available Backups: 可用备份
+Total backup space: "{{size}}": 备份总空间: {{size}}
+No backups found: 未找到备份
+Restore: 恢复
+Delete: 删除
+Create a backup of your data?: 创建您数据的备份?
+Backup created successfully: 备份创建成功
+Move to another location?: 移动到其他位置?
+This will replace all current data. Are you sure?: 这将替换所有当前数据。您确定吗?
+Restore from "{{filename}}"? This will replace all current data.: 从{{filename}}恢复?这将替换所有当前数据。
+Delete this backup? This action cannot be undone.: 删除此备份?此操作无法撤销。
+Restore completed. The application will restart.: 恢复完成。应用程序将重新启动。
+Creating backup...: 正在创建备份...
+Restoring backup...: 正在恢复备份...
+Backup deleted successfully: 备份删除成功
+Failed to delete backup: 删除备份失败
+Invalid backup file: 无效的备份文件
+The selected file is not a valid PasteBar backup: 选择的文件不是有效的PasteBar备份
+Created: 创建时间
+Size: 大小
\ No newline at end of file
diff --git a/packages/pastebar-app-ui/src/pages/index.tsx b/packages/pastebar-app-ui/src/pages/index.tsx
index 118d4cff..b6dccf7b 100644
--- a/packages/pastebar-app-ui/src/pages/index.tsx
+++ b/packages/pastebar-app-ui/src/pages/index.tsx
@@ -3,6 +3,7 @@ import { Navigate, RouteObject } from 'react-router-dom'
import ClipboardHistoryPage from './main/ClipboardHistoryPage'
import PasteMenuPage from './main/PasteMenuPage'
import AppSettingsPage from './settings/AppSettings'
+import BackupRestoreSettings from './settings/BackupRestoreSettings'
import ClipboardHistorySettings from './settings/ClipboardHistorySettings'
import ManageCollections from './settings/collections/ManageCollections'
import SecuritySettings from './settings/SecuritySettings'
@@ -32,6 +33,7 @@ export default [
{ path: 'items', element: },
{ path: 'history', element: },
{ path: 'preferences', element: },
+ { path: 'backup-restore', element: },
{ path: 'security', element: },
],
},
diff --git a/packages/pastebar-app-ui/src/pages/settings/AppSettings.tsx b/packages/pastebar-app-ui/src/pages/settings/AppSettings.tsx
index dbb3c2a6..59d474f7 100644
--- a/packages/pastebar-app-ui/src/pages/settings/AppSettings.tsx
+++ b/packages/pastebar-app-ui/src/pages/settings/AppSettings.tsx
@@ -89,6 +89,23 @@ export default function AppSettingsPage() {
)}
+
+ {({ isActive }) => (
+
+ {t('Backup and Restore', { ns: 'backuprestore' })}
+
+ )}
+
+
([])
+ const [totalSize, setTotalSize] = useState('')
+ const [isLoadingBackups, setIsLoadingBackups] = useState(false)
+
+ const loadBackups = async () => {
+ setIsLoadingBackups(true)
+ try {
+ const result = await invoke('list_backups')
+ setBackups(result.backups)
+ setTotalSize(result.total_size_formatted)
+ } catch (error) {
+ console.error('Failed to load backups:', error)
+ toast({
+ title: t('Error', { ns: 'common' }),
+ description: 'Failed to load backup list',
+ variant: 'destructive',
+ })
+ } finally {
+ setIsLoadingBackups(false)
+ }
+ }
+
+ useEffect(() => {
+ loadBackups()
+ }, [])
+
+ const handleCreateBackup = async () => {
+ setIsCreatingBackup(true)
+ try {
+ const backupPath = await invoke('create_backup', {
+ includeImages,
+ })
+
+ toast({
+ title: t('Backup created successfully', { ns: 'backuprestore' }),
+ description: backupPath,
+ })
+
+ // Reload backup list
+ await loadBackups()
+ } catch (error) {
+ console.error('Failed to create backup:', error)
+ toast({
+ title: t('Error', { ns: 'common' }),
+ description: `Failed to create backup: ${error}`,
+ variant: 'destructive',
+ })
+ } finally {
+ setIsCreatingBackup(false)
+ }
+ }
+
+ const handleRestoreBackup = async (backupPath: string, filename: string) => {
+ setIsRestoring(true)
+ try {
+ await invoke('restore_backup', { backupPath })
+
+ toast({
+ title: t('Restore completed. The application will restart.', { ns: 'backuprestore' }),
+ description: `Restored from ${filename}`,
+ })
+
+ // Application should restart after restore
+ } catch (error) {
+ console.error('Failed to restore backup:', error)
+ toast({
+ title: t('Error', { ns: 'common' }),
+ description: `Failed to restore backup: ${error}`,
+ variant: 'destructive',
+ })
+ } finally {
+ setIsRestoring(false)
+ }
+ }
+
+ const handleRestoreFromFile = async () => {
+ try {
+ const selectedFile = await invoke('select_backup_file')
+
+ if (selectedFile) {
+ const filename = selectedFile.split(/[/\\]/).pop() || 'selected file'
+ await handleRestoreBackup(selectedFile, filename)
+ }
+ } catch (error) {
+ console.error('Failed to select backup file:', error)
+ toast({
+ title: t('Error', { ns: 'common' }),
+ description: `Failed to select backup file: ${error}`,
+ variant: 'destructive',
+ })
+ }
+ }
+
+ const handleDeleteBackup = async (backupPath: string, filename: string) => {
+ try {
+ await invoke('delete_backup', { backupPath })
+
+ toast({
+ title: t('Backup deleted successfully', { ns: 'backuprestore' }),
+ description: filename,
+ })
+
+ // Reload backup list
+ await loadBackups()
+ } catch (error) {
+ console.error('Failed to delete backup:', error)
+ toast({
+ title: t('Failed to delete backup', { ns: 'backuprestore' }),
+ description: `${error}`,
+ variant: 'destructive',
+ })
+ }
+ }
+
+ return (
+
+
+ {({ height }) => (
+
+
+ {/* Header */}
+
+
+
+ {t('Backup and Restore', { ns: 'backuprestore' })}
+
+
+
+ {/* Create Backup Section */}
+
+
+
+
+ {t('Create Backup', { ns: 'backuprestore' })}
+
+
+
+
+ setIncludeImages(checked as boolean)}
+ />
+
+
+
+
+
+
+
+
+
+
+ {t('Create a backup of your data?', { ns: 'backuprestore' })}
+
+
+ This will create a backup file containing your database
+ {includeImages ? ' and images' : ''}.
+
+
+
+ {t('Cancel', { ns: 'common' })}
+
+ {t('Create Backup', { ns: 'backuprestore' })}
+
+
+
+
+
+
+
+ {/* Restore Section */}
+
+
+
+
+ {t('Restore Data', { ns: 'backuprestore' })}
+
+
+
+
+
+
+
+ {/* Available Backups */}
+
+
+
+ {t('Available Backups', { ns: 'backuprestore' })}
+
+ {totalSize && (
+
+ {t('Total backup space: {{size}}', {
+ ns: 'settings',
+ size: totalSize
+ })}
+
+ )}
+
+
+ {isLoadingBackups ? (
+
+
+ Loading backups...
+
+ ) : backups.length === 0 ? (
+
+
+ {t('No backups found', { ns: 'backuprestore' })}
+
+ ) : (
+
+ {backups.map((backup) => (
+
+
+
+
+
+ {backup.filename}
+
+
+ {t('Created', { ns: 'common' })}: {backup.created_date}
+
+
+ {t('Size', { ns: 'common' })}: {backup.size_formatted}
+
+
+
+
+
+
+
+
+
+
+
+ {t('Restore from {{filename}}? This will replace all current data.', {
+ ns: 'settings',
+ filename: backup.filename
+ })}
+
+
+ This action cannot be undone. All current data will be replaced with the backup data.
+
+
+
+ {t('Cancel', { ns: 'common' })}
+ handleRestoreBackup(backup.full_path, backup.filename)}
+ className="bg-red-600 hover:bg-red-700"
+ >
+ {t('Restore', { ns: 'backuprestore' })}
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('Delete this backup? This action cannot be undone.', { ns: 'backuprestore' })}
+
+
+ {backup.filename} ({backup.size_formatted}) will be permanently deleted.
+
+
+
+ {t('Cancel', { ns: 'common' })}
+ handleDeleteBackup(backup.full_path, backup.filename)}
+ className="bg-red-600 hover:bg-red-700"
+ >
+ {t('Delete', { ns: 'backuprestore' })}
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/pastebar_settings.yaml b/pastebar_settings.yaml
new file mode 100644
index 00000000..ab8df09e
--- /dev/null
+++ b/pastebar_settings.yaml
@@ -0,0 +1,2 @@
+custom_db_path: null
+data: {}
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 020ec3a9..26e18684 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -428,6 +428,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "base64ct"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
+
[[package]]
name = "bcrypt"
version = "0.15.1"
@@ -622,6 +628,26 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
[[package]]
name = "cairo-rs"
version = "0.15.12"
@@ -670,6 +696,10 @@ name = "cc"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
+dependencies = [
+ "jobserver",
+ "libc",
+]
[[package]]
name = "cesu8"
@@ -922,6 +952,12 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
[[package]]
name = "convert_case"
version = "0.4.0"
@@ -2780,6 +2816,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+[[package]]
+name = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
@@ -3867,6 +3912,17 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "password-hash"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
[[package]]
name = "pastebar-app"
version = "0.0.1"
@@ -3939,6 +3995,7 @@ dependencies = [
"winapi",
"window-state",
"winreg 0.52.0",
+ "zip",
]
[[package]]
@@ -3947,6 +4004,18 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+ "hmac",
+ "password-hash",
+ "sha2",
+]
+
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@@ -7482,9 +7551,47 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
+ "aes",
"byteorder",
+ "bzip2",
+ "constant_time_eq",
"crc32fast",
"crossbeam-utils",
+ "flate2",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+ "time",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.11.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "5.0.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.15+zstd.1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
+dependencies = [
+ "cc",
+ "pkg-config",
]
[[package]]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 6d5c2623..cb365d52 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -85,6 +85,7 @@ tl = { version = "0.7.7" }
tld = "2.33.0"
url = "2.4.1"
html-escape = "0.2.13"
+zip = "0.6"
[target.'cfg(target_os = "macos")'.dependencies]
macos-accessibility-client = { git = "https://github.com/kurdin/macos-accessibility-client", branch = "master", version = "0.0.1" }
diff --git a/src-tauri/src/commands/backup_restore_commands.rs b/src-tauri/src/commands/backup_restore_commands.rs
new file mode 100644
index 00000000..84092781
--- /dev/null
+++ b/src-tauri/src/commands/backup_restore_commands.rs
@@ -0,0 +1,389 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::io::Write;
+use chrono::{DateTime, Local};
+use serde::{Deserialize, Serialize};
+use tauri::api::dialog::FileDialogBuilder;
+use zip::{ZipWriter, ZipArchive};
+use zip::write::FileOptions;
+use std::io::{Read, Seek};
+
+use crate::db::APP_CONSTANTS;
+use crate::services::utils::debug_output;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct BackupInfo {
+ pub filename: String,
+ pub full_path: String,
+ pub created_date: String,
+ pub size: u64,
+ pub size_formatted: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct BackupListResponse {
+ pub backups: Vec,
+ pub total_size: u64,
+ pub total_size_formatted: String,
+}
+
+fn get_data_dir() -> PathBuf {
+ // Get current data directory - could be custom or default
+ APP_CONSTANTS.get().unwrap().app_data_dir.clone()
+}
+
+fn get_backup_filename() -> String {
+ let now = Local::now();
+ format!("pastebar-data-backup-{}.zip", now.format("%Y-%m-%d-%H-%M"))
+}
+
+fn format_file_size(size: u64) -> String {
+ const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
+ let mut size_f = size as f64;
+ let mut unit_index = 0;
+
+ while size_f >= 1024.0 && unit_index < UNITS.len() - 1 {
+ size_f /= 1024.0;
+ unit_index += 1;
+ }
+
+ if unit_index == 0 {
+ format!("{} {}", size, UNITS[unit_index])
+ } else {
+ format!("{:.1} {}", size_f, UNITS[unit_index])
+ }
+}
+
+fn add_directory_to_zip(
+ zip: &mut ZipWriter,
+ dir_path: &Path,
+ base_path: &Path,
+) -> Result<(), Box> {
+ if !dir_path.exists() {
+ debug_output(|| {
+ println!("Directory does not exist: {}", dir_path.display());
+ });
+ return Ok(());
+ }
+
+ let options = FileOptions::default()
+ .compression_method(zip::CompressionMethod::Deflated)
+ .unix_permissions(0o755);
+
+ for entry in fs::read_dir(dir_path)? {
+ let entry = entry?;
+ let path = entry.path();
+ let relative_path = path.strip_prefix(base_path)?;
+
+ if path.is_dir() {
+ // Add directory entry
+ let dir_name = format!("{}/", relative_path.display());
+ zip.start_file(dir_name, options)?;
+
+ // Recursively add directory contents
+ add_directory_to_zip(zip, &path, base_path)?;
+ } else {
+ // Add file
+ let mut file = fs::File::open(&path)?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer)?;
+
+ zip.start_file(relative_path.to_string_lossy(), options)?;
+ zip.write_all(&buffer)?;
+ }
+ }
+
+ Ok(())
+}
+
+#[tauri::command]
+pub async fn create_backup(include_images: bool) -> Result {
+ debug_output(|| {
+ println!("Creating backup with include_images: {}", include_images);
+ });
+
+ let data_dir = get_data_dir();
+ let backup_filename = get_backup_filename();
+ let backup_path = data_dir.join(&backup_filename);
+
+ // Database file path
+ let db_filename = if cfg!(debug_assertions) {
+ "local.pastebar-db.data"
+ } else {
+ "pastebar-db.data"
+ };
+ let db_path = data_dir.join(db_filename);
+
+ if !db_path.exists() {
+ return Err("Database file not found".to_string());
+ }
+
+ // Create zip file
+ let file = fs::File::create(&backup_path)
+ .map_err(|e| format!("Failed to create backup file: {}", e))?;
+ let mut zip = ZipWriter::new(file);
+
+ let options = FileOptions::default()
+ .compression_method(zip::CompressionMethod::Deflated)
+ .unix_permissions(0o644);
+
+ // Add database file
+ let mut db_file = fs::File::open(&db_path)
+ .map_err(|e| format!("Failed to open database file: {}", e))?;
+ let mut db_buffer = Vec::new();
+ db_file.read_to_end(&mut db_buffer)
+ .map_err(|e| format!("Failed to read database file: {}", e))?;
+
+ zip.start_file(db_filename, options)
+ .map_err(|e| format!("Failed to start database file in zip: {}", e))?;
+ zip.write_all(&db_buffer)
+ .map_err(|e| format!("Failed to write database to zip: {}", e))?;
+
+ // Add image directories if requested
+ if include_images {
+ let clip_images_dir = data_dir.join("clip-images");
+ let history_images_dir = data_dir.join("history-images");
+
+ if clip_images_dir.exists() {
+ add_directory_to_zip(&mut zip, &clip_images_dir, &data_dir)
+ .map_err(|e| format!("Failed to add clip-images directory: {}", e))?;
+ }
+
+ if history_images_dir.exists() {
+ add_directory_to_zip(&mut zip, &history_images_dir, &data_dir)
+ .map_err(|e| format!("Failed to add history-images directory: {}", e))?;
+ }
+ }
+
+ zip.finish()
+ .map_err(|e| format!("Failed to finalize zip file: {}", e))?;
+
+ debug_output(|| {
+ println!("Backup created successfully: {}", backup_path.display());
+ });
+
+ Ok(backup_path.to_string_lossy().to_string())
+}
+
+#[tauri::command]
+pub async fn list_backups() -> Result {
+ let data_dir = get_data_dir();
+ let mut backups = Vec::new();
+ let mut total_size = 0u64;
+
+ if let Ok(entries) = fs::read_dir(&data_dir) {
+ for entry in entries {
+ if let Ok(entry) = entry {
+ let path = entry.path();
+ if let Some(filename) = path.file_name() {
+ let filename_str = filename.to_string_lossy();
+ if filename_str.starts_with("pastebar-data-backup-") && filename_str.ends_with(".zip") {
+ if let Ok(metadata) = entry.metadata() {
+ let size = metadata.len();
+ total_size += size;
+
+ // Parse date from filename
+ let created_date = if let Some(date_part) = filename_str
+ .strip_prefix("pastebar-data-backup-")
+ .and_then(|s| s.strip_suffix(".zip"))
+ {
+ // Format: YYYY-MM-DD-HH-MM
+ if let Ok(parsed_date) = DateTime::parse_from_str(
+ &format!("{} +0000", date_part.replace('-', " ").replacen(' ', "-", 2).replacen(' ', "-", 1).replacen(' ', ":", 1)),
+ "%Y-%m-%d-%H-%M %z"
+ ) {
+ parsed_date.format("%B %d, %Y at %I:%M %p").to_string()
+ } else {
+ "Unknown date".to_string()
+ }
+ } else {
+ "Unknown date".to_string()
+ };
+
+ backups.push(BackupInfo {
+ filename: filename_str.to_string(),
+ full_path: path.to_string_lossy().to_string(),
+ created_date,
+ size,
+ size_formatted: format_file_size(size),
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Sort by filename (which includes date) in descending order
+ backups.sort_by(|a, b| b.filename.cmp(&a.filename));
+
+ Ok(BackupListResponse {
+ backups,
+ total_size,
+ total_size_formatted: format_file_size(total_size),
+ })
+}
+
+#[tauri::command]
+pub fn select_backup_file() -> Result