diff --git a/addon.go b/addon.go new file mode 100644 index 0000000..32fd701 --- /dev/null +++ b/addon.go @@ -0,0 +1,182 @@ +package main + +import ( + "archive/zip" + "bytes" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + "regexp" + "strings" +) + +type Addon struct { + Name string `json:"name"` + URL *url.URL `json:"url"` +} +var versionRegex = regexp.MustCompile(`(\d+\.\d+\.\d+)`) + +func NewAddon(name, aurl string) *Addon { + a := &Addon{Name: name} + var err error + a.URL, err = url.Parse(aurl) + if err != nil { + Error.Printf("invalid url: %s", aurl) + return nil + } + return a +} + +func (a *Addon) GetRemoteTocURL() *url.URL { + return a.URL.JoinPath("raw", "branch", "master", a.Name+".toc") +} +func (a *Addon) GetRemoteReleaseURL() *url.URL { + return a.URL.JoinPath("media", "branch", "master", a.Name+".zip") +} +func (a *Addon) GetRemoteRelease() (body []byte, err error) { + url := a.GetRemoteReleaseURL() + response, err := http.Get(url.String()) + if err != nil { + return nil, fmt.Errorf("error getting remote release: %w", err) + } + return io.ReadAll(response.Body) +} +func (a *Addon) HasBreakingChanges(lhsToc, rhsToc string) bool { + lhsLines := strings.Split(lhsToc, "\n") + rhsLines := strings.Split(rhsToc, "\n") + for i, lhsLine := range lhsLines { + rhsLine := rhsLines[i] + lhsLine = strings.TrimSpace(lhsLine) + rhsLine = strings.TrimSpace(rhsLine) + // We don't care about ## lines + if strings.HasPrefix(lhsLine, "#") && strings.HasPrefix(rhsLine, "#") { + continue + } + // Nor do we care about empty lines + if lhsLine == "" && rhsLine == "" { + continue + } + + log.Printf("%s %s", lhsLine, rhsLine) + if i >= len(rhsLines) || rhsLine != lhsLine { + return true + } + } + return false +} +func UpdateFile(localPath string, data []byte) (err error) { + err = os.MkdirAll(filepath.Dir(localPath), 0755) + if err != nil { + return fmt.Errorf("error creating directory: %w", err) + } + fileHandle, err := os.OpenFile(localPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + defer fileHandle.Close() + _, err = fileHandle.Write(data) + if err != nil { + return fmt.Errorf("error writing file: %w", err) + } + + return nil +} +func (a *Addon) Update(body []byte) (err error) { + log.Printf("Updating %s", a.Name) + zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) + if err != nil { + return fmt.Errorf("error creating zip reader: %w", err) + } + log.Printf("Found %d files", len(zipReader.File)) + hasBreakingChanges := false + for _, file := range zipReader.File { + if file.FileInfo().IsDir() { + continue + } + log.Printf("Found file %s", file.Name) + fileHandle, err := file.Open() + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + fileData, err := io.ReadAll(fileHandle) + if err != nil { + return fmt.Errorf("error reading file: %w", err) + } + + if strings.HasSuffix(file.Name, ".toc") { + localToc, err := os.ReadFile(a.GetLocalTocPath()) + if err != nil { + if os.IsNotExist(err) { + localToc = []byte{} + } else { + return fmt.Errorf("error reading local toc: %w", err) + } + } + if a.HasBreakingChanges(string(localToc), string(fileData)) { + log.Printf("Has breaking changes") + hasBreakingChanges = true + } + } + + localPath := filepath.Join(gamePath, "Interface", "AddOns", file.Name) + log.Printf("Updating file %s", localPath) + err = UpdateFile(localPath, fileData) + if err != nil { + return fmt.Errorf("error updating file: %w", err) + } + } + if hasBreakingChanges { + Warning.Printf("Has breaking changes") + } + return nil +} +func (a *Addon) GetLocalTocPath() string { + return filepath.Join(gamePath, "Interface", "AddOns", a.Name, a.Name+".toc") +} +func (a *Addon) GetRemoteVersion() (version string, err error) { + url := a.GetRemoteTocURL() + log.Printf("Fetching remote version from %s", url.String()) + response, err := http.Get(url.String()) + if err != nil { + return "", fmt.Errorf("error getting remote version: %w", err) + } + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("error getting remote version with status: %s", response.Status) + } + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + if err != nil { + return "", fmt.Errorf("error reading remote version: %w", err) + } + return GetVersion(string(body)) +} +func (a *Addon) GetLocalVersion() (version string, err error) { + file, err := os.Open(a.GetLocalTocPath()) + if err != nil { + if os.IsNotExist(err) { + return "0.0.0", nil + } + return "", fmt.Errorf("error opening local toc: %w", err) + } + defer file.Close() + body, err := io.ReadAll(file) + if err != nil { + return "", fmt.Errorf("error reading local toc: %w", err) + } + return GetVersion(string(body)) +} +func (a *Addon) IsUpToDate() bool { + remoteVersion, err := a.GetRemoteVersion() + if err != nil { + return false + } + localVersion, err := a.GetLocalVersion() + if err != nil { + return false + } + return remoteVersion == localVersion +} diff --git a/addonService.go b/addonService.go new file mode 100644 index 0000000..7786686 --- /dev/null +++ b/addonService.go @@ -0,0 +1,17 @@ +package main + +type AddonService struct { + Addons []*Addon +} + +var addons = []*Addon{ + NewAddon("Channeler", "https://git.site.quack-lab.dev/dave/wow_channeler"), + NewAddon("Heimdall", "https://git.site.quack-lab.dev/dave/wow-Heimdall"), + NewAddon("Dechickenator", "https://git.site.quack-lab.dev/dave/wow_dechickenator"), +} + +func NewAddonService() *AddonService { + return &AddonService{ + Addons: addons, + } +} diff --git a/main.go b/main.go index 898432b..bd000ea 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,13 @@ package main import ( - "archive/zip" - "bytes" "embed" + "encoding/json" "fmt" "io" "log" - "net/http" - "net/url" "os" "path/filepath" - "regexp" - "strings" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" @@ -36,196 +31,36 @@ func init() { //go:embed all:frontend/dist var assets embed.FS var gamePath string -var versionRegex = regexp.MustCompile(`(\d+\.\d+\.\d+)`) - -type Addon struct { - Name string - URL *url.URL -} - -func NewAddon(name, aurl string) *Addon { - a := &Addon{Name: name} - var err error - a.URL, err = url.Parse(aurl) - if err != nil { - Error.Printf("invalid url: %s", aurl) - return nil - } - return a -} - -var addons = []*Addon{ - NewAddon("Channeler", "https://git.site.quack-lab.dev/dave/wow_channeler"), -} - -func (a *Addon) GetRemoteTocURL() *url.URL { - return a.URL.JoinPath("raw", "branch", "master", a.Name+".toc") -} -func (a *Addon) GetRemoteReleaseURL() *url.URL { - return a.URL.JoinPath("media", "branch", "master", a.Name+".zip") -} -func (a *Addon) GetRemoteRelease() (body []byte, err error) { - url := a.GetRemoteReleaseURL() - response, err := http.Get(url.String()) - if err != nil { - return nil, fmt.Errorf("error getting remote release: %w", err) - } - return io.ReadAll(response.Body) -} -func (a *Addon) HasBreakingChanges(lhsToc, rhsToc string) bool { - lhsLines := strings.Split(lhsToc, "\n") - rhsLines := strings.Split(rhsToc, "\n") - for i, lhsLine := range lhsLines { - rhsLine := rhsLines[i] - lhsLine = strings.TrimSpace(lhsLine) - rhsLine = strings.TrimSpace(rhsLine) - // We don't care about ## lines - if strings.HasPrefix(lhsLine, "#") && strings.HasPrefix(rhsLine, "#") { - continue - } - // Nor do we care about empty lines - if lhsLine == "" && rhsLine == "" { - continue - } - - log.Printf("%s %s", lhsLine, rhsLine) - if i >= len(rhsLines) || rhsLine != lhsLine { - return true - } - } - return false -} -func UpdateFile(localPath string, data []byte) (err error) { - err = os.MkdirAll(filepath.Dir(localPath), 0755) - if err != nil { - return fmt.Errorf("error creating directory: %w", err) - } - fileHandle, err := os.OpenFile(localPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - return fmt.Errorf("error opening file: %w", err) - } - defer fileHandle.Close() - _, err = fileHandle.Write(data) - if err != nil { - return fmt.Errorf("error writing file: %w", err) - } - - return nil -} -func (a *Addon) Update(body []byte) (err error) { - log.Printf("Updating %s", a.Name) - zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) - if err != nil { - return fmt.Errorf("error creating zip reader: %w", err) - } - log.Printf("Found %d files", len(zipReader.File)) - hasBreakingChanges := false - for _, file := range zipReader.File { - if file.FileInfo().IsDir() { - continue - } - log.Printf("Found file %s", file.Name) - fileHandle, err := file.Open() - if err != nil { - return fmt.Errorf("error opening file: %w", err) - } - fileData, err := io.ReadAll(fileHandle) - if err != nil { - return fmt.Errorf("error reading file: %w", err) - } - - if strings.HasSuffix(file.Name, ".toc") { - localToc, err := os.ReadFile(a.GetLocalTocPath()) - if err != nil { - if os.IsNotExist(err) { - localToc = []byte{} - } else { - return fmt.Errorf("error reading local toc: %w", err) - } - } - if a.HasBreakingChanges(string(localToc), string(fileData)) { - log.Printf("Has breaking changes") - hasBreakingChanges = true - } - } - - localPath := filepath.Join(gamePath, "Interface", "AddOns", file.Name) - log.Printf("Updating file %s", localPath) - err = UpdateFile(localPath, fileData) - if err != nil { - return fmt.Errorf("error updating file: %w", err) - } - } - if hasBreakingChanges { - Warning.Printf("Has breaking changes") - } - return nil -} -func (a *Addon) GetLocalTocPath() string { - return filepath.Join(gamePath, "Interface", "AddOns", a.Name, a.Name+".toc") -} -func (a *Addon) GetRemoteVersion() (version string, err error) { - url := a.GetRemoteTocURL() - log.Printf("Fetching remote version from %s", url.String()) - response, err := http.Get(url.String()) - if err != nil { - return "", fmt.Errorf("error getting remote version: %w", err) - } - if response.StatusCode != http.StatusOK { - return "", fmt.Errorf("error getting remote version with status: %s", response.Status) - } - defer response.Body.Close() - body, err := io.ReadAll(response.Body) - if err != nil { - return "", fmt.Errorf("error reading remote version: %w", err) - } - return GetVersion(string(body)) -} -func (a *Addon) GetLocalVersion() (version string, err error) { - file, err := os.Open(a.GetLocalTocPath()) - if err != nil { - return "", fmt.Errorf("error opening local toc: %w", err) - } - defer file.Close() - body, err := io.ReadAll(file) - if err != nil { - return "", fmt.Errorf("error reading local toc: %w", err) - } - return GetVersion(string(body)) -} -func (a *Addon) IsUpToDate() bool { - remoteVersion, err := a.GetRemoteVersion() - if err != nil { - return false - } - localVersion, err := a.GetLocalVersion() - if err != nil { - return false - } - return remoteVersion == localVersion -} +var addonService *AddonService func main() { + addonService = NewAddonService() gamePath = filepath.Join("C:\\", "Games", "WoWRuski") - // for _, addon := range addons { - // log.Printf("%#v", addon.IsUpToDate()) - // log.Printf("%#v", addon.GetRemoteReleaseURL()) - // } - file, err := os.ReadFile("Heimdall.zip") - if err != nil { - log.Printf("error reading file: %s", err) - return + for _, addon := range addonService.Addons { + localVersion, err := addon.GetLocalVersion() + if err != nil { + Error.Printf("error getting local version: %s", err) + continue + } + log.Printf("%#v", localVersion) + remoteVersion, err := addon.GetRemoteVersion() + if err != nil { + Error.Printf("error getting remote version: %s", err) + continue + } + log.Printf("%#v", remoteVersion) + body, err := json.Marshal(addon) + if err != nil { + Error.Printf("error marshalling addon: %s", err) + continue + } + log.Printf("%s", string(body)) } - err = addons[0].Update(file) - if err != nil { - log.Printf("error updating addon: %s", err) - return - } - log.Printf("Addon updated") + return app := NewApp() - err = wails.Run(&options.App{ + err := wails.Run(&options.App{ Title: "wails-template", Width: 1024, Height: 768,