<commit_before>package server

import (
	"net"
	"strconv"
	"sync"

	"firempq/db"
	"firempq/log"

	. "firempq/api"
	. "firempq/common"
	. "firempq/errors"
	. "firempq/parsers"
	. "firempq/response"
	. "firempq/services"
	. "firempq/utils"
)

var EOM = []byte{'\n'}

const (
	CMD_PING       = "PING"
	CMD_CREATE_SVC = "CRT"
	CMD_DROP_SVC   = "DROP"
	CMD_QUIT       = "QUIT"
	CMD_UNIX_TS    = "TS"
	CMD_LIST       = "LIST"
	CMD_CTX        = "CTX"
	CMD_LOGLEVEL   = "LOGLEVEL"
	CMD_PANIC      = "PANIC"
	CMD_DBSTATS    = "DBSTATS"
)

type FuncHandler func([]string) IResponse

type SessionHandler struct {
	connLock  sync.Mutex
	conn      net.Conn
	active    bool
	ctx       ServiceContext
	stopChan  chan struct{}
	tokenizer *Tokenizer
	svcs      *ServiceManager
}

func NewSessionHandler(conn net.Conn, services *ServiceManager) *SessionHandler {
	sh := &SessionHandler{
		conn:      conn,
		tokenizer: NewTokenizer(),
		ctx:       nil,
		active:    true,
		svcs:      services,
		stopChan:  make(chan struct{}),
	}
	sh.QuitListener()
	return sh
}

func (s *SessionHandler) QuitListener() {
	quitChan := GetQuitChan()
	go func() {
		select {
		case <-quitChan:
			s.Stop()
			s.WriteResponse(ERR_CONN_CLOSING)
			if s.ctx != nil {
				s.ctx.Finish()
			}
			s.conn.Close()
			return
		case <-s.stopChan:
		}
	}()
}

// DispatchConn dispatcher. Entry point to start connection handling.
func (s *SessionHandler) DispatchConn() {
	addr := s.conn.RemoteAddr().String()
	log.Debug("Client connected: %s", addr)
	s.WriteResponse(NewStrResponse("HELLO FIREMPQ-0.1"))
	for s.active {
		cmdTokens, err := s.tokenizer.ReadTokens(s.conn)
		if err == nil {
			resp := s.processCmdTokens(cmdTokens)
			err = s.WriteResponse(resp)
		}
		if err != nil {
			log.LogConnError(err)
			break
		}
	}
	close(s.stopChan)
	if s.ctx != nil {
		s.ctx.Finish()
	}
	s.conn.Close()
	log.Debug("Client disconnected: %s", addr)
}

// Basic token processing that looks for global commands,
// if there is no token match it will look into current context
// to see if there is a processor for the rest of the tokens.
func (s *SessionHandler) processCmdTokens(cmdTokens []string) IResponse {
	if len(cmdTokens) == 0 {
		return OK_RESPONSE
	}

	cmd := cmdTokens[0]
	tokens := cmdTokens[1:]

	switch cmd {
	case CMD_QUIT:
		return s.quitHandler(tokens)
	case CMD_CTX:
		return s.ctxHandler(tokens)
	case CMD_CREATE_SVC:
		return s.createServiceHandler(tokens)
	case CMD_DROP_SVC:
		return s.dropServiceHandler(tokens)
	case CMD_LIST:
		return s.listServicesHandler(tokens)
	case CMD_LOGLEVEL:
		return logLevelHandler(tokens)
	case CMD_PING:
		return pingHandler(tokens)
	case CMD_UNIX_TS:
		return tsHandler(tokens)
	case CMD_PANIC:
		return panicHandler(tokens)
	case CMD_DBSTATS:
		return dbstatHandler(tokens)
	default:
		if s.ctx == nil {
			return InvalidRequest("Unknown command: " + cmd)
		} else {
			return s.ctx.Call(cmd, tokens)
		}
	}
}

// WriteResponse writes IResponse into connection writer.
func (s *SessionHandler) WriteResponse(resp IResponse) error {
	s.connLock.Lock()
	defer s.connLock.Unlock()
	if err := resp.WriteResponse(s.conn); err != nil {
		return err
	}
	if _, err := s.conn.Write(EOM); err != nil {
		return err
	}
	return nil
}

// Handler that creates a service.
func (s *SessionHandler) createServiceHandler(tokens []string) IResponse {
	if len(tokens) < 1 {
		return InvalidRequest("At least service name should be provided")
	}

	svcName := tokens[0]
	if len(svcName) > 256 {
		return InvalidRequest("Service name can not be longer than 256 characters")
	}

	if !ValidateItemId(svcName) {
		return ERR_ID_IS_WRONG
	}

	svcType := STYPE_PRIORITY_QUEUE
	// If number of tokens is 2, the second token may be a service type
	// if it is not one of known service types, it might be a parameter.
	if len(tokens) >= 2 {
		// This is queue type.
		if tokens[2] == STYPE_PRIORITY_QUEUE {
			tokens = tokens[2:]
		} else { // This is a parameter name.
			tokens = tokens[1:]
		}
	}

	_, exists := s.svcs.GetService(svcName)
	if exists {
		return ConflictRequest("Service exists already")
	}

	return s.svcs.CreateService(svcType, svcName, tokens)
}

