2025-11-11 19:14:44 +00:00
package main
import (
"dmxconnect/hardware"
"fmt"
"github.com/rs/zerolog/log"
"os"
"path/filepath"
"time"
"github.com/wailsapp/wails/v2/pkg/runtime"
"gopkg.in/yaml.v2"
)
const (
projectsDirectory = "projects" // The directory were are stored all the projects
avatarsDirectory = "frontend/public" // The directory were are stored all the avatars
projectExtension = ".dmxproj" // The extension of a DMX Connect project
)
// GetProjects gets all the projects in the projects directory
func ( a * App ) GetProjects ( ) ( [ ] ProjectMetaData , error ) {
projects := [ ] ProjectMetaData { }
f , err := os . Open ( projectsDirectory )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Msg ( "unable to open the projects directory" )
return nil , fmt . Errorf ( "unable to open the projects directory: %v" , err )
}
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectsDirectory" , projectsDirectory ) . Msg ( "projects directory opened" )
files , err := f . Readdir ( 0 )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Msg ( "unable to read the projects directory" )
return nil , fmt . Errorf ( "unable to read the projects directory: %v" , err )
}
log . Trace ( ) . Str ( "file" , "project" ) . Any ( "projectsFiles" , files ) . Msg ( "project files got" )
for _ , fileInfo := range files {
// Open the file and get the show name
fileData , err := os . ReadFile ( filepath . Join ( projectsDirectory , fileInfo . Name ( ) ) )
if err != nil {
log . Warn ( ) . Str ( "file" , "project" ) . Str ( "projectFile" , fileInfo . Name ( ) ) . Msg ( "unable to open the project file" )
continue
}
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectFile" , fileInfo . Name ( ) ) . Any ( "fileData" , fileData ) . Msg ( "project file read" )
projectObject := ProjectInfo { }
err = yaml . Unmarshal ( fileData , & projectObject )
if err != nil {
log . Warn ( ) . Str ( "file" , "project" ) . Str ( "projectFile" , fileInfo . Name ( ) ) . Msg ( "project has invalid format" )
continue
}
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectFile" , fileInfo . Name ( ) ) . Msg ( "project file unmarshalled" )
// Add the SaveFile property
projects = append ( projects , ProjectMetaData {
Name : projectObject . ShowInfo . Name ,
Save : fileInfo . Name ( ) ,
} )
}
log . Info ( ) . Str ( "file" , "project" ) . Any ( "projectsList" , projects ) . Msg ( "got the projects list" )
return projects , nil
}
// CreateProject creates a new blank project
func ( a * App ) CreateProject ( ) error {
// Create new project information
date := time . Now ( )
projectInfo := ProjectInfo {
ShowInfo {
Name : "My new show" ,
Date : fmt . Sprintf ( "%04d-%02d-%02dT%02d:%02d" , date . Year ( ) , date . Month ( ) , date . Day ( ) , date . Hour ( ) , date . Minute ( ) ) ,
Avatar : "appicon.png" ,
Comments : "Write your comments here" ,
} ,
make ( map [ string ] hardware . PeripheralInfo ) ,
}
// The project isn't saved for now
a . projectSave = ""
return a . OpenProject ( projectInfo )
}
// OpenProjectFromDisk opens a project based on its filename
func ( a * App ) OpenProjectFromDisk ( projectFile string ) error {
// Open the project file
projectPath := filepath . Join ( projectsDirectory , projectFile )
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectPath" , projectPath ) . Msg ( "project path is created" )
content , err := os . ReadFile ( projectPath )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Str ( "projectFile" , projectFile ) . Msg ( "Unable to read the project file" )
return fmt . Errorf ( "unable to read the project file: %v" , err )
}
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectPath" , projectPath ) . Msg ( "project file read" )
// Import project data structure
projectInfo := ProjectInfo { }
err = yaml . Unmarshal ( content , & projectInfo )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Str ( "projectFile" , projectFile ) . Msg ( "Unable to get the project information" )
return fmt . Errorf ( "unable to get the project information: %v" , err )
}
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectPath" , projectPath ) . Msg ( "project information got" )
// The project is saved
a . projectSave = projectFile
return a . OpenProject ( projectInfo )
}
// OpenProject opens a project based on its information
func ( a * App ) OpenProject ( projectInfo ProjectInfo ) error {
// Close the current project
err := a . CloseCurrentProject ( )
if err != nil {
return fmt . Errorf ( "unable to close project: %w" , err )
}
// Open the project
a . projectInfo = projectInfo
// Send an event with the project data
runtime . EventsEmit ( a . ctx , "LOAD_PROJECT" , projectInfo . ShowInfo )
// Load all peripherals of the project
projectPeripherals := a . projectInfo . PeripheralsInfo
for key , value := range projectPeripherals {
2025-11-29 17:42:42 +01:00
_ , err = a . hardwareManager . RegisterPeripheral ( a . ctx , value )
2025-11-11 19:14:44 +00:00
if err != nil {
2025-11-14 10:46:24 +00:00
return fmt . Errorf ( "unable to register the peripheral S/N '%s': %w" , key , err )
2025-11-11 19:14:44 +00:00
}
}
return nil
}
// CloseCurrentProject closes the current project
func ( a * App ) CloseCurrentProject ( ) error {
// Unregistrer all peripherals of the project
projectPeripherals := a . projectInfo . PeripheralsInfo
for key , value := range projectPeripherals {
2025-11-29 17:42:42 +01:00
err := a . hardwareManager . UnregisterPeripheral ( a . ctx , value )
2025-11-11 19:14:44 +00:00
if err != nil {
return fmt . Errorf ( "unable to unregister the peripheral S/N '%s': %w" , key , err )
}
}
// Unload project info in the front
return nil
}
// ChooseAvatarPath opens a filedialog to choose the show avatar
func ( a * App ) ChooseAvatarPath ( ) ( string , error ) {
// Open the file dialog box
filePath , err := runtime . OpenFileDialog ( a . ctx , runtime . OpenDialogOptions {
Title : "Choose your show avatar" ,
Filters : [ ] runtime . FileFilter {
{
DisplayName : "Images" ,
Pattern : "*.png;*.jpg;*.jpeg" ,
} ,
} ,
} )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Msg ( "unable to open the avatar dialog" )
return "" , err
}
log . Debug ( ) . Str ( "file" , "project" ) . Msg ( "avatar dialog is opened" )
// Copy the avatar to the application avatars path
avatarPath := filepath . Join ( avatarsDirectory , filepath . Base ( filePath ) )
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "avatarPath" , avatarPath ) . Msg ( "avatar path is created" )
_ , err = copy ( filePath , avatarPath )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Str ( "avatarsDirectory" , avatarsDirectory ) . Str ( "fileBase" , filepath . Base ( filePath ) ) . Msg ( "unable to copy the avatar file" )
return "" , err
}
log . Info ( ) . Str ( "file" , "project" ) . Str ( "avatarFileName" , filepath . Base ( filePath ) ) . Msg ( "got the new avatar file" )
return filepath . Base ( filePath ) , nil
}
// UpdateShowInfo updates the show information
func ( a * App ) UpdateShowInfo ( showInfo ShowInfo ) {
a . projectInfo . ShowInfo = showInfo
log . Info ( ) . Str ( "file" , "project" ) . Any ( "showInfo" , showInfo ) . Msg ( "show information was updated" )
}
// SaveProject saves the project
func ( a * App ) SaveProject ( ) ( string , error ) {
// If there is no save file, create a new one with the show name
if a . projectSave == "" {
date := time . Now ( )
a . projectSave = fmt . Sprintf ( "%04d%02d%02d%02d%02d%02d%s" , date . Year ( ) , date . Month ( ) , date . Day ( ) , date . Hour ( ) , date . Minute ( ) , date . Second ( ) , projectExtension )
log . Debug ( ) . Str ( "file" , "project" ) . Str ( "newProjectSave" , a . projectSave ) . Msg ( "projectSave is null, getting a new one" )
}
data , err := yaml . Marshal ( a . projectInfo )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Any ( "projectInfo" , a . projectInfo ) . Msg ( "unable to format the project information" )
return "" , err
}
log . Trace ( ) . Str ( "file" , "project" ) . Any ( "projectInfo" , a . projectInfo ) . Msg ( "projectInfo has been marshalled" )
// Create the project directory if not exists
err = os . MkdirAll ( projectsDirectory , os . ModePerm )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Str ( "projectsDirectory" , projectsDirectory ) . Msg ( "unable to create the projects directory" )
return "" , err
}
log . Trace ( ) . Str ( "file" , "project" ) . Str ( "projectsDirectory" , projectsDirectory ) . Msg ( "projects directory has been created" )
err = os . WriteFile ( filepath . Join ( projectsDirectory , a . projectSave ) , data , os . ModePerm )
if err != nil {
log . Err ( err ) . Str ( "file" , "project" ) . Str ( "projectsDirectory" , projectsDirectory ) . Str ( "projectSave" , a . projectSave ) . Msg ( "unable to save the project" )
return "" , err
}
log . Info ( ) . Str ( "file" , "project" ) . Str ( "projectFileName" , a . projectSave ) . Msg ( "project has been saved" )
return a . projectSave , nil
}
// ShowInfo defines the information of the show
type ShowInfo struct {
Name string ` yaml:"name" `
Date string ` yaml:"date" `
Avatar string ` yaml:"avatar" `
Comments string ` yaml:"comments" `
}
// ProjectMetaData defines all the minimum information for a lighting project
type ProjectMetaData struct {
Name string // Show name
Save string // The save file of the project
}
// ProjectInfo defines all the information for a lighting project
type ProjectInfo struct {
ShowInfo ShowInfo ` yaml:"show" ` // Show information
PeripheralsInfo map [ string ] hardware . PeripheralInfo ` yaml:"peripherals" ` // Peripherals information
}