Skip to content

Commit

Permalink
TV guide redo - part 1 (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbenincasa authored Mar 22, 2024
1 parent 27887e0 commit ef5d156
Show file tree
Hide file tree
Showing 25 changed files with 692 additions and 490 deletions.
4 changes: 2 additions & 2 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
"bin": "dist/index.js",
"scripts": {
"build": "tsc -p tsconfig.build.json",
"build-dev": "tsc -p tsconfig.build.json --noEmit --watch",
"build-dev": "NODE_ENV=development tsc -p tsconfig.build.json --noEmit --watch",
"typecheck": "tsc -p tsconfig.build.json --noEmit",
"bundle": "tsx scripts/bundle.ts",
"clean": "rimraf build",
"debug": "tsx watch --tsconfig ./tsconfig.build.json --ignore 'src/streams' --inspect-brk src",
"dev": "tsx watch --tsconfig ./tsconfig.build.json --ignore 'build' --ignore 'src/streams' src",
"dev": "NODE_ENV=development tsx watch --tsconfig ./tsconfig.build.json --ignore 'build' --ignore 'src/streams' src",
"make-exec": "tsx scripts/makeExecutable.ts",
"generate-db-cache": "mikro-orm-esm cache:generate --combined --ts",
"mikro-orm": "mikro-orm-esm",
Expand Down
129 changes: 71 additions & 58 deletions server/src/api/debugApi.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Loaded, wrap } from '@mikro-orm/core';
import { ChannelLineupQuery } from '@tunarr/types/api';
import { ChannelLineupSchema } from '@tunarr/types/schemas';
import dayjs from 'dayjs';
import { FastifyRequest } from 'fastify';
import { first, isNil, isUndefined, map, range } from 'lodash-es';
import { inspect } from 'util';
import { compact, first, isNil, isUndefined } from 'lodash-es';
import z from 'zod';
import { ChannelCache } from '../channelCache.js';
import { toApiLineupItem } from '../dao/channelDb.js';
import { getEm } from '../dao/dataSource.js';
import {
StreamLineupItem,
Expand All @@ -17,9 +17,10 @@ import * as helperFuncs from '../helperFuncs.js';
import createLogger from '../logger.js';
import { PlexPlayer } from '../plexPlayer.js';
import { PlexTranscoder } from '../plexTranscoder.js';
import { TVGuideService as TVGuideServiceLegacy } from '../services/tvGuideServiceLegacy.js';
import { ContextChannel, Maybe, PlayerContext } from '../types.js';
import { RouterPluginAsyncCallback } from '../types/serverType.js';
import { binarySearchRange } from '../util/binarySearch.js';
import { mapAsyncSeq } from '../util.js';

const logger = createLogger(import.meta);

Expand Down Expand Up @@ -226,13 +227,10 @@ export const debugApi: RouterPluginAsyncCallback = async (fastify) => {
? dayjs(req.query.endTime)
: startTime.add(duration, 'milliseconds');

const t = req.serverCtx.guideService.prepareRefresh(
[wrap(channel!).toJSON()],
dayjs.duration(endTime.diff(startTime)).asMilliseconds(),
await req.serverCtx.guideService.refreshGuide(
dayjs.duration(endTime.diff(startTime)),
);

await req.serverCtx.guideService.refresh(t);

return res
.status(200)
.send(
Expand All @@ -246,63 +244,78 @@ export const debugApi: RouterPluginAsyncCallback = async (fastify) => {
);

fastify.get(
'/debug/helpers/program_at_time',
'/debug/helpers/compare_guides',
{
schema: {
querystring: ChannelQuerySchema.querystring.extend({
ts: z.coerce.number(),
}),
querystring: ChannelLineupQuery,
tags: ['Channels'],
response: {
200: z.array(
z.object({
old: ChannelLineupSchema,
new: ChannelLineupSchema,
}),
),
},
},
},
async (req, res) => {
const channel = (await req.serverCtx.channelDB.getChannelAndPrograms(
req.query.channelId,
))!;

const lineup = await req.serverCtx.channelDB.loadLineup(channel.uuid);

const acc = req.serverCtx.guideService.makeAccumulated({
channel: wrap(channel).toJSON(),
lineup,
});

function getProgramAtTime(t: number) {
const duration = channel.duration;
console.log(dayjs.duration(duration).asMilliseconds());
const howFarPastStart = t - channel.startTime;
const numCycles = Math.floor(howFarPastStart / duration);
const howFarIntoCurrentCycle = howFarPastStart % duration;

const idx = binarySearchRange(acc, howFarIntoCurrentCycle);
// const howFarIntoProgram = lineup.items[idx!].durationMs - acc[idx!];
console.log(
dayjs(channel.startTime)
.add(numCycles * duration)
.add(acc[idx!])
.format(),
);
const allChannels =
await req.serverCtx.channelDB.getAllChannelsAndPrograms();

const startTime = dayjs(req.query.from);
// const duration =
// channel!.duration <= 0
// ? dayjs.duration(1, 'day').asMilliseconds()
// : channel!.duration;
const endTime = dayjs(req.query.to);

const lineups = await mapAsyncSeq(
allChannels,
undefined,
async (channel) => {
const guideServiceLegacy = new TVGuideServiceLegacy(
req.serverCtx.xmltv,
req.serverCtx.cacheImageService,
req.serverCtx.eventService,
req.serverCtx.channelDB,
);
const t = guideServiceLegacy.prepareRefresh(
[wrap(channel).toJSON()],
dayjs.duration(endTime.diff(startTime)).asMilliseconds(),
);

return idx;
}
const idx = getProgramAtTime(req.query.ts);
// This isn't right... we have to round to the nearest "next" cycle
// Take found index, add durations from the remaining programs, subtract
// how far into the 'current' program we are. Then we have the timestamp
// of when the next "cycle" begins. Take the diff from the target date and
// the top of the next "cycle" to find how many 'full' cycles there are. Also
// find the remainder (i.e. the amount of time into the final cycle before we stop)
const nextT = dayjs(req.query.ts).add(3, 'days').unix() * 1000;
const cyclesInDuration = Math.floor(
dayjs.duration(nextT - req.query.ts).asMilliseconds() /
channel.duration,
await Promise.all([
guideServiceLegacy.refresh(t),
req.serverCtx.guideService.refreshGuide(
dayjs.duration(endTime.diff(startTime)),
),
]);

const oldLineup = await guideServiceLegacy.getChannelLineup(
channel.uuid,
startTime.toDate(),
endTime.toDate(),
);

const newLineup = await req.serverCtx.guideService.getChannelLineup(
channel.uuid,
startTime.toDate(),
endTime.toDate(),
);

if (!isNil(newLineup) && !isNil(oldLineup)) {
return {
old: oldLineup,
new: newLineup,
};
} else {
return;
}
},
);
// const idx2 = getProgramAtTime(nextT);
const x = map(range(0, cyclesInDuration * lineup.items.length), (i) => {
return (idx! + i) % lineup.items.length;
});
console.log(inspect(x));

return res.status(200).send(toApiLineupItem(channel, lineup.items[idx!]));
return res.status(200).send(compact(lineups));
},
);

Expand Down
24 changes: 22 additions & 2 deletions server/src/dao/channelDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { globalOptions } from '../globals.js';
import createLogger from '../logger.js';
import { Nullable } from '../types.js';
import { typedProperty } from '../types/path.js';
import { groupByFunc, groupByUniqAndMap } from '../util.js';
import { groupByFunc, groupByUniqAndMap, mapReduceAsyncSeq } from '../util.js';
import { fileExists } from '../util/fsUtil.js';
import { dbProgramToContentProgram } from './converters/programConverters.js';
import { getEm } from './dataSource.js';
Expand Down Expand Up @@ -334,6 +334,26 @@ export class ChannelDB {
return null;
}

async loadAllLineups() {
return mapReduceAsyncSeq(
await this.getAllChannelsAndPrograms(),
async (channel) => {
return {
channel,
lineup: await this.loadLineup(channel.uuid),
};
},
(prev, { channel, lineup }) => ({
...prev,
[channel.uuid]: { channel, lineup },
}),
{} as Record<
string,
{ channel: Loaded<Channel, 'programs'>; lineup: Lineup }
>,
);
}

async loadLineup(channelId: string) {
const db = await this.getFileDb(channelId);
await db.read();
Expand Down Expand Up @@ -669,7 +689,7 @@ function channelProgramToLineupItemFunc(
}),
redirect: (program) => ({
type: 'redirect',
channel: '', // TODO fix this....!
channel: program.channel,
durationMs: program.duration,
}),
flex: (program) => ({
Expand Down
10 changes: 0 additions & 10 deletions server/src/dao/derived_types/Lineup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Program as ProgramDTO } from '@tunarr/types';
import { LineupSchedule } from '@tunarr/types/api';
import { Program } from '../entities/Program.js';

export type Lineup = {
items: LineupItem[];
Expand Down Expand Up @@ -51,11 +49,3 @@ function isItemOfType<T extends LineupItem>(discrim: string) {
export const isContentItem = isItemOfType<ContentItem>('content');
export const isOfflineItem = isItemOfType<OfflineItem>('offline');
export const isRedirectItem = isItemOfType<RedirectItem>('redirect');

export function contentItemToProgramDTO(
backingItem: Program,
): Partial<ProgramDTO> {
return {
...backingItem.toDTO(),
};
}
9 changes: 7 additions & 2 deletions server/src/dao/entities/BaseEntity.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Entity, PrimaryKey, Property } from '@mikro-orm/core';
import {
Entity,
BaseEntity as OrmBaseEntity,
PrimaryKey,
Property,
} from '@mikro-orm/core';
import { v4 } from 'uuid';

@Entity({ abstract: true })
export abstract class BaseEntity {
export abstract class BaseEntity extends OrmBaseEntity {
@PrimaryKey()
uuid: string = v4();

Expand Down
4 changes: 2 additions & 2 deletions server/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import path from 'path';
import * as winston from 'winston';
import 'winston-daily-rotate-file';

import chalk from 'chalk';
import { isUndefined, join } from 'lodash-es';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
import { isProduction } from './util.js';

const __filename = fileURLToPath(import.meta.url);
Expand All @@ -21,7 +21,7 @@ const hformat = (module: ImportMeta) => {
const moduleLabel = isProduction ? '' : ` ${getLabel(module)}`;
return winston.format.printf(
({ level, label, message, timestamp, ...metadata }) => {
let msg = `${timestamp} [${level}]${moduleLabel}${
let msg = `${timestamp} [${level}]${moduleLabel} ${
label ? `[${label}]` : ''
}: ${message} `;
for (const key of Object.keys(metadata)) {
Expand Down
10 changes: 9 additions & 1 deletion server/src/serverContext.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AsyncLocalStorage } from 'async_hooks';
import { isUndefined, once } from 'lodash-es';
import path from 'path';
import { ChannelCache } from './channelCache.js';
Expand All @@ -14,7 +15,6 @@ import { FileCacheService } from './services/fileCacheService.js';
import { M3uService } from './services/m3uService.js';
import { TVGuideService } from './services/tvGuideService.js';
import { XmlTvWriter } from './xmltv.js';
import { AsyncLocalStorage } from 'async_hooks';

export type ServerContext = {
channelDB: ChannelDB;
Expand Down Expand Up @@ -53,6 +53,13 @@ export const serverContext: () => Promise<ServerContext> = once(async () => {
channelDB,
);

const guideService2 = new TVGuideService(
xmltv,
cacheImageService,
eventService,
channelDB,
);

const customShowDB = new CustomShowDB();

return {
Expand All @@ -63,6 +70,7 @@ export const serverContext: () => Promise<ServerContext> = once(async () => {
m3uService,
eventService,
guideService,
guideService2,
hdhrService: new HdhrService(settings, channelDB),
customShowDB,
channelCache,
Expand Down
Loading

0 comments on commit ef5d156

Please sign in to comment.