Implement early version of command get
parent
17f24f765c
commit
d2c8df3593
9
go.mod
9
go.mod
|
|
@ -2,4 +2,11 @@ module xdcc-cli
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require github.com/PuerkitoBio/goquery v1.8.0
|
require (
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
|
github.com/Workiva/go-datastructures v1.0.53
|
||||||
|
github.com/fluffle/goirc v1.1.1
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/vbauerster/mpb v3.4.0+incompatible
|
||||||
|
github.com/vbauerster/mpb/v7 v7.1.5
|
||||||
|
)
|
||||||
|
|
|
||||||
56
go.sum
56
go.sum
|
|
@ -1,11 +1,67 @@
|
||||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||||
|
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||||
|
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||||
|
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
||||||
|
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||||
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fluffle/goirc v1.1.1 h1:6nO+7rrED3Kp3mngoi9OmQmQHevNwDfjGpYUdWc1s0k=
|
||||||
|
github.com/fluffle/goirc v1.1.1/go.mod h1:iRzPLv2vkuZEtbns5LioYguJkRh/bvshuWg7izf1yeE=
|
||||||
|
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
||||||
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
|
github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tLAD8IceomUCNw=
|
||||||
|
github.com/vbauerster/mpb v3.4.0+incompatible/go.mod h1:zAHG26FUhVKETRu+MWqYXcI70POlC6N8up9p1dID7SU=
|
||||||
|
github.com/vbauerster/mpb/v7 v7.1.5 h1:vtUEUfQHmNeJETyF4AcRCOV6RC4wqFwNORy52UMXPbQ=
|
||||||
|
github.com/vbauerster/mpb/v7 v7.1.5/go.mod h1:4M8+qAoQqV60WDNktBM5k05i1iTrXE7rjKOHEVkVlec=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg=
|
||||||
|
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
159
main.go
159
main.go
|
|
@ -1,9 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var registry *XdccProviderRegistry = nil
|
var registry *XdccProviderRegistry = nil
|
||||||
|
|
@ -13,20 +17,156 @@ func init() {
|
||||||
registry.AddProvider(&XdccEuProvider{})
|
registry.AddProvider(&XdccEuProvider{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func search(fileName string) {
|
var defaultColWidths []int = []int{100, 10, -1}
|
||||||
printer := NewTablePrinter([]string{"File Name", "Network", "Channel"})
|
|
||||||
|
|
||||||
res, _ := registry.Search(fileName)
|
const (
|
||||||
for _, fileInfo := range res {
|
KiloByte = 1024
|
||||||
printer.AddRow(Row{fileInfo.Name, fileInfo.Network, fileInfo.Channel})
|
MegaByte = KiloByte * 1024
|
||||||
|
GigaByte = MegaByte * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func FloatToString(value float64) string {
|
||||||
|
if value-float64(int64(value)) > 0 {
|
||||||
|
return strconv.FormatFloat(value, 'f', 2, 32)
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(value, 'f', 0, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSize(size int64) string {
|
||||||
|
if size < 0 {
|
||||||
|
return "--"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if size >= GigaByte {
|
||||||
|
return FloatToString(float64(size)/float64(GigaByte)) + "GB"
|
||||||
|
} else if size >= MegaByte {
|
||||||
|
return FloatToString(float64(size)/float64(MegaByte)) + "MB"
|
||||||
|
} else if size >= KiloByte {
|
||||||
|
return FloatToString(float64(size)/float64(KiloByte)) + "KB"
|
||||||
|
}
|
||||||
|
return FloatToString(float64(size)) + "B"
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchCommand(args []string) {
|
||||||
|
printer := NewTablePrinter([]string{"File Name", "Size", "URL"})
|
||||||
|
printer.SetMaxWidths(defaultColWidths)
|
||||||
|
|
||||||
|
res, _ := registry.Search(args[0])
|
||||||
|
for _, fileInfo := range res {
|
||||||
|
printer.AddRow(Row{fileInfo.Name, formatSize(fileInfo.Size), fileInfo.Url})
|
||||||
|
}
|
||||||
|
|
||||||
|
printer.SortByColumn(0) // sort by filename
|
||||||
printer.Print()
|
printer.Print()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doTransfer(transfer *XdccTransfer) {
|
||||||
|
//pb := NewProgressBar()
|
||||||
|
|
||||||
|
err := transfer.Start()
|
||||||
|
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
evts := transfer.PollEvents()
|
||||||
|
quit := false
|
||||||
|
for !quit {
|
||||||
|
e := <-evts
|
||||||
|
switch evtType := e.(type) {
|
||||||
|
case *TransferStartedEvent:
|
||||||
|
// pb.SetTotal(int(evtType.FileSize))
|
||||||
|
// pb.SetFileName(evtType.FileName)
|
||||||
|
// pb.SetState(ProgressStateDownloading)
|
||||||
|
case *TransferProgessEvent:
|
||||||
|
// pb.Increment(int(evtType.transferBytes))
|
||||||
|
case *TransferCompletedEvent:
|
||||||
|
// pb.SetState(ProgressStateCompleted)
|
||||||
|
print(evtType)
|
||||||
|
quit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: do clean-up operations here
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFlags(flagSet *flag.FlagSet, args []string) []string {
|
||||||
|
findFirstFlag := func(args []string) int {
|
||||||
|
for i, arg := range args {
|
||||||
|
if strings.HasPrefix(arg, "-") || strings.HasPrefix(arg, "--") {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
flagIdx := findFirstFlag(args)
|
||||||
|
if flagIdx >= 0 {
|
||||||
|
flagSet.Parse(args[flagIdx:])
|
||||||
|
return args[:flagIdx]
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadUrlListFile(filePath string) []string {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlList := make([]string, 0)
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
urlList = append(urlList, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlList
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommand(args []string) {
|
||||||
|
searchCmd := flag.NewFlagSet("get", flag.ExitOnError)
|
||||||
|
path := searchCmd.String("o", ".", "output folder of dowloaded file")
|
||||||
|
inputFile := searchCmd.String("i", "", "input file containing a list of urls")
|
||||||
|
|
||||||
|
urlList := parseFlags(searchCmd, args)
|
||||||
|
|
||||||
|
if *inputFile != "" {
|
||||||
|
urlList = append(urlList, loadUrlListFile(*inputFile)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for _, urlStr := range urlList {
|
||||||
|
if strings.HasPrefix(urlStr, "irc://") {
|
||||||
|
url, err := parseIRCFileURl(urlStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
transfer := NewXdccTransfer(*url, *path)
|
||||||
|
go func(transfer *XdccTransfer) {
|
||||||
|
doTransfer(transfer)
|
||||||
|
wg.Done()
|
||||||
|
}(transfer)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
searchCmd := flag.NewFlagSet("foo", flag.ExitOnError)
|
|
||||||
fileName := searchCmd.String("f", "", "name of the file to search")
|
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Println("one of the following subcommands is expected: [search, get]")
|
fmt.Println("one of the following subcommands is expected: [search, get]")
|
||||||
|
|
@ -35,10 +175,9 @@ func main() {
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "search":
|
case "search":
|
||||||
searchCmd.Parse(os.Args[2:])
|
searchCommand(os.Args[2:])
|
||||||
search(*fileName)
|
|
||||||
case "get":
|
case "get":
|
||||||
break
|
getCommand(os.Args[2:])
|
||||||
default:
|
default:
|
||||||
fmt.Println("no such command: ", os.Args[1])
|
fmt.Println("no such command: ", os.Args[1])
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
25
url.go
25
url.go
|
|
@ -14,13 +14,19 @@ type IRCFileURL struct {
|
||||||
Slot int
|
Slot int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IRCBot struct {
|
||||||
|
Network string
|
||||||
|
Channel string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
const ircFileURLFields = 4
|
const ircFileURLFields = 4
|
||||||
|
|
||||||
func parseSlot(slotStr string) (int, error) {
|
func parseSlot(slotStr string) (int, error) {
|
||||||
if !strings.HasPrefix(slotStr, "#") {
|
if strings.HasPrefix(slotStr, "#") {
|
||||||
return -1, errors.New("invalid slot")
|
strconv.Atoi(strings.TrimPrefix(slotStr, "#"))
|
||||||
}
|
}
|
||||||
return strconv.Atoi(strings.TrimPrefix(slotStr, "#"))
|
return strconv.Atoi(slotStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// url has the following format: irc://network/channel/bot/#slot
|
// url has the following format: irc://network/channel/bot/#slot
|
||||||
|
|
@ -39,12 +45,21 @@ func parseIRCFileURl(url string) (*IRCFileURL, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &IRCFileURL{
|
fileUrl := &IRCFileURL{
|
||||||
Network: fields[0],
|
Network: fields[0],
|
||||||
Channel: fields[1],
|
Channel: fields[1],
|
||||||
UserName: fields[2],
|
UserName: fields[2],
|
||||||
Slot: slot,
|
Slot: slot,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(fileUrl.Channel, "#") {
|
||||||
|
fileUrl.Channel = "#" + fileUrl.Channel
|
||||||
|
}
|
||||||
|
return fileUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (url *IRCFileURL) GetBot() IRCBot {
|
||||||
|
return IRCBot{Network: url.Network, Channel: url.Channel, Name: url.UserName}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (url *IRCFileURL) String() string {
|
func (url *IRCFileURL) String() string {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
irc "github.com/fluffle/goirc/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
const IRCClientUserName = "xdcc-cli"
|
||||||
|
|
||||||
|
type CTCPRequest interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CTCPResponse interface {
|
||||||
|
Parse(args []string) error
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type XdccSendReq struct {
|
||||||
|
Slot int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (send *XdccSendReq) String() string {
|
||||||
|
return fmt.Sprintf("xdcc send #%d", send.Slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XdccSendRes struct {
|
||||||
|
FileName string
|
||||||
|
IP net.IP
|
||||||
|
Port int
|
||||||
|
FileSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint32ToIP(n int) net.IP {
|
||||||
|
a := byte((n >> 24) & 255)
|
||||||
|
b := byte((n >> 16) & 255)
|
||||||
|
c := byte((n >> 8) & 255)
|
||||||
|
d := byte(n & 255)
|
||||||
|
return net.IPv4(a, b, c, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
const XdccSendResArgs = 4
|
||||||
|
|
||||||
|
func (send *XdccSendRes) Name() string {
|
||||||
|
return SEND
|
||||||
|
}
|
||||||
|
|
||||||
|
func (send *XdccSendRes) Parse(args []string) error {
|
||||||
|
if len(args) != XdccSendResArgs {
|
||||||
|
return errors.New("invalid number of arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
send.FileName = args[0]
|
||||||
|
|
||||||
|
ipUint32, err := strconv.Atoi(args[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
send.IP = uint32ToIP(ipUint32)
|
||||||
|
send.Port, err = strconv.Atoi(args[2])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
send.FileSize, err = strconv.Atoi(args[3])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SEND = "SEND"
|
||||||
|
VERSION = "\x01VERSION\x01"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseCTCPRes(text string) (CTCPResponse, error) {
|
||||||
|
fields := strings.Fields(text)
|
||||||
|
|
||||||
|
var resp CTCPResponse = nil
|
||||||
|
|
||||||
|
switch strings.TrimSpace(fields[0]) {
|
||||||
|
case SEND:
|
||||||
|
resp = &XdccSendRes{}
|
||||||
|
case VERSION:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return nil, errors.New("invalid command: " + fields[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
err := resp.Parse(fields[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultEventChanSize = 1024
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) Start() error {
|
||||||
|
return transfer.conn.Connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
type XdccEvent interface{}
|
||||||
|
|
||||||
|
type TransferAbortedEvent struct {
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
|
type XdccTransfer struct {
|
||||||
|
filePath string
|
||||||
|
url IRCFileURL
|
||||||
|
conn *irc.Conn
|
||||||
|
started bool
|
||||||
|
events chan XdccEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferManager struct {
|
||||||
|
transfers map[IRCBot]*XdccTransfer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TransferManager) addTransfer(fileUrl *IRCFileURL, filePath string) {
|
||||||
|
transfer, ok := tm.transfers[fileUrl.GetBot()]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
transfer = NewXdccTransfer(*fileUrl, filePath)
|
||||||
|
tm.transfers[fileUrl.GetBot()] = transfer
|
||||||
|
// add transfer
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXdccTransfer(url IRCFileURL, filePath string) *XdccTransfer {
|
||||||
|
conn := irc.SimpleClient(IRCClientUserName + strconv.Itoa(int(rand.Uint32())))
|
||||||
|
conn.Config().Server = url.Network
|
||||||
|
conn.Config().NewNick = func(nick string) string {
|
||||||
|
return nick + "" + strconv.Itoa(int(rand.Uint32()))
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &XdccTransfer{
|
||||||
|
conn: conn,
|
||||||
|
url: url,
|
||||||
|
filePath: filePath,
|
||||||
|
started: false,
|
||||||
|
events: make(chan XdccEvent, defaultEventChanSize),
|
||||||
|
}
|
||||||
|
t.setupHandlers(url.Channel, url.UserName, url.Slot)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) send(req CTCPRequest) {
|
||||||
|
transfer.conn.Privmsg(transfer.url.UserName, req.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) setupHandlers(channel string, userName string, slot int) {
|
||||||
|
conn := transfer.conn
|
||||||
|
|
||||||
|
// e.g. join channel on connect.
|
||||||
|
conn.HandleFunc(irc.CONNECTED,
|
||||||
|
func(conn *irc.Conn, line *irc.Line) {
|
||||||
|
fmt.Println("connected ", channel)
|
||||||
|
conn.Join(channel)
|
||||||
|
})
|
||||||
|
|
||||||
|
// send xdcc send on successfull join
|
||||||
|
conn.HandleFunc(irc.JOIN,
|
||||||
|
func(conn *irc.Conn, line *irc.Line) {
|
||||||
|
if line.Args[0] == channel && !transfer.started {
|
||||||
|
fmt.Println("contacting ", transfer.url.UserName)
|
||||||
|
transfer.send(&XdccSendReq{Slot: slot})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.HandleFunc(irc.CTCP,
|
||||||
|
func(conn *irc.Conn, line *irc.Line) {
|
||||||
|
fmt.Println(line.Text())
|
||||||
|
res, err := parseCTCPRes(line.Text())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1) // TODO: correct clean up
|
||||||
|
}
|
||||||
|
transfer.handleCTCPRes(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.HandleFunc(irc.DISCONNECTED,
|
||||||
|
func(conn *irc.Conn, line *irc.Line) {
|
||||||
|
if !transfer.started {
|
||||||
|
transfer.notifyEvent(&TransferAbortedEvent{Error: "disconnected from server"})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) PollEvents() chan XdccEvent {
|
||||||
|
return transfer.events
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferProgessEvent struct {
|
||||||
|
transferBytes uint64
|
||||||
|
transferRate float32
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadBufSize = 1024
|
||||||
|
|
||||||
|
type TransferStartedEvent struct {
|
||||||
|
FileName string
|
||||||
|
FileSize uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferCompletedEvent struct{}
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) notifyEvent(e XdccEvent) {
|
||||||
|
select {
|
||||||
|
case transfer.events <- e:
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpeedMonitorReader struct {
|
||||||
|
reader io.Reader
|
||||||
|
elapsedTime time.Duration
|
||||||
|
currValue uint64
|
||||||
|
currentSpeed float64
|
||||||
|
onUpdate func(amount int, speed float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSpeedMonitorReader(reader io.Reader, onUpdate func(int, float64)) *SpeedMonitorReader {
|
||||||
|
return &SpeedMonitorReader{
|
||||||
|
reader: reader,
|
||||||
|
elapsedTime: time.Duration(0),
|
||||||
|
currValue: 0,
|
||||||
|
currentSpeed: 0,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (monitor *SpeedMonitorReader) Read(buf []byte) (int, error) {
|
||||||
|
now := time.Now()
|
||||||
|
n, err := monitor.reader.Read(buf)
|
||||||
|
elapsedTime := time.Since(now)
|
||||||
|
monitor.currValue += uint64(n)
|
||||||
|
monitor.elapsedTime += elapsedTime
|
||||||
|
|
||||||
|
if monitor.elapsedTime > time.Second {
|
||||||
|
monitor.currentSpeed = float64(monitor.currValue) / monitor.elapsedTime.Seconds()
|
||||||
|
monitor.onUpdate(int(monitor.currValue), monitor.currentSpeed)
|
||||||
|
monitor.currValue = 0
|
||||||
|
monitor.elapsedTime = time.Duration(0)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) handleXdccSendRes(send *XdccSendRes) {
|
||||||
|
go func() {
|
||||||
|
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: send.IP, Port: send.Port})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to reach host %s:%d", send.IP.String(), send.Port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(transfer.filePath+"/"+send.FileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
fileWriter := bufio.NewWriter(file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer.notifyEvent(&TransferStartedEvent{
|
||||||
|
FileName: send.FileName,
|
||||||
|
FileSize: uint64(send.FileSize),
|
||||||
|
})
|
||||||
|
transfer.started = true
|
||||||
|
|
||||||
|
reader := NewSpeedMonitorReader(conn, func(dowloadedAmount int, speed float64) {
|
||||||
|
transfer.notifyEvent(&TransferProgessEvent{
|
||||||
|
transferRate: float32(speed),
|
||||||
|
transferBytes: uint64(dowloadedAmount),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// download loop
|
||||||
|
downloadedBytesTotal := 0
|
||||||
|
buf := make([]byte, downloadBufSize)
|
||||||
|
for downloadedBytesTotal < send.FileSize {
|
||||||
|
n, err := reader.Read(buf)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fileWriter.Write(buf[:n]); err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadedBytesTotal += n
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer.notifyEvent(&TransferCompletedEvent{})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transfer *XdccTransfer) handleCTCPRes(resp CTCPResponse) {
|
||||||
|
switch r := resp.(type) {
|
||||||
|
case *XdccSendRes:
|
||||||
|
transfer.handleXdccSendRes(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue