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:
CyferShepard
2024-03-27 11:51:44 +02:00
parent 36004546dc
commit 11d4e42eec
4 changed files with 138 additions and 127 deletions

View File

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

View File

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

View File

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

View File

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