mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Fixes for issues #171.
it seems jellyfin keeps a count of items that are missing for certain media types. IE you have 5/10 episodes for a season, itl keep the remaining 5 as virtual objects and are marked as missing. These have been filtered out. Once a resync is completed. they will be marked as archived. A purge of cached items in the library wil delete all archived items
This commit is contained in:
@@ -136,6 +136,8 @@ class JellyfinAPI {
|
||||
startIndex: startIndex,
|
||||
recursive: recursive,
|
||||
limit: increment,
|
||||
isMissing: false,
|
||||
excludeLocationTypes: "Virtual",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -185,6 +187,8 @@ class JellyfinAPI {
|
||||
startIndex: startIndex,
|
||||
recursive: recursive,
|
||||
limit: increment,
|
||||
isMissing: false,
|
||||
excludeLocationTypes: "Virtual",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -46,18 +46,37 @@ function groupActivity(rows) {
|
||||
return groupedResults;
|
||||
}
|
||||
|
||||
async function purgeLibraryItems(id, withActivity) {
|
||||
const { rows: items } = await db.query(`select * from jf_library_items where "ParentId"=$1 and archived=true`, [id]);
|
||||
async function purgeLibraryItems(id, withActivity, purgeAll = false) {
|
||||
let items_query = `select * from jf_library_items where "ParentId"=$1`;
|
||||
|
||||
const { rows: items } = await db.query(items_query, [id]);
|
||||
let seasonIds = [];
|
||||
let episodeIds = [];
|
||||
|
||||
for (const item of items) {
|
||||
const { rows: seasons } = await db.query(`select * from jf_library_seasons where "SeriesId"=$1 and archived=true`, [item.Id]);
|
||||
let season_query = `select * from jf_library_seasons where "SeriesId"=$1`;
|
||||
if (!item.archived && !purgeAll) {
|
||||
season_query += " and archived=true";
|
||||
}
|
||||
const { rows: seasons } = await db.query(season_query, [item.Id]);
|
||||
seasonIds.push(...seasons.map((item) => item.Id));
|
||||
const { rows: episodes } = await db.query(`select * from jf_library_episodes where "SeriesId"=$1 and archived=true`, [
|
||||
item.Id,
|
||||
]);
|
||||
episodeIds.push(...episodes.map((item) => item.Id));
|
||||
if (seasons.length > 0) {
|
||||
for (const season of seasons) {
|
||||
let episode_query = `select * from jf_library_episodes where "SeasonId"=$1`;
|
||||
if (!item.archived && !season.archived && !purgeAll) {
|
||||
episode_query += " and archived=true";
|
||||
}
|
||||
const { rows: episodes } = await db.query(episode_query, [season.Id]);
|
||||
episodeIds.push(...episodes.map((item) => item.Id));
|
||||
}
|
||||
} else {
|
||||
let episode_query = `select * from jf_library_episodes where "SeriesId"=$1`;
|
||||
if (!item.archived && !purgeAll) {
|
||||
episode_query += " and archived=true";
|
||||
}
|
||||
const { rows: episodes } = await db.query(episode_query, [item.Id]);
|
||||
episodeIds.push(...episodes.map((item) => item.Id));
|
||||
}
|
||||
}
|
||||
|
||||
if (episodeIds.length > 0) {
|
||||
@@ -68,7 +87,11 @@ async function purgeLibraryItems(id, withActivity) {
|
||||
await db.deleteBulk("jf_library_seasons", seasonIds);
|
||||
}
|
||||
|
||||
await db.query(`delete from jf_library_items where "ParentId"=$1 and archived=true`, [id]);
|
||||
items_query = items_query.replace("select *", "delete");
|
||||
if (!purgeAll) {
|
||||
items_query += ` and archived=true`;
|
||||
}
|
||||
await db.query(items_query, [id]);
|
||||
|
||||
if (withActivity) {
|
||||
const deleteQuery = {
|
||||
@@ -582,7 +605,7 @@ router.post("/getSeasons", async (req, res) => {
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT s.*,i.archived, i."PrimaryImageHash", (select count(e.*) "Episodes" from jf_library_episodes e where e."SeasonId"=s."Id") ,(select sum(ii."Size") "Size" from jf_library_episodes e join jf_item_info ii on ii."Id"=e."EpisodeId" where e."SeasonId"=s."Id") FROM jf_library_seasons s left join jf_library_items i on i."Id"=s."SeriesId" where "SeriesId"=$1`,
|
||||
`SELECT s.*, i."PrimaryImageHash", (select count(e.*) "Episodes" from jf_library_episodes e where e."SeasonId"=s."Id") ,(select sum(ii."Size") "Size" from jf_library_episodes e join jf_item_info ii on ii."Id"=e."EpisodeId" where e."SeasonId"=s."Id") FROM jf_library_seasons s left join jf_library_items i on i."Id"=s."SeriesId" where "SeriesId"=$1`,
|
||||
[Id]
|
||||
);
|
||||
res.send(rows);
|
||||
@@ -602,7 +625,7 @@ router.post("/getEpisodes", async (req, res) => {
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT e.*,i.archived, i."PrimaryImageHash" FROM jf_library_episodes e left join jf_library_items i on i."Id"=e."SeriesId" where "SeasonId"=$1`,
|
||||
`SELECT e.*, i."PrimaryImageHash" FROM jf_library_episodes e left join jf_library_items i on i."Id"=e."SeriesId" where "SeasonId"=$1`,
|
||||
[Id]
|
||||
);
|
||||
res.send(rows);
|
||||
@@ -658,28 +681,39 @@ router.delete("/item/purge", async (req, res) => {
|
||||
res.send("No Item ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows: episodes } = await db.query(`select * from jf_library_episodes where "SeriesId"=$1`, [id]);
|
||||
if (episodes.length > 0) {
|
||||
await db.query(`delete from jf_library_episodes where "SeriesId"=$1`, [id]);
|
||||
}
|
||||
|
||||
const { rows: seasons } = await db.query(`select * from jf_library_seasons where "SeriesId"=$1`, [id]);
|
||||
const { rows: items } = await db.query(`select * from jf_library_items where "Id"=$1`, [id]);
|
||||
const { rows: seasons } = await db.query(`select * from jf_library_seasons where "SeriesId"=$1 or "Id"=$1`, [id]);
|
||||
if (seasons.length > 0) {
|
||||
await db.query(`delete from jf_library_seasons where "SeriesId"=$1`, [id]);
|
||||
}
|
||||
|
||||
await db.query(`delete from jf_library_items where "Id"=$1`, [id]);
|
||||
|
||||
if (withActivity) {
|
||||
const deleteQuery = {
|
||||
text: `DELETE FROM jf_playback_activity WHERE${
|
||||
episodes.length > 0 ? ` "EpisodeId" IN (${pgp.as.csv(episodes.map((item) => item.EpisodeId))}) OR` : ""
|
||||
}${
|
||||
seasons.length > 0 ? ` "SeasonId" IN (${pgp.as.csv(seasons.map((item) => item.SeasonId))}) OR` : ""
|
||||
} "NowPlayingItemId"='${id}'`,
|
||||
};
|
||||
await db.query(deleteQuery);
|
||||
for (const season of seasons) {
|
||||
let delete_season_episodes_query = 'delete from jf_library_episodes where "SeasonId"=$1';
|
||||
if (!season.archived && (items.length > 0 ? !items[0].archived : true)) {
|
||||
delete_season_episodes_query += " and archived=true";
|
||||
}
|
||||
await db.query(delete_season_episodes_query, [season.Id]);
|
||||
if (season.archived || (items.length > 0 && items[0].archived)) {
|
||||
await db.query(`delete from jf_library_seasons where "Id"=$1`, [season.Id]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const { rows: episodes } = await db.query(`select * from jf_library_episodes where "EpisodeId"=$1 and archived=true`, [id]);
|
||||
if (episodes.length > 0) {
|
||||
await db.query(`delete from jf_library_episodes where "EpisodeId"=$1 and archived=true`, [id]);
|
||||
}
|
||||
if (items.length > 0 && items[0].archived) {
|
||||
await db.query(`delete from jf_library_episodes where "SeriesId"=$1`, [id]);
|
||||
await db.query(`delete from jf_library_seasons where "SeriesId"=$1`, [id]);
|
||||
await db.query(`delete from jf_library_items where "Id"=$1`, [id]);
|
||||
}
|
||||
if (withActivity) {
|
||||
const deleteQuery = {
|
||||
text: `DELETE FROM jf_playback_activity WHERE${
|
||||
episodes.length > 0 ? ` "EpisodeId" IN (${pgp.as.csv(episodes.map((item) => item.EpisodeId))}) OR` : ""
|
||||
}${
|
||||
seasons.length > 0 ? ` "SeasonId" IN (${pgp.as.csv(seasons.map((item) => item.SeasonId))}) OR` : ""
|
||||
} "NowPlayingItemId"='${id}'`,
|
||||
};
|
||||
await db.query(deleteQuery);
|
||||
}
|
||||
}
|
||||
|
||||
sendUpdate("GeneralAlert", {
|
||||
@@ -706,7 +740,7 @@ router.delete("/library/purge", async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
await purgeLibraryItems(id, withActivity);
|
||||
await purgeLibraryItems(id, withActivity, true);
|
||||
|
||||
await db.query(`delete from jf_libraries where "Id"=$1`, [id]);
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ async function syncShowItems(data) {
|
||||
episodesToInsert = episodesToInsert.filter((episode) => !existingIdsEpisodes.some((id) => id === episode.EpisodeId));
|
||||
}
|
||||
|
||||
//Bulkinsert new data not on db
|
||||
//Bulkinsert new seasons not on db
|
||||
if (seasonsToInsert.length !== 0) {
|
||||
let result = await db.insertBulk("jf_library_seasons", seasonsToInsert, jf_library_seasons_columns);
|
||||
if (result.Result === "SUCCESS") {
|
||||
@@ -257,7 +257,7 @@ async function syncShowItems(data) {
|
||||
}
|
||||
}
|
||||
|
||||
//Bulkinsert new data not on db
|
||||
//Bulkinsert new episodes not on db
|
||||
if (episodesToInsert.length !== 0) {
|
||||
let result = await db.insertBulk("jf_library_episodes", episodesToInsert, jf_library_episodes_columns);
|
||||
if (result.Result === "SUCCESS") {
|
||||
@@ -286,7 +286,7 @@ async function syncShowItems(data) {
|
||||
syncTask.loggedData.push({ color: "orange", Message: toArchiveSeasons.length + " Seasons Archived." });
|
||||
}
|
||||
if (toArchiveEpisodes.length > 0) {
|
||||
await _sync.updateSingleFieldOnDB("jf_library_episodes", toArchiveEpisodes, "archived", true);
|
||||
await _sync.updateSingleFieldOnDB("jf_library_episodes", toArchiveEpisodes, "archived", true, "EpisodeId");
|
||||
|
||||
syncTask.loggedData.push({ color: "orange", Message: toArchiveEpisodes.length + " Episodes Archived." });
|
||||
}
|
||||
@@ -310,9 +310,7 @@ async function syncItemInfo(seasons_and_episodes, library_items) {
|
||||
syncTask.loggedData.push({ color: "yellow", Message: "Beginning File Info Sync" });
|
||||
|
||||
let Items = library_items.filter((item) => item.Type !== "Series" && item.Type !== "Folder" && item.Id !== undefined);
|
||||
let Episodes = seasons_and_episodes.filter(
|
||||
(item) => item.Type === "Episode" && item.LocationType !== "Virtual" && item.Id !== undefined
|
||||
);
|
||||
let Episodes = seasons_and_episodes.filter((item) => item.Type === "Episode" && item.Id !== undefined);
|
||||
|
||||
let insertItemInfoCount = 0;
|
||||
let insertEpisodeInfoCount = 0;
|
||||
|
||||
@@ -1,123 +1,98 @@
|
||||
const db = require("../db");
|
||||
const moment = require('moment');
|
||||
const moment = require("moment");
|
||||
const sync = require("../routes/sync");
|
||||
const taskName=require('../logging/taskName');
|
||||
const taskName = require("../logging/taskName");
|
||||
const taskstate = require("../logging/taskstate");
|
||||
const triggertype = require("../logging/triggertype");
|
||||
|
||||
async function RecentlyAddedItemsSyncTask() {
|
||||
try{
|
||||
try {
|
||||
await db.query(
|
||||
`UPDATE jf_logging SET "Result"='${taskstate.FAILED}' WHERE "Name"='${taskName.partialsync}' AND "Result"='${taskstate.RUNNING}'`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error Cleaning up Sync Tasks: " + error);
|
||||
}
|
||||
|
||||
await db.query(
|
||||
`UPDATE jf_logging SET "Result"='${taskstate.FAILED}' WHERE "Name"='${taskName.partialsync}' AND "Result"='${taskstate.RUNNING}'`
|
||||
);
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
console.log('Error Cleaning up Sync Tasks: '+error);
|
||||
}
|
||||
let interval = 10000;
|
||||
|
||||
let interval=10000;
|
||||
let taskDelay = 60; //in minutes
|
||||
|
||||
let taskDelay=60; //in minutes
|
||||
async function fetchTaskSettings() {
|
||||
try {
|
||||
//get interval from db
|
||||
|
||||
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
|
||||
|
||||
if (settingsjson.length > 0) {
|
||||
const settings = settingsjson[0].settings || {};
|
||||
|
||||
let synctasksettings = settings.Tasks?.PartialJellyfinSync || {};
|
||||
|
||||
async function fetchTaskSettings()
|
||||
{
|
||||
try{//get interval from db
|
||||
if (synctasksettings.Interval) {
|
||||
taskDelay = synctasksettings.Interval;
|
||||
} else {
|
||||
synctasksettings.Interval = taskDelay;
|
||||
|
||||
|
||||
const settingsjson = await db
|
||||
.query('SELECT settings FROM app_config where "ID"=1')
|
||||
.then((res) => res.rows);
|
||||
|
||||
if (settingsjson.length > 0) {
|
||||
const settings = settingsjson[0].settings || {};
|
||||
|
||||
let synctasksettings = settings.Tasks?.PartialJellyfinSync || {};
|
||||
|
||||
if (synctasksettings.Interval) {
|
||||
taskDelay=synctasksettings.Interval;
|
||||
} else {
|
||||
synctasksettings.Interval=taskDelay;
|
||||
|
||||
if(!settings.Tasks)
|
||||
{
|
||||
settings.Tasks = {};
|
||||
if (!settings.Tasks) {
|
||||
settings.Tasks = {};
|
||||
}
|
||||
if (!settings.Tasks.PartialJellyfinSync) {
|
||||
settings.Tasks.PartialJellyfinSync = {};
|
||||
}
|
||||
settings.Tasks.PartialJellyfinSync = synctasksettings;
|
||||
|
||||
let query = 'UPDATE app_config SET settings=$1 where "ID"=1';
|
||||
|
||||
await db.query(query, [settings]);
|
||||
}
|
||||
if(!settings.Tasks.PartialJellyfinSync)
|
||||
{
|
||||
settings.Tasks.PartialJellyfinSync = {};
|
||||
}
|
||||
settings.Tasks.PartialJellyfinSync = synctasksettings;
|
||||
|
||||
|
||||
let query = 'UPDATE app_config SET settings=$1 where "ID"=1';
|
||||
|
||||
await db.query(query, [settings]);
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.log("Sync Task Settings Error: " + error);
|
||||
}
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
console.log('Sync Task Settings Error: '+error);
|
||||
}
|
||||
}
|
||||
|
||||
async function intervalCallback() {
|
||||
clearInterval(intervalTask);
|
||||
try {
|
||||
let current_time = moment();
|
||||
const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||
|
||||
|
||||
async function intervalCallback() {
|
||||
clearInterval(intervalTask);
|
||||
try{
|
||||
let current_time = moment();
|
||||
const { rows: config } = await db.query(
|
||||
'SELECT * FROM app_config where "ID"=1'
|
||||
);
|
||||
|
||||
if (config.length===0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null)
|
||||
{
|
||||
if (!config || config.length === 0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const last_execution=await db.query( `SELECT "TimeRun","Result"
|
||||
}
|
||||
|
||||
const last_execution = await db
|
||||
.query(
|
||||
`SELECT "TimeRun","Result"
|
||||
FROM public.jf_logging
|
||||
WHERE "Name"='${taskName.partialsync}'
|
||||
ORDER BY "TimeRun" DESC
|
||||
LIMIT 1`).then((res) => res.rows);
|
||||
if(last_execution.length!==0)
|
||||
{
|
||||
LIMIT 1`
|
||||
)
|
||||
.then((res) => res.rows);
|
||||
if (last_execution.length !== 0) {
|
||||
await fetchTaskSettings();
|
||||
let last_execution_time = moment(last_execution[0].TimeRun).add(taskDelay, 'minutes');
|
||||
let last_execution_time = moment(last_execution[0].TimeRun).add(taskDelay, "minutes");
|
||||
|
||||
if(!current_time.isAfter(last_execution_time) || last_execution[0].Result ===taskstate.RUNNING)
|
||||
{
|
||||
intervalTask = setInterval(intervalCallback, interval);
|
||||
return;
|
||||
if (!current_time.isAfter(last_execution_time) || last_execution[0].Result === taskstate.RUNNING) {
|
||||
intervalTask = setInterval(intervalCallback, interval);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Running Recently Added Scheduled Sync");
|
||||
await sync.partialSync(triggertype.Automatic);
|
||||
console.log("Scheduled Recently Added Sync Complete");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log('Running Recently Added Scheduled Sync');
|
||||
await sync.partialSync(triggertype.Automatic);
|
||||
console.log('Scheduled Recently Added Sync Complete');
|
||||
|
||||
} catch (error)
|
||||
{
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
|
||||
intervalTask = setInterval(intervalCallback, interval);
|
||||
intervalTask = setInterval(intervalCallback, interval);
|
||||
}
|
||||
|
||||
let intervalTask = setInterval(intervalCallback, interval);
|
||||
|
||||
|
||||
let intervalTask = setInterval(intervalCallback, interval);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user