Merge pull request #366 from CyferShepard/unstable

Hotfix Release V1.1.5
This commit is contained in:
Thegan Govender
2025-04-11 23:32:50 +02:00
committed by GitHub
12 changed files with 251 additions and 32 deletions

View File

@@ -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) => {

View File

@@ -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();
}

View File

@@ -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) => {

View File

@@ -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

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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" });

View File

@@ -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" });

View File

@@ -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
View File

@@ -1,6 +1,6 @@
{
"name": "jfstat",
"version": "1.1.4",
"version": "1.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "jfstat",
"version": "1.1.4",
"version": "1.1.5",
"private": true,
"main": "src/index.jsx",
"scripts": {

View File

@@ -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>