diff --git a/backend/migrations/046_jf_library_items_table_add_archived_column.js b/backend/migrations/046_jf_library_items_table_add_archived_column.js index e53210b..6a27786 100644 --- a/backend/migrations/046_jf_library_items_table_add_archived_column.js +++ b/backend/migrations/046_jf_library_items_table_add_archived_column.js @@ -7,49 +7,6 @@ exports.up = async function(knex) { table.boolean('archived').defaultTo(false); }); - - await knex.schema.raw(` - DROP FUNCTION IF EXISTS public.fs_last_library_activity(text); - - CREATE OR REPLACE FUNCTION public.fs_last_library_activity( - libraryid text) - RETURNS TABLE("Id" text, "EpisodeId" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, archived boolean, "LastPlayed" interval) - LANGUAGE 'plpgsql' - COST 100 - VOLATILE PARALLEL UNSAFE - ROWS 1000 - - AS $BODY$ - BEGIN - RETURN QUERY - SELECT * - FROM ( - SELECT DISTINCT ON (i."Name", e."Name") - i."Id", - a."EpisodeId", - i."Name", - e."Name" AS "EpisodeName", - CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber", - CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber", - i."PrimaryImageHash", - a."UserId", - a."UserName", - i.archived, - (NOW() - a."ActivityDateInserted") as "LastPlayed" - FROM jf_playback_activity a - JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId" - JOIN jf_libraries l ON i."ParentId" = l."Id" - LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId" - LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId" - WHERE l."Id" = libraryid - ORDER BY i."Name", e."Name", a."ActivityDateInserted" DESC - ) AS latest_distinct_rows - ORDER BY "LastPlayed" - LIMIT 15; - END; - - - $BODY$;`); } }catch (error) { console.error(error); @@ -62,47 +19,6 @@ exports.down = async function(knex) { table.dropColumn('archived'); }); - await knex.schema.raw(` - DROP FUNCTION IF EXISTS public.fs_last_library_activity(text); - - CREATE OR REPLACE FUNCTION public.fs_last_library_activity( - libraryid text) - RETURNS TABLE("Id" text, "EpisodeId" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, "LastPlayed" interval) - LANGUAGE 'plpgsql' - COST 100 - VOLATILE PARALLEL UNSAFE - ROWS 1000 - - AS $BODY$ - BEGIN - RETURN QUERY - SELECT * - FROM ( - SELECT DISTINCT ON (i."Name", e."Name") - i."Id", - a."EpisodeId", - i."Name", - e."Name" AS "EpisodeName", - CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber", - CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber", - i."PrimaryImageHash", - a."UserId", - a."UserName", - (NOW() - a."ActivityDateInserted") as "LastPlayed" - FROM jf_playback_activity a - JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId" - JOIN jf_libraries l ON i."ParentId" = l."Id" - LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId" - LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId" - WHERE l."Id" = libraryid - ORDER BY i."Name", e."Name", a."ActivityDateInserted" DESC - ) AS latest_distinct_rows - ORDER BY "LastPlayed" - LIMIT 15; - END; - - - $BODY$;`); } catch (error) { console.error(error); } diff --git a/backend/migrations/052_jf_libraries_table_add_archived_column.js b/backend/migrations/052_jf_libraries_table_add_archived_column.js new file mode 100644 index 0000000..8e3982f --- /dev/null +++ b/backend/migrations/052_jf_libraries_table_add_archived_column.js @@ -0,0 +1,26 @@ +exports.up = async function(knex) { + try + { + const hasTable = await knex.schema.hasTable('jf_libraries'); + if (hasTable) { + await knex.schema.alterTable('jf_libraries', function(table) { + table.boolean('archived').defaultTo(false); + + }); + + } +}catch (error) { + console.error(error); +} +}; + +exports.down = async function(knex) { + try { + await knex.schema.alterTable('jf_libraries', function(table) { + table.dropColumn('archived'); + }); + + } catch (error) { + console.error(error); + } +}; diff --git a/backend/migrations/053_js_library_stats_overview.js b/backend/migrations/053_js_library_stats_overview.js new file mode 100644 index 0000000..3ae5834 --- /dev/null +++ b/backend/migrations/053_js_library_stats_overview.js @@ -0,0 +1,132 @@ +exports.up = async function(knex) { + try + { + await knex.schema.raw(` + DROP VIEW public.js_library_stats_overview; + + CREATE OR REPLACE VIEW public.js_library_stats_overview + AS + SELECT DISTINCT ON (l."Id") l."Id", + l."Name", + l."ServerId", + l."IsFolder", + l."Type", + l."CollectionType", + l."ImageTagsPrimary", + i."Id" AS "ItemId", + i."Name" AS "ItemName", + i."Type" AS "ItemType", + i."PrimaryImageHash", + s."IndexNumber" AS "SeasonNumber", + e."IndexNumber" AS "EpisodeNumber", + e."Name" AS "EpisodeName", + ( SELECT count(*) AS count + FROM jf_playback_activity a + JOIN jf_library_items i_1 ON a."NowPlayingItemId" = i_1."Id" + WHERE i_1."ParentId" = l."Id") AS "Plays", + ( SELECT sum(a."PlaybackDuration") AS sum + FROM jf_playback_activity a + JOIN jf_library_items i_1 ON a."NowPlayingItemId" = i_1."Id" + WHERE i_1."ParentId" = l."Id") AS total_playback_duration, + l.total_play_time::numeric AS total_play_time, + l.item_count AS "Library_Count", + l.season_count AS "Season_Count", + l.episode_count AS "Episode_Count", + l.archived, + now() - latest_activity."ActivityDateInserted" AS "LastActivity" + + FROM jf_libraries l + LEFT JOIN ( SELECT DISTINCT ON (i_1."ParentId") jf_playback_activity."Id", + jf_playback_activity."IsPaused", + jf_playback_activity."UserId", + jf_playback_activity."UserName", + jf_playback_activity."Client", + jf_playback_activity."DeviceName", + jf_playback_activity."DeviceId", + jf_playback_activity."ApplicationVersion", + jf_playback_activity."NowPlayingItemId", + jf_playback_activity."NowPlayingItemName", + jf_playback_activity."SeasonId", + jf_playback_activity."SeriesName", + jf_playback_activity."EpisodeId", + jf_playback_activity."PlaybackDuration", + jf_playback_activity."ActivityDateInserted", + jf_playback_activity."PlayMethod", + i_1."ParentId" + FROM jf_playback_activity + JOIN jf_library_items i_1 ON i_1."Id" = jf_playback_activity."NowPlayingItemId" + ORDER BY i_1."ParentId", jf_playback_activity."ActivityDateInserted" DESC) latest_activity ON l."Id" = latest_activity."ParentId" + LEFT JOIN jf_library_items i ON i."Id" = latest_activity."NowPlayingItemId" + LEFT JOIN jf_library_seasons s ON s."Id" = latest_activity."SeasonId" + LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" + ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC;`); + + }catch (error) { + console.error(error); + } + }; + + exports.down = async function(knex) { + try { + await knex.schema.raw(` + DROP VIEW public.js_library_stats_overview; + + CREATE OR REPLACE VIEW public.js_library_stats_overview + AS + SELECT DISTINCT ON (l."Id") l."Id", + l."Name", + l."ServerId", + l."IsFolder", + l."Type", + l."CollectionType", + l."ImageTagsPrimary", + i."Id" AS "ItemId", + i."Name" AS "ItemName", + i."Type" AS "ItemType", + i."PrimaryImageHash", + s."IndexNumber" AS "SeasonNumber", + e."IndexNumber" AS "EpisodeNumber", + e."Name" AS "EpisodeName", + ( SELECT count(*) AS count + FROM jf_playback_activity a + JOIN jf_library_items i_1 ON a."NowPlayingItemId" = i_1."Id" + WHERE i_1."ParentId" = l."Id") AS "Plays", + ( SELECT sum(a."PlaybackDuration") AS sum + FROM jf_playback_activity a + JOIN jf_library_items i_1 ON a."NowPlayingItemId" = i_1."Id" + WHERE i_1."ParentId" = l."Id") AS total_playback_duration, + l.total_play_time::numeric AS total_play_time, + l.item_count AS "Library_Count", + l.season_count AS "Season_Count", + l.episode_count AS "Episode_Count", + now() - latest_activity."ActivityDateInserted" AS "LastActivity" + + FROM jf_libraries l + LEFT JOIN ( SELECT DISTINCT ON (i_1."ParentId") jf_playback_activity."Id", + jf_playback_activity."IsPaused", + jf_playback_activity."UserId", + jf_playback_activity."UserName", + jf_playback_activity."Client", + jf_playback_activity."DeviceName", + jf_playback_activity."DeviceId", + jf_playback_activity."ApplicationVersion", + jf_playback_activity."NowPlayingItemId", + jf_playback_activity."NowPlayingItemName", + jf_playback_activity."SeasonId", + jf_playback_activity."SeriesName", + jf_playback_activity."EpisodeId", + jf_playback_activity."PlaybackDuration", + jf_playback_activity."ActivityDateInserted", + jf_playback_activity."PlayMethod", + i_1."ParentId" + FROM jf_playback_activity + JOIN jf_library_items i_1 ON i_1."Id" = jf_playback_activity."NowPlayingItemId" + ORDER BY i_1."ParentId", jf_playback_activity."ActivityDateInserted" DESC) latest_activity ON l."Id" = latest_activity."ParentId" + LEFT JOIN jf_library_items i ON i."Id" = latest_activity."NowPlayingItemId" + LEFT JOIN jf_library_seasons s ON s."Id" = latest_activity."SeasonId" + LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" + ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC;`); + } catch (error) { + console.error(error); + } + }; diff --git a/backend/models/jf_libraries.js b/backend/models/jf_libraries.js index aeab06f..fd9a16f 100644 --- a/backend/models/jf_libraries.js +++ b/backend/models/jf_libraries.js @@ -7,6 +7,7 @@ "Type", "CollectionType", "ImageTagsPrimary", + "archived", ]; const jf_libraries_mapping = (item) => ({ @@ -18,6 +19,7 @@ CollectionType: item.CollectionType? item.CollectionType : 'mixed', ImageTagsPrimary: item.ImageTags && item.ImageTags.Primary ? item.ImageTags.Primary : null, + archived: false, }); module.exports = { diff --git a/backend/routes/sync.js b/backend/routes/sync.js index a19e215..c6c38a3 100644 --- a/backend/routes/sync.js +++ b/backend/routes/sync.js @@ -372,24 +372,22 @@ async function syncLibraryFolders(data) await _sync.insertData("jf_libraries",dataToInsert,jf_libraries_columns); } -//----------------------DELETE FUNCTION - //GET EPISODES IN SEASONS - //GET SEASONS IN SHOWS - //GET SHOWS IN LIBRARY - //FINALY DELETE LIBRARY - const toDeleteIds = existingIds.filter((id) =>!data.some((row) => row.Id === id )); - if (toDeleteIds.length > 0) { - sendUpdate(syncTask.wsKey,{type:"Update",message:"Cleaning Up Old Library Data"}); + //archive libraries and items instead of deleting them - const ItemsToDelete=await db.query(`SELECT "Id" FROM jf_library_items where "ParentId" in (${toDeleteIds.map(id => `'${id}'`).join(',')})`).then((res) => res.rows.map((row) => row.Id)); - if (ItemsToDelete.length > 0) { - await _sync.removeData("jf_library_items",ItemsToDelete); + const toArchiveLibraryIds = existingIds.filter((id) =>!data.some((row) => row.Id === id )); + if (toArchiveLibraryIds.length > 0) { + sendUpdate(syncTask.wsKey,{type:"Update",message:"Archiving old Library Data"}); + + const ItemsToArchive=await db.query(`SELECT "Id" FROM jf_library_items where "ParentId" in (${toArchiveLibraryIds.map(id => `'${id}'`).join(',')})`).then((res) => res.rows.map((row) => row.Id)); + if (ItemsToArchive.length > 0) { + await _sync.updateSingleFieldOnDB("jf_library_items",ItemsToArchive,"archived",true); } - - await _sync.removeData("jf_libraries",toDeleteIds); + await _sync.updateSingleFieldOnDB("jf_libraries",toArchiveLibraryIds,"archived",true); } + + } async function syncLibraryItems(data) { diff --git a/src/pages/components/item-info/item-options.jsx b/src/pages/components/item-info/item-options.jsx index ba949f8..7913db7 100644 --- a/src/pages/components/item-info/item-options.jsx +++ b/src/pages/components/item-info/item-options.jsx @@ -32,7 +32,7 @@ function ItemOptions(props) { }).then((response) => { console.log(response); setShow(false); - // navigate(-1); + navigate(-1); }).catch((error) => { console.log({error:error,token:token}); }); diff --git a/src/pages/libraries.jsx b/src/pages/libraries.jsx index 13e841f..3265edb 100644 --- a/src/pages/libraries.jsx +++ b/src/pages/libraries.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import axios from "axios"; import Config from "../lib/config"; @@ -6,12 +6,16 @@ import "./css/library/libraries.css"; import Loading from "./components/general/loading"; import LibraryCard from "./components/library/library-card"; import ErrorBoundary from "./components/general/ErrorBoundary"; +import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon'; +import EyeFillIcon from 'remixicon-react/EyeFillIcon'; +import { Tooltip } from "react-bootstrap"; function Libraries() { const [data, setData] = useState(); const [metadata, setMetaData] = useState(); const [config, setConfig] = useState(null); + const [showArchived, setShowArchived] = useState(false); useEffect(() => { const fetchConfig = async () => { @@ -77,11 +81,28 @@ function Libraries() { return (