feat: Improve UI spacing and layout - Increase window size from 1200x800 to 1400x900 - Add more padding and gaps between UI elements - Improve button sizing and spacing - Better status panel layout with flex distribution - Enhanced activity log readability - Remove unnecessary start/stop buttons (auto-sync only) - Remove progress bar (not needed for continuous sync) - Clean up unused sync methods

This commit is contained in:
Geert Rademakes 2025-08-28 11:38:14 +02:00
parent 73d9a41ca8
commit 39b7fb59aa
16 changed files with 853 additions and 130 deletions

View File

@ -0,0 +1,22 @@
# Rekordbox Sync Desktop Application Configuration
# Generated on Sat Aug 16 15:03:29 CEST 2025
# S3 Configuration
S3_ENDPOINT=https://garage.geertrademakers.nl
S3_REGION=garage
S3_ACCESS_KEY_ID=GK1c1a4a30946eb1e7f8d60847
S3_SECRET_ACCESS_KEY=2ed6673f0e3c42d347adeb54ba6b95a1ebc6414750f2a95e1d3d89758f1add63
S3_BUCKET_NAME=music
S3_USE_SSL=true
# Sync Configuration
SYNC_LOCAL_PATH=/Users/geertrademakers/Music/s3-sync-test
SYNC_INTERVAL=30000
SYNC_AUTO_START=true
SYNC_CONFLICT_RESOLUTION=newer-wins
# UI Configuration
UI_THEME=system
UI_LANGUAGE=en
UI_NOTIFICATIONS=true
UI_MINIMIZE_TO_TRAY=true

View File

@ -0,0 +1,22 @@
# Rekordbox Sync Desktop Application Configuration
# Generated on Sat Aug 16 15:03:29 CEST 2025
# S3 Configuration
S3_ENDPOINT=https://garage.geertrademakers.nl
S3_REGION=garage
S3_ACCESS_KEY_ID=GK1c1a4a30946eb1e7f8d60847
S3_SECRET_ACCESS_KEY=2ed6673f0e3c42d347adeb54ba6b95a1ebc6414750f2a95e1d3d89758f1add63
S3_BUCKET_NAME=music
S3_USE_SSL=true
# Sync Configuration
SYNC_LOCAL_PATH=/Users/geertrademakers/Desktop/s3-music-sync-dir
SYNC_INTERVAL=30000
SYNC_AUTO_START=true
SYNC_CONFLICT_RESOLUTION=newer-wins
# UI Configuration
UI_THEME=system
UI_LANGUAGE=en
UI_NOTIFICATIONS=true
UI_MINIMIZE_TO_TRAY=true

View File

