package sesshist import ( "errors" "log" "strconv" "strings" "sync" "github.com/curusarn/resh/pkg/histfile" "github.com/curusarn/resh/pkg/histlist" "github.com/curusarn/resh/pkg/records" ) // Dispatch Recall() calls to an apropriate session history (sesshist) type Dispatch struct { sessions map[string]*sesshist mutex sync.RWMutex history *histfile.Histfile historyInitSize int } // NewDispatch creates a new sesshist.Dispatch and starts necessary gorutines func NewDispatch(sessionsToInit chan records.Record, sessionsToDrop chan string, recordsToAdd chan records.Record, history *histfile.Histfile, historyInitSize int) *Dispatch { s := Dispatch{ sessions: map[string]*sesshist{}, history: history, historyInitSize: historyInitSize, } go s.sessionInitializer(sessionsToInit) go s.sessionDropper(sessionsToDrop) go s.recordAdder(recordsToAdd) return &s } func (s *Dispatch) sessionInitializer(sessionsToInit chan records.Record) { for { record := <-sessionsToInit log.Println("sesshist: got session to init - " + record.SessionID) s.initSession(record.SessionID) } } func (s *Dispatch) sessionDropper(sessionsToDrop chan string) { for { sessionID := <-sessionsToDrop log.Println("sesshist: got session to drop - " + sessionID) s.dropSession(sessionID) } } func (s *Dispatch) recordAdder(recordsToAdd chan records.Record) { for { record := <-recordsToAdd if record.PartOne { log.Println("sesshist: got record to add - " + record.CmdLine) s.addRecentRecord(record.SessionID, record) } // TODO: we will need to handle part2 as well eventually } } // InitSession struct func (s *Dispatch) initSession(sessionID string) error { log.Println("sesshist: initializing session - " + sessionID) s.mutex.RLock() _, found := s.sessions[sessionID] s.mutex.RUnlock() if found == true { return errors.New("sesshist ERROR: Can't INIT already existing session " + sessionID) } log.Println("sesshist: loading history to populate session - " + sessionID) historyCmdLines := s.history.GetRecentCmdLines(s.historyInitSize) s.mutex.Lock() defer s.mutex.Unlock() // init sesshist and populate it with history loaded from file s.sessions[sessionID] = &sesshist{ recentCmdLines: historyCmdLines, } log.Println("sesshist: session init done - " + sessionID) return nil } // DropSession struct func (s *Dispatch) dropSession(sessionID string) error { s.mutex.RLock() _, found := s.sessions[sessionID] s.mutex.RUnlock() if found == false { return errors.New("sesshist ERROR: Can't DROP not existing session " + sessionID) } s.mutex.Lock() defer s.mutex.Unlock() delete(s.sessions, sessionID) return nil } // AddRecent record to session func (s *Dispatch) addRecentRecord(sessionID string, record records.Record) error { log.Println("sesshist: Adding a record, RLocking main lock ...") s.mutex.RLock() log.Println("sesshist: Getting a session ...") session, found := s.sessions[sessionID] log.Println("sesshist: RUnlocking main lock ...") s.mutex.RUnlock() if found == false { log.Println("sesshist ERROR: addRecontRecord(): No session history for SessionID " + sessionID + " - creating session history.") s.initSession(sessionID) return s.addRecentRecord(sessionID, record) } log.Println("sesshist: RLocking session lock (w/ defer) ...") session.mutex.Lock() defer session.mutex.Unlock() session.recentRecords = append(session.recentRecords, record) // remove previous occurance of record log.Println("sesshist: Looking for duplicate cmdLine ...") cmdLine := record.CmdLine // trim spaces to have less duplicates in the sesshist cmdLine = strings.TrimRight(cmdLine, " ") idx, found := session.recentCmdLines.LastIndex[cmdLine] if found { log.Println("sesshist: Removing duplicate cmdLine at index:", idx, " out of", len(session.recentCmdLines.List), "...") session.recentCmdLines.List = append(session.recentCmdLines.List[:idx], session.recentCmdLines.List[idx+1:]...) } log.Println("sesshist: Updating last index ...") session.recentCmdLines.LastIndex[cmdLine] = len(session.recentCmdLines.List) // append new record log.Println("sesshist: Appending cmdLine ...") session.recentCmdLines.List = append(session.recentCmdLines.List, cmdLine) log.Println("sesshist: record:", record.CmdLine, "; added to session:", sessionID, "; session len:", len(session.recentCmdLines.List), "; session len (records):", len(session.recentRecords)) return nil } // Recall command from recent session history func (s *Dispatch) Recall(sessionID string, histno int, prefix string) (string, error) { log.Println("sesshist - recall: RLocking main lock ...") s.mutex.RLock() log.Println("sesshist - recall: Getting session history struct ...") session, found := s.sessions[sessionID] s.mutex.RUnlock() if found == false { // go s.initSession(sessionID) return "", errors.New("sesshist ERROR: No session history for SessionID " + sessionID + " - should we create one?") } log.Println("sesshist - recall: Locking session lock ...") session.mutex.Lock() defer session.mutex.Unlock() if prefix == "" { log.Println("sesshist - recall: Getting records by histno ...") return session.getRecordByHistno(histno) } log.Println("sesshist - recall: Searching for records by prefix ...") return session.searchRecordByPrefix(prefix, histno) } // Inspect commands in recent session history func (s *Dispatch) Inspect(sessionID string, count int) ([]string, error) { prefix := "" log.Println("sesshist - inspect: RLocking main lock ...") s.mutex.RLock() log.Println("sesshist - inspect: Getting session history struct ...") session, found := s.sessions[sessionID] s.mutex.RUnlock() if found == false { // go s.initSession(sessionID) return nil, errors.New("sesshist ERROR: No session history for SessionID " + sessionID + " - should we create one?") } log.Println("sesshist - inspect: Locking session lock ...") session.mutex.Lock() defer session.mutex.Unlock() if prefix == "" { log.Println("sesshist - inspect: Getting records by histno ...") idx := len(session.recentCmdLines.List) - count if idx < 0 { idx = 0 } return session.recentCmdLines.List[idx:], nil } log.Println("sesshist - inspect: Searching for records by prefix ... ERROR - Not implemented") return nil, errors.New("sesshist ERROR: Inspect - Searching for records by prefix Not implemented yet") } type sesshist struct { mutex sync.Mutex recentRecords []records.Record recentCmdLines histlist.Histlist } func (s *sesshist) getRecordByHistno(histno int) (string, error) { // addRecords() appends records to the end of the slice // -> this func handles the indexing if histno == 0 { return "", errors.New("sesshist ERROR: 'histno == 0' is not a record from history") } if histno < 0 { return "", errors.New("sesshist ERROR: 'histno < 0' is a command from future (not supperted yet)") } index := len(s.recentCmdLines.List) - histno if index < 0 { return "", errors.New("sesshist ERROR: 'histno > number of commands in the session' (" + strconv.Itoa(len(s.recentCmdLines.List)) + ")") } return s.recentCmdLines.List[index], nil } func (s *sesshist) searchRecordByPrefix(prefix string, histno int) (string, error) { if histno == 0 { return "", errors.New("sesshist ERROR: 'histno == 0' is not a record from history") } if histno < 0 { return "", errors.New("sesshist ERROR: 'histno < 0' is a command from future (not supperted yet)") } index := len(s.recentCmdLines.List) - histno if index < 0 { return "", errors.New("sesshist ERROR: 'histno > number of commands in the session' (" + strconv.Itoa(len(s.recentCmdLines.List)) + ")") } cmdLines := []string{} for i := len(s.recentCmdLines.List) - 1; i >= 0; i-- { if strings.HasPrefix(s.recentCmdLines.List[i], prefix) { cmdLines = append(cmdLines, s.recentCmdLines.List[i]) if len(cmdLines) >= histno { break } } } if len(cmdLines) < histno { return "", errors.New("sesshist ERROR: 'histno > number of commands matching with given prefix' (" + strconv.Itoa(len(cmdLines)) + ")") } return cmdLines[histno-1], nil }