refactor: improve project loading

This commit is contained in:
Carlos Valente
2025-03-29 21:57:30 +01:00
committed by arc-alex
parent fc4c1bf22e
commit 5c6b06a0d4
17 changed files with 137 additions and 173 deletions
@@ -31,6 +31,7 @@ import {
setLastLoadedProject,
} from '../app-state-service/AppStateService.js';
import { runtimeService } from '../runtime-service/RuntimeService.js';
import { getFirstRundown } from '../rundown-service/rundownUtils.js';
import {
copyCorruptFile,
@@ -40,7 +41,6 @@ import {
moveCorruptFile,
parseJsonFile,
} from './projectServiceUtils.js';
import { getFirstRundown } from '../rundown-service/rundownUtils.js';
type ProjectState =
| {
@@ -70,26 +70,50 @@ function init() {
export async function getCurrentProject(): Promise<{ filename: string; pathToFile: string }> {
if (currentProjectState.status === 'PENDING') {
const lastLoadedProject = await initialiseProject();
currentProjectState = {
status: 'INITIALIZED',
currentProjectName: lastLoadedProject,
};
await initialiseProject();
}
const pathToFile = getPathToProject(currentProjectState.currentProjectName);
// we know the project is loaded since we force initialisation above
const pathToFile = getPathToProject(currentProjectState.currentProjectName as string);
return { filename: currentProjectState.currentProjectName, pathToFile };
return { filename: currentProjectState.currentProjectName as string, pathToFile };
}
/**
* Private function loads a project file and handles necessary side effects
*/
async function loadProject(projectData: DatabaseModel, projectName: string) {
// we need to make sure the file name is unique in the projects directory
const pathToNewFile = generateUniqueFileName(publicDir.projectsDir, projectName);
// change LowDB to point to new file
await initPersistence(getPathToProject(pathToNewFile), projectData);
logger.info(LogOrigin.Server, `Loaded project ${projectName}`);
// stop the runtime service
runtimeService.stop();
// load the first rundown in the project
const firstRundown = getFirstRundown(projectData.rundowns);
await initRundown(firstRundown, projectData.customFields);
// persist the project selection
const newName = getFileNameFromPath(pathToNewFile);
await setLastLoadedProject(newName);
// update the service state
currentProjectState = {
status: 'INITIALIZED',
currentProjectName: newName,
};
return newName;
}
/**
* Loads the demo project
*/
export async function loadDemoProject(): Promise<string> {
const pathToNewFile = generateUniqueFileName(publicDir.projectsDir, config.demoProject);
await initPersistence(getPathToProject(pathToNewFile), demoDb);
const newName = getFileNameFromPath(pathToNewFile);
await setLastLoadedProject(newName);
return newName;
return loadProject(demoDb, config.demoProject);
}
/**
@@ -97,11 +121,7 @@ export async function loadDemoProject(): Promise<string> {
* to be composed in the loading functions
*/
async function loadNewProject(): Promise<string> {
const pathToNewFile = generateUniqueFileName(publicDir.projectsDir, config.newProject);
await initPersistence(getPathToProject(pathToNewFile), dbModel);
const newName = getFileNameFromPath(pathToNewFile);
await setLastLoadedProject(newName);
return newName;
return loadProject(dbModel, config.newProject);
}
/**
@@ -129,46 +149,32 @@ export async function initialiseProject(): Promise<string> {
// check what was loaded before
const previousProject = await getLastLoadedProject();
// in normal circumstances we dont have a previous project if it is the first app start
// in which case we want to load a demo project
if (!previousProject) {
return loadDemoProject();
}
// try and load the previous project
const filePath = doesProjectExist(previousProject);
if (filePath === null) {
logger.warning(LogOrigin.Server, `Previous project file ${previousProject} not found`);
return loadNewProject();
}
try {
const fileData = await parseJsonFile(filePath);
const result = parseDatabaseModel(fileData);
let parsedFileName = previousProject;
let parsedFilePath = filePath;
if (result.errors.length > 0) {
logger.warning(LogOrigin.Server, 'Project loaded with errors');
parsedFileName = await handleCorruptedFile(filePath, previousProject);
parsedFilePath = getPathToProject(parsedFileName);
}
await initPersistence(parsedFilePath, result.data);
await setLastLoadedProject(parsedFileName);
return parsedFileName;
const projectName = await loadProjectFile(previousProject);
return projectName;
} catch (error) {
// if we are here, most likely the json parsing failed and the file is corrupt
logger.warning(LogOrigin.Server, `Unable to load previous project ${previousProject}: ${getErrorMessage(error)}`);
await moveCorruptFile(filePath, previousProject).catch((_) => {
try {
const pathToFile = getPathToProject(previousProject);
await moveCorruptFile(pathToFile, previousProject);
} catch (_) {
/* while we have to catch the error, we dont need to handle it */
});
return loadNewProject();
}
}
return loadNewProject();
}
/**
* Loads a data from a file into the runtime
*/
export async function loadProjectFile(name: string) {
export async function loadProjectFile(name: string): Promise<string> {
const filePath = doesProjectExist(name);
if (filePath === null) {
throw new Error('Project file not found');
@@ -178,31 +184,14 @@ export async function loadProjectFile(name: string) {
const fileData = await parseJsonFile(filePath);
const result = parseDatabaseModel(fileData);
let parsedFileName = name;
let parsedFilePath = filePath;
if (result.errors.length > 0) {
logger.warning(LogOrigin.Server, 'Project loaded with errors');
parsedFileName = await handleCorruptedFile(filePath, name);
parsedFilePath = getPathToProject(parsedFileName);
}
// change LowDB to point to new file
await initPersistence(parsedFilePath, result.data);
logger.info(LogOrigin.Server, `Loaded project ${parsedFileName}`);
// persist the project selection
await setLastLoadedProject(parsedFileName);
// since load happens at runtime, we need to update the services that depend on the data
// apply data model
runtimeService.stop();
const { rundowns, customFields } = result.data;
// apply the rundown
const firstRundown = getFirstRundown(rundowns);
await initRundown(firstRundown, customFields);
const projectName = await loadProject(result.data, parsedFileName);
return projectName;
}
/**
@@ -239,7 +228,7 @@ export async function duplicateProjectFile(originalFile: string, newFilename: st
/**
* Renames an existing project file
*/
export async function renameProjectFile(originalFile: string, newFilename: string) {
export async function renameProjectFile(originalFile: string, newFilename: string): Promise<string> {
const projectFilePath = doesProjectExist(originalFile);
if (projectFilePath === null) {
throw new Error('Project file not found');
@@ -257,50 +246,23 @@ export async function renameProjectFile(originalFile: string, newFilename: strin
const isLoaded = await isLastLoadedProject(originalFile);
if (isLoaded) {
const fileData = await parseJsonFile(pathToRenamed);
const result = parseDatabaseModel(fileData);
const projectData = parseDatabaseModel(fileData);
// change LowDB to point to new file
await initPersistence(pathToRenamed, result.data);
logger.info(LogOrigin.Server, `Loaded project ${newFilename}`);
// persist the project selection
await setLastLoadedProject(newFilename);
// apply data model
runtimeService.stop();
const { rundowns, customFields } = result.data;
// apply the rundown
const firstRundown = getFirstRundown(rundowns);
await initRundown(firstRundown, customFields);
const newFileName = await loadProject(projectData.data, newFilename);
return newFileName;
}
return newFilename;
}
/**
* Creates a new project file and applies its result
*/
export async function createProject(filename: string, initialData: Partial<DatabaseModel>) {
export async function createProject(filename: string, initialData: Partial<DatabaseModel>): Promise<string> {
const data = safeMerge(dbModel, initialData);
const fileNameWithExtension = ensureJsonExtension(filename);
const uniqueFileName = generateUniqueFileName(publicDir.projectsDir, fileNameWithExtension);
const newFile = getPathToProject(uniqueFileName);
// change LowDB to point to new file
await initPersistence(newFile, data);
// apply data to running services
// we dont need to parse since we are creating a new file
await patchCurrentProject(data);
// update app state to point to new value
setLastLoadedProject(uniqueFileName);
// update the service state
currentProjectState.currentProjectName = uniqueFileName;
return uniqueFileName;
const newFilename = await loadProject(data, fileNameWithExtension);
return newFilename;
}
/**
@@ -338,7 +300,7 @@ export async function patchCurrentProject(data: Partial<DatabaseModel>) {
* We currently ignore all other rundowns
*/
const firstRundown = getFirstRundown(result.rundowns);
initRundown(firstRundown, result.customFields);
await initRundown(firstRundown, result.customFields);
}
const updatedData = await getDataProvider().getData();