mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Merge pull request #366 from CyferShepard/unstable
Hotfix Release V1.1.5
This commit is contained in:
@@ -160,7 +160,19 @@ async function query({
|
||||
const countResult = await client.query(countQuery, values);
|
||||
const totalRows = parseInt(countResult.rows.length > 0 ? countResult.rows[0].count : 0, 10);
|
||||
|
||||
const skippedColumns = ["Name", "NowPlayingItemName"];
|
||||
const skippedColumns = [
|
||||
"Name",
|
||||
"NowPlayingItemName",
|
||||
"SeriesName",
|
||||
"SeasonName",
|
||||
"Id",
|
||||
"NowPlayingItemId",
|
||||
"ParentId",
|
||||
"SeriesId",
|
||||
"SeasonId",
|
||||
"EpisodeId",
|
||||
"ServerId",
|
||||
];
|
||||
// Convert integer fields in the result rows
|
||||
const convertedRows = result.rows.map((row) => {
|
||||
return Object.keys(row).reduce((acc, key) => {
|
||||
|
||||
@@ -20,6 +20,10 @@ class TaskManager {
|
||||
const worker = new Worker(task.path);
|
||||
|
||||
worker.on("message", (message) => {
|
||||
if (message.type === "log") {
|
||||
// Handle console.log messages
|
||||
console.log(`[Worker Log]: ${message.message}`);
|
||||
}
|
||||
if (message.status === "complete" && onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
@@ -63,6 +63,13 @@ async function deleteBulk(table_name, data, pkName) {
|
||||
return { Result: result, message: "" + message };
|
||||
}
|
||||
|
||||
function formatForCsv(value) {
|
||||
if (typeof value === "number") {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function updateSingleFieldBulk(table_name, data, field_name, new_value, where_field) {
|
||||
const client = await pool.connect();
|
||||
let result = "SUCCESS";
|
||||
@@ -74,16 +81,20 @@ async function updateSingleFieldBulk(table_name, data, field_name, new_value, wh
|
||||
await client.query("BEGIN");
|
||||
|
||||
if (data && data.length !== 0) {
|
||||
data = data.map((item) => formatForCsv(item));
|
||||
const updateQuery = {
|
||||
text: `UPDATE ${table_name} SET "${field_name}"='${new_value}' WHERE "${where_field}" IN (${pgp.as.csv(data)})`,
|
||||
};
|
||||
// console.log(deleteQuery);
|
||||
message = updateQuery.text;
|
||||
console.log(updateQuery.text);
|
||||
await client.query(updateQuery);
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
message = data.length + " Rows updated.";
|
||||
} catch (error) {
|
||||
error.query = message;
|
||||
console.log(error);
|
||||
await client.query("ROLLBACK");
|
||||
message = "Bulk update error: " + error;
|
||||
result = "ERROR";
|
||||
@@ -180,7 +191,19 @@ async function query(text, params, refreshViews = false) {
|
||||
}
|
||||
}
|
||||
|
||||
const skippedColumns = ["Name", "NowPlayingItemName"];
|
||||
const skippedColumns = [
|
||||
"Name",
|
||||
"NowPlayingItemName",
|
||||
"SeriesName",
|
||||
"SeasonName",
|
||||
"Id",
|
||||
"NowPlayingItemId",
|
||||
"ParentId",
|
||||
"SeriesId",
|
||||
"SeasonId",
|
||||
"EpisodeId",
|
||||
"ServerId",
|
||||
];
|
||||
// Convert integer fields in the result rows
|
||||
const convertedRows = result.rows.map((row) => {
|
||||
return Object.keys(row).reduce((acc, key) => {
|
||||
|
||||
@@ -71,9 +71,10 @@ class sync {
|
||||
let result = await db.updateSingleFieldBulk(tablename, dataToUpdate, field_name, field_value, where_field);
|
||||
if (result.Result === "SUCCESS") {
|
||||
syncTask.loggedData.push({ color: "dodgerblue", Message: dataToUpdate.length + " Rows updated." });
|
||||
return true;
|
||||
} else {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error: " + result.message });
|
||||
throw new Error("Error :" + result.message);
|
||||
syncTask.loggedData.push({ color: "red", Message: result.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,11 +140,21 @@ async function syncLibraryFolders(data, existing_excluded_libraries) {
|
||||
)
|
||||
.then((res) => res.rows.map((row) => row.Id));
|
||||
if (ItemsToArchive.length > 0) {
|
||||
await _sync.updateSingleFieldOnDB("jf_library_items", ItemsToArchive, "archived", true);
|
||||
const success = await _sync.updateSingleFieldOnDB("jf_library_items", ItemsToArchive, "archived", true);
|
||||
if (!success) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library items" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library items");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _sync.updateSingleFieldOnDB("jf_libraries", toArchiveLibraryIds, "archived", true);
|
||||
const success = await _sync.updateSingleFieldOnDB("jf_libraries", toArchiveLibraryIds, "archived", true);
|
||||
if (!success) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library");
|
||||
}
|
||||
}
|
||||
|
||||
for (const view of db.materializedViews) {
|
||||
@@ -183,7 +194,12 @@ async function archiveLibraryItems(fetchedData) {
|
||||
let toArchiveIds = existingIds.filter((id) => !fetchedData.some((row) => row === id));
|
||||
|
||||
if (toArchiveIds.length > 0) {
|
||||
await _sync.updateSingleFieldOnDB("jf_library_items", toArchiveIds, "archived", true);
|
||||
const success = await _sync.updateSingleFieldOnDB("jf_library_items", toArchiveIds, "archived", true);
|
||||
if (!success) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library items" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library items");
|
||||
}
|
||||
syncTask.loggedData.push({ color: "orange", Message: toArchiveIds.length + " Library Items Archived." });
|
||||
}
|
||||
}
|
||||
@@ -282,11 +298,21 @@ async function archiveSeasonsAndEpisodes(fetchedSeasons, fetchedEpisodes) {
|
||||
let toArchiveEpisodes = existingIdsEpisodes.filter((EpisodeId) => !fetchedEpisodes.some((row) => row === EpisodeId));
|
||||
|
||||
if (toArchiveSeasons.length > 0) {
|
||||
await _sync.updateSingleFieldOnDB("jf_library_seasons", toArchiveSeasons, "archived", true);
|
||||
const success = await _sync.updateSingleFieldOnDB("jf_library_seasons", toArchiveSeasons, "archived", true);
|
||||
if (!success) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library seasons" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library seasons");
|
||||
}
|
||||
syncTask.loggedData.push({ color: "orange", Message: toArchiveSeasons.length + " Seasons Archived." });
|
||||
}
|
||||
if (toArchiveEpisodes.length > 0) {
|
||||
await _sync.updateSingleFieldOnDB("jf_library_episodes", toArchiveEpisodes, "archived", true, "EpisodeId");
|
||||
const success = await _sync.updateSingleFieldOnDB("jf_library_episodes", toArchiveEpisodes, "archived", true, "EpisodeId");
|
||||
if (!success) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library episodes" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library episodes");
|
||||
}
|
||||
|
||||
syncTask.loggedData.push({ color: "orange", Message: toArchiveEpisodes.length + " Episodes Archived." });
|
||||
}
|
||||
@@ -375,9 +401,21 @@ async function removeOrphanedData() {
|
||||
const archived_seasons = await db
|
||||
.query(`select "Id" from jf_library_seasons where archived=true`)
|
||||
.then((res) => res.rows.map((row) => row.Id));
|
||||
await _sync.updateSingleFieldOnDB("jf_library_seasons", archived_items, "archived", true, "SeriesId");
|
||||
await _sync.updateSingleFieldOnDB("jf_library_episodes", archived_items, "archived", true, "SeriesId");
|
||||
await _sync.updateSingleFieldOnDB("jf_library_episodes", archived_seasons, "archived", true, "SeasonId");
|
||||
if (!(await _sync.updateSingleFieldOnDB("jf_library_seasons", archived_items, "archived", true, "SeriesId"))) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library seasons" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library seasons");
|
||||
}
|
||||
if (!(await _sync.updateSingleFieldOnDB("jf_library_episodes", archived_items, "archived", true, "SeriesId"))) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library episodes" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library episodes");
|
||||
}
|
||||
if (!(await _sync.updateSingleFieldOnDB("jf_library_episodes", archived_seasons, "archived", true, "SeasonId"))) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library episodes" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
throw new Error("Error archiving library episodes");
|
||||
}
|
||||
|
||||
await db.query(`DELETE FROM public.jf_library_episodes
|
||||
where "SeasonId" is null
|
||||
|
||||
@@ -7,7 +7,9 @@ const configClass = require("../classes/config");
|
||||
const API = require("../classes/api-loader");
|
||||
const { sendUpdate } = require("../ws");
|
||||
const { isNumber } = require("@mui/x-data-grid/internals");
|
||||
const MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK = process.env.MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK ? Number(process.env.MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK) : 1;
|
||||
const MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK = process.env.MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK
|
||||
? Number(process.env.MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK)
|
||||
: 1;
|
||||
|
||||
async function getSessionsInWatchDog(SessionData, WatchdogData) {
|
||||
let existingData = await WatchdogData.filter((wdData) => {
|
||||
@@ -170,18 +172,26 @@ async function ActivityMonitor(interval) {
|
||||
/////get data from jf_playback_activity within the last hour with progress of <=80% for current items in session
|
||||
|
||||
const ExistingRecords = await db
|
||||
.query(`SELECT * FROM jf_recent_playback_activity(1)`)
|
||||
.then((res) =>
|
||||
res.rows.filter(
|
||||
(row) =>
|
||||
playbackToInsert.some((pbi) => pbi.NowPlayingItemId === row.NowPlayingItemId && pbi.EpisodeId === row.EpisodeId) &&
|
||||
row.Progress <= 80.0
|
||||
)
|
||||
);
|
||||
.query(`SELECT * FROM jf_recent_playback_activity(1) limit 0`)
|
||||
.then((res) => {
|
||||
if (res.rows && Array.isArray(res.rows) && res.rows.length > 0) {
|
||||
return res.rows.filter(
|
||||
(row) =>
|
||||
playbackToInsert.some(
|
||||
(pbi) => pbi.NowPlayingItemId === row.NowPlayingItemId && pbi.EpisodeId === row.EpisodeId
|
||||
) && row.Progress <= 80.0
|
||||
);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error fetching existing records:", err);
|
||||
});
|
||||
let ExistingDataToUpdate = [];
|
||||
|
||||
//for each item in playbackToInsert, check if it exists in the recent playback activity and update accordingly. insert new row if updating existing exceeds the runtime
|
||||
if (playbackToInsert.length >0 && ExistingRecords.length >0) {
|
||||
if (playbackToInsert.length > 0 && ExistingRecords.length > 0) {
|
||||
ExistingDataToUpdate = playbackToInsert.filter((playbackData) => {
|
||||
const existingrow = ExistingRecords.find((existing) => {
|
||||
let newDurationWithingRunTime = true;
|
||||
|
||||
@@ -9,6 +9,19 @@ const { sendUpdate } = require("../ws");
|
||||
|
||||
async function runBackupTask(triggerType = triggertype.Automatic) {
|
||||
try {
|
||||
console.log = (...args) => {
|
||||
const formattedArgs = args.map((arg) => {
|
||||
if (typeof arg === "object" && arg !== null) {
|
||||
try {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} catch (e) {
|
||||
return "[Circular]";
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
parentPort.postMessage({ type: "log", message: formattedArgs.join(" ") });
|
||||
};
|
||||
const uuid = randomUUID();
|
||||
const refLog = { logData: [], uuid: uuid };
|
||||
|
||||
|
||||
@@ -4,6 +4,19 @@ const sync = require("../routes/sync");
|
||||
|
||||
async function runFullSyncTask(triggerType = triggertype.Automatic) {
|
||||
try {
|
||||
console.log = (...args) => {
|
||||
const formattedArgs = args.map((arg) => {
|
||||
if (typeof arg === "object" && arg !== null) {
|
||||
try {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} catch (e) {
|
||||
return "[Circular]";
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
parentPort.postMessage({ type: "log", message: formattedArgs.join(" ") });
|
||||
};
|
||||
await sync.fullSync(triggerType);
|
||||
|
||||
parentPort.postMessage({ status: "complete" });
|
||||
|
||||
@@ -3,6 +3,19 @@ const sync = require("../routes/sync");
|
||||
|
||||
async function runPlaybackReportingPluginSyncTask() {
|
||||
try {
|
||||
console.log = (...args) => {
|
||||
const formattedArgs = args.map((arg) => {
|
||||
if (typeof arg === "object" && arg !== null) {
|
||||
try {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} catch (e) {
|
||||
return "[Circular]";
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
parentPort.postMessage({ type: "log", message: formattedArgs.join(" ") });
|
||||
};
|
||||
await sync.syncPlaybackPluginData();
|
||||
|
||||
parentPort.postMessage({ status: "complete" });
|
||||
|
||||
@@ -4,6 +4,19 @@ const sync = require("../routes/sync");
|
||||
|
||||
async function runPartialSyncTask(triggerType = triggertype.Automatic) {
|
||||
try {
|
||||
console.log = (...args) => {
|
||||
const formattedArgs = args.map((arg) => {
|
||||
if (typeof arg === "object" && arg !== null) {
|
||||
try {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} catch (e) {
|
||||
return "[Circular]";
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
parentPort.postMessage({ type: "log", message: formattedArgs.join(" ") });
|
||||
};
|
||||
await sync.partialSync(triggerType);
|
||||
|
||||
parentPort.postMessage({ status: "complete" });
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"private": true,
|
||||
"main": "src/index.jsx",
|
||||
"scripts": {
|
||||
|
||||
@@ -15,6 +15,7 @@ import Tooltip from "@mui/material/Tooltip";
|
||||
import IpInfoModal from "../ip-info";
|
||||
import { Trans } from "react-i18next";
|
||||
import baseUrl from "../../../lib/baseurl";
|
||||
import { lineHeight } from "@mui/system";
|
||||
|
||||
function ticksToTimeString(ticks) {
|
||||
// Convert ticks to seconds
|
||||
@@ -133,14 +134,46 @@ function SessionCard(props) {
|
||||
<Col className="col-auto session-details-title text-end text-uppercase">
|
||||
<Trans i18nKey="ACTIVITY_TABLE.DEVICE" />
|
||||
</Col>
|
||||
<Col className="col-auto ellipse">{props.data.session.DeviceName}</Col>
|
||||
<Col
|
||||
className="col-auto ellipse"
|
||||
style={{
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<Tooltip title={props.data.session.DeviceName}>
|
||||
<span
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
}}
|
||||
>
|
||||
{props.data.session.DeviceName}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="col-auto session-details-title text-end text-uppercase">
|
||||
<Trans i18nKey="ACTIVITY_TABLE.CLIENT" />
|
||||
</Col>
|
||||
<Col className="col-auto ellipse">
|
||||
{props.data.session.Client + " " + props.data.session.ApplicationVersion}
|
||||
<Col
|
||||
className="col-auto ellipse"
|
||||
style={{
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<Tooltip title={props.data.session.Client + " " + props.data.session.ApplicationVersion}>
|
||||
<span
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
}}
|
||||
>
|
||||
{props.data.session.Client + " " + props.data.session.ApplicationVersion}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
{props.data.session.NowPlayingItem.VideoStream !== "" && (
|
||||
@@ -148,7 +181,24 @@ function SessionCard(props) {
|
||||
<Col className="col-auto session-details-title text-end text-uppercase">
|
||||
<Trans i18nKey="VIDEO" />
|
||||
</Col>
|
||||
<Col className="col-auto ellipse">{props.data.session.NowPlayingItem.VideoStream}</Col>
|
||||
<Col
|
||||
className="col-auto ellipse"
|
||||
style={{
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<Tooltip title={props.data.session.NowPlayingItem.VideoStream}>
|
||||
<span
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
}}
|
||||
>
|
||||
{props.data.session.NowPlayingItem.VideoStream}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{props.data.session.NowPlayingItem.AudioStream !== "" && (
|
||||
@@ -156,7 +206,24 @@ function SessionCard(props) {
|
||||
<Col className="col-auto session-details-title text-end text-uppercase">
|
||||
<Trans i18nKey="AUDIO" />
|
||||
</Col>
|
||||
<Col className="col-auto ellipse">{props.data.session.NowPlayingItem.AudioStream}</Col>
|
||||
<Col
|
||||
className="col-auto ellipse"
|
||||
style={{
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<Tooltip title={props.data.session.NowPlayingItem.AudioStream}>
|
||||
<span
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
}}
|
||||
>
|
||||
{props.data.session.NowPlayingItem.AudioStream}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{props.data.session.NowPlayingItem.SubtitleStream !== "" && (
|
||||
@@ -164,9 +231,22 @@ function SessionCard(props) {
|
||||
<Col className="col-auto session-details-title text-end text-uppercase">
|
||||
<Trans i18nKey="SUBTITLES" />
|
||||
</Col>
|
||||
<Col className="col-auto ellipse">
|
||||
<Col
|
||||
className="col-auto ellipse"
|
||||
style={{
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<Tooltip title={props.data.session.NowPlayingItem.SubtitleStream}>
|
||||
<span>{props.data.session.NowPlayingItem.SubtitleStream}</span>
|
||||
<span
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
}}
|
||||
>
|
||||
{props.data.session.NowPlayingItem.SubtitleStream}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user