Merge pull request #85 from curusarn/bug_handle_invalid_json

skip invalid history records and fix history file during start up
pull/89/head v2.5.15
Šimon Let 6 years ago committed by GitHub
commit d120929030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      cmd/cli/main.go
  2. 32
      pkg/histfile/histfile.go
  3. 103
      pkg/records/records.go

@ -34,6 +34,8 @@ var commit string
// special constant recognized by RESH wrappers // special constant recognized by RESH wrappers
const exitCodeExecute = 111 const exitCodeExecute = 111
var debug bool
func main() { func main() {
output, exitCode := runReshCli() output, exitCode := runReshCli()
fmt.Print(output) fmt.Print(output)
@ -59,6 +61,7 @@ func runReshCli() (string, int) {
log.Fatal("Error reading config:", err) log.Fatal("Error reading config:", err)
} }
if config.Debug { if config.Debug {
debug = true
log.SetFlags(log.LstdFlags | log.Lmicroseconds) log.SetFlags(log.LstdFlags | log.Lmicroseconds)
} }
@ -225,20 +228,26 @@ func filterTerms(terms []string) []string {
} }
func newQueryFromString(queryInput string, pwd string) query { func newQueryFromString(queryInput string, pwd string) query {
if debug {
log.Println("QUERY input = <" + queryInput + ">") log.Println("QUERY input = <" + queryInput + ">")
}
terms := strings.Fields(queryInput) terms := strings.Fields(queryInput)
var logStr string var logStr string
for _, term := range terms { for _, term := range terms {
logStr += " <" + term + ">" logStr += " <" + term + ">"
} }
if debug {
log.Println("QUERY raw terms =" + logStr) log.Println("QUERY raw terms =" + logStr)
}
terms = filterTerms(terms) terms = filterTerms(terms)
logStr = "" logStr = ""
for _, term := range terms { for _, term := range terms {
logStr += " <" + term + ">" logStr += " <" + term + ">"
} }
if debug {
log.Println("QUERY filtered terms =" + logStr) log.Println("QUERY filtered terms =" + logStr)
log.Println("QUERY pwd =" + pwd) log.Println("QUERY pwd =" + pwd)
}
return query{terms: terms, pwd: pwd} return query{terms: terms, pwd: pwd}
} }
@ -445,9 +454,11 @@ func (m manager) SelectPaste(g *gocui.Gui, v *gocui.View) error {
} }
func (m manager) UpdateData(input string) { func (m manager) UpdateData(input string) {
if debug {
log.Println("EDIT start") log.Println("EDIT start")
log.Println("len(fullRecords) =", len(m.s.fullRecords)) log.Println("len(fullRecords) =", len(m.s.fullRecords))
log.Println("len(data) =", len(m.s.data)) log.Println("len(data) =", len(m.s.data))
}
query := newQueryFromString(input, m.pwd) query := newQueryFromString(input, m.pwd)
var data []item var data []item
itemSet := make(map[string]bool) itemSet := make(map[string]bool)
@ -468,7 +479,9 @@ func (m manager) UpdateData(input string) {
data = append(data, itm) data = append(data, itm)
// log.Println("DATA =", itm.display) // log.Println("DATA =", itm.display)
} }
if debug {
log.Println("len(tmpdata) =", len(data)) log.Println("len(tmpdata) =", len(data))
}
sort.SliceStable(data, func(p, q int) bool { sort.SliceStable(data, func(p, q int) bool {
return data[p].hits > data[q].hits return data[p].hits > data[q].hits
}) })
@ -480,10 +493,12 @@ func (m manager) UpdateData(input string) {
m.s.data = append(m.s.data, itm) m.s.data = append(m.s.data, itm)
} }
m.s.highlightedItem = 0 m.s.highlightedItem = 0
if debug {
log.Println("len(fullRecords) =", len(m.s.fullRecords)) log.Println("len(fullRecords) =", len(m.s.fullRecords))
log.Println("len(data) =", len(m.s.data)) log.Println("len(data) =", len(m.s.data))
log.Println("EDIT end") log.Println("EDIT end")
} }
}
func (m manager) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { func (m manager) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
gocui.DefaultEditor.Edit(v, key, ch, mod) gocui.DefaultEditor.Edit(v, key, ch, mod)
@ -563,8 +578,10 @@ func (m manager) Layout(g *gocui.Gui) error {
// v.SetHighlight(m.s.highlightedItem, true) // v.SetHighlight(m.s.highlightedItem, true)
// } // }
} }
if debug {
log.Println("len(data) =", len(m.s.data)) log.Println("len(data) =", len(m.s.data))
log.Println("highlightedItem =", m.s.highlightedItem) log.Println("highlightedItem =", m.s.highlightedItem)
}
return nil return nil
} }