@ -0,0 +1,255 @@
# Rekordbox Sync - Desktop Companion
A desktop application for bidirectional synchronization between a Garage-hosted S3 instance and your local computer, specifically designed for Rekordbox music libraries.
## 🚀 Features
- **Bidirectional S3 Sync**: Seamlessly sync files between your local machine and S3 storage
- **Incremental Sync**: Only sync files that have changed since the last sync
- **Automatic Cleanup**: Removes temporary files before syncing
- **Real-time Monitoring**: Continuous sync with configurable intervals
- **Error Handling**: Robust error handling with automatic retries
- **Progress Tracking**: Real-time progress updates and file counting
- **Cross-platform**: Built with Electron for macOS, Windows, and Linux
## 🔧 Prerequisites
### AWS CLI v2
This tool requires AWS CLI v2 to be installed on your system. The AWS CLI provides the `aws s3 sync` command which offers superior performance and reliability compared to other S3 sync tools.
#### Installation Options:
**Option 1: Automatic Installation (macOS)**
```bash
npm run install-aws-cli
```
**Option 2: Manual Installation**
- Download from: https://awscli.amazonaws.com/
- Follow the installation guide: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
**Option 3: Homebrew (macOS)**
```bash
brew install awscli
```
#### Verify Installation
```bash
aws --version
```
## 📦 Installation
1. **Clone the repository**
```bash
git clone <repository-url>
cd rekordbox-reader/packages/desktop-sync
```
2. **Install dependencies**
```bash
npm install
```
3. **Configure environment**
```bash
cp .env.example .env
# Edit .env with your S3 configuration
```
4. **Build the application**
```bash
npm run build
```
5. **Start the application**
```bash
npm run dev
```
## ⚙️ Configuration
Create a `.env` file in the project root with the following variables:
```env
# S3 Configuration (Garage)
S3_ENDPOINT=http://your-garage-instance:3900
S3_REGION=garage
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
S3_BUCKET_NAME=your-bucket-name
S3_USE_SSL=false
# Sync Configuration
SYNC_LOCAL_PATH=/path/to/your/local/music/folder
SYNC_INTERVAL=30000
SYNC_AUTO_START=false
SYNC_CONFLICT_RESOLUTION=newer-wins
# UI Configuration
UI_THEME=dark
UI_LANGUAGE=en
```
### Garage S3 Configuration
For Garage S3 compatibility, ensure your configuration includes:
- **Endpoint**: Your Garage instance URL (e.g., `http://localhost:3900`)
- **Region**: Usually `garage` or `us-east-1`
- **SSL**: Set to `false` for local Garage instances
## 🎯 Usage
### Starting Sync
1. Launch the application
2. Click "Start Sync" to begin bidirectional synchronization
3. The app will:
- Download all files from S3 to local (first time)
- Upload new/changed local files to S3
- Start continuous bidirectional sync
### Sync Modes
#### **Initial Sync**
- Downloads all files from S3 to local
- Ensures local folder matches S3 bucket contents
- Excludes temporary files (`.tmp`, `.temp`, `.part`, `.DS_Store`)
#### **Continuous Sync**
- Monitors both local and S3 for changes
- Automatically syncs new, modified, or deleted files
- Runs every 30 seconds by default
- Maintains bidirectional consistency
#### **Force Full Sync**
- Completely resynchronizes all files
- Useful for resolving sync conflicts
- Deletes and re-downloads all files
### File Handling
- **Temporary Files**: Automatically excluded and cleaned up
- **Conflict Resolution**: Newer timestamp wins by default
- **Delete Propagation**: Files deleted locally are removed from S3 and vice versa
- **Incremental Updates**: Only changed files are transferred
## 🔍 Monitoring
### Real-time Status
- Current sync phase (downloading, uploading, watching)
- Progress percentage and file counts
- Transfer speed and ETA
- Error messages and retry attempts
### Activity Log
- Detailed AWS CLI output
- File operations and sync events
- Error tracking and resolution
### File Counts
- Accurate local file counting
- S3 bucket file statistics
- Sync progress tracking
## 🛠️ Development
### Project Structure
```
src/
├── main.ts # Main Electron process
├── preload.ts # Preload script for IPC
├── services/
│ ├── awsS3Service.ts # AWS S3 sync service
│ ├── configManager.ts # Configuration management
│ ├── fileWatcher.ts # Local file system monitoring
│ └── syncManager.ts # Sync orchestration
└── renderer/ # UI components
├── index.html
├── renderer.js
└── styles.css
```
### Available Scripts
- `npm run dev` - Development mode with hot reload
- `npm run build` - Build TypeScript to JavaScript
- `npm run start` - Start the built application
- `npm run package` - Package for distribution
- `npm run install-aws-cli` - Install AWS CLI (macOS)
### Building
```bash
npm run build
npm start
```
## 🚨 Troubleshooting
### Common Issues
**AWS CLI Not Found**
```bash
# Check if AWS CLI is installed
aws --version
# Install if missing
npm run install-aws-cli
```
**Sync Fails to Start**
- Verify S3 credentials in `.env`
- Check network connectivity to Garage instance
- Ensure local sync path exists and is writable
**Files Not Syncing**
- Check file permissions
- Verify S3 bucket access
- Review activity log for error messages
**Performance Issues**
- AWS CLI v2 provides optimal performance
- Consider adjusting sync interval
- Monitor network bandwidth usage
### Debug Mode
Enable detailed logging by setting environment variables:
```bash
DEBUG=* npm run dev
```
## 📊 Performance
- **AWS CLI v2**: Optimized for S3 operations
- **Incremental Sync**: Only transfers changed files
- **Parallel Operations**: Efficient file transfer
- **Memory Management**: Minimal memory footprint
- **Network Optimization**: Intelligent retry and backoff
## 🔒 Security
- **Credential Management**: Secure storage of S3 credentials
- **Local Storage**: Credentials stored locally, never transmitted
- **SSL Support**: Configurable SSL/TLS for S3 endpoints
- **Access Control**: Follows S3 bucket policies and IAM permissions
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## 📄 License
MIT License - see LICENSE file for details
## 🙏 Acknowledgments
- **AWS CLI**: Powerful S3 sync capabilities
- **Electron**: Cross-platform desktop framework
- **Garage**: Self-hosted S3-compatible storage
- **Rekordbox**: Professional DJ software
---
**Note**: This tool is designed for personal and professional use with Garage S3 storage. Ensure compliance with your organization's data policies and S3 usage guidelines.

View File

