generated from thinkode/modelRepository
Fix peripherals states and implements plug and reload feature. Graphics improvements. Stability improvements. Reviewed-on: #28
249 lines
8.9 KiB
Go
249 lines
8.9 KiB
Go
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 {
|
|
hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err)
|
|
}
|
|
_, err = hostFinder.RegisterPeripheral(a.ctx, value)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to register the peripheral S/N '%s'", key)
|
|
}
|
|
}
|
|
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 {
|
|
hostFinder, err := a.hardwareManager.GetFinder(value.ProtocolName)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to find the finder '%s': %w", value.ProtocolName, err)
|
|
}
|
|
err = hostFinder.UnregisterPeripheral(a.ctx, value)
|
|
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
|
|
}
|