github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/wait.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "errors" 24 "fmt" 25 "os" 26 "os/signal" 27 "time" 28 29 "github.com/snapcore/snapd/client" 30 "github.com/snapcore/snapd/i18n" 31 "github.com/snapcore/snapd/progress" 32 ) 33 34 var ( 35 maxGoneTime = 5 * time.Second 36 pollTime = 100 * time.Millisecond 37 ) 38 39 type waitMixin struct { 40 clientMixin 41 NoWait bool `long:"no-wait"` 42 skipAbort bool 43 } 44 45 var waitDescs = mixinDescs{ 46 // TRANSLATORS: This should not start with a lowercase letter. 47 "no-wait": i18n.G("Do not wait for the operation to finish but just print the change id."), 48 } 49 50 var noWait = errors.New("no wait for op") 51 52 func (wmx waitMixin) wait(id string) (*client.Change, error) { 53 if wmx.NoWait { 54 fmt.Fprintf(Stdout, "%s\n", id) 55 return nil, noWait 56 } 57 cli := wmx.client 58 // Intercept sigint 59 c := make(chan os.Signal, 2) 60 signal.Notify(c, os.Interrupt) 61 go func() { 62 sig := <-c 63 // sig is nil if c was closed 64 if sig == nil || wmx.skipAbort { 65 return 66 } 67 _, err := wmx.client.Abort(id) 68 if err != nil { 69 fmt.Fprintf(Stderr, err.Error()+"\n") 70 } 71 }() 72 73 pb := progress.MakeProgressBar() 74 defer func() { 75 pb.Finished() 76 // next two not strictly needed for CLI, but without 77 // them the tests will leak goroutines. 78 signal.Stop(c) 79 close(c) 80 }() 81 82 tMax := time.Time{} 83 84 var lastID string 85 lastLog := map[string]string{} 86 for { 87 var rebootingErr error 88 chg, err := cli.Change(id) 89 if err != nil { 90 // a client.Error means we were able to communicate with 91 // the server (got an answer) 92 if e, ok := err.(*client.Error); ok { 93 return nil, e 94 } 95 96 // an non-client error here means the server most 97 // likely went away 98 // XXX: it actually can be a bunch of other things; fix client to expose it better 99 now := time.Now() 100 if tMax.IsZero() { 101 tMax = now.Add(maxGoneTime) 102 } 103 if now.After(tMax) { 104 return nil, err 105 } 106 pb.Spin(i18n.G("Waiting for server to restart")) 107 time.Sleep(pollTime) 108 continue 109 } 110 if maintErr, ok := cli.Maintenance().(*client.Error); ok && maintErr.Kind == client.ErrorKindSystemRestart { 111 rebootingErr = maintErr 112 } 113 if !tMax.IsZero() { 114 pb.Finished() 115 tMax = time.Time{} 116 } 117 118 for _, t := range chg.Tasks { 119 switch { 120 case t.Status != "Doing": 121 continue 122 case t.Progress.Total == 1: 123 pb.Spin(t.Summary) 124 nowLog := lastLogStr(t.Log) 125 if lastLog[t.ID] != nowLog { 126 pb.Notify(nowLog) 127 lastLog[t.ID] = nowLog 128 } 129 case t.ID == lastID: 130 pb.Set(float64(t.Progress.Done)) 131 default: 132 pb.Start(t.Summary, float64(t.Progress.Total)) 133 lastID = t.ID 134 } 135 break 136 } 137 138 if chg.Ready { 139 if chg.Status == "Done" { 140 return chg, nil 141 } 142 143 if chg.Err != "" { 144 return chg, errors.New(chg.Err) 145 } 146 147 return nil, fmt.Errorf(i18n.G("change finished in status %q with no error message"), chg.Status) 148 } 149 150 if rebootingErr != nil { 151 return nil, rebootingErr 152 } 153 154 // note this very purposely is not a ticker; we want 155 // to sleep 100ms between calls, not call once every 156 // 100ms. 157 time.Sleep(pollTime) 158 } 159 } 160 161 func lastLogStr(logs []string) string { 162 if len(logs) == 0 { 163 return "" 164 } 165 return logs[len(logs)-1] 166 }