@ -0,0 +1,5 @@
# This is a placeholder for the PNG icon
# You can convert the SVG to PNG using:
# - Online tools like convertio.co
# - Command line: convert icon.svg icon.png (if ImageMagick is installed)
# - Or use any image editor that supports SVG import

View File

@ -0,0 +1,36 @@
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3498db;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2980b9;stop-opacity:1" />
</linearGradient>
<linearGradient id="sync" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#27ae60;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2ecc71;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background circle -->
<circle cx="256" cy="256" r="240" fill="url(#bg)" stroke="#2c3e50" stroke-width="16"/>
<!-- Sync arrows -->
<g fill="url(#sync)">
<!-- Left arrow -->
<path d="M 120 200 L 160 200 L 160 160 L 200 200 L 160 240 L 160 200 Z"/>
<!-- Right arrow -->
<path d="M 392 312 L 352 312 L 352 352 L 312 312 L 352 272 L 352 312 Z"/>
</g>
<!-- Music note -->
<g fill="white">
<ellipse cx="200" cy="280" rx="12" ry="16"/>
<rect x="188" y="240" width="8" height="40" rx="4"/>
<ellipse cx="312" cy="232" rx="12" ry="16"/>
<rect x="300" y="192" width="8" height="40" rx="4"/>
</g>
<!-- Center sync symbol -->
<circle cx="256" cy="256" r="40" fill="none" stroke="white" stroke-width="8" stroke-dasharray="20,10"/>
<circle cx="256" cy="256" r="20" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,29 @@
# Rekordbox Sync Desktop Application Configuration
# Copy this file to .env and fill in your values
# S3 Configuration
S3_ENDPOINT=https://garage.geertrademakers.nl
S3_REGION=garage
S3_ACCESS_KEY_ID=your_access_key_here
S3_SECRET_ACCESS_KEY=your_secret_key_here
S3_BUCKET_NAME=music
S3_USE_SSL=true
# Sync Configuration
SYNC_LOCAL_PATH=/path/to/your/music/folder
SYNC_INTERVAL=30000
SYNC_AUTO_START=false
SYNC_CONFLICT_RESOLUTION=newer-wins
# UI Configuration
UI_THEME=system
UI_LANGUAGE=en
UI_NOTIFICATIONS=true
UI_MINIMIZE_TO_TRAY=true
# Notes:
# - SYNC_INTERVAL is in milliseconds (30000 = 30 seconds)
# - SYNC_CONFLICT_RESOLUTION options: newer-wins, local-wins, remote-wins
# - UI_THEME options: system, light, dark
# - Boolean values: true/false (as strings)
# - Paths should use forward slashes (/) even on Windows

View File

@ -0,0 +1,53 @@
#!/bin/bash
# AWS CLI v2 Installer for macOS
# This script downloads and installs AWS CLI v2 on macOS
set -e
echo "🚀 Installing AWS CLI v2 for macOS..."
# Check if AWS CLI is already installed
if command -v aws &> /dev/null; then
echo "✅ AWS CLI is already installed:"
aws --version
exit 0
fi
# Check if we're on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "❌ This script is for macOS only. Please install AWS CLI manually for your platform."
exit 1
fi
# Create temporary directory
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
echo "📥 Downloading AWS CLI v2..."
# Download AWS CLI v2 for macOS
curl -O https://awscli.amazonaws.com/AWSCLIV2.pkg
echo "🔧 Installing AWS CLI v2..."
# Install the package
sudo installer -pkg AWSCLIV2.pkg -target /
# Clean up
cd - > /dev/null
rm -rf "$TEMP_DIR"
echo "✅ AWS CLI v2 installed successfully!"
# Verify installation
if command -v aws &> /dev/null; then
echo "🔍 AWS CLI version:"
aws --version
echo ""
echo "🎉 Installation completed! You can now use the desktop sync tool."
else
echo "❌ Installation failed. Please try installing manually:"
echo " https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
exit 1
fi

View File