// Drop service.
func (s *SessionHandler) dropServiceHandler(tokens []string) IResponse {
	if len(tokens) == 0 {<commit_msg>Service type is not required now to create a queue.<commit_after>package server

import (
	"net"
	"strconv"
	"sync"

	"firempq/db"
	"firempq/log"

	. "firempq/api"
	. "firempq/common"
	. "firempq/errors"
	. "firempq/parsers"
	. "firempq/response"
	. "firempq/services"
	. "firempq/utils"
)

var EOM = []byte{'\n'}

const (
	CMD_PING       = "PING"
	CMD_CREATE_SVC = "CRT"
	CMD_DROP_SVC   = "DROP"
	CMD_QUIT       = "QUIT"
	CMD_UNIX_TS    = "TS"
	CMD_LIST       = "LIST"
	CMD_CTX        = "CTX"
	CMD_LOGLEVEL   = "LOGLEVEL"
	CMD_PANIC      = "PANIC"
	CMD_DBSTATS    = "DBSTATS"
)

type FuncHandler func([]string) IResponse

type SessionHandler struct {
	connLock  sync.Mutex
	conn      net.Conn
	active    bool
	ctx       ServiceContext
	stopChan  chan struct{}
	tokenizer *Tokenizer
	svcs      *ServiceManager
}

func NewSessionHandler(conn net.Conn, services *ServiceManager) *SessionHandler {
	sh := &SessionHandler{
		conn:      conn,
		tokenizer: NewTokenizer(),
		ctx:       nil,
		active:    true,
		svcs:      services,
		stopChan:  make(chan struct{}),
	}
	sh.QuitListener()
	return sh
}

func (s *SessionHandler) QuitListener() {
	quitChan := GetQuitChan()
	go func() {
		select {
		case <-quitChan:
			s.Stop()
			s.WriteResponse(ERR_CONN_CLOSING)
			if s.ctx != nil {
				s.ctx.Finish()
			}
			s.conn.Close()
			return
		case <-s.stopChan:
		}
	}()
}

// DispatchConn dispatcher. Entry point to start connection handling.
func (s *SessionHandler) DispatchConn() {
	addr := s.conn.RemoteAddr().String()
	log.Debug("Client connected: %s", addr)
	s.WriteResponse(NewStrResponse("HELLO FIREMPQ-0.1"))
	for s.active {
		cmdTokens, err := s.tokenizer.ReadTokens(s.conn)
		if err == nil {
			resp := s.processCmdTokens(cmdTokens)
			err = s.WriteResponse(resp)
		}
		if err != nil {
			log.LogConnError(err)
			break
		}
	}
	close(s.stopChan)
	if s.ctx != nil {
		s.ctx.Finish()
	}
	s.conn.Close()
	log.Debug("Client disconnected: %s", addr)
}

// Basic token processing that looks for global commands,
// if there is no token match it will look into current context
// to see if there is a processor for the rest of the tokens.
func (s *SessionHandler) processCmdTokens(cmdTokens []string) IResponse {
	if len(cmdTokens) == 0 {
		return OK_RESPONSE
	}

	cmd := cmdTokens[0]
	tokens := cmdTokens[1:]

	switch cmd {
	case CMD_QUIT:
		return s.quitHandler(tokens)
	case CMD_CTX:
		return s.ctxHandler(tokens)
	case CMD_CREATE_SVC:
		return s.createServiceHandler(tokens)
	case CMD_DROP_SVC:
		return s.dropServiceHandler(tokens)
	case CMD_LIST:
		return s.listServicesHandler(tokens)
	case CMD_LOGLEVEL:
		return logLevelHandler(tokens)
	case CMD_PING:
		return pingHandler(tokens)
	case CMD_UNIX_TS:
		return tsHandler(tokens)
	case CMD_PANIC:
		return panicHandler(tokens)
	case CMD_DBSTATS:
		return dbstatHandler(tokens)
	default:
		if s.ctx == nil {
			return InvalidRequest("Unknown command: " + cmd)
		} else {
			return s.ctx.Call(cmd, tokens)
		}
	}
}

// WriteResponse writes IResponse into connection writer.
func (s *SessionHandler) WriteResponse(resp IResponse) error {
	s.connLock.Lock()
	defer s.connLock.Unlock()
	if err := resp.WriteResponse(s.conn); err != nil {
		return err
	}
	if _, err := s.conn.Write(EOM); err != nil {
		return err
	}
	return nil
}

// Handler that creates a service.
func (s *SessionHandler) createServiceHandler(tokens []string) IResponse {
	if len(tokens) < 1 {
		return InvalidRequest("Service name should be provided")
	}
	if len(tokens) > 1 {
		return InvalidRequest("At least service name should be provided")
	}

	svcName := tokens[0]
	if len(svcName) > 256 {
		return InvalidRequest("Service name can not be longer than 256 characters")
	}

	if !ValidateItemId(svcName) {
		return ERR_ID_IS_WRONG
	}

	_, exists := s.svcs.GetService(svcName)
	if exists {
		return ConflictRequest("Service exists already")
	}

	return s.svcs.CreateService(STYPE_PRIORITY_QUEUE, svcName, tokens[1:])
}

// Drop service.
func (s *SessionHandler) dropServiceHandler(tokens []string) IResponse {
	if len(tokens) == 0 {
{'fraction_non_alphanumeric': 0.11928811928811929, 'fraction_numerical': 0.004208754208754209, 'mean_word_length': 3.597567716970702, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3069281', 'n_tokens_mistral': 3245, 'n_tokens_neox': 3025, 'n_words': 768}