Implement DCC streaming

This commit is contained in:
Ken-Håvard Lieng 2020-05-20 07:21:12 +02:00
parent 973578bb49
commit e33b9f05e4
15 changed files with 407 additions and 218 deletions

File diff suppressed because one or more lines are too long

View File

@ -134,6 +134,22 @@ export default function handleSocket({
}
},
dcc_send({ server, from, filename, url }) {
const serverName = getState().servers[server]?.name || server;
dispatch(
openModal('confirm', {
question: `${from} on ${serverName} is sending you: ${filename}`,
confirmation: 'Download',
onConfirm: () => {
const a = document.createElement('a');
a.href = url;
a.click();
}
})
);
},
_connected(connected) {
dispatch(setConnected(connected));
}

View File

@ -109,8 +109,14 @@ func init() {
viper.BindPFlags(rootCmd.PersistentFlags())
viper.BindPFlags(rootCmd.Flags())
viper.SetDefault("hexIP", false)
viper.SetDefault("verify_certificates", true)
viper.SetDefault("https.enabled", true)
viper.SetDefault("https.port", 443)
viper.SetDefault("auth.anonymous", true)
viper.SetDefault("auth.login", true)
viper.SetDefault("auth.registration", true)
viper.SetDefault("dcc.enabled", true)
viper.SetDefault("dcc.autoget.delete", true)
}
func initConfig(configPath string, overwrite bool) {

View File

@ -4,8 +4,6 @@ port = 80
# Hex encode the users IP and use it as the ident
hexIP = false
verify_certificates = true
# Automatically download files from DCC SENDs
autoget = false
# Defaults for the client connect form
[defaults]
@ -63,6 +61,20 @@ secret = ""
key = ""
secret = ""
[dcc]
# Receive files through DCC, the user gets to choose if they want to accept the file,
# the file then gets streamed to the user
enabled = true
[dcc.autoget]
# Instead of streaming the file to the user, dispatch automatically downloads
# DCC files and sends a download link to the user once its done
enabled = false
# Delete the file after the user has downloaded it once
delete = true
# Delete the file after a certain time period of inactivity, not implemented yet
delete_after = "30m"
# Strict-Transport-Security
[https.hsts]
enabled = false

View File

@ -14,12 +14,12 @@ type Config struct {
Dev bool
HexIP bool
VerifyCertificates bool `mapstructure:"verify_certificates"`
Autoget bool
Headers map[string]string
Defaults Defaults
HTTPS HTTPS
LetsEncrypt LetsEncrypt
Auth Auth
DCC DCC
}
type Defaults struct {
@ -65,6 +65,17 @@ type Provider struct {
Secret string
}
type DCC struct {
Enabled bool
Autoget Autoget
}
type Autoget struct {
Enabled bool
Delete bool
DeleteAfter time.Duration `mapstructure:"delete_after"`
}
func LoadConfig() (*Config, chan *Config) {
viper.SetConfigName("config")
viper.AddConfigPath(storage.Path.ConfigRoot())

View File

@ -41,7 +41,6 @@ func Serve(handler http.Handler, cfg Config) error {
httpsSrv := &http.Server{
Addr: net.JoinHostPort(cfg.Addr, cfg.PortHTTPS),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
Handler: handler,
}
@ -101,7 +100,6 @@ func Serve(handler http.Handler, cfg Config) error {
}
} else {
httpSrv.ReadTimeout = 5 * time.Second
httpSrv.WriteTimeout = 10 * time.Second
httpSrv.IdleTimeout = 120 * time.Second
httpSrv.Handler = handler

View File

@ -26,12 +26,8 @@ type Client struct {
// Source is the reply to SOURCE CTCP messages
Source string
DownloadFolder string
Autoget bool
Messages chan *Message
ConnectionChanged chan ConnectionState
Progress chan DownloadProgress
Features *Features
nick string
channels []string
@ -59,7 +55,6 @@ func NewClient(nick, username string) *Client {
Realname: nick,
Messages: make(chan *Message, 32),
ConnectionChanged: make(chan ConnectionState, 4),
Progress: make(chan DownloadProgress, 4),
out: make(chan string, 32),
quit: make(chan struct{}),
reconnect: make(chan struct{}),

View File

@ -47,13 +47,6 @@ func (c *Client) handleCTCP(ctcp *CTCP, msg *Message) {
case "CLIENTINFO":
c.ReplyCTCP(msg.Nick, ctcp.Command, ClientInfo)
case "DCC":
if strings.HasPrefix(ctcp.Params, "SEND") {
if dccSend := ParseDCCSend(ctcp); dccSend != nil {
go c.Download(dccSend)
}
}
case "FINGER", "VERSION":
if c.Version != "" {
c.ReplyCTCP(msg.Nick, ctcp.Command, c.Version)

View File

@ -2,14 +2,11 @@ package irc
import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
"math"
"net"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@ -52,27 +49,16 @@ func ParseDCCSend(ctcp *CTCP) *DCCSend {
return nil
}
func (c *Client) Download(pack *DCCSend) {
if !c.Autoget {
// TODO: ask user if he/she wants to download the file
return
}
c.Progress <- DownloadProgress{
func DownloadDCC(w io.Writer, pack *DCCSend, progress chan DownloadProgress) error {
if progress != nil {
progress <- DownloadProgress{
File: pack.File,
}
file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
c.downloadFailed(pack, err)
return
}
defer file.Close()
conn, err := net.Dial("tcp", net.JoinHostPort(pack.IP, pack.Port))
if err != nil {
c.downloadFailed(pack, err)
return
return err
}
defer conn.Close()
@ -87,24 +73,26 @@ func (c *Client) Download(pack *DCCSend) {
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
c.downloadFailed(pack, err)
return
return err
}
if n == 0 {
break
}
}
if _, err := file.Write(buf[:n]); err != nil {
c.downloadFailed(pack, err)
return
if _, err := w.Write(buf[:n]); err != nil {
return err
}
accBytes += uint64(n)
totalBytes += uint64(n)
conn.Write(uint64Bytes(totalBytes))
_, err = conn.Write(uint64Bytes(totalBytes))
if err != nil {
return err
}
if progress != nil {
if dt := time.Since(prevUpdate); dt >= time.Second {
prevUpdate = time.Now()
@ -119,7 +107,7 @@ func (c *Client) Download(pack *DCCSend) {
bytesRemaining := float64(pack.Length - totalBytes)
percentage := 100 * (float64(totalBytes) / float64(pack.Length))
c.Progress <- DownloadProgress{
progress <- DownloadProgress{
Speed: humanReadableByteCount(averageSpeed, true),
PercCompletion: percentage,
BytesRemaining: humanReadableByteCount(bytesRemaining, false),
@ -130,21 +118,18 @@ func (c *Client) Download(pack *DCCSend) {
}
}
}
}
c.Progress <- DownloadProgress{
if progress != nil {
progress <- DownloadProgress{
PercCompletion: 100,
BytesCompleted: humanReadableByteCount(float64(totalBytes), false),
SecondsElapsed: secondsSince(start),
File: pack.File,
}
}
func (c *Client) downloadFailed(pack *DCCSend, err error) {
c.Progress <- DownloadProgress{
PercCompletion: -1,
File: pack.File,
Error: err,
}
return nil
}
type DownloadProgress struct {
@ -158,14 +143,6 @@ type DownloadProgress struct {
SecondsToGo float64 `json:"eta"`
}
func (p DownloadProgress) ToJSON() string {
progress, err := json.Marshal(p)
if err != nil {
return ""
}
return string(progress)
}
func intToIP(n int) string {
var byte1 = n & 255
var byte2 = ((n >> 8) & 255)

View File

@ -73,9 +73,6 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
}
}
i.DownloadFolder = storage.Path.Downloads(state.user.Username)
i.Autoget = cfg.Autoget
state.setIRC(server.Host, i)
i.Connect(address)
go newIRCHandler(i, state).run()

View File

@ -3,8 +3,10 @@ package server
import (
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"unicode"
"github.com/kjk/betterguid"
@ -26,6 +28,7 @@ type ircHandler struct {
userBuffers map[string][]string
motdBuffer MOTD
listBuffer storage.ChannelListIndex
dccProgress chan irc.DownloadProgress
handlers map[string]func(*irc.Message)
}
@ -35,6 +38,7 @@ func newIRCHandler(client *irc.Client, state *State) *ircHandler {
client: client,
state: state,
userBuffers: make(map[string][]string),
dccProgress: make(chan irc.DownloadProgress, 4),
}
i.initHandlers()
return i
@ -64,7 +68,7 @@ func (i *ircHandler) run() {
i.log("Connected")
}
case progress := <-i.client.Progress:
case progress := <-i.dccProgress:
if progress.Error != nil {
i.sendDCCInfo("%s: Download failed (%s)", true, progress.File, progress.Error)
} else if progress.PercCompletion == 100 {
@ -175,10 +179,46 @@ func (i *ircHandler) mode(msg *irc.Message) {
}
}
func (i *ircHandler) message(msg *irc.Message) {
if ctcp := msg.ToCTCP(); ctcp != nil && ctcp.Command != "ACTION" {
func (i *ircHandler) receiveDCCSend(pack *irc.DCCSend, msg *irc.Message) {
cfg := i.state.srv.Config()
if cfg.DCC.Enabled {
if cfg.DCC.Autoget.Enabled {
file, err := os.OpenFile(storage.Path.DownloadedFile(i.state.user.Username, pack.File), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return
}
defer file.Close()
irc.DownloadDCC(file, pack, i.dccProgress)
} else {
i.state.setPendingDCC(pack.File, pack)
i.state.sendJSON("dcc_send", DCCSend{
Server: i.client.Host,
From: msg.Nick,
Filename: pack.File,
URL: fmt.Sprintf("%s://%s/downloads/%s/%s",
i.state.String("scheme"), i.state.String("host"), i.state.user.Username, pack.File),
})
time.Sleep(150 * time.Second)
i.state.deletePendingDCC(pack.File)
}
}
}
func (i *ircHandler) message(msg *irc.Message) {
if ctcp := msg.ToCTCP(); ctcp != nil {
if ctcp.Command == "DCC" && strings.HasPrefix(ctcp.Params, "SEND") {
if pack := irc.ParseDCCSend(ctcp); pack != nil {
go i.receiveDCCSend(pack, msg)
return
}
} else if ctcp.Command != "ACTION" {
return
}
}
message := Message{
ID: betterguid.New(),
@ -431,8 +471,7 @@ func (i *ircHandler) initHandlers() {
}
func (i *ircHandler) log(v ...interface{}) {
s := fmt.Sprintln(v...)
log.Println("[IRC]", i.state.user.ID, i.client.Host, s[:len(s)-1])
log.Println("[IRC]", i.state.user.ID, i.client.Host, fmt.Sprint(v...))
}
func (i *ircHandler) sendDCCInfo(message string, log bool, a ...interface{}) {

View File

@ -222,6 +222,13 @@ type ChannelForward struct {
New string
}
type DCCSend struct {
Server string
From string
Filename string
URL string
}
type Tab struct {
storage.Tab
}

View File

@ -3060,7 +3060,110 @@ func (v *Error) UnmarshalJSON(data []byte) error {
func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer27(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, out *ConnectionUpdate) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, out *DCCSend) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "server":
out.Server = string(in.String())
case "from":
out.From = string(in.String())
case "filename":
out.Filename = string(in.String())
case "url":
out.URL = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer, in DCCSend) {
out.RawByte('{')
first := true
_ = first
if in.Server != "" {
const prefix string = ",\"server\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Server))
}
if in.From != "" {
const prefix string = ",\"from\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.From))
}
if in.Filename != "" {
const prefix string = ",\"filename\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Filename))
}
if in.URL != "" {
const prefix string = ",\"url\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.URL))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v DCCSend) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v DCCSend) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *DCCSend) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *DCCSend) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, out *ConnectionUpdate) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3097,7 +3200,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer, in ConnectionUpdate) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer, in ConnectionUpdate) {
out.RawByte('{')
first := true
_ = first
@ -3143,27 +3246,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v ConnectionUpdate) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ConnectionUpdate) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ConnectionUpdate) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ConnectionUpdate) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, out *ClientCert) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer30(in *jlexer.Lexer, out *ClientCert) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3196,7 +3299,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer, in ClientCert) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer30(out *jwriter.Writer, in ClientCert) {
out.RawByte('{')
first := true
_ = first
@ -3222,27 +3325,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v ClientCert) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer30(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ClientCert) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer30(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ClientCert) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer30(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ClientCert) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer30(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer30(in *jlexer.Lexer, out *ChannelSearchResult) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer31(in *jlexer.Lexer, out *ChannelSearchResult) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3308,7 +3411,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer30(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer30(out *jwriter.Writer, in ChannelSearchResult) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer31(out *jwriter.Writer, in ChannelSearchResult) {
out.RawByte('{')
first := true
_ = first
@ -3367,25 +3470,25 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer30(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v ChannelSearchResult) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer30(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer31(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelSearchResult) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer30(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer31(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelSearchResult) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer30(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer31(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelSearchResult) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer30(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer31(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchStorage1(in *jlexer.Lexer, out *storage.ChannelListItem) {
isTopLevel := in.IsStart()
@ -3454,7 +3557,7 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchStorage1(out *jwriter.Writer,
}
out.RawByte('}')
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer31(in *jlexer.Lexer, out *ChannelSearch) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer32(in *jlexer.Lexer, out *ChannelSearch) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3489,7 +3592,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer31(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer31(out *jwriter.Writer, in ChannelSearch) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer32(out *jwriter.Writer, in ChannelSearch) {
out.RawByte('{')
first := true
_ = first
@ -3525,27 +3628,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer31(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v ChannelSearch) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer31(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer32(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelSearch) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer31(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer32(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelSearch) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer31(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer32(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelSearch) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer31(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer32(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer32(in *jlexer.Lexer, out *ChannelForward) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer33(in *jlexer.Lexer, out *ChannelForward) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3580,7 +3683,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer32(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer32(out *jwriter.Writer, in ChannelForward) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer33(out *jwriter.Writer, in ChannelForward) {
out.RawByte('{')
first := true
_ = first
@ -3616,27 +3719,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer32(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v ChannelForward) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer32(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer33(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelForward) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer32(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer33(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelForward) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer32(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer33(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelForward) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer32(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer33(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer33(in *jlexer.Lexer, out *Away) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer34(in *jlexer.Lexer, out *Away) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3669,7 +3772,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer33(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer33(out *jwriter.Writer, in Away) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer34(out *jwriter.Writer, in Away) {
out.RawByte('{')
first := true
_ = first
@ -3695,23 +3798,23 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer33(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v Away) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer33(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer34(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Away) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer33(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer34(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Away) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer33(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer34(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Away) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer33(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer34(l, v)
}

View File

@ -3,6 +3,7 @@ package server
import (
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
@ -10,6 +11,7 @@ import (
"github.com/gorilla/websocket"
"github.com/khlieng/dispatch/config"
"github.com/khlieng/dispatch/pkg/https"
"github.com/khlieng/dispatch/pkg/irc"
"github.com/khlieng/dispatch/pkg/session"
"github.com/khlieng/dispatch/storage"
)
@ -202,9 +204,21 @@ func (d *Dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
filename := params[2]
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
http.ServeFile(w, r, storage.Path.DownloadedFile(state.user.Username, filename))
if pack, ok := state.getPendingDCC(filename); ok {
state.deletePendingDCC(filename)
w.Header().Set("Content-Length", strconv.FormatUint(pack.Length, 10))
irc.DownloadDCC(w, pack, nil)
} else {
file := storage.Path.DownloadedFile(state.user.Username, filename)
http.ServeFile(w, r, file)
if d.Config().DCC.Autoget.Delete {
os.Remove(file)
}
}
} else {
fail(w, http.StatusNotFound)
}

View File

@ -22,6 +22,7 @@ type State struct {
irc map[string]*irc.Client
connectionState map[string]irc.ConnectionState
pendingDCCSends map[string]*irc.DCCSend
ircLock sync.Mutex
ws map[string]*wsConn
@ -39,6 +40,7 @@ func NewState(user *storage.User, srv *Dispatch) *State {
stateData: stateData{m: map[string]interface{}{}},
irc: make(map[string]*irc.Client),
connectionState: make(map[string]irc.ConnectionState),
pendingDCCSends: make(map[string]*irc.DCCSend),
ws: make(map[string]*wsConn),
broadcast: make(chan WSResponse, 32),
srv: srv,
@ -102,6 +104,25 @@ func (s *State) setConnectionState(server string, state irc.ConnectionState) {
s.ircLock.Unlock()
}
func (s *State) getPendingDCC(filename string) (*irc.DCCSend, bool) {
s.ircLock.Lock()
pack, ok := s.pendingDCCSends[filename]
s.ircLock.Unlock()
return pack, ok
}
func (s *State) setPendingDCC(filename string, pack *irc.DCCSend) {
s.ircLock.Lock()
s.pendingDCCSends[filename] = pack
s.ircLock.Unlock()
}
func (s *State) deletePendingDCC(filename string) {
s.ircLock.Lock()
delete(s.pendingDCCSends, filename)
s.ircLock.Unlock()
}
func (s *State) setWS(addr string, w *wsConn) {
s.wsLock.Lock()
s.ws[addr] = w