2 Commits

15 changed files with 229 additions and 210 deletions

View File

@@ -1,28 +1,12 @@
version: "2"
run: run:
tests: false go: "1.24"
skip-dirs:
- data
- vendor
modules-download-mode: readonly modules-download-mode: readonly
tests: false
allow-parallel-runners: true allow-parallel-runners: true
go: '1.21'
output:
sort-results: true
linters-settings:
gosec:
excludes:
# not relevant here
- G404 # Insecure random number source (rand)
- G306 # Expect WriteFile permissions to be 0600 or less (gosec)
linters: linters:
disable-all: true default: none
enable: enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- asasalint - asasalint
- asciicheck - asciicheck
- bodyclose - bodyclose
@@ -30,20 +14,20 @@ linters:
- contextcheck - contextcheck
- decorder - decorder
- durationcheck - durationcheck
- err113
- errcheck
- errchkjson - errchkjson
- errorlint - errorlint
- exportloopref
- ginkgolinter - ginkgolinter
- gocheckcompilerdirectives - gocheckcompilerdirectives
- gocritic - gocritic
- godot - godot
- goerr113
- gofmt
- goimports
- gosec - gosec
- gosmopolitan - gosmopolitan
- govet
- grouper - grouper
- importas - importas
- ineffassign
- ireturn - ireturn
- makezero - makezero
- mirror - mirror
@@ -59,8 +43,39 @@ linters:
- reassign - reassign
- revive - revive
- rowserrcheck - rowserrcheck
- stylecheck - staticcheck
- unconvert - unconvert
- unused
- wastedassign - wastedassign
- whitespace - whitespace
- wsl - wsl
settings:
gosec:
excludes:
- G404
- G306
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- data
- vendor
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- data
- vendor
- third_party$
- builtin$
- examples$

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env gmake -f #!/usr/bin/env gmake -f
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false
BINARY=i3status-go BINARY=i3status-go
TEST1=battery-test TEST1=battery-test
@@ -13,6 +13,7 @@ build:
go build ${BUILDOPTS} -o ${BINARY} ./cmd/${BINARY} go build ${BUILDOPTS} -o ${BINARY} ./cmd/${BINARY}
battery-test: battery-test:
rm -rf ${TEST1}
go build ${BUILDOPTS} -o ${TEST1} ./cmd/${TEST1} go build ${BUILDOPTS} -o ${TEST1} ./cmd/${TEST1}
## Remove binary with golang compiler' means ## Remove binary with golang compiler' means

View File

