Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Commit

Permalink
Merge f540ce4 into e860668
Browse files Browse the repository at this point in the history
  • Loading branch information
jryio committed May 4, 2020
2 parents e860668 + f540ce4 commit 70402c7
Show file tree
Hide file tree
Showing 6 changed files with 474 additions and 11 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
os:
- linux
Expand Down
5 changes: 2 additions & 3 deletions keybase/platform_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -49,11 +48,11 @@ func appBundleForPath(path string) string {
}

func libraryDir() (string, error) {
usr, err := user.Current()
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(usr.HomeDir, "Library"), nil
return filepath.Join(homeDir, "Library"), nil
}

// Dir returns where to store config
Expand Down
5 changes: 5 additions & 0 deletions protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ type Update struct {
NeedUpdate bool `json:"needUpdate"`
}

func (u Update) missingAsset() bool {
return u.Asset == nil || u.Asset.URL == ""

}

// UpdateOptions are options used to find an update
type UpdateOptions struct {
// Version is the current version of the app
Expand Down
22 changes: 22 additions & 0 deletions service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,34 @@ func run(f flags) {
ulog.Error(err)
os.Exit(1)
}
// Keybase service expects to parse this output as a boolean.
// Do not change unless changing in both locations
// https: //github.com/keybase/client/blob/master/go/client/cmd_update.go
fmt.Println(needUpdate)
case "check":
if err := updateCheckFromFlags(f, ulog); err != nil {
ulog.Error(err)
os.Exit(1)
}
case "download-latest":
ctx, updater := keybase.NewUpdaterContext(f.appName, f.pathToKeybase, ulog, keybase.CheckPassive)
updateAvailable, _, err := updater.CheckAndDownload(ctx)
if err != nil {
ulog.Error(err)
os.Exit(1)
}
// Keybase service expects to parse this output as a boolean.
// Do not change unless changing in both locations
// https: //github.com/keybase/client/blob/master/go/client/cmd_update.go
fmt.Println(updateAvailable)
case "apply-downloaded":
ctx, updater := keybase.NewUpdaterContext(f.appName, f.pathToKeybase, ulog, keybase.Check)
applied, err := updater.ApplyDownloaded(ctx)
if err != nil {
ulog.Error(err)
os.Exit(1)
}
fmt.Println(applied)
case "service", "":
svc := serviceFromFlags(f, ulog)
svc.Run()
Expand Down
175 changes: 171 additions & 4 deletions updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path/filepath"
"regexp"
"strconv"
"strings"
"time"

"github.com/keybase/go-updater/util"
Expand Down Expand Up @@ -115,10 +116,7 @@ func (u *Updater) update(ctx Context, options UpdateOptions) (*Update, error) {
}
u.log.Infof("Got update with version: %s", update.Version)

// Linux updates don't have assets so it's ok to prompt for update above before
// we check for nil asset.
if update.Asset == nil || update.Asset.URL == "" {
u.log.Info("No update asset to apply")
if update.missingAsset() {
return update, nil
}

Expand Down Expand Up @@ -184,6 +182,72 @@ func (u *Updater) update(ctx Context, options UpdateOptions) (*Update, error) {
return update, nil
}

func (u *Updater) ApplyDownloaded(ctx Context) (bool, error) {
options := ctx.UpdateOptions()

// 1. check with the api server again for the latest update to be sure that a
// new update has not come out since our last call to CheckAndDownload
u.log.Infof("Attempting to apply previously downloaded update")
update, err := u.checkForUpdate(ctx, options)
if err != nil {
return false, findErr(err)
}

// Only report apply success/failure
applied, err := u.applyDownloaded(ctx, update, options)
defer report(ctx, err, update, options)
if err != nil {
return false, err
}
return applied, nil

}

// ApplyDownloaded will look for an previously downloaded update and attempt to apply it without prompting.
// CheckAndDownload must be called first so that we have a download asset available to apply.
func (u *Updater) applyDownloaded(ctx Context, update *Update, options UpdateOptions) (applied bool, err error) {
if update == nil || !update.NeedUpdate {
return false, fmt.Errorf("No previously downloaded update to apply since client is update to date")
}
u.log.Infof("Got update with version: %s", update.Version)

if update.missingAsset() {
return false, fmt.Errorf("Update contained no asset to apply. Update version: %s", update.Version)
}

// 2. check the disk via FindDownloadedAsset. Compare our API result to this
// result. If the downloaded update is stale, clear it and start over.
downloadedAssetPath, err := u.FindDownloadedAsset(update.Asset.Name)
if err != nil {
return false, err
}
defer func() {
if err := u.CleanupPreviousUpdates(); err != nil {
u.log.Infof("Error cleaning up previous downloads: %v", err)
}
}()
if downloadedAssetPath == "" {
return false, fmt.Errorf("No downloaded asset found for version: %s", update.Version)
}
update.Asset.LocalPath = downloadedAssetPath

// 3. otherwise use the update on disk and apply it.
if err = util.CheckDigest(update.Asset.Digest, downloadedAssetPath, u.log); err != nil {
return false, verifyErr(err)
}
u.log.Infof("Verify asset: %s", downloadedAssetPath)
if err := ctx.Verify(*update); err != nil {
return false, verifyErr(err)
}

tmpDir := os.TempDir()
if err := u.apply(ctx, *update, options, tmpDir); err != nil {
return false, err
}

return true, nil
}

func (u *Updater) apply(ctx Context, update Update, options UpdateOptions, tmpDir string) error {
u.log.Info("Before apply")
if err := ctx.BeforeApply(update); err != nil {
Expand Down Expand Up @@ -263,6 +327,59 @@ func (u *Updater) NeedUpdate(ctx Context) (upToDate bool, err error) {
return update.NeedUpdate, nil
}

func (u *Updater) CheckAndDownload(ctx Context) (updateAvailable, updateWasDownloaded bool, err error) {
options := ctx.UpdateOptions()
update, err := u.checkForUpdate(ctx, options)
if err != nil {
return false, false, err
}

if !update.NeedUpdate || update.missingAsset() {
return false, false, nil
}

var tmpDir string
defer func() {
// If anything in this process errors cleanup the downloaded asset
if err != nil {
if err := u.CleanupPreviousUpdates(); err != nil {
u.log.Infof("Error cleaning up previous downloads: %v", err)
}
}
if tmpDir != "" {
u.Cleanup(tmpDir)
}
}()
var digestChecked bool
downloadedAssetPath, err := u.FindDownloadedAsset(update.Asset.Name)
if downloadedAssetPath == "" || err != nil {
u.log.Infof("Could not find existing download asset for version: %s. Downloading new asset.", update.Version)
tmpDir = u.tempDir()
// This will set update.Asset.LocalPath
if err := u.downloadAsset(update.Asset, tmpDir, options); err != nil {
return false, false, downloadErr(err)
}
updateWasDownloaded = true
digestChecked = true
downloadedAssetPath = update.Asset.LocalPath
}
// Verify depends on LocalPath being set to the downloaded asset
update.Asset.LocalPath = downloadedAssetPath

u.log.Infof("Verify asset: %s", downloadedAssetPath)
if err := ctx.Verify(*update); err != nil {
return false, false, verifyErr(err)
}

if !digestChecked {
if err = util.CheckDigest(update.Asset.Digest, downloadedAssetPath, u.log); err != nil {
return false, false, verifyErr(err)
}
}

return true, updateWasDownloaded, nil
}

// promptForUpdateAction prompts the user for permission to apply an update
func (u *Updater) promptForUpdateAction(ctx Context, update Update, options UpdateOptions) (UpdatePromptResponse, error) {
u.log.Debug("Prompt for update")
Expand Down Expand Up @@ -366,6 +483,7 @@ func (u *Updater) tempDir() string {
}

var tempDirRE = regexp.MustCompile(`^KeybaseUpdater.([ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]{52}|\d{18,})$`)
var keybaseAssetRE = regexp.MustCompile(`^Keybase(-|_)(.*)$`)

// CleanupPreviousUpdates removes temporary files from previous updates.
func (u *Updater) CleanupPreviousUpdates() (err error) {
Expand Down Expand Up @@ -402,3 +520,52 @@ func (u *Updater) Cleanup(tmpDir string) {
}
}
}

// Inspect previously downloaded updates to avoid redownloading
func (u *Updater) FindDownloadedAsset(assetName string) (matchingAssetPath string, err error) {
if assetName == "" {
return "", fmt.Errorf("No asset name provided")
}
parent := os.TempDir()
if parent == "" || parent == "." {
return matchingAssetPath, fmt.Errorf("temp directory is %v", parent)
}

files, err := ioutil.ReadDir(parent)
if err != nil {
return matchingAssetPath, fmt.Errorf("listing parent directory: %v", err)
}

for _, fi := range files {
if !fi.IsDir() || !tempDirRE.MatchString(fi.Name()) {
continue
}

keybaseTempDirAbs := filepath.Join(parent, fi.Name())
walkErr := filepath.Walk(keybaseTempDirAbs, func(fullPath string, info os.FileInfo, inErr error) (err error) {
if inErr != nil {
return inErr
}

if info.IsDir() {
if fullPath == keybaseTempDirAbs {
return nil
}
return filepath.SkipDir
}

path := strings.TrimPrefix(fullPath, keybaseTempDirAbs+string(filepath.Separator))
if path == assetName {
matchingAssetPath = fullPath
return filepath.SkipDir
}

return nil
})

if walkErr != nil {
return "", walkErr
}
}
return matchingAssetPath, nil
}
Loading

0 comments on commit 70402c7

Please sign in to comment.