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 }