github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/spinner/windows_spinner.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package spinner 21 22 import ( 23 "fmt" 24 "io" 25 "os" 26 "os/signal" 27 "strings" 28 "sync" 29 "syscall" 30 31 "time" 32 33 "github.com/1aal/kubeblocks/pkg/cli/printer" 34 ) 35 36 var char = []string{"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"} 37 38 type WindowsSpinner struct { // no thread/goroutine safe 39 msg string 40 lastOutput string 41 FinalMSG string 42 active bool 43 chars []string 44 cancel chan struct{} 45 Writer io.Writer 46 delay time.Duration 47 mu *sync.RWMutex 48 } 49 50 func NewWindowsSpinner(w io.Writer, opts ...Option) *WindowsSpinner { 51 res := &WindowsSpinner{ 52 chars: char, 53 active: false, 54 cancel: make(chan struct{}, 1), 55 Writer: w, 56 mu: &sync.RWMutex{}, 57 delay: 100 * time.Millisecond, 58 } 59 for _, opt := range opts { 60 opt(res) 61 } 62 63 c := make(chan os.Signal, 1) 64 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 65 go func() { 66 <-c 67 res.Done("") 68 os.Exit(0) 69 }() 70 res.Start() 71 return res 72 } 73 74 func (s *WindowsSpinner) updateSpinnerMessage(msg string) { 75 s.msg = fmt.Sprintf(" %s", msg) 76 } 77 78 func (s *WindowsSpinner) Done(status string) { 79 if status != "" { 80 s.FinalMSG = fmt.Sprintf("%s %s\n", strings.TrimPrefix(s.msg, " "), status) 81 } 82 s.stop() 83 } 84 85 func (s *WindowsSpinner) Success() { 86 if len(s.msg) == 0 { 87 return 88 } 89 s.Done(printer.BoldGreen("OK")) 90 91 } 92 93 func (s *WindowsSpinner) Fail() { 94 if len(s.msg) == 0 { 95 return 96 } 97 s.Done(printer.BoldRed("FAIL")) 98 } 99 100 func (s *WindowsSpinner) Start() { 101 s.active = true 102 103 go func() { 104 for { 105 for i := 0; i < len(s.chars); i++ { 106 select { 107 case <-s.cancel: 108 return 109 default: 110 s.mu.Lock() 111 if !s.active { 112 defer s.mu.Unlock() 113 return 114 } 115 outPlain := fmt.Sprintf("\r%s%s", s.chars[i], s.msg) 116 s.erase() 117 s.lastOutput = outPlain 118 fmt.Fprint(s.Writer, outPlain) 119 s.mu.Unlock() 120 time.Sleep(s.delay) 121 } 122 } 123 } 124 }() 125 } 126 127 func (s *WindowsSpinner) SetMessage(msg string) { 128 s.mu.Lock() 129 defer s.mu.Unlock() 130 s.msg = msg 131 } 132 133 func (s *WindowsSpinner) SetFinalMsg(msg string) { 134 s.FinalMSG = msg 135 } 136 137 // remove lastOutput 138 func (s *WindowsSpinner) erase() { 139 split := strings.Split(s.lastOutput, "\n") 140 for i := 0; i < len(split); i++ { 141 if i > 0 { 142 fmt.Fprint(s.Writer, "\033[A") 143 } 144 fmt.Fprint(s.Writer, "\r\033[K") 145 } 146 } 147 148 // stop stops the indicator. 149 func (s *WindowsSpinner) stop() { 150 s.mu.Lock() 151 defer s.mu.Unlock() 152 if s.active { 153 s.active = false 154 if s.FinalMSG != "" { 155 s.erase() 156 fmt.Fprint(s.Writer, s.FinalMSG) 157 } 158 s.cancel <- struct{}{} 159 close(s.cancel) 160 } 161 }