@ -33,7 +33,7 @@
<div class="status-panel"> <div class="status-panel">
<div class="status-item"> <div class="status-item">
<span class="status-label">Sync Status:</span> <span class="status-label">Sync Status:</span>
<span id="syncStatus" class="status-value">Stopped</span> <span id="syncStatus" class="status-value">Initializing...</span>
</div> </div>
<div class="status-item"> <div class="status-item">
<span class="status-label">Last Sync:</span> <span class="status-label">Last Sync:</span>
@ -44,22 +44,14 @@
<span id="filesSynced" class="status-value">0</span> <span id="filesSynced" class="status-value">0</span>
</div> </div>
<div class="status-item"> <div class="status-item">
<span class="status-label">Progress:</span> <span class="status-label">Sync Mode:</span>
<div class="progress-container"> <span id="syncMode" class="status-value">Auto-sync</span>
<div id="progressBar" class="progress-bar" style="width: 0%"></div>
</div>
</div> </div>
<div id="syncDetails" class="sync-details"></div> <div id="syncDetails" class="sync-details"></div>
</div> </div>
<!-- Control Panel --> <!-- Control Panel -->
<div class="control-panel"> <div class="control-panel">
<button id="startSyncBtn" class="btn btn-primary">
<i class="fas fa-play"></i> Start Sync
</button>
<button id="stopSyncBtn" class="btn btn-danger" disabled>
<i class="fas fa-stop"></i> Stop Sync
</button>
<button id="forceSyncBtn" class="btn btn-secondary"> <button id="forceSyncBtn" class="btn btn-secondary">
<i class="fas fa-sync"></i> Force Full Sync <i class="fas fa-sync"></i> Force Full Sync
</button> </button>

View File

