<commit_before>// Low-level AniDB UDP API client library
//
// Implements the commands essential to setting up and tearing down an API connection,
// as well as an asynchronous layer. Throttles sends internally according to API spec.
//
// This library doesn't implement caching; beware since aggressive caching is an
// implementation requirement. Not doing so can get you banned.
package udpapi

import (
	"bytes"
	"compress/zlib"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	AniDBUDPServer = "api.anidb.net"
	AniDBUDPPort   = 9000
)

type AniDBUDP struct {
	// Interval between keep-alive packets; only sent when PUSH notifications are enabled (default: 20 minutes)
	KeepAliveInterval time.Duration

	// The time to wait before a packet is considered lost (default: 45 seconds)
	Timeout time.Duration

	// Channel where PUSH notifications are sent to
	Notifications chan APIReply

	session string

	conn *net.UDPConn
	ecb  *ecbState

	counter uint16
	ctrLock sync.Mutex

	tagRouter  map[string]chan APIReply
	routerLock sync.RWMutex

	sendCh chan packet

	breakRecv chan bool
	breakSend chan bool

	// notifyState *notifyState
	pingTimer *time.Timer
}

// Creates and initializes the AniDBUDP struct
func NewAniDBUDP() *AniDBUDP {
	c := &AniDBUDP{
		KeepAliveInterval: 20 * time.Minute,
		Timeout:           45 * time.Second,
		Notifications:     make(chan APIReply, 5),
		tagRouter:         make(map[string]chan APIReply),
	}
	return c
}

// Key-value list of parameters.
type ParamMap map[string]interface{}

// Returns a query-like string representation of the ParamMap
func (m ParamMap) String() string {
	keys := make([]string, 0, len(m))
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	parts := make([]string, 0, len(m))
	for _, k := range keys {
		parts = append(parts, strings.Join([]string{k, fmt.Sprint((m)[k])}, "="))
	}
	return strings.Join(parts, "&")
}

// Sends the requested query to the AniDB UDP API server.
//
// Returns a channel through which the eventual reply is sent.
//
// See http://wiki.anidb.net/w/UDP_API_Definition for the defined commands.
func (a *AniDBUDP) SendRecv(command string, args ParamMap) <-chan APIReply {
	a.ctrLock.Lock()
	tag := fmt.Sprintf("T%d", a.counter)
	a.counter++
	a.ctrLock.Unlock()

	args["tag"] = tag
	if a.session != "" {
		args["s"] = a.session
	}

	if err := a.dial(); err != nil {
		ch <- newErrorWrapper(err)
		close(ch)
		return ch
	}

	ch := make(chan APIReply, 1)

	a.routerLock.Lock()
	a.tagRouter[tag] = ch
	a.routerLock.Unlock()

	reply := make(chan APIReply, 1)
	go func() {
		<-a.send(command, args)
		timeout := time.After(a.Timeout)

		select {
		case <-timeout:<commit_msg>udpapi: escape & as &amp; and \n as <br/><commit_after>// Low-level AniDB UDP API client library
//
// Implements the commands essential to setting up and tearing down an API connection,
// as well as an asynchronous layer. Throttles sends internally according to API spec.
//
// This library doesn't implement caching; beware since aggressive caching is an
// implementation requirement. Not doing so can get you banned.
package udpapi

import (
	"bytes"
	"compress/zlib"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	AniDBUDPServer = "api.anidb.net"
	AniDBUDPPort   = 9000
)

type AniDBUDP struct {
	// Interval between keep-alive packets; only sent when PUSH notifications are enabled (default: 20 minutes)
	KeepAliveInterval time.Duration

	// The time to wait before a packet is considered lost (default: 45 seconds)
	Timeout time.Duration

	// Channel where PUSH notifications are sent to
	Notifications chan APIReply

	session string

	conn *net.UDPConn
	ecb  *ecbState

	counter uint16
	ctrLock sync.Mutex

	tagRouter  map[string]chan APIReply
	routerLock sync.RWMutex

	sendCh chan packet

	breakRecv chan bool
	breakSend chan bool

	// notifyState *notifyState
	pingTimer *time.Timer
}

// Creates and initializes the AniDBUDP struct
func NewAniDBUDP() *AniDBUDP {
	c := &AniDBUDP{
		KeepAliveInterval: 20 * time.Minute,
		Timeout:           45 * time.Second,
		Notifications:     make(chan APIReply, 5),
		tagRouter:         make(map[string]chan APIReply),
	}
	return c
}

// Key-value list of parameters.
type ParamMap map[string]interface{}

// Returns a query-like string representation of the ParamMap
func (m ParamMap) String() string {
	keys := make([]string, 0, len(m))
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	parts := make([]string, 0, len(m))
	for _, k := range keys {
		parts = append(parts, strings.Join([]string{k, fmt.Sprint((m)[k])}, "="))
	}
	return strings.Join(parts, "&")
}

// Sends the requested query to the AniDB UDP API server.
//
// Returns a channel through which the eventual reply is sent.
//
// See http://wiki.anidb.net/w/UDP_API_Definition for the defined commands.
func (a *AniDBUDP) SendRecv(command string, args ParamMap) <-chan APIReply {
	a.ctrLock.Lock()
	tag := fmt.Sprintf("T%d", a.counter)
	a.counter++
	a.ctrLock.Unlock()

	args["tag"] = tag
	if a.session != "" {
		args["s"] = a.session
	}
	for k, v := range args {
		v = strings.Replace(v, "\n", "<br/>", -1)
		args[k] = strings.Replace(v, "&", "&amp;", -1)
	}

	if err := a.dial(); err != nil {
		ch <- newErrorWrapper(err)
		close(ch)
		return ch
	}

	ch := make(chan APIReply, 1)

	a.routerLock.Lock()
	a.tagRouter[tag] = ch
	a.routerLock.Unlock()

	reply := make(chan APIReply, 1)
	go func() {
		<-a.send(command, args)
		timeout := time.After(a.Timeout)

		select {
		case <-timeout:
{'fraction_non_alphanumeric': 0.127336860670194, 'fraction_numerical': 0.007407407407407408, 'mean_word_length': 3.58447857720291, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 5, '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': '1716303', 'n_tokens_mistral': 2108, 'n_tokens_neox': 2017, 'n_words': 614}