From 0cc0aec5b3257775935ac6e57e64393366d6714a Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 21 Jul 2020 18:50:35 +0300 Subject: [PATCH] + update package: perform update tasks --- update/check.go | 92 +++++++- update/test/AdGuardHome.tar.gz | Bin 0 -> 211 bytes update/test/AdGuardHome.zip | Bin 0 -> 565 bytes update/update_test.go | 205 ++++++++++++++++++ update/updater.go | 384 ++++++++++++++++++++++++++++++++- 5 files changed, 669 insertions(+), 12 deletions(-) create mode 100644 update/test/AdGuardHome.tar.gz create mode 100644 update/test/AdGuardHome.zip create mode 100644 update/update_test.go diff --git a/update/check.go b/update/check.go index 56116459..75b551bb 100644 --- a/update/check.go +++ b/update/check.go @@ -1,13 +1,101 @@ package update +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" +) + +const versionCheckPeriod = 8 * 60 * 60 + +// VersionInfo - VersionInfo type VersionInfo struct { NewVersion string Announcement string AnnouncementURL string SelfUpdateMinVersion string CanAutoUpdate bool + PackageURL string } -func (u *Updater) GetVersionResponse() (VersionInfo, error) { - return VersionInfo{}, nil +// GetVersionResponse - GetVersionResponse +func (u *Updater) GetVersionResponse(forceRecheck bool) (VersionInfo, error) { + if !forceRecheck && + u.versionCheckLastTime.Unix()+versionCheckPeriod > time.Now().Unix() { + return u.parseVersionResponse(u.versionJSON) + } + + resp, err := u.Client.Get(u.VersionURL) + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + + if err != nil { + return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %s", u.VersionURL, err) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %s", u.VersionURL, err) + } + + u.versionJSON = body + u.versionCheckLastTime = time.Now() + + return u.parseVersionResponse(body) +} + +func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) { + info := VersionInfo{} + versionJSON := make(map[string]interface{}) + err := json.Unmarshal(data, &versionJSON) + if err != nil { + return info, fmt.Errorf("version.json: %s", err) + } + + var ok1, ok2, ok3, ok4 bool + info.NewVersion, ok1 = versionJSON["version"].(string) + info.Announcement, ok2 = versionJSON["announcement"].(string) + info.AnnouncementURL, ok3 = versionJSON["announcement_url"].(string) + info.SelfUpdateMinVersion, ok4 = versionJSON["selfupdate_min_version"].(string) + if !ok1 || !ok2 || !ok3 || !ok4 { + return info, fmt.Errorf("version.json: invalid data") + } + + var ok bool + info.PackageURL, ok = u.getDownloadURL(versionJSON) + if ok && + info.NewVersion != u.VersionString && + u.VersionString >= info.SelfUpdateMinVersion { + info.CanAutoUpdate = true + } + + return info, nil +} + +// Get download URL for the current GOOS/GOARCH/ARMVersion +func (u *Updater) getDownloadURL(json map[string]interface{}) (string, bool) { + var key string + + if u.Arch == "arm" && u.ARMVersion != "" { + // the key is: + // download_linux_armv5 for ARMv5 + // download_linux_armv6 for ARMv6 + // download_linux_armv7 for ARMv7 + key = fmt.Sprintf("download_%s_%sv%s", u.OS, u.Arch, u.ARMVersion) + } + + val, ok := json[key] + if !ok { + // the key is download_linux_arm or download_linux_arm64 for regular ARM versions + key = fmt.Sprintf("download_%s_%s", u.OS, u.Arch) + val, ok = json[key] + } + + if !ok { + return "", false + } + + return val.(string), true } diff --git a/update/test/AdGuardHome.tar.gz b/update/test/AdGuardHome.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..b292c6d99775a366f7c27b29e8c60a872e284c9d GIT binary patch literal 211 zcmV;^04)C>iwFST1{Yrd|LxY%3c?^124Ej$Pp~|5>a~#1|%>U`Q)t8$t z`!ViGUzGpK=buwaF#l^{xeGv9h55f~wL9ywHIesNcK^Hg9|P{QkNHop%3T0L2qA=4 N_XLI*()j=s002w_X;%OM literal 0 HcmV?d00001 diff --git a/update/test/AdGuardHome.zip b/update/test/AdGuardHome.zip new file mode 100644 index 0000000000000000000000000000000000000000..c984a34709b7480be26869771f9a4e75407a3d82 GIT binary patch literal 565 zcmWIWW@Zs#;9y{2*wpzo07$_B50K@U;$E6ql;V+}o2nn+%??rq2LVu>91J{@P<2WJ z)gbFcV(X<=q#Cm@fXv$d{!TM9P$eS}Bg_&&GfN!JEFVv2SHECay^@L&V>Ylkv->XP zVw%H`W{xPDIYF+DF21gMxhcj>U{iSaU6n$Z!pJ1T3 backup/README.md + err = copySupportingFiles(u.unpackedFiles, u.workDir, u.backupDir) + if err != nil { + return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", + u.workDir, u.backupDir, err) + } + + return nil +} + +func (u *Updater) replace() error { + // update/README.md -> workdir/README.md + err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir) + if err != nil { + return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", + u.updateDir, u.workDir, err) + } + + log.Debug("updater: renaming: %s -> %s", u.currentExeName, u.backupExeName) + err = os.Rename(u.currentExeName, u.backupExeName) + if err != nil { + return err + } + + if u.OS == "windows" { + // rename fails with "File in use" error + err = copyFile(u.updateExeName, u.currentExeName) + } else { + err = os.Rename(u.updateExeName, u.currentExeName) + } + if err != nil { + return err + } + log.Debug("updater: renamed: %s -> %s", u.updateExeName, u.currentExeName) + return nil +} + +func (u *Updater) clean() { + _ = os.RemoveAll(u.updateDir) +} + +// Download package file and save it to disk +func (u *Updater) downloadPackageFile(url string, filename string) error { + resp, err := u.Client.Get(url) + if err != nil { + return fmt.Errorf("HTTP request failed: %s", err) + } + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + + log.Debug("updater: reading HTTP body") + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("ioutil.ReadAll() failed: %s", err) + } + + _ = os.Mkdir(u.updateDir, 0755) + + log.Debug("updater: saving package to file") + err = ioutil.WriteFile(filename, body, 0644) + if err != nil { + return fmt.Errorf("ioutil.WriteFile() failed: %s", err) + } + return nil +} + +// Unpack all files from .tar.gz file to the specified directory +// Existing files are overwritten +// All files are created inside 'outdir', subdirectories are not created +// Return the list of files (not directories) written +func tarGzFileUnpack(tarfile, outdir string) ([]string, error) { + f, err := os.Open(tarfile) + if err != nil { + return nil, fmt.Errorf("os.Open(): %s", err) + } + defer func() { + _ = f.Close() + }() + + gzReader, err := gzip.NewReader(f) + if err != nil { + return nil, fmt.Errorf("gzip.NewReader(): %s", err) + } + + var files []string + var err2 error + tarReader := tar.NewReader(gzReader) + for { + header, err := tarReader.Next() + if err == io.EOF { + err2 = nil + break + } + if err != nil { + err2 = fmt.Errorf("tarReader.Next(): %s", err) + break + } + + _, inputNameOnly := filepath.Split(header.Name) + if len(inputNameOnly) == 0 { + continue + } + + outputName := filepath.Join(outdir, inputNameOnly) + + if header.Typeflag == tar.TypeDir { + err = os.Mkdir(outputName, os.FileMode(header.Mode&0777)) + if err != nil && !os.IsExist(err) { + err2 = fmt.Errorf("os.Mkdir(%s): %s", outputName, err) + break + } + log.Debug("updater: created directory %s", outputName) + continue + } else if header.Typeflag != tar.TypeReg { + log.Debug("updater: %s: unknown file type %d, skipping", inputNameOnly, header.Typeflag) + continue + } + + f, err := os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode&0777)) + if err != nil { + err2 = fmt.Errorf("os.OpenFile(%s): %s", outputName, err) + break + } + _, err = io.Copy(f, tarReader) + if err != nil { + _ = f.Close() + err2 = fmt.Errorf("io.Copy(): %s", err) + break + } + err = f.Close() + if err != nil { + err2 = fmt.Errorf("f.Close(): %s", err) + break + } + + log.Debug("updater: created file %s", outputName) + files = append(files, header.Name) + } + + _ = gzReader.Close() + return files, err2 +} + +// Unpack all files from .zip file to the specified directory +// Existing files are overwritten +// All files are created inside 'outdir', subdirectories are not created +// Return the list of files (not directories) written +func zipFileUnpack(zipfile, outdir string) ([]string, error) { + r, err := zip.OpenReader(zipfile) + if err != nil { + return nil, fmt.Errorf("zip.OpenReader(): %s", err) + } + defer r.Close() + + var files []string + var err2 error + var zr io.ReadCloser + for _, zf := range r.File { + zr, err = zf.Open() + if err != nil { + err2 = fmt.Errorf("zip file Open(): %s", err) + break + } + + fi := zf.FileInfo() + inputNameOnly := fi.Name() + if len(inputNameOnly) == 0 { + continue + } + + outputName := filepath.Join(outdir, inputNameOnly) + + if fi.IsDir() { + err = os.Mkdir(outputName, fi.Mode()) + if err != nil && !os.IsExist(err) { + err2 = fmt.Errorf("os.Mkdir(): %s", err) + break + } + log.Tracef("created directory %s", outputName) + continue + } + + f, err := os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) + if err != nil { + err2 = fmt.Errorf("os.OpenFile(): %s", err) + break + } + _, err = io.Copy(f, zr) + if err != nil { + _ = f.Close() + err2 = fmt.Errorf("io.Copy(): %s", err) + break + } + err = f.Close() + if err != nil { + err2 = fmt.Errorf("f.Close(): %s", err) + break + } + + log.Tracef("created file %s", outputName) + files = append(files, inputNameOnly) + } + + _ = zr.Close() + return files, err2 +} + +// Copy file on disk +func copyFile(src, dst string) error { + d, e := ioutil.ReadFile(src) + if e != nil { + return e + } + e = ioutil.WriteFile(dst, d, 0644) + if e != nil { + return e + } + return nil +} + +func copySupportingFiles(files []string, srcdir, dstdir string) error { + for _, f := range files { + _, name := filepath.Split(f) + if name == "AdGuardHome" || name == "AdGuardHome.exe" || name == "AdGuardHome.yaml" { + continue + } + + src := filepath.Join(srcdir, name) + dst := filepath.Join(dstdir, name) + + err := copyFile(src, dst) + if err != nil && !os.IsNotExist(err) { + return err + } + + log.Debug("updater: copied: %s -> %s", src, dst) + } return nil }