@ -11,8 +11,6 @@ class RekordboxSyncRenderer {
*/ */
initializeElements() { initializeElements() {
// Buttons // Buttons
this.startBtn = document.getElementById('startSyncBtn');
this.stopBtn = document.getElementById('stopSyncBtn');
this.exportEnvBtn = document.getElementById('exportEnvBtn'); this.exportEnvBtn = document.getElementById('exportEnvBtn');
// Status elements // Status elements
@ -20,6 +18,7 @@ class RekordboxSyncRenderer {
this.filesSyncedElement = document.getElementById('filesSynced'); this.filesSyncedElement = document.getElementById('filesSynced');
this.lastSyncElement = document.getElementById('lastSync'); this.lastSyncElement = document.getElementById('lastSync');
this.syncDetailsElement = document.getElementById('syncDetails'); this.syncDetailsElement = document.getElementById('syncDetails');
this.syncModeElement = document.getElementById('syncMode');
// Activity log // Activity log
this.activityLogElement = document.getElementById('activityLog'); this.activityLogElement = document.getElementById('activityLog');
@ -76,30 +75,14 @@ class RekordboxSyncRenderer {
* Setup Electron IPC listeners * Setup Electron IPC listeners
*/ */
setupElectronListeners() { setupElectronListeners() {
console.log('🔌 Setting up Electron IPC listeners...');
console.log('🔍 Window object:', window);
console.log('🔍 Electron API object:', window.electronAPI);
if (!window.electronAPI) { if (!window.electronAPI) {
console.error('❌ Electron API not available'); console.error('❌ Electron API not available');
console.error('❌ This means the preload script failed to load');
return; return;
} }
console.log('✅ Electron API is available, setting up listeners...');
// Sync status updates // Sync status updates
window.electronAPI.on('sync-status-changed', (status) => { window.electronAPI.on('sync-status-changed', (status) => {
console.log('📊 Received sync status update:', status);
console.log('🔍 Status details:', {
isRunning: status.isRunning,
currentPhase: status.currentPhase,
actualFileCount: status.actualFileCount,
stats: status.stats
});
console.log('🔄 Calling updateSyncStatus...');
this.updateSyncStatus(status); this.updateSyncStatus(status);
console.log('✅ updateSyncStatus completed');
}); });
// File change events // File change events
@ -159,11 +142,7 @@ class RekordboxSyncRenderer {
if (this.startBtn) this.startBtn.disabled = false; if (this.startBtn) this.startBtn.disabled = false;
if (this.stopBtn) this.stopBtn.disabled = true; if (this.stopBtn) this.stopBtn.disabled = true;
// Reset progress bar on error
const progressBar = document.getElementById('progressBar');
if (progressBar) {
progressBar.style.width = '0%';
}
}); });
// Engine events // Engine events
@ -213,33 +192,7 @@ class RekordboxSyncRenderer {
} }
} }
/**
* Start sync
*/
async startSync() {
try {
this.addActivityLog('info', 'Starting sync...');
await window.electronAPI.invoke('sync:start');
this.addActivityLog('success', 'Sync started successfully');
} catch (error) {
console.error('❌ Failed to start sync:', error);
this.addActivityLog('error', `Failed to start sync: ${error.message || 'Unknown error'}`);
}
}
/**
* Stop sync
*/
async stopSync() {
try {
this.addActivityLog('info', 'Stopping sync...');
await window.electronAPI.invoke('sync:stop');
this.addActivityLog('info', 'Sync stopped');
} catch (error) {
console.error('❌ Failed to stop sync:', error);
this.addActivityLog('error', `Failed to stop sync: ${error.message || 'Unknown error'}`);
}
}
/** /**
* Force full sync * Force full sync
@ -278,8 +231,6 @@ class RekordboxSyncRenderer {
this.syncStatusElement.textContent = statusText; this.syncStatusElement.textContent = statusText;
this.syncStatusElement.className = 'status-value running'; this.syncStatusElement.className = 'status-value running';
if (this.startBtn) this.startBtn.disabled = true;
if (this.stopBtn) this.stopBtn.disabled = false;
} else { } else {
if (status.completedCount > 0 && status.pendingCount === 0 && status.inProgressCount === 0) { if (status.completedCount > 0 && status.pendingCount === 0 && status.inProgressCount === 0) {
this.syncStatusElement.textContent = 'Completed'; this.syncStatusElement.textContent = 'Completed';
@ -288,11 +239,7 @@ class RekordboxSyncRenderer {
this.syncStatusElement.textContent = 'Stopped'; this.syncStatusElement.textContent = 'Stopped';
this.syncStatusElement.className = 'status-value stopped'; this.syncStatusElement.className = 'status-value stopped';
// Reset progress bar when stopped
const progressBar = document.getElementById('progressBar');
if (progressBar) {
progressBar.style.width = '0%';
}
} }
if (this.startBtn) this.startBtn.disabled = false; if (this.startBtn) this.startBtn.disabled = false;
if (this.stopBtn) this.stopBtn.disabled = true; if (this.stopBtn) this.stopBtn.disabled = true;
@ -347,14 +294,8 @@ class RekordboxSyncRenderer {
this.addActivityLog('info', `${status.currentPhase}: ${status.progressMessage}`); this.addActivityLog('info', `${status.currentPhase}: ${status.progressMessage}`);
} }
// Update progress bar // Log progress changes to reduce spam
if (status.progress && status.progress.percent > 0) { if (status.progress && status.progress.percent > 0) {
const progressBar = document.getElementById('progressBar');
if (progressBar) {
progressBar.style.width = `${status.progress.percent}%`;
}
// Only log progress changes to reduce spam
const currentProgress = status.progress.percent; const currentProgress = status.progress.percent;
if (!this.lastLoggedProgress || Math.abs(currentProgress - this.lastLoggedProgress) >= 10) { if (!this.lastLoggedProgress || Math.abs(currentProgress - this.lastLoggedProgress) >= 10) {
const progressText = `Progress: ${currentProgress}% - ${status.progress.message || 'Syncing files...'}`; const progressText = `Progress: ${currentProgress}% - ${status.progress.message || 'Syncing files...'}`;

View File

@ -52,17 +52,19 @@ body {
/* Buttons */ /* Buttons */
.btn { .btn {
padding: 0.5rem 1rem; padding: 1rem 2rem;
border: none; border: none;
border-radius: 6px; border-radius: 10px;
cursor: pointer; cursor: pointer;
font-size: 0.9rem; font-size: 1rem;
font-weight: 500; font-weight: 500;
transition: all 0.2s ease; transition: all 0.2s ease;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.75rem;
text-decoration: none; text-decoration: none;
min-width: 140px;
justify-content: center;
} }
.btn:hover { .btn:hover {
@ -124,12 +126,12 @@ body {
/* Main content */ /* Main content */
.main-content { .main-content {
flex: 1; flex: 1;
padding: 1.5rem; padding: 2rem;
overflow-y: auto; overflow-y: auto;
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto 1fr; grid-template-rows: auto auto 1fr;
gap: 1.5rem; gap: 2rem;
grid-template-areas: grid-template-areas:
"status control" "status control"
"activity activity" "activity activity"
@ -140,32 +142,37 @@ body {
.status-panel { .status-panel {
grid-area: status; grid-area: status;
background: white; background: white;
padding: 1.5rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
align-items: center; align-items: center;
gap: 1.5rem;
} }
.status-item { .status-item {
text-align: center; text-align: center;
flex: 1;
min-width: 0;
} }
.status-label { .status-label {
display: block; display: block;
font-size: 0.8rem; font-size: 0.85rem;
color: #7f8c8d; color: #7f8c8d;
margin-bottom: 0.5rem; margin-bottom: 0.75rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
font-weight: 500;
} }
.status-value { .status-value {
display: block; display: block;
font-size: 1.2rem; font-size: 1.3rem;
font-weight: 600; font-weight: 600;
color: #2c3e50; color: #2c3e50;
line-height: 1.2;
} }
#syncStatus { #syncStatus {
@ -195,49 +202,17 @@ body {
color: #721c24; color: #721c24;
} }
/* Progress Bar */
.progress-container {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-top: 0.5rem;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #3498db, #2ecc71);
border-radius: 4px;
transition: width 0.3s ease;
position: relative;
}
.progress-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* Control Panel */ /* Control Panel */
.control-panel { .control-panel {
grid-area: control; grid-area: control;
background: white; background: white;
padding: 1.5rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
display: flex; display: flex;
gap: 1rem; gap: 1.5rem;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
@ -248,7 +223,7 @@ body {
.activity-panel { .activity-panel {
grid-area: activity; grid-area: activity;
background: white; background: white;
padding: 1.5rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
display: flex; display: flex;
@ -256,11 +231,12 @@ body {
} }
.activity-panel h3 { .activity-panel h3 {
margin-bottom: 1rem; margin-bottom: 1.5rem;
color: #2c3e50; color: #2c3e50;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.75rem;
font-size: 1.2rem;
} }
.activity-log { .activity-log {
@ -272,16 +248,18 @@ body {
text-align: center; text-align: center;
color: #95a5a6; color: #95a5a6;
font-style: italic; font-style: italic;
padding: 2rem; padding: 3rem 2rem;
font-size: 1.1rem;
} }
.activity-item { .activity-item {
padding: 0.75rem; padding: 1rem;
margin-bottom: 0.5rem; margin-bottom: 0.75rem;
border-radius: 6px; border-radius: 8px;
background: #f8f9fa; background: #f8f9fa;
border-left: 4px solid #95a5a6; border-left: 4px solid #95a5a6;
font-size: 0.9rem; font-size: 0.95rem;
line-height: 1.4;
} }
.activity-item.info { .activity-item.info {

View File

@ -0,0 +1,61 @@
#!/usr/bin/env node
/**
* Check if AWS CLI is available on the system
* This script is run during postinstall to ensure AWS CLI is available
*/
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
console.log('🔍 Checking AWS CLI availability...');
// Check if AWS CLI is available
function checkAwsCli() {
return new Promise((resolve) => {
const process = spawn('aws', ['--version'], { stdio: 'pipe' });
process.on('close', (code) => {
resolve(code === 0);
});
process.on('error', () => {
resolve(false);
});
});
}
async function main() {
const isAvailable = await checkAwsCli();
if (isAvailable) {
console.log('✅ AWS CLI is available');
// Get version
const versionProcess = spawn('aws', ['--version'], { stdio: 'pipe' });
versionProcess.stdout.on('data', (data) => {
console.log(`📋 Version: ${data.toString().trim()}`);
});
console.log('🎉 You can now use the desktop sync tool with AWS S3!');
} else {
console.log('❌ AWS CLI is not available');
console.log('');
console.log('📋 To install AWS CLI v2:');
console.log('');
console.log(' Option 1: Run the installer script:');
console.log(' npm run install-aws-cli');
console.log('');
console.log(' Option 2: Install manually:');
console.log(' https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html');
console.log('');
console.log(' Option 3: Use Homebrew (macOS):');
console.log(' brew install awscli');
console.log('');
console.log('⚠️ The desktop sync tool requires AWS CLI to function properly.');
console.log(' Please install AWS CLI before using the sync functionality.');
}
}
main().catch(console.error);

View File

@ -0,0 +1,87 @@
#!/usr/bin/env node
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
console.log('🔍 Checking MinIO Client installation...');
function checkMinio() {
return new Promise((resolve) => {
const minio = spawn('mc', ['--version'], { stdio: 'pipe' });
let output = '';
let error = '';
minio.stdout.on('data', (data) => {
output += data.toString();
});
minio.stderr.on('data', (data) => {
error += data.toString();
});
minio.on('close', (code) => {
if (code === 0) {
const versionMatch = output.match(/mc version (RELEASE\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z)/);
if (versionMatch) {
console.log(`✅ MinIO Client is installed: ${versionMatch[1]}`);
resolve(true);
} else {
console.log('✅ MinIO Client is installed (version unknown)');
resolve(true);
}
} else {
console.log('❌ MinIO Client is not installed or not in PATH');
resolve(false);
}
});
minio.on('error', () => {
console.log('❌ MinIO Client is not installed or not in PATH');
resolve(false);
});
});
}
function showInstallInstructions() {
console.log('\n📥 MinIO Client Installation Instructions:');
console.log('==========================================');
if (process.platform === 'darwin') {
console.log('\n🍎 macOS:');
console.log(' brew install minio/stable/mc');
console.log(' # Or download from: https://min.io/download');
} else if (process.platform === 'win32') {
console.log('\n🪟 Windows:');
console.log(' # Download from: https://min.io/download');
console.log(' # Extract and add to PATH');
} else if (process.platform === 'linux') {
console.log('\n🐧 Linux:');
console.log(' # Ubuntu/Debian:');
console.log(' wget https://dl.min.io/client/mc/release/linux-amd64/mc');
console.log(' chmod +x mc');
console.log(' sudo mv mc /usr/local/bin/');
console.log(' # Or: curl https://dl.min.io/client/mc/release/linux-amd64/mc -o mc && chmod +x mc && sudo mv mc /usr/local/bin/');
}
console.log('\n📚 After installation:');
console.log(' 1. Run: mc alias set garage https://your-garage-endpoint access-key secret-key');
console.log(' 2. Test with: mc ls garage/bucket-name');
console.log('\n🔗 Documentation: https://min.io/docs/minio/linux/reference/minio-mc.html');
}
async function main() {
const isInstalled = await checkMinio();
if (!isInstalled) {
showInstallInstructions();
process.exit(1);
} else {
console.log('\n🎉 MinIO Client is ready to use!');
console.log('💡 You can now run: npm start');
}
}
main().catch(console.error);

View File

@ -0,0 +1,89 @@
#!/usr/bin/env node
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
console.log('🔍 Checking rclone installation...');
function checkRclone() {
return new Promise((resolve) => {
const rclone = spawn('rclone', ['version'], { stdio: 'pipe' });
let output = '';
let error = '';
rclone.stdout.on('data', (data) => {
output += data.toString();
});
rclone.stderr.on('data', (data) => {
error += data.toString();
});
rclone.on('close', (code) => {
if (code === 0) {
const versionMatch = output.match(/rclone v(\d+\.\d+\.\d+)/);
if (versionMatch) {
console.log(`✅ Rclone is installed: ${versionMatch[1]}`);
resolve(true);
} else {
console.log('✅ Rclone is installed (version unknown)');
resolve(true);
}
} else {
console.log('❌ Rclone is not installed or not in PATH');
resolve(false);
}
});
rclone.on('error', () => {
console.log('❌ Rclone is not installed or not in PATH');
resolve(false);
});
});
}
function showInstallInstructions() {
console.log('\n📥 Rclone Installation Instructions:');
console.log('=====================================');
if (process.platform === 'darwin') {
console.log('\n🍎 macOS:');
console.log(' brew install rclone');
console.log(' # Or download from: https://rclone.org/downloads/');
} else if (process.platform === 'win32') {
console.log('\n🪟 Windows:');
console.log(' # Download from: https://rclone.org/downloads/');
console.log(' # Extract and add to PATH');
} else if (process.platform === 'linux') {
console.log('\n🐧 Linux:');
console.log(' # Ubuntu/Debian:');
console.log(' curl https://rclone.org/install.sh | sudo bash');
console.log(' # Or: sudo apt install rclone');
console.log(' # CentOS/RHEL:');
console.log(' sudo yum install rclone');
}
console.log('\n📚 After installation:');
console.log(' 1. Run: rclone config');
console.log(' 2. Create a new remote named "music"');
console.log(' 3. Choose S3 provider');
console.log(' 4. Enter your Garage S3 credentials');
console.log('\n🔗 Documentation: https://rclone.org/s3/');
}
async function main() {
const isInstalled = await checkRclone();
if (!isInstalled) {
showInstallInstructions();
process.exit(1);
} else {
console.log('\n🎉 Rclone is ready to use!');
console.log('💡 You can now run: npm start');
}
}
main().catch(console.error);

View File

@ -0,0 +1,152 @@
#!/bin/bash
# Rekordbox Sync .env Setup Script
echo "🔧 Setting up Rekordbox Sync .env configuration file..."
# Check if .env already exists
if [ -f ".env" ]; then
echo "⚠️ .env file already exists!"
read -p "Do you want to overwrite it? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "❌ Setup cancelled."
exit 1
fi
fi
# Get S3 configuration
echo ""
echo "🌐 S3 Configuration:"
read -p "S3 Endpoint (default: https://garage.geertrademakers.nl): " s3_endpoint
s3_endpoint=${s3_endpoint:-https://garage.geertrademakers.nl}
read -p "S3 Region (default: garage): " s3_region
s3_region=${s3_region:-garage}
read -p "S3 Access Key ID: " s3_access_key
if [ -z "$s3_access_key" ]; then
echo "❌ S3 Access Key ID is required!"
exit 1
fi
read -s -p "S3 Secret Access Key: " s3_secret_key
echo
if [ -z "$s3_secret_key" ]; then
echo "❌ S3 Secret Access Key is required!"
exit 1
fi
read -p "S3 Bucket Name (default: music): " s3_bucket
s3_bucket=${s3_bucket:-music}
read -p "Use SSL? (Y/n): " -n 1 -r
echo
s3_use_ssl="true"
if [[ $REPLY =~ ^[Nn]$ ]]; then
s3_use_ssl="false"
fi
# Get sync configuration
echo ""
echo "🔄 Sync Configuration:"
read -p "Local Music Folder Path: " sync_local_path
if [ -z "$sync_local_path" ]; then
echo "❌ Local music folder path is required!"
exit 1
fi
read -p "Sync Interval in seconds (default: 30): " sync_interval
sync_interval=${sync_interval:-30}
sync_interval=$((sync_interval * 1000)) # Convert to milliseconds
read -p "Auto-start sync on app launch? (y/N): " -n 1 -r
echo
sync_auto_start="false"
if [[ $REPLY =~ ^[Yy]$ ]]; then
sync_auto_start="true"
fi
echo ""
echo "Conflict Resolution Strategy:"
echo "1) newer-wins (recommended)"
echo "2) local-wins"
echo "3) remote-wins"
read -p "Choose strategy (1-3, default: 1): " conflict_resolution
case $conflict_resolution in
2) conflict_resolution="local-wins" ;;
3) conflict_resolution="remote-wins" ;;
*) conflict_resolution="newer-wins" ;;
esac
# Get UI configuration
echo ""
echo "🎨 UI Configuration:"
echo "Theme options:"
echo "1) system (follows OS theme)"
echo "2) light"
echo "3) dark"
read -p "Choose theme (1-3, default: 1): " ui_theme
case $ui_theme in
2) ui_theme="light" ;;
3) ui_theme="dark" ;;
*) ui_theme="system" ;;
esac
read -p "Show notifications? (Y/n): " -n 1 -r
echo
ui_notifications="true"
if [[ $REPLY =~ ^[Nn]$ ]]; then
ui_notifications="false"
fi
read -p "Minimize to system tray? (Y/n): " -n 1 -r
echo
ui_minimize_to_tray="true"
if [[ $REPLY =~ ^[Nn]$ ]]; then
ui_minimize_to_tray="false"
fi
# Create .env file
echo ""
echo "📝 Creating .env file..."
cat > .env << EOF
# Rekordbox Sync Desktop Application Configuration
# Generated on $(date)
# S3 Configuration
S3_ENDPOINT=$s3_endpoint
S3_REGION=$s3_region
S3_ACCESS_KEY_ID=$s3_access_key
S3_SECRET_ACCESS_KEY=$s3_secret_key
S3_BUCKET_NAME=$s3_bucket
S3_USE_SSL=$s3_use_ssl
# Sync Configuration
SYNC_LOCAL_PATH=$sync_local_path
SYNC_INTERVAL=$sync_interval
SYNC_AUTO_START=$sync_auto_start
SYNC_CONFLICT_RESOLUTION=$conflict_resolution
# UI Configuration
UI_THEME=$ui_theme
UI_LANGUAGE=en
UI_NOTIFICATIONS=$ui_notifications
UI_MINIMIZE_TO_TRAY=$ui_minimize_to_tray
EOF
echo "✅ .env file created successfully!"
echo ""
echo "🔍 Configuration summary:"
echo " S3 Endpoint: $s3_endpoint"
echo " S3 Region: $s3_region"
echo " S3 Bucket: $s3_bucket"
echo " Local Path: $sync_local_path"
echo " Sync Interval: $((sync_interval / 1000)) seconds"
echo " Auto-start: $sync_auto_start"
echo " Conflict Resolution: $conflict_resolution"
echo " Theme: $ui_theme"
echo ""
echo "🚀 You can now start the application with: npm run dev"
echo "📖 The .env file will be automatically loaded on startup."

View File

@ -61,10 +61,10 @@ class RekordboxSyncApp {
*/ */
private async createMainWindow(): Promise<void> { private async createMainWindow(): Promise<void> {
this.mainWindow = new BrowserWindow({ this.mainWindow = new BrowserWindow({
width: 1200, width: 1400,
height: 800, height: 900,
minWidth: 800, minWidth: 1000,
minHeight: 600, minHeight: 700,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,

View File

@ -469,6 +469,7 @@ export class AwsS3Service extends EventEmitter {
'--exclude', '*.temp', '--exclude', '*.temp',
'--exclude', '*.part', '--exclude', '*.part',
'--exclude', '.DS_Store', '--exclude', '.DS_Store',
'--exclude', '**/.DS_Store',
'--exclude', '*.crdownload' '--exclude', '*.crdownload'
]); ]);