package main import ( "changeme/hardware" "context" "fmt" "io" "log" "os" "path/filepath" "strings" "sync" "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 ) // App struct type App struct { ctx context.Context hardwareManager *hardware.HardwareManager // For managing all the hardware wmiMutex sync.Mutex // Avoid some WMI operations at the same time projectInfo ProjectInfo // The project information structure projectSave string // The file name of the project // FOR TESTING PURPOSE ONLY ftdi *hardware.FTDIPeripheral } // NewApp creates a new App application struct func NewApp() *App { // Create a new hadware manager hardwareManager := hardware.NewHardwareManager() hardwareManager.RegisterFinder(hardware.NewMIDIFinder()) hardwareManager.RegisterFinder(hardware.NewFTDIFinder()) return &App{ hardwareManager: hardwareManager, projectSave: "", projectInfo: ProjectInfo{ PeripheralsInfo: make(map[string]hardware.PeripheralInfo), }, } } // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { a.ctx = ctx err := a.hardwareManager.Start(ctx) if err != nil { log.Fatalf("Unable to start the device manager: %s", err) return } } // CreateProject creates a new blank project func (a *App) CreateProject() ShowInfo { date := time.Now() a.projectSave = "" a.projectInfo.ShowInfo = 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", } return a.projectInfo.ShowInfo } // GetPeripherals gets all the peripherals connected func (a *App) GetPeripherals() error { return a.hardwareManager.Scan(a.ctx) } // 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.Fatalf("Unable to open the projects directory: %v", err) return nil, err } files, err := f.Readdir(0) if err != nil { log.Fatalf("Unable to read the projects directory: %v", err) return nil, err } for _, fileInfo := range files { // Open the file and get the show name fileData, err := os.ReadFile(filepath.Join(projectsDirectory, fileInfo.Name())) if err == nil { projectObject := ProjectInfo{} err = yaml.Unmarshal(fileData, &projectObject) if err == nil { // Add the SaveFile property projects = append(projects, ProjectMetaData{ Name: projectObject.ShowInfo.Name, Save: fileInfo.Name(), }) } } } return projects, nil } // GetProjectInfo returns the information of the saved project func (a *App) GetProjectInfo(projectFile string) (ProjectInfo, error) { // Open the project file projectPath := filepath.Join(projectsDirectory, projectFile) content, err := os.ReadFile(projectPath) if err != nil { log.Fatalf("Unable to read the project file: %v", err) return ProjectInfo{}, err } a.projectInfo = ProjectInfo{} err = yaml.Unmarshal(content, &a.projectInfo) if err != nil { log.Fatalf("Unable to get the project information: %v", err) return ProjectInfo{}, err } // Load it into the app a.projectSave = projectFile // Return the show information return a.projectInfo, 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 { return "", err } // Copy the avatar to the application avatars path avatarPath := filepath.Join(avatarsDirectory, filepath.Base(filePath)) _, err = copy(filePath, avatarPath) if err != nil { return "", err } return filepath.Base(filePath), nil } // UpdateShowInfo updates the show information func (a *App) UpdateShowInfo(showInfo ShowInfo) { fmt.Printf("%s\n", showInfo) a.projectInfo.ShowInfo = showInfo } // AddPeripheral adds a peripheral to the project func (a *App) AddPeripheral(protocolName string, peripheralID string) error { // Get the device from its finder p, found := a.hardwareManager.GetPeripheral(protocolName, peripheralID) if !found { return fmt.Errorf("Unable to localize the peripheral %s", peripheralID) } // Add the peripheral ID to the project a.projectInfo.PeripheralsInfo[peripheralID] = p.GetInfo() // TODO: Connect the peripheral return nil } // RemovePeripheral adds a peripheral to the project func (a *App) RemovePeripheral(protocolName string, peripheralID string) error { // TODO: Disconnect the peripheral // Remove the peripheral ID from the project delete(a.projectInfo.PeripheralsInfo, peripheralID) return nil } // SaveProject saves the project func (a *App) SaveProject() (string, error) { log.Printf("Saving the project %s to %s", a.projectInfo.ShowInfo.Name, a.projectSave) // 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) } data, err := yaml.Marshal(a.projectInfo) if err != nil { return "", err } // Create the project directory if not exists err = os.MkdirAll(projectsDirectory, os.ModePerm) if err != nil { return "", err } err = os.WriteFile(filepath.Join(projectsDirectory, a.projectSave), data, os.ModePerm) if err != nil { return "", err } 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 } func formatString(input string) string { // Convertir en minuscules lowerCaseString := strings.ToLower(input) // Remplacer les espaces par des underscores formattedString := strings.ReplaceAll(lowerCaseString, " ", "_") return formattedString } func copy(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) if err != nil { return 0, err } if !sourceFileStat.Mode().IsRegular() { return 0, fmt.Errorf("%s is not a regular file", src) } source, err := os.Open(src) if err != nil { return 0, err } defer source.Close() destination, err := os.Create(dst) if err != nil { return 0, err } defer destination.Close() nBytes, err := io.Copy(destination, source) return nBytes, err } // FOR TESTING PURPOSE ONLY func (a *App) ConnectFTDI() error { // Create a new FTDI object var err error a.ftdi, err = hardware.NewFTDIPeripheral("FTDI TEST INTERFACE", "A50825I", 0, "Virtual FTDI finder") if err != nil { return err } return a.ftdi.Connect() } func (a *App) ActivateFTDI() error { return a.ftdi.Activate() } func (a *App) SetDeviceFTDI(channelValue byte) error { return a.ftdi.SetDeviceProperty(0, 0, channelValue) } func (a *App) DeactivateFTDI() error { return a.ftdi.Deactivate() } func (a *App) DisconnectFTDI() error { return a.ftdi.Disconnect() }