@ -46,13 +46,11 @@ func New(input chan records.Record, sessionsToDrop chan string,
go hf.loadHistory(bashHistoryPath, zshHistoryPath, maxInitHistSize, minInitHistSizeKB) go hf.loadHistory(bashHistoryPath, zshHistoryPath, maxInitHistSize, minInitHistSizeKB)
go hf.writer(input, signals, shutdownDone) go hf.writer(input, signals, shutdownDone)
go hf.sessionGC(sessionsToDrop) go hf.sessionGC(sessionsToDrop)
go hf.loadFullRecords()
return &hf return &hf
} }
// load records from resh history, reverse, enrich and save // load records from resh history, reverse, enrich and save
func (h *Histfile) loadFullRecords() { func (h *Histfile) loadFullRecords(recs []records.Record) {
recs := records.LoadFromFile(h.historyPath, math.MaxInt32)
for i := len(recs) - 1; i >= 0; i-- { for i := len(recs) - 1; i >= 0; i-- {
rec := recs[i] rec := recs[i]
h.fullRecords.AddRecord(rec) h.fullRecords.AddRecord(rec)
@ -83,10 +81,11 @@ func (h *Histfile) loadHistory(bashHistoryPath, zshHistoryPath string, maxInitHi
maxInitHistSize = math.MaxInt32 maxInitHistSize = math.MaxInt32
} }
log.Println("histfile: Loading resh history from file ...") log.Println("histfile: Loading resh history from file ...")
reshCmdLines := histlist.New() history := records.LoadFromFile(h.historyPath, math.MaxInt32)
go h.loadFullRecords(history)
// NOTE: keeping this weird interface for now because we might use it in the future // NOTE: keeping this weird interface for now because we might use it in the future
// when we only load bash or zsh history // when we only load bash or zsh history
records.LoadCmdLinesFromFile(&reshCmdLines, h.historyPath, maxInitHistSize) reshCmdLines := loadCmdLines(history)
log.Println("histfile: resh history loaded - cmdLine count:", len(reshCmdLines.List)) log.Println("histfile: resh history loaded - cmdLine count:", len(reshCmdLines.List))
if useNativeHistories == false { if useNativeHistories == false {
h.bashCmdLines = reshCmdLines h.bashCmdLines = reshCmdLines
@ -230,3 +229,26 @@ func (h *Histfile) DumpRecords() histcli.Histcli {
// don't forget locks in the future // don't forget locks in the future
return h.fullRecords return h.fullRecords
} }
func loadCmdLines(recs []records.Record) histlist.Histlist {
hl := histlist.New()
// go from bottom and deduplicate
var cmdLines []string
cmdLinesSet := map[string]bool{}
for i := len(recs) - 1; i >= 0; i-- {
cmdLine := recs[i].CmdLine
if cmdLinesSet[cmdLine] {
continue
}
cmdLinesSet[cmdLine] = true
cmdLines = append([]string{cmdLine}, cmdLines...)
// if len(cmdLines) > limit {
// break
// }
}
// add everything to histlist
for _, cmdLine := range cmdLines {
hl.AddCmdLine(cmdLine)
}
return hl
}

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"errors" "errors"
"io"
"log" "log"
"math" "math"
"os" "os"
@ -173,16 +174,16 @@ func Enriched(r Record) EnrichedRecord {
record.Command, record.FirstWord, err = GetCommandAndFirstWord(r.CmdLine) record.Command, record.FirstWord, err = GetCommandAndFirstWord(r.CmdLine)
if err != nil { if err != nil {
record.Errors = append(record.Errors, "GetCommandAndFirstWord error:"+err.Error()) record.Errors = append(record.Errors, "GetCommandAndFirstWord error:"+err.Error())
rec, _ := record.ToString() // rec, _ := record.ToString()
log.Println("Invalid command:", rec) // log.Println("Invalid command:", rec)
record.Invalid = true record.Invalid = true
return record return record
} }
err = r.Validate() err = r.Validate()
if err != nil { if err != nil {
record.Errors = append(record.Errors, "Validate error:"+err.Error()) record.Errors = append(record.Errors, "Validate error:"+err.Error())
rec, _ := record.ToString() // rec, _ := record.ToString()
log.Println("Invalid command:", rec) // log.Println("Invalid command:", rec)
record.Invalid = true record.Invalid = true
} }
return record return record
@ -306,7 +307,7 @@ func Stripped(r EnrichedRecord) EnrichedRecord {
func GetCommandAndFirstWord(cmdLine string) (string, string, error) { func GetCommandAndFirstWord(cmdLine string) (string, string, error) {
args, err := shellwords.Parse(cmdLine) args, err := shellwords.Parse(cmdLine)
if err != nil { if err != nil {
log.Println("shellwords Error:", err, " (cmdLine: <", cmdLine, "> )") // log.Println("shellwords Error:", err, " (cmdLine: <", cmdLine, "> )")
return "", "", err return "", "", err
} }
if len(args) == 0 { if len(args) == 0 {
@ -456,31 +457,10 @@ func (r *EnrichedRecord) DistanceTo(r2 EnrichedRecord, p DistParams) float64 {
return dist return dist
} }
// LoadCmdLinesFromFile loads cmdlines from file
func LoadCmdLinesFromFile(hl *histlist.Histlist, fname string, limit int) {
recs := LoadFromFile(fname, limit*3) // assume that at least 1/3 of commands is unique
// go from bottom and deduplicate
var cmdLines []string
cmdLinesSet := map[string]bool{}
for i := len(recs) - 1; i >= 0; i-- {
cmdLine := recs[i].CmdLine
if cmdLinesSet[cmdLine] {
continue
}
cmdLinesSet[cmdLine] = true
cmdLines = append([]string{cmdLine}, cmdLines...)
if len(cmdLines) > limit {
break
}
}
// add everything to histlist
for _, cmdLine := range cmdLines {
hl.AddCmdLine(cmdLine)
}
}
// LoadFromFile loads records from 'fname' file // LoadFromFile loads records from 'fname' file
func LoadFromFile(fname string, limit int) []Record { func LoadFromFile(fname string, limit int) []Record {
const allowedErrors = 1
var encounteredErrors int
// NOTE: limit does nothing atm // NOTE: limit does nothing atm
var recs []Record var recs []Record
file, err := os.Open(fname) file, err := os.Open(fname)
@ -492,7 +472,10 @@ func LoadFromFile(fname string, limit int) []Record {
defer file.Close() defer file.Close()
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
var i int
var firstErrLine int
for scanner.Scan() { for scanner.Scan() {
i++
record := Record{} record := Record{}
fallbackRecord := FallbackRecord{} fallbackRecord := FallbackRecord{}
line := scanner.Text() line := scanner.Text()
@ -500,16 +483,78 @@ func LoadFromFile(fname string, limit int) []Record {
if err != nil { if err != nil {
err = json.Unmarshal([]byte(line), &fallbackRecord) err = json.Unmarshal([]byte(line), &fallbackRecord)
if err != nil { if err != nil {
if encounteredErrors == 0 {
firstErrLine = i
}
encounteredErrors++
log.Println("Line:", line) log.Println("Line:", line)
log.Fatal("Decoding error:", err) log.Println("Decoding error:", err)
if encounteredErrors > allowedErrors {
log.Fatalf("Fatal: Encountered more than %d decoding errors (%d)", allowedErrors, encounteredErrors)
}
} }
record = Convert(&fallbackRecord) record = Convert(&fallbackRecord)
} }
recs = append(recs, record) recs = append(recs, record)
} }
if encounteredErrors > 0 {
// fix errors in the history file
log.Printf("There were %d decoding errors, the first error happend on line %d/%d", encounteredErrors, firstErrLine, i)
log.Println("Backing up current history file ...")
err := copyFile(fname, fname+".bak")
if err != nil {
log.Fatalln("Failed to backup history file with decode errors")
}
log.Println("Writing out a history file without errors ...")
err = writeHistory(fname, recs)
if err != nil {
log.Fatalln("Fatal: Failed write out new history")
}
}
return recs return recs
} }
func copyFile(source, dest string) error {
from, err := os.Open(source)
if err != nil {
// log.Println("Open() resh history file error:", err)
return err
}
defer from.Close()
// to, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0666)
to, err := os.Create(dest)
if err != nil {
// log.Println("Create() resh history backup error:", err)
return err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
// log.Println("Copy() resh history to backup error:", err)
return err
}
return nil
}
func writeHistory(fname string, history []Record) error {
file, err := os.Create(fname)
if err != nil {
// log.Println("Create() resh history error:", err)
return err
}
defer file.Close()
for _, rec := range history {
jsn, err := json.Marshal(rec)
if err != nil {
log.Fatalln("Encode error!")
}
file.Write(append(jsn, []byte("\n")...))
}
return nil
}
// LoadCmdLinesFromZshFile loads cmdlines from zsh history file // LoadCmdLinesFromZshFile loads cmdlines from zsh history file
func LoadCmdLinesFromZshFile(fname string) histlist.Histlist { func LoadCmdLinesFromZshFile(fname string) histlist.Histlist {
hl := histlist.New() hl := histlist.New()

Loading…
Cancel
Save