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  }