Compare commits
2 Commits
7065247277
...
9de7564c18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9de7564c18 | ||
|
|
d747830384 |
116
packages/backend/debug-mp3-files.js
Normal file
116
packages/backend/debug-mp3-files.js
Normal file
@ -0,0 +1,116 @@
|
||||
import { createClient } from 'webdav';
|
||||
import fs from 'fs';
|
||||
|
||||
async function debugMP3Files() {
|
||||
try {
|
||||
// Load configuration
|
||||
const configData = fs.readFileSync('storage-config.json', 'utf-8');
|
||||
const config = JSON.parse(configData);
|
||||
|
||||
console.log('🔍 WebDAV Configuration:');
|
||||
console.log('URL:', config.url);
|
||||
console.log('Username:', config.username);
|
||||
console.log('Base Path:', config.basePath);
|
||||
console.log('');
|
||||
|
||||
// Create WebDAV client
|
||||
const client = createClient(config.url, {
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
});
|
||||
|
||||
console.log('🔗 Testing connection...');
|
||||
const basePath = config.basePath || '/Music';
|
||||
|
||||
try {
|
||||
// Test deep listing
|
||||
console.log('📁 Testing deep directory listing for MP3 files...');
|
||||
const deepContents = await client.getDirectoryContents(basePath, { deep: true });
|
||||
|
||||
console.log('Deep listing response type:', typeof deepContents);
|
||||
console.log('Is array:', Array.isArray(deepContents));
|
||||
|
||||
if (Array.isArray(deepContents)) {
|
||||
console.log('Total items found:', deepContents.length);
|
||||
|
||||
// Filter for MP3 files specifically
|
||||
const mp3Files = deepContents.filter(item => {
|
||||
if (item && typeof item === 'object' && 'type' in item && item.type === 'file') {
|
||||
const filename = item.basename || item.filename.split('/').pop() || '';
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
console.log('🎵 MP3 Files found:', mp3Files.length);
|
||||
console.log('');
|
||||
|
||||
// Show first 20 MP3 files
|
||||
console.log('🎵 First 20 MP3 files:');
|
||||
mp3Files.slice(0, 20).forEach((file, index) => {
|
||||
const relativePath = file.filename.replace(basePath + '/', '');
|
||||
console.log(` ${index + 1}. ${relativePath} (${file.size} bytes)`);
|
||||
});
|
||||
|
||||
if (mp3Files.length > 20) {
|
||||
console.log(` ... and ${mp3Files.length - 20} more MP3 files`);
|
||||
}
|
||||
|
||||
// Check if there are any patterns in the file paths
|
||||
console.log('');
|
||||
console.log('📁 Directory distribution of MP3 files:');
|
||||
const dirCounts = new Map();
|
||||
mp3Files.forEach(file => {
|
||||
const relativePath = file.filename.replace(basePath + '/', '');
|
||||
const dir = relativePath.split('/')[0];
|
||||
dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
|
||||
});
|
||||
|
||||
const sortedDirs = Array.from(dirCounts.entries()).sort((a, b) => b[1] - a[1]);
|
||||
sortedDirs.forEach(([dir, count]) => {
|
||||
console.log(` 📁 ${dir}: ${count} MP3 files`);
|
||||
});
|
||||
|
||||
// Test if there's a limit by checking specific directories
|
||||
console.log('');
|
||||
console.log('🔍 Testing specific directories for MP3 files...');
|
||||
|
||||
const testDirs = ['Gekocht', 'Merijn Music', 'Musica'];
|
||||
for (const testDir of testDirs) {
|
||||
try {
|
||||
const dirPath = `${basePath}/${testDir}`;
|
||||
const dirContents = await client.getDirectoryContents(dirPath, { deep: true });
|
||||
const dirItems = Array.isArray(dirContents) ? dirContents : [dirContents];
|
||||
const dirMp3Files = dirItems.filter(item => {
|
||||
if (item && typeof item === 'object' && 'type' in item && item.type === 'file') {
|
||||
const filename = item.basename || item.filename.split('/').pop() || '';
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
console.log(` 📁 ${testDir}: ${dirMp3Files.length} MP3 files`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ ${testDir}: Error - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('❌ Deep listing returned non-array response:', deepContents);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error during WebDAV operations:', error);
|
||||
console.error('Error details:', error.message);
|
||||
if (error.response) {
|
||||
console.error('Response status:', error.response.status);
|
||||
console.error('Response data:', error.response.data);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load configuration or create client:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the debug function
|
||||
debugMP3Files().catch(console.error);
|
||||
122
packages/backend/debug-webdav-files.js
Normal file
122
packages/backend/debug-webdav-files.js
Normal file
@ -0,0 +1,122 @@
|
||||
import { createClient } from 'webdav';
|
||||
import fs from 'fs';
|
||||
|
||||
async function debugWebDAVFiles() {
|
||||
try {
|
||||
// Load configuration
|
||||
const configData = fs.readFileSync('storage-config.json', 'utf-8');
|
||||
const config = JSON.parse(configData);
|
||||
|
||||
console.log('🔍 WebDAV Configuration:');
|
||||
console.log('URL:', config.url);
|
||||
console.log('Username:', config.username);
|
||||
console.log('Base Path:', config.basePath);
|
||||
console.log('');
|
||||
|
||||
// Create WebDAV client
|
||||
const client = createClient(config.url, {
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
});
|
||||
|
||||
console.log('🔗 Testing connection...');
|
||||
const basePath = config.basePath || '/Music';
|
||||
|
||||
try {
|
||||
const baseContents = await client.getDirectoryContents(basePath);
|
||||
console.log('✅ Connection successful');
|
||||
console.log('Base directory contents count:', Array.isArray(baseContents) ? baseContents.length : 1);
|
||||
console.log('');
|
||||
|
||||
// Test deep listing
|
||||
console.log('📁 Testing deep directory listing...');
|
||||
const deepContents = await client.getDirectoryContents(basePath, { deep: true });
|
||||
|
||||
console.log('Deep listing response type:', typeof deepContents);
|
||||
console.log('Is array:', Array.isArray(deepContents));
|
||||
|
||||
if (Array.isArray(deepContents)) {
|
||||
console.log('Total items found:', deepContents.length);
|
||||
|
||||
// Count files vs directories
|
||||
let fileCount = 0;
|
||||
let dirCount = 0;
|
||||
const fileExtensions = new Map();
|
||||
const directories = new Set();
|
||||
|
||||
for (const item of deepContents) {
|
||||
if (item && typeof item === 'object' && 'type' in item) {
|
||||
if (item.type === 'file') {
|
||||
fileCount++;
|
||||
|
||||
// Track file extensions
|
||||
const ext = item.basename?.split('.').pop()?.toLowerCase() || 'unknown';
|
||||
fileExtensions.set(ext, (fileExtensions.get(ext) || 0) + 1);
|
||||
} else if (item.type === 'directory') {
|
||||
dirCount++;
|
||||
const relativePath = item.filename.replace(basePath + '/', '');
|
||||
if (relativePath) {
|
||||
directories.add(relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('📊 File Statistics:');
|
||||
console.log('Files:', fileCount);
|
||||
console.log('Directories:', dirCount);
|
||||
console.log('Total items:', fileCount + dirCount);
|
||||
console.log('');
|
||||
|
||||
console.log('📁 Directory structure (first 20):');
|
||||
const sortedDirs = Array.from(directories).sort();
|
||||
sortedDirs.slice(0, 20).forEach(dir => {
|
||||
console.log(' 📁', dir);
|
||||
});
|
||||
if (sortedDirs.length > 20) {
|
||||
console.log(` ... and ${sortedDirs.length - 20} more directories`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
console.log('🎵 File extensions:');
|
||||
const sortedExts = Array.from(fileExtensions.entries()).sort((a, b) => b[1] - a[1]);
|
||||
sortedExts.forEach(([ext, count]) => {
|
||||
console.log(` .${ext}: ${count} files`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Show some sample files
|
||||
console.log('🎵 Sample files (first 10):');
|
||||
const sampleFiles = deepContents
|
||||
.filter(item => item && typeof item === 'object' && 'type' in item && item.type === 'file')
|
||||
.slice(0, 10);
|
||||
|
||||
sampleFiles.forEach((file, index) => {
|
||||
const relativePath = file.filename.replace(basePath + '/', '');
|
||||
console.log(` ${index + 1}. ${relativePath} (${file.size} bytes)`);
|
||||
});
|
||||
|
||||
if (fileCount > 10) {
|
||||
console.log(` ... and ${fileCount - 10} more files`);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('❌ Deep listing returned non-array response:', deepContents);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error during WebDAV operations:', error);
|
||||
console.error('Error details:', error.message);
|
||||
if (error.response) {
|
||||
console.error('Response status:', error.response.status);
|
||||
console.error('Response data:', error.response.data);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load configuration or create client:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the debug function
|
||||
debugWebDAVFiles().catch(console.error);
|
||||
@ -37,6 +37,9 @@ export class AudioMetadataService {
|
||||
'wma': 'WMA',
|
||||
'OPUS': 'OPUS',
|
||||
'opus': 'OPUS',
|
||||
'AIFF': 'AIFF',
|
||||
'aiff': 'AIFF',
|
||||
'aif': 'AIFF',
|
||||
};
|
||||
|
||||
// Try to map the container format
|
||||
@ -204,7 +207,7 @@ export class AudioMetadataService {
|
||||
*/
|
||||
isAudioFile(fileName: string): boolean {
|
||||
const supportedFormats = [
|
||||
'mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma', 'opus'
|
||||
'mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma', 'opus', 'aif', 'aiff'
|
||||
];
|
||||
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export interface JobProgress {
|
||||
jobId: string;
|
||||
type: 's3-sync' | 'song-matching';
|
||||
type: 'storage-sync' | 'song-matching';
|
||||
status: 'running' | 'completed' | 'failed';
|
||||
progress: number; // 0-100
|
||||
current: number;
|
||||
@ -185,6 +185,8 @@ class BackgroundJobService {
|
||||
case 'ogg': return 'audio/ogg';
|
||||
case 'opus': return 'audio/opus';
|
||||
case 'wma': return 'audio/x-ms-wma';
|
||||
case 'aif': return 'audio/aiff';
|
||||
case 'aiff': return 'audio/aiff';
|
||||
default: return 'application/octet-stream';
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,7 +96,7 @@ export class WebDAVService implements StorageProvider {
|
||||
|
||||
return {
|
||||
key: finalKey,
|
||||
url: `${this.basePath}/${finalKey}`,
|
||||
url: `${this.config.url}${this.basePath}/${finalKey}`,
|
||||
size: file.length,
|
||||
contentType,
|
||||
};
|
||||
@ -112,6 +112,7 @@ export class WebDAVService implements StorageProvider {
|
||||
try {
|
||||
const response = await this.client.getDirectoryContents(searchPath, {
|
||||
deep: true,
|
||||
maxDepth: -1, // No depth limit
|
||||
});
|
||||
|
||||
// Handle both single item and array responses
|
||||
@ -146,6 +147,7 @@ export class WebDAVService implements StorageProvider {
|
||||
try {
|
||||
const response = await this.client.getDirectoryContents(searchPath, {
|
||||
deep: true,
|
||||
maxDepth: -1, // No depth limit
|
||||
});
|
||||
|
||||
// Handle both single item and array responses
|
||||
@ -172,8 +174,8 @@ export class WebDAVService implements StorageProvider {
|
||||
*/
|
||||
async getPresignedUrl(key: string, expiresIn: number = 3600): Promise<string> {
|
||||
// WebDAV doesn't support presigned URLs, so we return a direct URL
|
||||
// In a real implementation, you might want to implement token-based access
|
||||
const baseUrl = (this.client as any).getURL?.() || (this.client as any).toString() || this.config.url;
|
||||
// Use the config URL directly since WebDAV client doesn't expose getURL()
|
||||
const baseUrl = this.config.url;
|
||||
return `${baseUrl}${this.basePath}/${key}`;
|
||||
}
|
||||
|
||||
@ -222,7 +224,8 @@ export class WebDAVService implements StorageProvider {
|
||||
* Get streaming URL for a file
|
||||
*/
|
||||
async getStreamingUrl(key: string): Promise<string> {
|
||||
const baseUrl = (this.client as any).getURL?.() || (this.client as any).toString() || this.config.url;
|
||||
// Use the config URL directly since WebDAV client doesn't expose getURL()
|
||||
const baseUrl = this.config.url;
|
||||
return `${baseUrl}${this.basePath}/${key}`;
|
||||
}
|
||||
|
||||
@ -272,6 +275,8 @@ export class WebDAVService implements StorageProvider {
|
||||
case 'ogg': return 'audio/ogg';
|
||||
case 'opus': return 'audio/opus';
|
||||
case 'wma': return 'audio/x-ms-wma';
|
||||
case 'aif': return 'audio/aiff';
|
||||
case 'aiff': return 'audio/aiff';
|
||||
default: return 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
"url": "https://cloud.geertrademakers.nl/remote.php/dav/files/admin",
|
||||
"username": "admin",
|
||||
"password": "XPZK2-MGQ5W-7Yetf-nr8gf-s5g5Z",
|
||||
"basePath": "/Test"
|
||||
"basePath": "/Music"
|
||||
}
|
||||
45
packages/backend/test-audio-detection.js
Normal file
45
packages/backend/test-audio-detection.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { AudioMetadataService } from './dist/services/audioMetadataService.js';
|
||||
|
||||
const audioService = new AudioMetadataService();
|
||||
|
||||
// Test files from the debug output
|
||||
const testFiles = [
|
||||
'01 Gas Op Die Lollie.mp3',
|
||||
'ACRAZE - Do It To It (Extended Mix).mp3',
|
||||
'test.flac',
|
||||
'sample.wav',
|
||||
'music.m4a',
|
||||
'song.aac',
|
||||
'track.ogg',
|
||||
'audio.opus',
|
||||
'file.wma',
|
||||
'sound.aif',
|
||||
'music.aiff',
|
||||
'image.jpg',
|
||||
'archive.zip',
|
||||
'script.py',
|
||||
'info.nfo',
|
||||
'video.mp4',
|
||||
'installer.dmg',
|
||||
'playlist.m3u',
|
||||
'readme.md',
|
||||
'script.sh'
|
||||
];
|
||||
|
||||
console.log('🎵 Testing audio file detection:');
|
||||
console.log('');
|
||||
|
||||
testFiles.forEach(filename => {
|
||||
const isAudio = audioService.isAudioFile(filename);
|
||||
const status = isAudio ? '✅' : '❌';
|
||||
console.log(`${status} ${filename} -> ${isAudio ? 'AUDIO' : 'NOT AUDIO'}`);
|
||||
});
|
||||
|
||||
console.log('');
|
||||
console.log('📊 Summary:');
|
||||
const audioFiles = testFiles.filter(f => audioService.isAudioFile(f));
|
||||
const nonAudioFiles = testFiles.filter(f => !audioService.isAudioFile(f));
|
||||
|
||||
console.log(`Audio files: ${audioFiles.length}`);
|
||||
console.log(`Non-audio files: ${nonAudioFiles.length}`);
|
||||
console.log(`Total files: ${testFiles.length}`);
|
||||
113
packages/backend/test-background-job-flow.js
Normal file
113
packages/backend/test-background-job-flow.js
Normal file
@ -0,0 +1,113 @@
|
||||
import { StorageProviderFactory } from './dist/services/storageProvider.js';
|
||||
import { AudioMetadataService } from './dist/services/audioMetadataService.js';
|
||||
|
||||
async function testBackgroundJobFlow() {
|
||||
try {
|
||||
console.log('🔍 Testing Background Job Flow:');
|
||||
console.log('');
|
||||
|
||||
// Step 1: Load config and create provider (same as background job)
|
||||
console.log('1️⃣ Loading storage configuration...');
|
||||
const config = await StorageProviderFactory.loadConfig();
|
||||
console.log('Config provider:', config.provider);
|
||||
console.log('Config basePath:', config.basePath);
|
||||
console.log('');
|
||||
|
||||
// Step 2: Create storage service (same as background job)
|
||||
console.log('2️⃣ Creating storage service...');
|
||||
const storageService = await StorageProviderFactory.createProvider(config);
|
||||
console.log('Storage service created:', storageService.constructor.name);
|
||||
console.log('');
|
||||
|
||||
// Step 3: List all files (same as background job)
|
||||
console.log('3️⃣ Listing all files from storage...');
|
||||
const startTime = Date.now();
|
||||
const storageFiles = await storageService.listAllFiles();
|
||||
const endTime = Date.now();
|
||||
console.log(`✅ listAllFiles completed in ${endTime - startTime}ms`);
|
||||
console.log('Total storage files found:', storageFiles.length);
|
||||
console.log('');
|
||||
|
||||
// Step 4: Filter for audio files (same as background job)
|
||||
console.log('4️⃣ Filtering for audio files...');
|
||||
const audioMetadataService = new AudioMetadataService();
|
||||
|
||||
const audioFiles = storageFiles.filter(storageFile => {
|
||||
const filename = storageFile.key.split('/').pop() || storageFile.key;
|
||||
const isAudio = audioMetadataService.isAudioFile(filename);
|
||||
if (!isAudio) {
|
||||
console.log(` ❌ Not audio: ${filename}`);
|
||||
}
|
||||
return isAudio;
|
||||
});
|
||||
|
||||
console.log('Audio files found:', audioFiles.length);
|
||||
console.log('');
|
||||
|
||||
// Step 5: Show breakdown by file type
|
||||
console.log('5️⃣ File type breakdown:');
|
||||
const fileTypes = new Map();
|
||||
storageFiles.forEach(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
const ext = filename.split('.').pop()?.toLowerCase() || 'no-extension';
|
||||
fileTypes.set(ext, (fileTypes.get(ext) || 0) + 1);
|
||||
});
|
||||
|
||||
const sortedTypes = Array.from(fileTypes.entries()).sort((a, b) => b[1] - a[1]);
|
||||
sortedTypes.forEach(([ext, count]) => {
|
||||
const isAudio = audioMetadataService.isAudioFile(`test.${ext}`);
|
||||
const status = isAudio ? '🎵' : '📄';
|
||||
console.log(` ${status} .${ext}: ${count} files`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Step 6: Show MP3 breakdown
|
||||
console.log('6️⃣ MP3 files breakdown:');
|
||||
const mp3Files = storageFiles.filter(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
});
|
||||
|
||||
console.log('MP3 files found:', mp3Files.length);
|
||||
|
||||
// Show directory distribution of MP3 files
|
||||
const mp3DirCounts = new Map();
|
||||
mp3Files.forEach(file => {
|
||||
const dir = file.key.split('/')[0];
|
||||
mp3DirCounts.set(dir, (mp3DirCounts.get(dir) || 0) + 1);
|
||||
});
|
||||
|
||||
const sortedMp3Dirs = Array.from(mp3DirCounts.entries()).sort((a, b) => b[1] - a[1]);
|
||||
console.log('MP3 files by directory:');
|
||||
sortedMp3Dirs.forEach(([dir, count]) => {
|
||||
console.log(` 📁 ${dir}: ${count} MP3 files`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Step 7: Test with different prefixes (if WebDAV)
|
||||
if (config.provider === 'webdav') {
|
||||
console.log('7️⃣ Testing with different prefixes...');
|
||||
const testPrefixes = ['', 'Gekocht', 'Merijn Music', 'Musica'];
|
||||
for (const prefix of testPrefixes) {
|
||||
try {
|
||||
const prefixFiles = await storageService.listAllFiles(prefix);
|
||||
const prefixMp3Files = prefixFiles.filter(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
});
|
||||
console.log(` 📁 Prefix "${prefix}": ${prefixFiles.length} total, ${prefixMp3Files.length} MP3`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Prefix "${prefix}": Error - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to test background job flow:', error);
|
||||
console.error('Error details:', error.message);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testBackgroundJobFlow().catch(console.error);
|
||||
94
packages/backend/test-background-job-simulation.js
Normal file
94
packages/backend/test-background-job-simulation.js
Normal file
@ -0,0 +1,94 @@
|
||||
import { StorageProviderFactory } from './dist/services/storageProvider.js';
|
||||
import { AudioMetadataService } from './dist/services/audioMetadataService.js';
|
||||
|
||||
async function testBackgroundJobSimulation() {
|
||||
try {
|
||||
console.log('🔍 Testing Background Job Simulation:');
|
||||
console.log('');
|
||||
|
||||
// Step 1: Load config and create provider (exactly like background job)
|
||||
console.log('1️⃣ Loading storage configuration...');
|
||||
const config = await StorageProviderFactory.loadConfig();
|
||||
console.log('Config provider:', config.provider);
|
||||
console.log('Config basePath:', config.basePath);
|
||||
console.log('');
|
||||
|
||||
// Step 2: Create storage service (exactly like background job)
|
||||
console.log('2️⃣ Creating storage service...');
|
||||
const storageService = await StorageProviderFactory.createProvider(config);
|
||||
console.log('Storage service created:', storageService.constructor.name);
|
||||
console.log('');
|
||||
|
||||
// Step 3: Create audio metadata service (exactly like background job)
|
||||
console.log('3️⃣ Creating audio metadata service...');
|
||||
const audioMetadataService = new AudioMetadataService();
|
||||
console.log('Audio metadata service created');
|
||||
console.log('');
|
||||
|
||||
// Step 4: List all files (exactly like background job)
|
||||
console.log('4️⃣ Listing all files from storage...');
|
||||
const startTime = Date.now();
|
||||
const storageFiles = await storageService.listAllFiles();
|
||||
const endTime = Date.now();
|
||||
console.log(`✅ listAllFiles completed in ${endTime - startTime}ms`);
|
||||
console.log('Total storage files found:', storageFiles.length);
|
||||
console.log('');
|
||||
|
||||
// Step 5: Filter for audio files (exactly like background job)
|
||||
console.log('5️⃣ Filtering for audio files...');
|
||||
const audioFiles = storageFiles.filter(storageFile => {
|
||||
const filename = storageFile.key.split('/').pop() || storageFile.key;
|
||||
const isAudio = audioMetadataService.isAudioFile(filename);
|
||||
return isAudio;
|
||||
});
|
||||
|
||||
console.log('Audio files found:', audioFiles.length);
|
||||
console.log('');
|
||||
|
||||
// Step 6: Show MP3 breakdown
|
||||
console.log('6️⃣ MP3 files breakdown:');
|
||||
const mp3Files = audioFiles.filter(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
});
|
||||
|
||||
console.log('MP3 files found:', mp3Files.length);
|
||||
console.log('');
|
||||
|
||||
// Step 7: Show file type breakdown
|
||||
console.log('7️⃣ File type breakdown:');
|
||||
const fileTypes = new Map();
|
||||
audioFiles.forEach(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
const ext = filename.split('.').pop()?.toLowerCase() || 'no-extension';
|
||||
fileTypes.set(ext, (fileTypes.get(ext) || 0) + 1);
|
||||
});
|
||||
|
||||
const sortedTypes = Array.from(fileTypes.entries()).sort((a, b) => b[1] - a[1]);
|
||||
sortedTypes.forEach(([ext, count]) => {
|
||||
console.log(` .${ext}: ${count} files`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Step 8: Show directory breakdown
|
||||
console.log('8️⃣ Directory breakdown:');
|
||||
const dirCounts = new Map();
|
||||
audioFiles.forEach(file => {
|
||||
const dir = file.key.split('/')[0];
|
||||
dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
|
||||
});
|
||||
|
||||
const sortedDirs = Array.from(dirCounts.entries()).sort((a, b) => b[1] - a[1]);
|
||||
sortedDirs.forEach(([dir, count]) => {
|
||||
console.log(` 📁 ${dir}: ${count} audio files`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to test background job simulation:', error);
|
||||
console.error('Error details:', error.message);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testBackgroundJobSimulation().catch(console.error);
|
||||
51
packages/backend/test-current-webdav.js
Normal file
51
packages/backend/test-current-webdav.js
Normal file
@ -0,0 +1,51 @@
|
||||
import { StorageProviderFactory } from './dist/services/storageProvider.js';
|
||||
|
||||
async function testCurrentWebDAV() {
|
||||
try {
|
||||
console.log('🔍 Testing Current WebDAV Service:');
|
||||
console.log('');
|
||||
|
||||
// Load config and create provider (same as background job)
|
||||
const config = await StorageProviderFactory.loadConfig();
|
||||
console.log('Config provider:', config.provider);
|
||||
console.log('Config basePath:', config.basePath);
|
||||
console.log('');
|
||||
|
||||
// Create storage service
|
||||
const storageService = await StorageProviderFactory.createProvider(config);
|
||||
console.log('Storage service created:', storageService.constructor.name);
|
||||
console.log('');
|
||||
|
||||
// List all files
|
||||
console.log('📁 Listing all files...');
|
||||
const startTime = Date.now();
|
||||
const storageFiles = await storageService.listAllFiles();
|
||||
const endTime = Date.now();
|
||||
console.log(`✅ listAllFiles completed in ${endTime - startTime}ms`);
|
||||
console.log('Total storage files found:', storageFiles.length);
|
||||
console.log('');
|
||||
|
||||
// Filter for MP3 files
|
||||
const mp3Files = storageFiles.filter(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
});
|
||||
|
||||
console.log('🎵 MP3 files found:', mp3Files.length);
|
||||
console.log('');
|
||||
|
||||
// Show first 10 MP3 files
|
||||
console.log('🎵 First 10 MP3 files:');
|
||||
mp3Files.slice(0, 10).forEach((file, index) => {
|
||||
console.log(` ${index + 1}. ${file.key} (${file.size} bytes)`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to test current WebDAV service:', error);
|
||||
console.error('Error details:', error.message);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testCurrentWebDAV().catch(console.error);
|
||||
1
packages/backend/test-playback.mp3
Normal file
1
packages/backend/test-playback.mp3
Normal file
@ -0,0 +1 @@
|
||||
test content
|
||||
54
packages/backend/test-server-webdav.js
Normal file
54
packages/backend/test-server-webdav.js
Normal file
@ -0,0 +1,54 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
async function testServerWebDAV() {
|
||||
try {
|
||||
console.log('🔍 Testing Server WebDAV via API:');
|
||||
console.log('');
|
||||
|
||||
// Test the storage sync endpoint
|
||||
console.log('1️⃣ Testing storage sync endpoint...');
|
||||
const response = await fetch('http://localhost:3000/api/music/sync-s3', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ force: true })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
console.log('✅ Storage sync started:', result);
|
||||
console.log('');
|
||||
|
||||
// Wait a bit and check job progress
|
||||
console.log('2️⃣ Waiting for job to start...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const jobsResponse = await fetch('http://localhost:3000/api/background-jobs/jobs');
|
||||
if (jobsResponse.ok) {
|
||||
const jobs = await jobsResponse.json();
|
||||
const latestJob = jobs.jobs[jobs.jobs.length - 1];
|
||||
console.log('📊 Latest job status:', {
|
||||
jobId: latestJob.jobId,
|
||||
status: latestJob.status,
|
||||
progress: latestJob.progress,
|
||||
message: latestJob.message,
|
||||
current: latestJob.current,
|
||||
total: latestJob.total
|
||||
});
|
||||
|
||||
if (latestJob.result) {
|
||||
console.log('📊 Job result:', latestJob.result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('❌ Failed to start storage sync:', response.status, response.statusText);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to test server WebDAV:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testServerWebDAV().catch(console.error);
|
||||
92
packages/backend/test-webdav-service.js
Normal file
92
packages/backend/test-webdav-service.js
Normal file
@ -0,0 +1,92 @@
|
||||
import { WebDAVService } from './src/services/webdavService.js';
|
||||
import fs from 'fs';
|
||||
|
||||
async function testWebDAVService() {
|
||||
try {
|
||||
// Load configuration
|
||||
const configData = fs.readFileSync('storage-config.json', 'utf-8');
|
||||
const config = JSON.parse(configData);
|
||||
|
||||
console.log('🔍 Testing WebDAV Service:');
|
||||
console.log('URL:', config.url);
|
||||
console.log('Username:', config.username);
|
||||
console.log('Base Path:', config.basePath);
|
||||
console.log('');
|
||||
|
||||
// Create WebDAV service
|
||||
const webdavService = new WebDAVService(config);
|
||||
|
||||
console.log('🔗 Testing connection...');
|
||||
const connectionTest = await webdavService.testConnection();
|
||||
console.log('Connection test:', connectionTest ? '✅ Success' : '❌ Failed');
|
||||
console.log('');
|
||||
|
||||
if (connectionTest) {
|
||||
console.log('📁 Testing listAllFiles...');
|
||||
const startTime = Date.now();
|
||||
const allFiles = await webdavService.listAllFiles();
|
||||
const endTime = Date.now();
|
||||
|
||||
console.log(`✅ listAllFiles completed in ${endTime - startTime}ms`);
|
||||
console.log('Total files found:', allFiles.length);
|
||||
console.log('');
|
||||
|
||||
// Filter for MP3 files
|
||||
const mp3Files = allFiles.filter(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
});
|
||||
|
||||
console.log('🎵 MP3 Files found by service:', mp3Files.length);
|
||||
console.log('');
|
||||
|
||||
// Show first 20 MP3 files
|
||||
console.log('🎵 First 20 MP3 files from service:');
|
||||
mp3Files.slice(0, 20).forEach((file, index) => {
|
||||
console.log(` ${index + 1}. ${file.key} (${file.size} bytes)`);
|
||||
});
|
||||
|
||||
if (mp3Files.length > 20) {
|
||||
console.log(` ... and ${mp3Files.length - 20} more MP3 files`);
|
||||
}
|
||||
|
||||
// Check directory distribution
|
||||
console.log('');
|
||||
console.log('📁 Directory distribution of MP3 files:');
|
||||
const dirCounts = new Map();
|
||||
mp3Files.forEach(file => {
|
||||
const dir = file.key.split('/')[0];
|
||||
dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
|
||||
});
|
||||
|
||||
const sortedDirs = Array.from(dirCounts.entries()).sort((a, b) => b[1] - a[1]);
|
||||
sortedDirs.forEach(([dir, count]) => {
|
||||
console.log(` 📁 ${dir}: ${count} MP3 files`);
|
||||
});
|
||||
|
||||
// Test with different prefixes
|
||||
console.log('');
|
||||
console.log('🔍 Testing with different prefixes...');
|
||||
|
||||
const testPrefixes = ['', 'Gekocht', 'Merijn Music', 'Musica'];
|
||||
for (const prefix of testPrefixes) {
|
||||
try {
|
||||
const prefixFiles = await webdavService.listAllFiles(prefix);
|
||||
const prefixMp3Files = prefixFiles.filter(file => {
|
||||
const filename = file.key.split('/').pop() || file.key;
|
||||
return filename.toLowerCase().endsWith('.mp3');
|
||||
});
|
||||
console.log(` 📁 Prefix "${prefix}": ${prefixMp3Files.length} MP3 files`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Prefix "${prefix}": Error - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to test WebDAV service:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testWebDAVService().catch(console.error);
|
||||
@ -56,98 +56,42 @@ export const BackgroundJobProgress: React.FC<BackgroundJobProgressProps> = ({
|
||||
const { isOpen, onClose } = useDisclosure();
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Load all jobs
|
||||
const loadJobs = async () => {
|
||||
// Simple polling function
|
||||
const pollJobs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const jobsData = await api.getAllJobs();
|
||||
setJobs(jobsData);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load jobs');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Update specific job progress
|
||||
const updateJobProgress = async (jobId: string) => {
|
||||
try {
|
||||
const progress = await api.getJobProgress(jobId);
|
||||
|
||||
setJobs(prev => prev.map(job =>
|
||||
job.jobId === jobId ? progress : job
|
||||
));
|
||||
|
||||
// Handle job completion
|
||||
if (progress.status === 'completed' && onJobComplete) {
|
||||
onJobComplete(progress.result);
|
||||
} else if (progress.status === 'failed' && onJobError) {
|
||||
onJobError(progress.error || 'Job failed');
|
||||
// Handle job completion for the specific job if provided
|
||||
if (jobId) {
|
||||
const specificJob = jobsData.find((j: JobProgress) => j.jobId === jobId);
|
||||
if (specificJob) {
|
||||
if (specificJob.status === 'completed' && onJobComplete) {
|
||||
onJobComplete(specificJob.result);
|
||||
} else if (specificJob.status === 'failed' && onJobError) {
|
||||
onJobError(specificJob.error || 'Job failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error updating job progress:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Start polling for jobs and update progress
|
||||
const startPolling = () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
|
||||
const tick = async () => {
|
||||
try {
|
||||
// Always reload job list to detect newly started jobs
|
||||
const jobsData = await api.getAllJobs();
|
||||
setJobs(jobsData);
|
||||
|
||||
// Update progress for active jobs
|
||||
const activeJobIds = jobsData.filter((j: JobProgress) => j.status === 'running').map((j: JobProgress) => j.jobId);
|
||||
for (const id of activeJobIds) {
|
||||
await updateJobProgress(id);
|
||||
}
|
||||
|
||||
if (jobId) {
|
||||
await updateJobProgress(jobId);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore transient polling errors
|
||||
}
|
||||
};
|
||||
|
||||
// Adaptive interval: 2s if active jobs, else 10s
|
||||
const schedule = async () => {
|
||||
await tick();
|
||||
const hasActive = (jobs || []).some(j => j.status === 'running');
|
||||
const delay = hasActive ? 2000 : 10000;
|
||||
intervalRef.current = setTimeout(schedule, delay) as any;
|
||||
};
|
||||
|
||||
schedule();
|
||||
};
|
||||
|
||||
// Stop polling
|
||||
const stopPolling = () => {
|
||||
if (intervalRef.current) {
|
||||
clearTimeout(intervalRef.current as any);
|
||||
intervalRef.current = null;
|
||||
// ignore transient polling errors
|
||||
}
|
||||
};
|
||||
|
||||
// Start polling on mount and stop on unmount
|
||||
useEffect(() => {
|
||||
loadJobs();
|
||||
startPolling();
|
||||
return () => stopPolling();
|
||||
}, []);
|
||||
// Initial poll
|
||||
pollJobs();
|
||||
|
||||
// Set up interval polling
|
||||
const interval = setInterval(() => {
|
||||
pollJobs();
|
||||
}, 10000); // Simple 10-second interval
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
stopPolling();
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
}, [jobId, onJobComplete, onJobError]);
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
@ -246,7 +190,7 @@ export const BackgroundJobProgress: React.FC<BackgroundJobProgressProps> = ({
|
||||
<VStack spacing={4} align="stretch">
|
||||
<HStack justify="space-between">
|
||||
<Text fontWeight="bold" color="gray.100">All Jobs</Text>
|
||||
<Button size="sm" onClick={loadJobs} isLoading={loading}>
|
||||
<Button size="sm" onClick={() => pollJobs()} isLoading={loading}>
|
||||
Refresh
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user