start of DCC implementation
(server side) small refactoring and added speed calculation fixup! small refactoring and added speed calculation download progress fixup! download progress
This commit is contained in:
parent
9581a63e81
commit
ed2e56948e
@ -5,6 +5,9 @@ port = 80
|
||||
hexIP = false
|
||||
verify_certificates = true
|
||||
|
||||
download_folder = ""
|
||||
autoget = false
|
||||
|
||||
# Defaults for the client connect form
|
||||
[defaults]
|
||||
name = "freenode"
|
||||
|
@ -13,7 +13,9 @@ type Config struct {
|
||||
Port string
|
||||
Dev bool
|
||||
HexIP bool
|
||||
VerifyCertificates bool `mapstructure:"verify_certificates"`
|
||||
VerifyCertificates bool `mapstructure:"verify_certificates"`
|
||||
DownloadFolder string `mapstructure:"download_folder"`
|
||||
Autoget bool
|
||||
Headers map[string]string
|
||||
Defaults Defaults
|
||||
HTTPS HTTPS
|
||||
|
@ -3,7 +3,13 @@ package irc
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -21,8 +27,12 @@ type Client struct {
|
||||
Realname string
|
||||
HandleNickInUse func(string) string
|
||||
|
||||
DownloadFolder string
|
||||
Autoget bool
|
||||
|
||||
Messages chan *Message
|
||||
ConnectionChanged chan ConnectionState
|
||||
Progress chan DownloadProgress
|
||||
Features *Features
|
||||
nick string
|
||||
channels []string
|
||||
@ -50,6 +60,7 @@ func NewClient(nick, username string) *Client {
|
||||
Realname: nick,
|
||||
Messages: make(chan *Message, 32),
|
||||
ConnectionChanged: make(chan ConnectionState, 16),
|
||||
Progress: make(chan DownloadProgress, 16),
|
||||
out: make(chan string, 32),
|
||||
quit: make(chan struct{}),
|
||||
reconnect: make(chan struct{}),
|
||||
@ -207,3 +218,149 @@ func (c *Client) flushChannels() {
|
||||
}
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
func byteRead(totalBytes uint64) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, totalBytes)
|
||||
return b
|
||||
}
|
||||
|
||||
func round2(source float64) float64 {
|
||||
return math.Round(100*source) / 100
|
||||
}
|
||||
|
||||
const (
|
||||
_ = 1.0 << (10 * iota)
|
||||
kibibyte
|
||||
mebibyte
|
||||
gibibyte
|
||||
)
|
||||
|
||||
func humanReadableByteCount(b float64, speed bool) string {
|
||||
unit := ""
|
||||
value := b
|
||||
|
||||
switch {
|
||||
case b >= gibibyte:
|
||||
unit = "GiB"
|
||||
value = value / gibibyte
|
||||
case b >= mebibyte:
|
||||
unit = "MiB"
|
||||
value = value / mebibyte
|
||||
case b >= kibibyte:
|
||||
unit = "KiB"
|
||||
value = value / kibibyte
|
||||
case b > 1 || b == 0:
|
||||
unit = "bytes"
|
||||
case b == 1:
|
||||
unit = "byte"
|
||||
}
|
||||
|
||||
if speed {
|
||||
unit = unit + "/s"
|
||||
}
|
||||
|
||||
stringValue := strings.TrimSuffix(
|
||||
fmt.Sprintf("%.2f", value), ".00",
|
||||
)
|
||||
|
||||
return fmt.Sprintf("%s %s", stringValue, unit)
|
||||
}
|
||||
|
||||
func (c *Client) Download(pack *CTCP) {
|
||||
if !c.Autoget {
|
||||
// TODO: ask user if he/she wants to download the file
|
||||
return
|
||||
}
|
||||
c.Progress <- DownloadProgress{
|
||||
PercCompletion: 0,
|
||||
File: pack.File,
|
||||
}
|
||||
file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
c.Progress <- DownloadProgress{
|
||||
PercCompletion: -1,
|
||||
File: pack.File,
|
||||
Error: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", pack.IP, pack.Port))
|
||||
|
||||
if err != nil {
|
||||
c.Progress <- DownloadProgress{
|
||||
PercCompletion: -1,
|
||||
File: pack.File,
|
||||
Error: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
defer con.Close()
|
||||
|
||||
var avgSpeed float64
|
||||
var prevTime int64 = -1
|
||||
secondsElapsed := int64(0)
|
||||
totalBytes := uint64(0)
|
||||
buf := make([]byte, 0, 4*1024)
|
||||
start := time.Now().UnixNano()
|
||||
for {
|
||||
n, err := con.Read(buf[:cap(buf)])
|
||||
buf = buf[:n]
|
||||
if n == 0 {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := file.Write(buf); err != nil {
|
||||
c.Progress <- DownloadProgress{
|
||||
PercCompletion: -1,
|
||||
File: pack.File,
|
||||
Error: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cycleBytes := uint64(len(buf))
|
||||
totalBytes += cycleBytes
|
||||
percentage := round2(100 * float64(totalBytes) / float64(pack.Length))
|
||||
|
||||
now := time.Now().UnixNano()
|
||||
secondsElapsed = (now - start) / 1e9
|
||||
avgSpeed = round2(float64(totalBytes) / (float64(secondsElapsed)))
|
||||
speed := 0.0
|
||||
|
||||
if prevTime < 0 {
|
||||
speed = avgSpeed
|
||||
} else {
|
||||
speed = round2(1e9 * float64(cycleBytes) / (float64(now - prevTime)))
|
||||
}
|
||||
secondsToGo := (float64(pack.Length) - float64(totalBytes)) / speed
|
||||
prevTime = now
|
||||
con.Write(byteRead(totalBytes))
|
||||
c.Progress <- DownloadProgress{
|
||||
InstSpeed: humanReadableByteCount(speed, true),
|
||||
AvgSpeed: humanReadableByteCount(avgSpeed, true),
|
||||
PercCompletion: percentage,
|
||||
BytesRemaining: humanReadableByteCount(float64(pack.Length-totalBytes), false),
|
||||
BytesCompleted: humanReadableByteCount(float64(totalBytes), false),
|
||||
SecondsElapsed: secondsElapsed,
|
||||
SecondsToGo: secondsToGo,
|
||||
File: pack.File,
|
||||
}
|
||||
}
|
||||
con.Write(byteRead(totalBytes))
|
||||
c.Progress <- DownloadProgress{
|
||||
AvgSpeed: humanReadableByteCount(avgSpeed, true),
|
||||
PercCompletion: 100,
|
||||
BytesCompleted: humanReadableByteCount(float64(totalBytes), false),
|
||||
SecondsElapsed: secondsElapsed,
|
||||
File: pack.File,
|
||||
}
|
||||
}
|
||||
|
@ -239,6 +239,11 @@ func (c *Client) recv() {
|
||||
if c.HandleNickInUse != nil {
|
||||
go c.writeNick(c.HandleNickInUse(msg.Params[1]))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ctcp := msg.ToCTCP(); ctcp != nil {
|
||||
go c.Download(ctcp)
|
||||
}
|
||||
|
||||
c.Messages <- msg
|
||||
|
@ -1,7 +1,12 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
@ -12,6 +17,26 @@ type Message struct {
|
||||
Params []string
|
||||
}
|
||||
|
||||
type DownloadProgress struct {
|
||||
File string `json:"file"`
|
||||
Error error `json:"error"`
|
||||
BytesCompleted string `json:"bytes_completed"`
|
||||
BytesRemaining string `json:"bytes_remaining"`
|
||||
PercCompletion float64 `json:"perc_completion"`
|
||||
AvgSpeed string `json:"avg_speed"`
|
||||
InstSpeed string `json:"speed"`
|
||||
SecondsElapsed int64 `json:"elapsed"`
|
||||
SecondsToGo float64 `json:"eta"`
|
||||
}
|
||||
|
||||
// CTCP is used to parse a message into a CTCP message
|
||||
type CTCP struct {
|
||||
File string `json:"file"`
|
||||
IP string `json:"ip"`
|
||||
Port uint16 `json:"port"`
|
||||
Length uint64 `json:"length"`
|
||||
}
|
||||
|
||||
func (m *Message) LastParam() string {
|
||||
if len(m.Params) > 0 {
|
||||
return m.Params[len(m.Params)-1]
|
||||
@ -19,6 +44,40 @@ func (m *Message) LastParam() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ToCTCP tries to parse the message parameters into a CTCP message
|
||||
func (m *Message) ToCTCP() *CTCP {
|
||||
params := strings.Join(m.Params, " ")
|
||||
if strings.Contains(params, "DCC SEND") {
|
||||
// to be extra sure that there are non-printable characters
|
||||
params = strings.TrimFunc(params, func(r rune) bool {
|
||||
return !unicode.IsPrint(r)
|
||||
})
|
||||
parts := strings.Split(params, " ")
|
||||
ip, err := strconv.Atoi(parts[4])
|
||||
port, err := strconv.Atoi(parts[5])
|
||||
length, err := strconv.Atoi(parts[6])
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ip3 := uint32ToIP(ip)
|
||||
|
||||
filename := path.Base(parts[3])
|
||||
if filename == "/" || filename == "." {
|
||||
filename = ""
|
||||
}
|
||||
|
||||
return &CTCP{
|
||||
File: filename,
|
||||
IP: ip3,
|
||||
Port: uint16(port),
|
||||
Length: uint64(length),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseMessage(line string) *Message {
|
||||
msg := Message{}
|
||||
|
||||
@ -112,3 +171,19 @@ var unescapeTagReplacer = strings.NewReplacer(
|
||||
func unescapeTag(s string) string {
|
||||
return unescapeTagReplacer.Replace(s)
|
||||
}
|
||||
|
||||
func uint32ToIP(n int) string {
|
||||
var byte1 = n & 255
|
||||
var byte2 = ((n >> 8) & 255)
|
||||
var byte3 = ((n >> 16) & 255)
|
||||
var byte4 = ((n >> 24) & 255)
|
||||
return fmt.Sprintf("%d.%d.%d.%d", byte4, byte3, byte2, byte1)
|
||||
}
|
||||
|
||||
func (p DownloadProgress) ToJSON() string {
|
||||
progress, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(progress)
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
|
||||
}
|
||||
}
|
||||
|
||||
i.DownloadFolder = cfg.DownloadFolder
|
||||
i.Autoget = cfg.Autoget
|
||||
|
||||
state.setIRC(server.Host, i)
|
||||
i.Connect(address)
|
||||
go newIRCHandler(i, state).run()
|
||||
|
@ -63,6 +63,8 @@ func (i *ircHandler) run() {
|
||||
} else if state.Connected {
|
||||
i.log("Connected")
|
||||
}
|
||||
case progress := <-i.client.Progress:
|
||||
i.state.sendJSON("progress", progress.ToJSON())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user