sanbornm / go-selfupdate

Enable your Go applications to self update
MIT License
1.53k stars 176 forks source link

Move the json writer from the top to bottom of createUpdate #4

Closed thomasf closed 7 years ago

thomasf commented 9 years ago

Otherwise a client will will try to fetch files that are not yet written if the output path is directly served by the an http server.

thomasf commented 9 years ago

My version has deviated a bit now for the sake of simplicity so it's not directly transferable anymore:..

package main

import (
    "bytes"
    "compress/gzip"
    "crypto/sha256"
    "encoding/json"
    "flag"
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "path/filepath"

    "github.com/kr/binarydist"
)

var genDir string

type Version struct {
    Version string
    Path string
    Sha256  []byte
}

func generateSha256(path string) []byte {
    h := sha256.New()
    b, err := ioutil.ReadFile(path)
    if err != nil {
        fmt.Println(err)
    }
    h.Write(b)
    sum := h.Sum(nil)
    return sum
}

type gzReader struct {
    z, r io.ReadCloser
}

func (g *gzReader) Read(p []byte) (int, error) {
    return g.z.Read(p)
}

func (g *gzReader) Close() error {
    g.z.Close()
    return g.r.Close()
}

func newGzReader(r io.ReadCloser) io.ReadCloser {
    var err error
    g := new(gzReader)
    g.r = r
    g.z, err = gzip.NewReader(r)
    if err != nil {
        panic(err)
    }
    return g
}

func (v *Version) createUpdate(platform string) error {
    path := v.Path
    v.Sha256 = generateSha256(path)
    version := v.Version
    os.MkdirAll(filepath.Join(genDir, version), 0755)

    var buf bytes.Buffer
    w := gzip.NewWriter(&buf)
    f, err := ioutil.ReadFile(path)
    if err != nil {
        return err
    }
    w.Write(f)
    w.Close() // You must close this first to flush the bytes to the buffer.
    err = ioutil.WriteFile(filepath.Join(genDir, version, platform+".gz"), buf.Bytes(), 0755)

    files, err := ioutil.ReadDir(genDir)
    if err != nil {
        return err
    }

    for _, file := range files {
        if file.IsDir() == false {
            continue
        }
        if file.Name() == version {
            continue
        }

        os.Mkdir(filepath.Join(genDir, file.Name(), version), 0755)

        fName := filepath.Join(genDir, file.Name(), platform+".gz")
        old, err := os.Open(fName)
        if err != nil {
            // Don't have an old release for this os/arch, continue on
            continue
        }

        fName = filepath.Join(genDir, version, platform+".gz")
        newF, err := os.Open(fName)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Can't open %s: error: %s\n", fName, err)
            os.Exit(1)
        }

        ar := newGzReader(old)
        defer ar.Close()
        br := newGzReader(newF)
        defer br.Close()
        patch := new(bytes.Buffer)
        if err := binarydist.Diff(ar, br, patch); err != nil {
            panic(err)
        }
        ioutil.WriteFile(filepath.Join(genDir, file.Name(), version, platform), patch.Bytes(), 0755)
    }

    b, err := json.MarshalIndent(v, "", "    ")
    if err != nil {
        return err
    }
    err = ioutil.WriteFile(filepath.Join(genDir, platform+".json"), b, 0755)
    if err != nil {
        return err
    }

    return nil
}

func main() {
    outputDirFlag := flag.String("output", "public", "Output directory for writing updates")
    platformFlag := flag.String("platform", "", "Target platform in the form os-arch.")
    versionFlag := flag.String("version", "", "Version number to publish")
    binFlag := flag.String("binary", "", "Path to the binary for the published version.")

    flag.Parse()

    platform := *platformFlag
    appPath := *binFlag
    version := *versionFlag
    genDir = *outputDirFlag

    if platform == "" || appPath == "" || version == "" {
        flag.Usage()
        fmt.Println("ERROR: Platform, Binary and Version are required to be set.")
        os.Exit(1)
    }
    os.MkdirAll(genDir, 0755)

    v := Version{
        Version: version,
        Path: appPath,
    }

    err := v.createUpdate(platform)
    if err != nil {
        panic(err)
    }

}