diff --git a/packages/backend/src/routes/songs.ts b/packages/backend/src/routes/songs.ts
index 464cc9a..34fde89 100644
--- a/packages/backend/src/routes/songs.ts
+++ b/packages/backend/src/routes/songs.ts
@@ -195,34 +195,28 @@ router.get('/count', async (req: Request, res: Response) => {
}
});
-// Export library to XML format
+// Export library to XML format with streaming
router.get('/export', async (req: Request, res: Response) => {
try {
- console.log('Exporting library to XML format...');
-
- // Get all songs from database
- const allSongs = await Song.find({}).lean();
- console.log(`Found ${allSongs.length} songs to export`);
-
- // Get all playlists from database
- const allPlaylists = await Playlist.find({}).lean();
- console.log(`Found ${allPlaylists.length} playlists to export`);
-
- // Import the XML generation function
- const { exportToXml } = await import('../services/xmlService.js');
-
- // Generate XML content
- const xmlContent = exportToXml(allSongs, allPlaylists);
+ console.log('Starting streaming XML export...');
// Set response headers for file download
res.setHeader('Content-Type', 'application/xml');
res.setHeader('Content-Disposition', `attachment; filename="rekordbox-library-${new Date().toISOString().split('T')[0]}.xml"`);
+ res.setHeader('Transfer-Encoding', 'chunked');
- console.log('XML export completed successfully');
- res.send(xmlContent);
+ // Import the streaming XML generation function
+ const { streamToXml } = await import('../services/xmlService.js');
+
+ // Stream XML generation to response
+ await streamToXml(res);
+
+ console.log('Streaming XML export completed successfully');
} catch (error) {
console.error('Error exporting library:', error);
- res.status(500).json({ message: 'Error exporting library', error });
+ if (!res.headersSent) {
+ res.status(500).json({ message: 'Error exporting library', error });
+ }
}
});
diff --git a/packages/backend/src/services/xmlService.ts b/packages/backend/src/services/xmlService.ts
index d985cc8..5a13228 100644
--- a/packages/backend/src/services/xmlService.ts
+++ b/packages/backend/src/services/xmlService.ts
@@ -29,6 +29,97 @@ const buildXmlNode = (node: any): any => {
return xmlNode;
};
+export const streamToXml = async (res: any) => {
+ // Write XML header
+ res.write('\n');
+ res.write('\n');
+
+ // Start COLLECTION section
+ const songCount = await Song.countDocuments();
+ res.write(` \n`);
+
+ // Stream songs in batches to avoid memory issues
+ const batchSize = 100;
+ let processedSongs = 0;
+
+ while (processedSongs < songCount) {
+ const songs = await Song.find({})
+ .skip(processedSongs)
+ .limit(batchSize)
+ .lean();
+
+ for (const song of songs) {
+ res.write(` \n`);
+ } else {
+ res.write('/>\n');
+ }
+ }
+
+ processedSongs += songs.length;
+ console.log(`Streamed ${processedSongs}/${songCount} songs...`);
+ }
+
+ res.write(' \n');
+
+ // Start PLAYLISTS section
+ res.write(' \n');
+ res.write(' \n');
+
+ // Stream playlists
+ const playlists = await Playlist.find({}).lean();
+ for (const playlist of playlists) {
+ await streamPlaylistNode(res, playlist, 6);
+ }
+
+ res.write(' \n');
+ res.write(' \n');
+ res.write('');
+
+ res.end();
+};
+
+const streamPlaylistNode = async (res: any, node: any, indent: number) => {
+ const spaces = ' '.repeat(indent);
+ const nodeType = node.type === 'folder' ? '0' : '1';
+
+ if (node.type === 'folder') {
+ const childCount = node.children ? node.children.length : 0;
+ res.write(`${spaces}\n`);
+
+ if (node.children && node.children.length > 0) {
+ for (const child of node.children) {
+ await streamPlaylistNode(res, child, indent + 2);
+ }
+ }
+
+ res.write(`${spaces}\n`);
+ } else {
+ const trackCount = node.tracks ? node.tracks.length : 0;
+ res.write(`${spaces}\n`);
+
+ if (node.tracks && node.tracks.length > 0) {
+ for (const trackId of node.tracks) {
+ res.write(`${spaces} \n`);
+ }
+ }
+
+ res.write(`${spaces}\n`);
+ }
+};
+
+const escapeXml = (text: string): string => {
+ if (!text) return '';
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+};
+
export const exportToXml = (songs: any[], playlists: any[]): string => {
const xml = create({
DJ_PLAYLISTS: {