@@ -11,13 +11,12 @@ import (
// Program entry point. // Program entry point.
func main() { func main() {
for { for {
batteries, err := battery.GetAll() batteries, _ := battery.GetAll()
if err != nil { for index, battery := range batteries {
fmt.Printf("Unable to get battery info: %w", err) fmt.Printf("Battery %d: %s\n", index, spew.Sdump(battery))
} }
spew.Dump(batteries)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }

2
go.mod
View File

@@ -1,6 +1,6 @@
module i3status-go module i3status-go
go 1.22.6 go 1.24.2
require ( require (
github.com/adrg/xdg v0.5.0 github.com/adrg/xdg v0.5.0

View File

@@ -11,162 +11,166 @@ import (
// UpdateBatteryInfo updates info about battery charge. // UpdateBatteryInfo updates info about battery charge.
func (c *MyConfig) UpdateBatteryInfo() { func (c *MyConfig) UpdateBatteryInfo() {
var ( var (
ch int ch int
status string status string
Batts string Batts string
InitialDelay = 100 * time.Millisecond
LoopIterationDelay = 5 * time.Second
) )
ticker := time.NewTicker(time.Second * 5) Delay := InitialDelay
ticker := time.NewTicker(Delay)
for range ticker.C { for range ticker.C {
batteries, err := battery.GetAll() if Delay == InitialDelay {
Delay = LoopIterationDelay
ticker.Reset(Delay)
}
if err != nil { batteries, _ := battery.GetAll()
Batts = fmt.Sprintf( // In theory, this module should give for each entry its separate err, but in practice it gives
"<span font='%s' size='%s'>%s</span><span font='%s' size='%s'> no batt</span>", // one single err for all entries, so we cannot detemine whist exatly entry errored.
var (
battsInfo string
chStr string
)
for i, b := range batteries {
switch b.State.Raw {
case battery.Charging:
status = `▲`
case battery.Discharging:
status = `▼`
case battery.Empty:
status = `✘`
default:
status = `•`
}
// N.B. there can be case when battery is overcharged and shows >100%. It also can indicate that
// calibration data is out of date and battery should be re-calibrated.
ch = int(math.Round((b.Full - (b.Full - b.Current)) * (100 / b.Full)))
switch {
case ch <= 500 && ch >= 84:
if c.Battery.ChargeColor.Full == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.Full,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
case ch < 85 && ch > 40:
if c.Battery.ChargeColor.AlmostFull == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.AlmostFull,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
case ch <= 40 && ch >= 10:
if c.Battery.ChargeColor.AlmostEmpty == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.AlmostEmpty,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
case ch < 10 && ch >= 0:
if c.Battery.ChargeColor.Empty == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.Empty,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
default:
continue
}
battsInfo += fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span><span color='%s' background='%s' font='%s' size='%s'>B%d </span>%s<span color='%s' background='%s' font='%s' size='%s'> %s</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.SymbolFont, c.Battery.SymbolFont,
c.Battery.SymbolFontSize, c.Battery.SymbolFontSize,
c.Battery.Symbol, c.Battery.Symbol,
c.Battery.Color,
c.Battery.Background,
c.Battery.Font, c.Battery.Font,
c.Battery.FontSize, c.Battery.FontSize,
i,
chStr,
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
status,
) )
} else { }
var (
battsInfo string
chStr string
)
for i, b := range batteries { if battsInfo != "" {
switch b.State.Raw { Batts = battsInfo
case battery.Charging:
status = `▲`
case battery.Discharging:
status = `▼`
case battery.Empty:
status = `✘`
default:
status = `•`
}
// N.B. there can be case when battery is overcharged and shows >100%. It also can indicate that
// calibration data is out of date and battery should be re-calibrated.
ch = int(math.Round((b.Full - (b.Full - b.Current)) * (100 / b.Full)))
switch {
case ch > 85:
if c.Battery.ChargeColor.Full == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.Full,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
case ch < 85 && ch > 40:
if c.Battery.ChargeColor.AlmostFull == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.AlmostFull,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
case ch <= 40 && ch >= 10:
if c.Battery.ChargeColor.AlmostEmpty == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.AlmostEmpty,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
case ch < 10:
if c.Battery.ChargeColor.Empty == "" {
chStr = fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
} else {
chStr = fmt.Sprintf(
`<span color='%s' background='%s' font='%s' size='%s'>% 3d%%</span>`,
c.Battery.ChargeColor.Empty,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
ch,
)
}
}
battsInfo += fmt.Sprintf(
"<span color='%s' background='%s' font='%s' size='%s'>%s</span><span color='%s' background='%s' font='%s' size='%s'>B%d </span>%s<span color='%s' background='%s' font='%s' size='%s'> %s</span>",
c.Battery.Color,
c.Battery.Background,
c.Battery.SymbolFont,
c.Battery.SymbolFontSize,
c.Battery.Symbol,
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
i,
chStr,
c.Battery.Color,
c.Battery.Background,
c.Battery.Font,
c.Battery.FontSize,
status,
)
}
if battsInfo != "" {
Batts = battsInfo
}
} }
if c.Values.BatteryString != Batts { if c.Values.BatteryString != Batts {
c.Values.BatteryString = Batts c.Values.BatteryString = Batts
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
time.Sleep(5 * time.Second)
} }
} }

View File

@@ -7,9 +7,7 @@ import (
// UpdateClock get and updates (on i3bar) info about system clock. // UpdateClock get and updates (on i3bar) info about system clock.
func (c *MyConfig) UpdateClock() { func (c *MyConfig) UpdateClock() {
ticker := time.NewTicker(time.Second * 1) for {
for range ticker.C {
currentTime := time.Now() currentTime := time.Now()
hours, minutes, _ := currentTime.Clock() hours, minutes, _ := currentTime.Clock()
year, month, day := currentTime.Date() year, month, day := currentTime.Date()
@@ -23,5 +21,7 @@ func (c *MyConfig) UpdateClock() {
c.Values.ClockTime = myclock c.Values.ClockTime = myclock
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
time.Sleep(1 * time.Second)
} }
} }

View File

@@ -12,7 +12,7 @@ type Collection struct {
// Item just interface for stoing any data. // Item just interface for stoing any data.
type item struct { type item struct {
data any data interface{}
} }
// NewCollection creates new instance of "collection". // NewCollection creates new instance of "collection".
@@ -25,7 +25,7 @@ func NewCollection() *Collection {
} }
// Get returns value by given key. Or empty value. // Get returns value by given key. Or empty value.
func (collection *Collection) Get(key any) (any, bool) { func (collection *Collection) Get(key interface{}) (interface{}, bool) {
obj, exists := collection.items.Load(key) obj, exists := collection.items.Load(key)
if !exists { if !exists {
@@ -38,15 +38,15 @@ func (collection *Collection) Get(key any) (any, bool) {
} }
// Set saves value to given "collection". // Set saves value to given "collection".
func (collection *Collection) Set(key any, value any) { func (collection *Collection) Set(key interface{}, value interface{}) {
collection.items.Store(key, item{ collection.items.Store(key, item{
data: value, data: value,
}) })
} }
// Range apply function f to all keys/values in collection. Retuns immediatly on first error. // Range apply function f to all keys/values in collection. Retuns immediatly on first error.
func (collection *Collection) Range(f func(key, value any) bool) { func (collection *Collection) Range(f func(key, value interface{}) bool) {
fn := func(key, value any) bool { fn := func(key, value interface{}) bool {
item := value.(item) item := value.(item)
return f(key, item.data) return f(key, item.data)
@@ -56,7 +56,7 @@ func (collection *Collection) Range(f func(key, value any) bool) {
} }
// Delete deletes key and corresponding value from collection. // Delete deletes key and corresponding value from collection.
func (collection *Collection) Delete(key any) { func (collection *Collection) Delete(key interface{}) {
collection.items.Delete(key) collection.items.Delete(key)
} }

View File

@@ -252,7 +252,7 @@ type MyConfig struct {
// readConf reads and validates config if config does not exist, it puts default config to the same dir where i3 config // readConf reads and validates config if config does not exist, it puts default config to the same dir where i3 config
// is located. // is located.
func ReadConf(DefaultConfig []byte) (*MyConfig, error) { func ReadConf(defaultConfig []byte) (*MyConfig, error) {
var ( var (
path string path string
config *MyConfig config *MyConfig
@@ -272,7 +272,7 @@ func ReadConf(DefaultConfig []byte) (*MyConfig, error) {
// about it for now. // about it for now.
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
buf = DefaultConfig buf = defaultConfig
if err := os.WriteFile(path, buf, 0644); err != nil { if err := os.WriteFile(path, buf, 0644); err != nil {
return config, err return config, err
@@ -283,7 +283,7 @@ func ReadConf(DefaultConfig []byte) (*MyConfig, error) {
} else { } else {
// Config file looks too long for config... // Config file looks too long for config...
if fileInfo.Size() > 65535 { if fileInfo.Size() > 65535 {
err := fmt.Errorf("config file %s is too long for config", path) //nolint: goerr113 err := fmt.Errorf("config file %s is too long for config", path) //nolint: err113
return config, err return config, err
} }
@@ -1530,13 +1530,11 @@ func ReadConf(DefaultConfig []byte) (*MyConfig, error) {
for num, app := range sampleConfig.Apps { for num, app := range sampleConfig.Apps {
// app.Instance can be missing. // app.Instance can be missing.
// app.Class can be missing. // app.Class can be missing.
if app.Name == "" { if app.Name == "" {
app.Name = fmt.Sprintf("app%d", num) app.Name = fmt.Sprintf("app%d", num)
} }
// app.Args can be empty slice. In that case command will be run without aruments. // app.Args can be empty slice. In that case command will be run without aruments.
if app.Cmd == "" { if app.Cmd == "" {
app.Cmd = "true" app.Cmd = "true"
} }
@@ -1557,12 +1555,11 @@ func ReadConf(DefaultConfig []byte) (*MyConfig, error) {
app.BorderActive = sampleConfig.Color app.BorderActive = sampleConfig.Color
} }
// app.Separator can be omitted, in that case it is false
// app.SeparatorBlockWidth can be missing
if app.FullText == "" { if app.FullText == "" {
app.FullText = fmt.Sprintf(" %d ", num) app.FullText = fmt.Sprintf(" %d ", num)
} }
// app.Separator can be omitted, in that case it is false
// app.SeparatorBlockWidth can be missing
} }
} }

View File

@@ -12,9 +12,7 @@ import (
func (c *MyConfig) UpdateCPUTemperature() { func (c *MyConfig) UpdateCPUTemperature() {
n := len(c.CPUTemp.File) n := len(c.CPUTemp.File)
ticker := time.NewTicker(time.Second * 3) for {
for range ticker.C {
var ( var (
temperature = make([]int64, n) temperature = make([]int64, n)
tSum int64 tSum int64
@@ -73,5 +71,7 @@ func (c *MyConfig) UpdateCPUTemperature() {
c.Values.CPUTemperature = tAvg c.Values.CPUTemperature = tAvg
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
time.Sleep(3 * time.Second)
} }
} }

View File

@@ -105,9 +105,9 @@ func (c *MyConfig) I3EventParser(e *i3.WindowEvent) {
// interfaceToUint превращает данный интерфейс в Uint64. // interfaceToUint превращает данный интерфейс в Uint64.
// Если может, конечно :) . // Если может, конечно :) .
func interfaceToUint64(iface any) uint64 { func interfaceToUint64(iface interface{}) uint64 {
// А теперь мы начинаем дурдом, нам надо превратить ёбанный any в []string // А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string
// Поскольку any может быть чем угодно, перестрахуемся // Поскольку interface{} может быть чем угодно, перестрахуемся
if reflect.TypeOf(iface).Kind() == reflect.Uint64 { if reflect.TypeOf(iface).Kind() == reflect.Uint64 {
return reflect.ValueOf(iface).Uint() return reflect.ValueOf(iface).Uint()
} }
@@ -152,10 +152,10 @@ func HasWindows(className string, instanceName string) bool {
func FindWindowInstance(regexpStr string) bool { func FindWindowInstance(regexpStr string) bool {
var found = false var found = false
f := func(key any, value any) bool { f := func(key any, value any) bool { //nolint: revive
keyStr := reflect.ValueOf(key).String() keyStr := reflect.ValueOf(key).String()
re, err := regexp.Compile(regexpStr) //nolint:nolintlint, gocritic, we do not want shit our pants and panic on re, err := regexp.Compile(regexpStr) // we do not want shit our pants and panic on
// erroneous regexp, we want log it to stderr, that it. // erroneous regexp, we want log it to stderr, that it.
if err != nil { if err != nil {
@@ -183,10 +183,10 @@ func FindWindowInstance(regexpStr string) bool {
func FindWindowClass(regexpStr string) bool { func FindWindowClass(regexpStr string) bool {
var found = false var found = false
f := func(key interface{}, value interface{}) bool { f := func(key any, value any) bool { //nolint: revive
keyStr := reflect.ValueOf(key).String() keyStr := reflect.ValueOf(key).String()
re, err := regexp.Compile(regexpStr) //nolint:nolintlint, gocritic, we do not want shit our pants and panic on re, err := regexp.Compile(regexpStr) // we do not want shit our pants and panic on
// erroneous regexp, we want log it to stderr, that it. // erroneous regexp, we want log it to stderr, that it.
if err != nil { if err != nil {

View File

@@ -11,9 +11,7 @@ import (
// UpdateIfStatus updates network interfaces status for i3bar. // UpdateIfStatus updates network interfaces status for i3bar.
func (c *MyConfig) UpdateIfStatus() { func (c *MyConfig) UpdateIfStatus() {
ticker := time.NewTicker(time.Second * 3) for {
for range ticker.C {
var statusSum string var statusSum string
for _, item := range c.NetIf.If { for _, item := range c.NetIf.If {
@@ -55,5 +53,7 @@ func (c *MyConfig) UpdateIfStatus() {
c.Values.IfStatus = statusSum c.Values.IfStatus = statusSum
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
time.Sleep(3 * time.Second)
} }
} }

View File

@@ -9,9 +9,7 @@ import (
// UpdateLaStats вытаскивает показание LA за последнюю минуту. // UpdateLaStats вытаскивает показание LA за последнюю минуту.
func (c *MyConfig) UpdateLaStats() { func (c *MyConfig) UpdateLaStats() {
ticker := time.NewTicker(time.Second * 3) for {
for range ticker.C {
l, _ := load.Avg() l, _ := load.Avg()
lav := fmt.Sprintf("%.2f", l.Load1) lav := fmt.Sprintf("%.2f", l.Load1)
@@ -20,5 +18,7 @@ func (c *MyConfig) UpdateLaStats() {
c.Values.La = lav c.Values.La = lav
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
time.Sleep(3 * time.Second)
} }
} }

View File

@@ -16,13 +16,12 @@ type Mem struct {
// UpdateMemStats parses mem info stats. // UpdateMemStats parses mem info stats.
func (c *MyConfig) UpdateMemStats() { func (c *MyConfig) UpdateMemStats() {
ticker := time.NewTicker(time.Second * 3) for {
for range ticker.C {
v, err := mem.VirtualMemory() v, err := mem.VirtualMemory()
if err != nil { if err != nil {
log.Printf("Unable to get memory statistics: %s", err) log.Printf("Unable to get memory statistics: %s", err)
time.Sleep(1 * time.Second)
continue continue
} }
@@ -31,6 +30,7 @@ func (c *MyConfig) UpdateMemStats() {
if err != nil { if err != nil {
log.Printf("Unable to get swap statistics: %s", err) log.Printf("Unable to get swap statistics: %s", err)
time.Sleep(1 * time.Second)
continue continue
} }
@@ -49,5 +49,7 @@ func (c *MyConfig) UpdateMemStats() {
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
} }
time.Sleep(3 * time.Second)
} }
} }

View File

@@ -135,7 +135,7 @@ func (c *MyConfig) PaReinit() error {
} }
if cnt >= 5 { if cnt >= 5 {
return errors.New("timeout waiting pulseaudio to exit") //nolint: goerr113 return errors.New("timeout waiting pulseaudio to exit") //nolint: err113
} }
log.Print("Waiting for pulseaudio to exit") log.Print("Waiting for pulseaudio to exit")

View File

@@ -15,9 +15,7 @@ func (c *MyConfig) UpdateVPNStatus() {
vpnCheck string vpnCheck string
) )
ticker := time.NewTicker(time.Second * 3) for {
for range ticker.C {
if c.VPNFileCheck() { if c.VPNFileCheck() {
if c.Vpn.UpColor == "" { if c.Vpn.UpColor == "" {
vpnCheck = `⍋` vpnCheck = `⍋`
@@ -56,6 +54,8 @@ func (c *MyConfig) UpdateVPNStatus() {
c.Values.VPNStatus = vpnCStatus c.Values.VPNStatus = vpnCStatus
c.Channels.UpdateReady <- true c.Channels.UpdateReady <- true
} }
time.Sleep(3 * time.Second)
} }
} }
@@ -69,6 +69,7 @@ func (c *MyConfig) VPNTCPCheck() bool {
if err == nil { if err == nil {
_ = conn.Close() _ = conn.Close()
return true return true
} }