Skip to content

Commit

Permalink
Upsert programs before calculating groupings (#401)
Browse files Browse the repository at this point in the history
* Upsert programs before calculating groupings

This seems to resolve an issue where in the grouping upsert code, the
entity manager attempts to insert programs that it finds in its entity
map (it sees the objects as created and ready to be persisted). However,
we need special upsert code for the programs due to various unique key
constraints. This simply reoders the operations to save the programs
first and then calculate their groupings.

Also fixes a weird state bug I was experiencing in the program selector

* Fixes for a bunch of different InlineModal bugs

1. Fixes nested inline modals that had 'last row' functionality of
   wrapping (i.e. intermediate num items < row length)
2. Fixes last item index calculation function -- I slightly
   misunderstood what this was doing when rewriting it.
3. Fixes near-constant re-rendering of all InlineModals and PlexGridItem
   children due to a bad effect setup
  • Loading branch information
chrisbenincasa authored Apr 29, 2024
1 parent 54cc2fe commit 710c297
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 74 deletions.
41 changes: 19 additions & 22 deletions server/src/dao/programHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
} from '../util/index.js';
import { ProgramMinterFactory } from '../util/programMinter.js';
import { ProgramSourceType } from './custom_types/ProgramSourceType.js';
import { getEm, withDb } from './dataSource.js';
import { getEm } from './dataSource.js';
import { PlexServerSettings } from './entities/PlexServerSettings.js';
import { Program, ProgramType } from './entities/Program.js';
import {
Expand Down Expand Up @@ -123,20 +123,25 @@ export async function upsertContentPrograms(
.mapValues((programs) => groupBy(programs, (p) => p.externalSourceName!))
.value() as ProgramsBySource;

logger.debug('Upserting %d programs', programsToPersist.length);

const upsertedPrograms = flatten(
await mapAsyncSeq(chunk(programsToPersist, batchSize), (programs) =>
em.upsertMany(Program, programs, {
onConflictAction: 'merge',
onConflictFields: ['sourceType', 'externalSourceId', 'externalKey'],
onConflictExcludeFields: ['uuid'],
}),
),
);

// Fork a new entity manager here so we don't attempt to persist anything
// in the parent context. This function potentially does a lot of work
// but we don't want to accidentally not do an upsert of a program.
const programGroupingsBySource = await withDb(
async () => {
return await findAndUpdateProgramRelations(programsBySource);
},
undefined,
true,
);
const programGroupingsBySource =
await findAndUpdateProgramRelations(programsBySource);

logger.debug('Upserting %d programs', programsToPersist.length);

forEach(programsToPersist, (program) => {
forEach(upsertedPrograms, (program) => {
if (
program.type !== ProgramType.Episode &&
program.type !== ProgramType.Track
Expand Down Expand Up @@ -215,15 +220,7 @@ export async function upsertContentPrograms(
}
});

const upsertedPrograms = flatten(
await mapAsyncSeq(chunk(programsToPersist, batchSize), (programs) =>
em.upsertMany(Program, programs, {
onConflictAction: 'merge',
onConflictFields: ['sourceType', 'externalSourceId', 'externalKey'],
onConflictExcludeFields: ['uuid'],
}),
),
);
await em.flush();

return upsertedPrograms;
}
Expand Down Expand Up @@ -289,9 +286,9 @@ async function findAndUpdatePlexServerPrograms(
return;
}

const em = getEm();
const em = getEm().fork();

const plexServer = await getEm().findOne(PlexServerSettings, {
const plexServer = await em.findOne(PlexServerSettings, {
name: plexServerName,
});

Expand Down
56 changes: 25 additions & 31 deletions web/src/components/InlineModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { Box, Collapse, List } from '@mui/material';
import { PlexMedia, isPlexMedia, isTerminalItem } from '@tunarr/types/plex';
import { PlexMedia, isPlexMedia, isPlexParentItem } from '@tunarr/types/plex';
import { usePrevious } from '@uidotdev/usehooks';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useEffect, useCallback, useRef, useState, useMemo } from 'react';
import {
extractLastIndexes,
findFirstItemInNextRowIndex,
Expand All @@ -17,8 +11,9 @@ import {
import _ from 'lodash-es';
import useStore from '../store';
import { PlexGridItem } from './channel_config/PlexGridItem';
import { useIntersectionObserver } from 'usehooks-ts';
import { toggle } from '../helpers/util.ts';
import React from 'react';
import { useIntersectionObserver } from 'usehooks-ts';

type InlineModalProps = {
itemGuid: string;
Expand Down Expand Up @@ -76,7 +71,6 @@ export function InlineModal(props: InlineModalProps) {
const containerWidth = ref?.current?.getBoundingClientRect().width || 0;
const itemWidth =
gridItemRef?.current?.getBoundingClientRect().width || 0;

const imagesPerRow = getImagesPerRow(containerWidth, itemWidth);
setChildLimit(imagesPerRow * 2);
setItemWidth(itemWidth);
Expand All @@ -85,24 +79,29 @@ export function InlineModal(props: InlineModalProps) {
}
}, [ref, gridItemRef, previousItemGuid, itemGuid]);

const [childItemGuid, setChildItemGuid] = useState<string | null>(null);
const [childModalIndex, setChildModalIndex] = useState(-1);

const handleMoveModal = useCallback(
(index: number) => {
if (index === childModalIndex) {
setChildModalIndex(-1);
} else {
setChildModalIndex(index);
}
},
[childModalIndex],
const firstItemInNextRowIndex = useMemo(
() =>
findFirstItemInNextRowIndex(
childModalIndex,
rowSize,
modalChildren?.length ?? 0,
),
[childModalIndex, modalChildren?.length, rowSize],
);

const handleMoveModal = useCallback((index: number, item: PlexMedia) => {
setChildItemGuid((prev) => (prev === item.guid ? null : item.guid));
setChildModalIndex((prev) => (prev === index ? -1 : index));
}, []);

// TODO: Complete this by updating the limit below, not doing this
// right now because already working with a huge changeset.
const { ref: intersectionRef } = useIntersectionObserver({
onChange: (_, entry) => {
if (entry.isIntersecting) {
if (entry.isIntersecting && isOpen) {
if (childLimit < modalChildren.length) {
setChildLimit((prev) => prev + imagesPerRow * 2);
}
Expand All @@ -118,6 +117,8 @@ export function InlineModal(props: InlineModalProps) {
).includes(childModalIndex)
: false;

console.log('my children, should be open', type, modalChildren);

return (
<Box
ref={inlineModalRef}
Expand Down Expand Up @@ -164,26 +165,19 @@ export function InlineModal(props: InlineModalProps) {
.take(childLimit)
.map((child: PlexMedia, idx: number) => (
<React.Fragment key={child.guid}>
{!isTerminalItem(child) && (
{isPlexParentItem(child) && (
<InlineModal
itemGuid={child.guid}
modalIndex={childModalIndex}
open={
idx ===
findFirstItemInNextRowIndex(
childModalIndex,
rowSize,
modalChildren?.length || 0,
)
}
open={idx === firstItemInNextRowIndex}
rowSize={rowSize}
type={child.type}
/>
)}
<PlexGridItem
item={child}
index={idx}
modalIndex={modalIndex || childModalIndex}
modalIndex={modalIndex ?? childModalIndex}
ref={gridItemRef}
moveModal={handleMoveModal}
/>
Expand All @@ -192,7 +186,7 @@ export function InlineModal(props: InlineModalProps) {
.value()}
{/* This Modal is for last row items because they can't be inserted using the above inline modal */}
<InlineModal
itemGuid={itemGuid}
itemGuid={childItemGuid ?? ''}
modalIndex={childModalIndex}
rowSize={rowSize}
open={isFinalChildModalOpen}
Expand Down
1 change: 1 addition & 0 deletions web/src/components/channel_config/PlexGridItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const PlexGridItem = forwardRef(

useEffect(() => {
if (!isUndefined(children?.Metadata)) {
console.log(item.guid, children.Metadata);
addKnownMediaForServer(server.name, children.Metadata, item.guid);
}
}, [item.guid, server.name, children]);
Expand Down
28 changes: 17 additions & 11 deletions web/src/components/channel_config/PlexProgrammingSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ export default function PlexProgrammingSelector() {
const imageWidth = imageRef?.getBoundingClientRect().width;

// 16 is additional padding available in the parent container
setRowSize(getImagesPerRow(width ? width + 16 : 0, imageWidth || 0));
const rowSize = getImagesPerRow(width ? width + 16 : 0, imageWidth || 0);
setRowSize(rowSize);
setScrollParams(({ max }) => ({ max, limit: rowSize * 4 }));
}
}, [width, tabValue, viewType, modalGuid]);

Expand Down Expand Up @@ -319,19 +321,24 @@ export default function PlexProgrammingSelector() {
});

useEffect(() => {
if (!isUndefined(searchData)) {
// We're using this as an analogue for detecting the start of a new 'query'
if (searchData.pages.length === 1) {
const max = searchData.pages[0].totalSize ?? searchData.pages[0].size;
setScrollParams({
limit: rowSize * 4,
max,
});
if (searchData?.pages.length === 1) {
const size = searchData.pages[0].totalSize ?? searchData.pages[0].size;
if (scrollParams.max !== size) {
console.log('herehereh');
setScrollParams(({ limit }) => ({
limit,
max: size,
}));
}
}
}, [searchData?.pages, scrollParams.max]);

useEffect(() => {
if (!isUndefined(searchData)) {
// We probably wouldn't have made it this far if we didnt have a server, but
// putting this here to prevent crashes
if (selectedServer) {
console.log('still in this one');
const allMedia = chain(searchData.pages)
.reject((page) => page.size === 0)
.map((page) => page.Metadata)
Expand All @@ -340,7 +347,7 @@ export default function PlexProgrammingSelector() {
addKnownMediaForServer(selectedServer.name, allMedia);
}
}
}, [selectedServer, searchData, setScrollParams, rowSize]);
}, [scrollParams, selectedServer, searchData, rowSize]);

useEffect(() => {
if (
Expand Down Expand Up @@ -407,7 +414,6 @@ export default function PlexProgrammingSelector() {
(tabValue === 0
? firstItemInNexLibraryRowIndex
: firstItemInNextCollectionRowIndex);

return (
<React.Fragment key={item.guid}>
{isPlexParentItem(item) && (
Expand Down
7 changes: 6 additions & 1 deletion web/src/helpers/inlineModalUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,10 @@ export function findFirstItemInNextRowIndex(
}

export function extractLastIndexes(arr: PlexMedia[], x: number): number[] {
return range(x > arr.length ? 0 : x, arr.length);
const indexes = range(0, arr.length);
if (x > arr.length) {
return indexes;
}

return indexes.slice(-x);
}
20 changes: 11 additions & 9 deletions web/src/store/programmingSelector/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
PlexLibrarySection,
PlexMedia,
isPlexDirectory,
isTerminalItem,
} from '@tunarr/types/plex';
import { map, reject, some, uniq } from 'lodash-es';
import useStore from '..';
Expand Down Expand Up @@ -68,20 +67,23 @@ export const addKnownMediaForServer = (
hierarchy = state.contentHierarchyByServer[serverName];
}

const childrenByGuid = plexMedia
.filter((m) => !isTerminalItem(m))
.reduce((prev, media) => ({ ...prev, [uniqueId(media)]: [] }), {});

state.contentHierarchyByServer[serverName] = {
...state.contentHierarchyByServer[serverName],
...childrenByGuid,
};
// plexMedia
// .filter((m) => !isTerminalItem(m))
// .map(uniqueId)
// .forEach((id) => {
// if (!has(state.contentHierarchyByServer[serverName], id)) {
// state.contentHierarchyByServer[serverName][id] = [];
// }
// });

if (parentId) {
console.log('setting parent id', parentId);
if (!state.contentHierarchyByServer[serverName][parentId]) {
state.contentHierarchyByServer[serverName][parentId] = [];
}

console.log(plexMedia.map(uniqueId));

state.contentHierarchyByServer[serverName][parentId] = uniq([
...state.contentHierarchyByServer[serverName][parentId],
...plexMedia.map(uniqueId),
Expand Down

0 comments on commit 710c297

Please sign in to comment.