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
|
hexIP = false
|
||||||
verify_certificates = true
|
verify_certificates = true
|
||||||
|
|
||||||
|
download_folder = ""
|
||||||
|
autoget = false
|
||||||
|
|
||||||
# Defaults for the client connect form
|
# Defaults for the client connect form
|
||||||
[defaults]
|
[defaults]
|
||||||
name = "freenode"
|
name = "freenode"
|
||||||
|
@ -14,6 +14,8 @@ type Config struct {
|
|||||||
Dev bool
|
Dev bool
|
||||||
HexIP bool
|
HexIP bool
|
||||||
VerifyCertificates bool `mapstructure:"verify_certificates"`
|
VerifyCertificates bool `mapstructure:"verify_certificates"`
|
||||||
|
DownloadFolder string `mapstructure:"download_folder"`
|
||||||
|
Autoget bool
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
Defaults Defaults
|
Defaults Defaults
|
||||||
HTTPS HTTPS
|
HTTPS HTTPS
|
||||||
|
@ -3,7 +3,13 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -21,8 +27,12 @@ type Client struct {
|
|||||||
Realname string
|
Realname string
|
||||||
HandleNickInUse func(string) string
|
HandleNickInUse func(string) string
|
||||||
|
|
||||||
|
DownloadFolder string
|
||||||
|
Autoget bool
|
||||||
|
|
||||||
Messages chan *Message
|
Messages chan *Message
|
||||||
ConnectionChanged chan ConnectionState
|
ConnectionChanged chan ConnectionState
|
||||||
|
Progress chan DownloadProgress
|
||||||
Features *Features
|
Features *Features
|
||||||
nick string
|
nick string
|
||||||
channels []string
|
channels []string
|
||||||
@ -50,6 +60,7 @@ func NewClient(nick, username string) *Client {
|
|||||||
Realname: nick,
|
Realname: nick,
|
||||||
Messages: make(chan *Message, 32),
|
Messages: make(chan *Message, 32),
|
||||||
ConnectionChanged: make(chan ConnectionState, 16),
|
ConnectionChanged: make(chan ConnectionState, 16),
|
||||||
|
Progress: make(chan DownloadProgress, 16),
|
||||||
out: make(chan string, 32),
|
out: make(chan string, 32),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
reconnect: make(chan struct{}),
|
reconnect: make(chan struct{}),
|
||||||
@ -207,3 +218,149 @@ func (c *Client) flushChannels() {
|
|||||||
}
|
}
|
||||||
c.lock.Unlock()
|
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 {
|
if c.HandleNickInUse != nil {
|
||||||
go c.writeNick(c.HandleNickInUse(msg.Params[1]))
|
go c.writeNick(c.HandleNickInUse(msg.Params[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctcp := msg.ToCTCP(); ctcp != nil {
|
||||||
|
go c.Download(ctcp)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Messages <- msg
|
c.Messages <- msg
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
@ -12,6 +17,26 @@ type Message struct {
|
|||||||
Params []string
|
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 {
|
func (m *Message) LastParam() string {
|
||||||
if len(m.Params) > 0 {
|
if len(m.Params) > 0 {
|
||||||
return m.Params[len(m.Params)-1]
|
return m.Params[len(m.Params)-1]
|
||||||
@ -19,6 +44,40 @@ func (m *Message) LastParam() string {
|
|||||||
return ""
|
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 {
|
func ParseMessage(line string) *Message {
|
||||||
msg := Message{}
|
msg := Message{}
|
||||||
|
|
||||||
@ -112,3 +171,19 @@ var unescapeTagReplacer = strings.NewReplacer(
|
|||||||
func unescapeTag(s string) string {
|
func unescapeTag(s string) string {
|
||||||
return unescapeTagReplacer.Replace(s)
|
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)
|
state.setIRC(server.Host, i)
|
||||||
i.Connect(address)
|
i.Connect(address)
|
||||||
go newIRCHandler(i, state).run()
|
go newIRCHandler(i, state).run()
|
||||||
|
@ -63,6 +63,8 @@ func (i *ircHandler) run() {
|
|||||||
} else if state.Connected {
|
} else if state.Connected {
|
||||||
i.log("Connected")
|
i.log("Connected")
|
||||||
}
|
}
|
||||||
|
case progress := <-i.client.Progress:
|
||||||
|
i.state.sendJSON("progress", progress.ToJSON())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user