package main import ( "changeme/hardware" "context" "fmt" "io" "log" "os" "path/filepath" "strings" "sync" "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 // 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, } } // 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 } } // GetPeripherals gets all the peripherals connected func (a *App) GetPeripherals() { a.hardwareManager.Scan(a.ctx) } // GetProjects gets all the projects in the projects directory func (a *App) GetProjects() ([]ProjectInfo, error) { projects := []ProjectInfo{} 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 projectObject.ShowInfo.SaveFile = fileInfo.Name() projects = append(projects, projectObject) } } } return projects, nil } // GetProjectInfo returns the information of the saved project func (a *App) GetProjectInfo(projectFile string) (ShowInfo, error) { projectPath := filepath.Join(projectsDirectory, projectFile) content, err := os.ReadFile(projectPath) if err != nil { log.Fatalf("Unable to read the project file: %v", err) return ShowInfo{}, err } projectInfo := ProjectInfo{} err = yaml.Unmarshal(content, &projectInfo) if err != nil { log.Fatalf("Unable to get the project information: %v", err) return ShowInfo{}, err } projectInfo.ShowInfo.SaveFile = projectFile return projectInfo.ShowInfo, 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 } // SaveProject saves the project func (a *App) SaveProject(showInfo ShowInfo) (string, error) { log.Printf("Saving the project %s to %s", showInfo.Name, showInfo.SaveFile) // If there is no save file, create a new one with the show name if showInfo.SaveFile == "" { showInfo.SaveFile = fmt.Sprintf("%s%s", formatString(showInfo.Name), projectExtension) } project := ProjectInfo{} log.Printf("The number of universes: %d", showInfo.UniversesNumber) project.ShowInfo = showInfo data, err := yaml.Marshal(project) if err != nil { return "", err } err = os.WriteFile(filepath.Join(projectsDirectory, showInfo.SaveFile), data, os.ModePerm) if err != nil { return "", err } return showInfo.SaveFile, nil } // ShowInfo defines the information of the show type ShowInfo struct { Name string `yaml:"name"` Date string `yaml:"date"` UniversesNumber int `yaml:"universesNumber"` Avatar string `yaml:"avatar"` Comments string `yaml:"comments"` SaveFile string `yaml:"-"` } // ProjectInfo defines all the information for a lighting project type ProjectInfo struct { ShowInfo ShowInfo `yaml:"show"` // Show 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) 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() }