diff --git a/backend/WebsocketHandler.js b/backend/WebsocketHandler.js index 1ffacde..c331031 100644 --- a/backend/WebsocketHandler.js +++ b/backend/WebsocketHandler.js @@ -1,10 +1,11 @@ const WebSocket = require('ws'); -function createWebSocketServer(port) { - const wss = new WebSocket.Server({ port }); +function createWebSocketServer() { + var messages=[]; + const port=process.env.WS_PORT || 3004 ; + const wss = new WebSocket.Server({port}); // function to handle WebSocket connections - function handleConnection(ws) { console.log('Client connected'); @@ -17,24 +18,30 @@ function createWebSocketServer(port) { ws.on('close', () => { console.log('Client disconnected'); }); - } + ws.send(JSON.stringify(messages)); + } // call the handleConnection function for each new WebSocket connection wss.on('connection', handleConnection); // define a separate method that sends a message to all connected clients function sendMessageToClients(message) { + messages.push(message); wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { - let parsedMessage = JSON.stringify(message); - console.log(parsedMessage); - client.send(parsedMessage); + client.send(JSON.stringify(messages)); } }); } - return sendMessageToClients; + function clearMessages() { + messages=[]; + } + + return {sendMessageToClients, clearMessages, wss}; } -module.exports = createWebSocketServer; +const wsServer = createWebSocketServer(); + +module.exports = wsServer; diff --git a/backend/api.js b/backend/api.js index 13b4cae..ff9e219 100644 --- a/backend/api.js +++ b/backend/api.js @@ -187,7 +187,7 @@ router.get("/getHistory", async (req, res) => { const { rows } = await db.query( - `SELECT * FROM jf_playback_activity order by "ActivityDateInserted" desc` + `SELECT * FROM jf_all_playback_activity order by "ActivityDateInserted" desc` ); const groupedResults = {}; diff --git a/backend/backup.js b/backend/backup.js index b0bcc15..3d78613 100644 --- a/backend/backup.js +++ b/backend/backup.js @@ -1,7 +1,13 @@ const { Router } = require('express'); const { Pool } = require('pg'); const fs = require('fs'); -const readline = require('readline'); +const path = require('path'); +const moment = require('moment'); + +const wss = require("./WebsocketHandler"); + +var messages=[]; + const router = Router(); @@ -13,7 +19,7 @@ const postgresPort = process.env.POSTGRES_PORT; const postgresDatabase = process.env.POSTGRES_DATABASE || 'jfstat'; // Tables to back up -const tables = ['jf_libraries', 'jf_library_items', 'jf_library_seasons','jf_library_episodes','jf_users','jf_playback_activity']; +const tables = ['jf_libraries', 'jf_library_items', 'jf_library_seasons','jf_library_episodes','jf_users','jf_playback_activity','jf_playback_reporting_plugin_data','jf_item_info']; // Backup function async function backup() { @@ -26,19 +32,25 @@ async function backup() { }); // Get data from each table and append it to the backup file - const backupPath = './backup-data/backup.json'; + let now = moment(); + const backupPath = `./backup-data/backup_${now.format('yyyy-MM-DD HH-mm-ss')}.json`; const stream = fs.createWriteStream(backupPath, { flags: 'a' }); const backup_data=[]; + wss.clearMessages(); + wss.sendMessageToClients({ color: "yellow", Message: "Begin Backup "+backupPath }); for (let table of tables) { const query = `SELECT * FROM ${table}`; const { rows } = await pool.query(query); console.log(`Reading ${rows.length} rows for table ${table}`); + wss.sendMessageToClients({color: "dodgerblue",Message: `Saving ${rows.length} rows for table ${table}`}); - backup_data.push({[table]:rows}); + backup_data.push({[table]:rows}); // stream.write(JSON.stringify(backup_data)); } + + wss.sendMessageToClients({ color: "lawngreen", Message: "Backup Complete" }); stream.write(JSON.stringify(backup_data)); stream.end(); @@ -151,6 +163,55 @@ router.get('/restore', async (req, res) => { res.status(500).send('Backup failed'); } }); + + //list backup files + const backupfolder='backup-data'; + + + router.get('/files', (req, res) => { + const directoryPath = path.join(__dirname, backupfolder); + fs.readdir(directoryPath, (err, files) => { + if (err) { + res.status(500).send('Unable to read directory'); + } else { + const fileData = files.map(file => { + const filePath = path.join(directoryPath, file); + const stats = fs.statSync(filePath); + return { + name: file, + size: stats.size, + datecreated: stats.birthtime + }; + }); + res.json(fileData); + } + }); + }); + + + //download backup file + router.get('/files/:filename', (req, res) => { + const filePath = path.join(__dirname, backupfolder, req.params.filename); + res.download(filePath); + }); + + //delete backup + router.delete('/files/:filename', (req, res) => { + const filePath = path.join(__dirname, backupfolder, req.params.filename); + + fs.unlink(filePath, (err) => { + if (err) { + console.error(err); + res.status(500).send('An error occurred while deleting the file.'); + return; + } + + console.log(`${filePath} has been deleted.`); + res.status(200).send(`${filePath} has been deleted.`); + }); + }); + + diff --git a/backend/server.js b/backend/server.js index ca7b7e0..9a59194 100644 --- a/backend/server.js +++ b/backend/server.js @@ -51,7 +51,7 @@ app.use('/auth', authRouter); // mount the API router at /api, with JWT middlewa app.use('/api', verifyToken, apiRouter); // mount the API router at /api, with JWT middleware app.use('/sync', verifyToken, syncRouter); // mount the API router at /sync, with JWT middleware app.use('/stats', verifyToken, statsRouter); // mount the API router at /stats, with JWT middleware -app.use('/data', backupRouter); // mount the API router at /stats, with JWT middleware +app.use('/data', verifyToken, backupRouter); // mount the API router at /stats, with JWT middleware try{ createdb.createDatabase().then((result) => { diff --git a/backend/stats.js b/backend/stats.js index 58556a1..187b54e 100644 --- a/backend/stats.js +++ b/backend/stats.js @@ -419,7 +419,7 @@ router.post("/getGlobalItemStats", async (req, res) => { const { rows } = await db.query( `select count(*)"Plays", sum("PlaybackDuration") total_playback_duration - from jf_playback_activity + from jf_all_playback_activity jf_playback_activity where ("EpisodeId"='${itemid}' OR "SeasonId"='${itemid}' OR "NowPlayingItemId"='${itemid}') AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * ${_hours} AND NOW();` diff --git a/backend/sync.js b/backend/sync.js index 40c344a..8b43234 100644 --- a/backend/sync.js +++ b/backend/sync.js @@ -3,8 +3,9 @@ const pgp = require("pg-promise")(); const db = require("./db"); const axios = require("axios"); -const ws = require("./WebsocketHandler"); -const sendMessageToClients = ws(process.env.WS_PORT || 3004); +const wss = require("./WebsocketHandler"); +const socket=wss; + const router = express.Router(); @@ -134,7 +135,7 @@ async function syncUserData() const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); if (rows[0].JF_HOST === null || rows[0].JF_API_KEY === null) { res.send({ error: "Config Details Not Found" }); - sendMessageToClients({ Message: "Error: Config details not found!" }); + socket.sendMessageToClients({ Message: "Error: Config details not found!" }); return; } @@ -160,9 +161,9 @@ async function syncUserData() if (dataToInsert.length !== 0) { let result = await db.insertBulk("jf_users",dataToInsert,jf_users_columns); if (result.Result === "SUCCESS") { - sendMessageToClients(dataToInsert.length + " Rows Inserted."); + socket.sendMessageToClients(dataToInsert.length + " Rows Inserted."); } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -173,9 +174,9 @@ async function syncUserData() if (toDeleteIds.length > 0) { let result = await db.deleteBulk("jf_users",toDeleteIds); if (result.Result === "SUCCESS") { - sendMessageToClients(toDeleteIds.length + " Rows Removed."); + socket.sendMessageToClients(toDeleteIds.length + " Rows Removed."); } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } @@ -187,7 +188,7 @@ async function syncLibraryFolders() const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); if (rows[0].JF_HOST === null || rows[0].JF_API_KEY === null) { res.send({ error: "Config Details Not Found" }); - sendMessageToClients({ Message: "Error: Config details not found!" }); + socket.sendMessageToClients({ Message: "Error: Config details not found!" }); return; } @@ -213,9 +214,9 @@ async function syncLibraryFolders() if (dataToInsert.length !== 0) { let result = await db.insertBulk("jf_libraries",dataToInsert,jf_libraries_columns); if (result.Result === "SUCCESS") { - sendMessageToClients(dataToInsert.length + " Rows Inserted."); + socket.sendMessageToClients(dataToInsert.length + " Rows Inserted."); } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -226,9 +227,9 @@ async function syncLibraryFolders() if (toDeleteIds.length > 0) { let result = await db.deleteBulk("jf_libraries",toDeleteIds); if (result.Result === "SUCCESS") { - sendMessageToClients(toDeleteIds.length + " Rows Removed."); + socket.sendMessageToClients(toDeleteIds.length + " Rows Removed."); } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } @@ -244,9 +245,9 @@ async function syncLibraryItems() } const _sync = new sync(config[0].JF_HOST, config[0].JF_API_KEY); - sendMessageToClients({ color: "lawngreen", Message: "Syncing... 1/3" }); + socket.sendMessageToClients({ color: "lawngreen", Message: "Syncing... 1/3" }); - sendMessageToClients({color: "yellow",Message: "Beginning Library Item Sync",}); + socket.sendMessageToClients({color: "yellow",Message: "Beginning Library Item Sync",}); const admins = await _sync.getAdminUser(); const userid = admins[0].Id; @@ -289,7 +290,7 @@ async function syncLibraryItems() if (result.Result === "SUCCESS") { insertCounter += dataToInsert.length; } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -302,23 +303,23 @@ async function syncLibraryItems() if (result.Result === "SUCCESS") { deleteCounter +=toDeleteIds.length; } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } - sendMessageToClients({color: "dodgerblue",Message: insertCounter + " Library Items Inserted.",}); - sendMessageToClients({color: "orange",Message: deleteCounter + " Library Items Removed.",}); - sendMessageToClients({ color: "yellow", Message: "Item Sync Complete" }); + socket.sendMessageToClients({color: "dodgerblue",Message: insertCounter + " Library Items Inserted.",}); + socket.sendMessageToClients({color: "orange",Message: deleteCounter + " Library Items Removed.",}); + socket.sendMessageToClients({ color: "yellow", Message: "Item Sync Complete" }); // const { rows: cleanup } = await db.query('DELETE FROM jf_playback_activity where "NowPlayingItemId" not in (select "Id" from jf_library_items)' ); - // sendMessageToClients({ color: "orange", Message: cleanup.length+" orphaned activity logs removed" }); + // socket.sendMessageToClients({ color: "orange", Message: cleanup.length+" orphaned activity logs removed" }); } async function syncShowItems() { - sendMessageToClients({ color: "lawngreen", Message: "Syncing... 2/3" }); - sendMessageToClients({color: "yellow", Message: "Beginning Seasons and Episode sync",}); + socket.sendMessageToClients({ color: "lawngreen", Message: "Syncing... 2/3" }); + socket.sendMessageToClients({color: "yellow", Message: "Beginning Seasons and Episode sync",}); const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); @@ -387,7 +388,7 @@ async function syncShowItems() if (result.Result === "SUCCESS") { insertSeasonsCount += seasonsToInsert.length; } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -400,7 +401,7 @@ async function syncShowItems() if (result.Result === "SUCCESS") { deleteSeasonsCount +=toDeleteIds.length; } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } @@ -411,7 +412,7 @@ async function syncShowItems() if (result.Result === "SUCCESS") { insertEpisodeCount += episodesToInsert.length; } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -425,25 +426,25 @@ async function syncShowItems() if (result.Result === "SUCCESS") { deleteEpisodeCount +=toDeleteEpisodeIds.length; } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } - sendMessageToClients({ Message: "Sync complete for " + show.Name }); + socket.sendMessageToClients({ Message: "Sync complete for " + show.Name }); } - sendMessageToClients({color: "dodgerblue",Message: insertSeasonsCount + " Seasons inserted.",}); - sendMessageToClients({color: "orange",Message: deleteSeasonsCount + " Seasons Removed.",}); - sendMessageToClients({color: "dodgerblue",Message: insertEpisodeCount + " Episodes inserted.",}); - sendMessageToClients({color: "orange",Message: deleteEpisodeCount + " Episodes Removed.",}); - sendMessageToClients({ color: "yellow", Message: "Sync Complete" }); + socket.sendMessageToClients({color: "dodgerblue",Message: insertSeasonsCount + " Seasons inserted.",}); + socket.sendMessageToClients({color: "orange",Message: deleteSeasonsCount + " Seasons Removed.",}); + socket.sendMessageToClients({color: "dodgerblue",Message: insertEpisodeCount + " Episodes inserted.",}); + socket.sendMessageToClients({color: "orange",Message: deleteEpisodeCount + " Episodes Removed.",}); + socket.sendMessageToClients({ color: "yellow", Message: "Sync Complete" }); } async function syncItemInfo() { - sendMessageToClients({ color: "lawngreen", Message: "Syncing... 3/3" }); - sendMessageToClients({color: "yellow", Message: "Beginning File Info Sync",}); + socket.sendMessageToClients({ color: "lawngreen", Message: "Syncing... 3/3" }); + socket.sendMessageToClients({color: "yellow", Message: "Beginning File Info Sync",}); const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); @@ -485,7 +486,7 @@ async function syncItemInfo() if (result.Result === "SUCCESS") { insertItemInfoCount += ItemInfoToInsert.length; } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -498,7 +499,7 @@ async function syncItemInfo() if (result.Result === "SUCCESS") { deleteItemInfoCount +=toDeleteItemInfoIds.length; } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } @@ -529,7 +530,7 @@ async function syncItemInfo() if (result.Result === "SUCCESS") { insertEpisodeInfoCount += EpisodeInfoToInsert.length; } else { - sendMessageToClients({ + socket.sendMessageToClients({ color: "red", Message: "Error performing bulk insert:" + result.message, }); @@ -542,24 +543,24 @@ async function syncItemInfo() if (result.Result === "SUCCESS") { deleteEpisodeInfoCount +=toDeleteEpisodeInfoIds.length; } else { - sendMessageToClients({color: "red",Message: result.message,}); + socket.sendMessageToClients({color: "red",Message: result.message,}); } } console.log(Episode.Name) } - sendMessageToClients({color: "dodgerblue",Message: insertItemInfoCount + " Item Info inserted.",}); - sendMessageToClients({color: "orange",Message: deleteItemInfoCount + " Item Info Removed.",}); - sendMessageToClients({color: "dodgerblue",Message: insertEpisodeInfoCount + " Episodes Info inserted.",}); - sendMessageToClients({color: "orange",Message: deleteEpisodeInfoCount + " Episodes Info Removed.",}); - sendMessageToClients({ color: "lawngreen", Message: "Sync Complete" }); + socket.sendMessageToClients({color: "dodgerblue",Message: insertItemInfoCount + " Item Info inserted.",}); + socket.sendMessageToClients({color: "orange",Message: deleteItemInfoCount + " Item Info Removed.",}); + socket.sendMessageToClients({color: "dodgerblue",Message: insertEpisodeInfoCount + " Episodes Info inserted.",}); + socket.sendMessageToClients({color: "orange",Message: deleteEpisodeInfoCount + " Episodes Info Removed.",}); + socket.sendMessageToClients({ color: "lawngreen", Message: "Sync Complete" }); } async function syncPlaybackPluginData() { - sendMessageToClients({ color: "lawngreen", Message: "Syncing... 3/3" }); - sendMessageToClients({color: "yellow", Message: "Beginning File Info Sync",}); + socket.sendMessageToClients({ color: "lawngreen", Message: "Syncing... 3/3" }); + socket.sendMessageToClients({color: "yellow", Message: "Beginning File Info Sync",}); try { const { rows: config } = await db.query( @@ -624,12 +625,15 @@ async function syncPlaybackPluginData() ///////////////////////////////////////Sync All router.get("/beingSync", async (req, res) => { + socket.clearMessages(); + await syncUserData(); await syncLibraryFolders(); await syncLibraryItems(); await syncShowItems(); await syncItemInfo(); + res.send(); }); diff --git a/src/pages/components/general/last-watched-card.js b/src/pages/components/general/last-watched-card.js index 5fcb292..23278a3 100644 --- a/src/pages/components/general/last-watched-card.js +++ b/src/pages/components/general/last-watched-card.js @@ -55,6 +55,13 @@ function LastWatchedCard(props) {