Apply project (#843)

This commit is contained in:
Carlos Valente
2024-03-22 12:52:55 +01:00
committed by GitHub
parent 8c5aaa0901
commit 6178ed1e4e
4 changed files with 80 additions and 70 deletions
+18 -32
View File
@@ -9,15 +9,11 @@ import {
import type { Request, Response } from 'express';
import fs from 'fs';
import { join } from 'path';
import { DataProvider } from '../../classes/data-provider/DataProvider.js';
import { failEmptyObjects } from '../../utils/routerUtils.js';
import { resolveDbPath, resolveProjectsDirectory } from '../../setup/index.js';
import * as projectService from '../../services/project-service/ProjectService.js';
import { runtimeService } from '../../services/runtime-service/RuntimeService.js';
import { setRundown } from '../../services/rundown-service/RundownService.js';
import { ensureJsonExtension } from '../../utils/fileManagement.js';
import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js';
import { appStateService } from '../../services/app-state-service/AppStateService.js';
@@ -31,23 +27,10 @@ export async function patchPartialProjectFile(req: Request, res: Response<Databa
}
try {
const patchDb: Partial<DatabaseModel> = {
project: req.body?.project,
settings: req.body?.settings,
viewSettings: req.body?.viewSettings,
osc: req.body?.osc,
urlPresets: req.body?.urlPresets,
customFields: req.body?.customFields,
};
const { rundown, project, settings, viewSettings, urlPresets, customFields, osc, http } = req.body;
const patchDb: DatabaseModel = { rundown, project, settings, viewSettings, urlPresets, customFields, osc, http };
const maybeRundown = req.body?.rundown;
await DataProvider.mergeIntoData(patchDb);
if (maybeRundown !== undefined) {
// it is likely cheaper to invalidate cache than to calculate diff
runtimeService.stop();
await setRundown(maybeRundown);
}
const newData = DataProvider.getData();
const newData = await projectService.applyDataModel(patchDb);
res.status(200).send(newData);
} catch (error) {
res.status(400).send({ message: String(error) });
@@ -93,9 +76,7 @@ export async function createProjectFile(req: Request, res: Response<{ filename:
}
export async function projectDownload(_req: Request, res: Response) {
const { title } = DataProvider.getProjectData();
const fileTitle = title || 'ontime data';
const fileTitle = projectService.getProjectTitle();
res.download(resolveDbPath, `${fileTitle}.json`, (err) => {
if (err) {
res.status(500).send({
@@ -116,9 +97,14 @@ export async function postProjectFile(req: Request, res: Response<MessageRespons
try {
const options = req.query;
const filePath = req.file.path;
await projectService.applyProjectFile(filePath, options);
res.status(201).send({ message: 'ok' });
const { filename, path } = req.file;
await projectService.handleUploadedFile(path, filename);
await projectService.applyProjectFile(filename, options);
res.status(201).send({
message: `Loaded project ${filename}`,
});
} catch (error) {
res.status(400).send({ message: `Failed parsing ${error}` });
}
@@ -141,15 +127,15 @@ export async function listProjects(_req: Request, res: Response<ProjectFileListR
*/
export async function loadProject(req: Request, res: Response<MessageResponse | ErrorResponse>) {
try {
const filename = req.body.filename;
const filePath = join(resolveProjectsDirectory, filename);
if (!fs.existsSync(filePath)) {
const name = req.body.filename;
if (!projectService.doesProjectExist(name)) {
return res.status(404).send({ message: 'File not found' });
}
await projectService.applyProjectFile(filePath);
await projectService.applyProjectFile(name);
res.status(201).send({
message: `Loaded project ${filename}`,
message: `Loaded project ${name}`,
});
} catch (error) {
res.status(500).send({ message: String(error) });
@@ -25,7 +25,7 @@ export class DataProvider {
static async setProjectData(newData: Partial<ProjectData>) {
data.project = { ...data.project, ...newData };
await this.persist();
this.persist();
return data.project;
}
@@ -35,7 +35,7 @@ export class DataProvider {
static async setCustomFields(newData: CustomFields): Promise<CustomFields> {
data.customFields = { ...newData };
await this.persist();
this.persist();
return data.customFields;
}
@@ -45,7 +45,8 @@ export class DataProvider {
static async setRundown(newData: OntimeRundown) {
data.rundown = [...newData];
await this.persist();
this.persist();
return data.rundown;
}
static getSettings(): Readonly<Settings> {
@@ -54,7 +55,8 @@ export class DataProvider {
static async setSettings(newData: Settings) {
data.settings = { ...newData };
await this.persist();
this.persist();
return data.settings;
}
static getOsc(): OSCSettings {
@@ -71,7 +73,8 @@ export class DataProvider {
static async setUrlPresets(newData: URLPreset[]) {
data.urlPresets = newData;
await this.persist();
this.persist();
return data.urlPresets;
}
static getViewSettings() {
@@ -80,18 +83,19 @@ export class DataProvider {
static async setViewSettings(newData: ViewSettings) {
data.viewSettings = { ...newData };
await this.persist();
this.persist();
return data.viewSettings;
}
static async setOsc(newData: OSCSettings): Promise<OSCSettings> {
data.osc = { ...newData };
await this.persist();
this.persist();
return data.osc;
}
static async setHttp(newData: HttpSettings): Promise<HttpSettings> {
data.http = { ...newData };
await this.persist();
this.persist();
return data.http;
}
@@ -116,6 +120,9 @@ export class DataProvider {
data.urlPresets = mergedData.urlPresets;
data.customFields = mergedData.customFields;
data.rundown = mergedData.rundown;
await this.persist();
this.persist();
return data;
}
}
@@ -2,9 +2,9 @@ import { DatabaseModel, GetInfo, ProjectData, ProjectFile, ProjectFileListRespon
import { copyFile, rename, stat, writeFile } from 'fs/promises';
import { existsSync } from 'fs';
import { basename, join } from 'path';
import { join } from 'path';
import { notifyChanges, setRundown } from '../rundown-service/RundownService.js';
import { initRundown } from '../rundown-service/RundownService.js';
import { DataProvider } from '../../classes/data-provider/DataProvider.js';
import { runtimeService } from '../runtime-service/RuntimeService.js';
import { getNetworkInterfaces } from '../../utils/networkInterfaces.js';
@@ -33,22 +33,31 @@ type Options = {
/**
* Handles a file from the upload folder and applies its data
*/
export async function applyProjectFile(filePath: string, options?: Options) {
export async function applyProjectFile(name: string, options?: Options) {
const filePath = join(resolveProjectsDirectory, name);
const data = parseProjectFile(filePath);
// move file to project folder
const filename = basename(filePath);
const newFilePath = join(resolveProjectsDirectory, filename);
await rename(filePath, newFilePath);
// change LowDB to point to new file
await switchDb(filename);
await switchDb(name);
// apply data model
await applyDataModel(data, options);
// persist the project selection
await appStateService.updateDatabaseConfig(filename);
await appStateService.updateDatabaseConfig(name);
}
/**
* Copies a file from upload folder to the projects folder
* @param filePath
* @param name
* @returns
*/
export async function handleUploadedFile(filePath: string, name: string) {
const newFilePath = join(resolveProjectsDirectory, name);
await rename(filePath, newFilePath);
await deleteFile(filePath);
return name;
}
/**
@@ -196,18 +205,27 @@ export function extractPin(value: string | undefined | null, fallback: string |
/**
* applies a partial database model
*/
export async function applyDataModel(data: Partial<DatabaseModel>, options?: Options) {
export async function applyDataModel(data: Partial<DatabaseModel>, _options?: Options) {
runtimeService.stop();
const newRundown = data.rundown || [];
const { rundown, ...rest } = data;
if (options?.onlyRundown === 'true') {
setRundown(newRundown ?? []);
} else {
await DataProvider.mergeIntoData(rest);
setRundown(rundown ?? []);
// TODO: allow partial project merge from options
const { rundown, customFields, ...rest } = data;
const newData = await DataProvider.mergeIntoData(rest);
if (rundown != null) {
initRundown(rundown, customFields ?? {});
}
notifyChanges({ timer: true, external: true });
return newData;
}
/**
* Checks whether a project of a given name exists
* @param name
*/
export function doesProjectExist(name: string): boolean {
const projectFilePath = join(resolveProjectsDirectory, name);
return existsSync(projectFilePath);
}
/**
@@ -240,3 +258,11 @@ export const validateProjectFiles = (projectFiles: { filename?: string; newFilen
return errors;
};
/**
* Get current project title or fallback
*/
export function getProjectTitle(): string {
const { title } = DataProvider.getProjectData();
return title || 'ontime data';
}
@@ -210,7 +210,7 @@ function updateRuntimeOnChange() {
/**
* Notify services of changes in the rundown
*/
export function notifyChanges(options: { timer?: boolean | string[]; external?: boolean }) {
function notifyChanges(options: { timer?: boolean | string[]; external?: boolean }) {
if (options.timer) {
const playableEvents = getPlayableEvents();
@@ -243,12 +243,3 @@ export async function initRundown(rundown: OntimeRundown, customFields: CustomFi
// notify timer of change
notifyChanges({ timer: true });
}
/**
* Overrides the rundown with the given
* @param rundown
*/
export async function setRundown(rundown: OntimeRundown) {
await cache.setRundown(rundown);
notifyChanges({ timer: true });
}