2019-07-20 10:26:52 +00:00
|
|
|
package sftpd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/drakkan/sftpgo/dataprovider"
|
|
|
|
"github.com/drakkan/sftpgo/logger"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
transferUpload = iota
|
|
|
|
transferDownload
|
|
|
|
)
|
|
|
|
|
2019-07-30 18:51:29 +00:00
|
|
|
// Transfer contains the transfer details for an upload or a download.
|
|
|
|
// It implements the io Reader and Writer interface to handle files downloads and uploads
|
2019-07-20 10:26:52 +00:00
|
|
|
type Transfer struct {
|
|
|
|
file *os.File
|
|
|
|
path string
|
|
|
|
start time.Time
|
|
|
|
bytesSent int64
|
|
|
|
bytesReceived int64
|
|
|
|
user dataprovider.User
|
|
|
|
connectionID string
|
|
|
|
transferType int
|
|
|
|
lastActivity time.Time
|
|
|
|
isNewFile bool
|
|
|
|
}
|
|
|
|
|
2019-07-30 18:51:29 +00:00
|
|
|
// ReadAt reads len(p) bytes from the File to download starting at byte offset off and updates the bytes sent.
|
|
|
|
// It handles download bandwidth throttling too
|
2019-07-20 10:26:52 +00:00
|
|
|
func (t *Transfer) ReadAt(p []byte, off int64) (n int, err error) {
|
2019-07-27 07:38:09 +00:00
|
|
|
t.lastActivity = time.Now()
|
2019-07-20 10:26:52 +00:00
|
|
|
readed, e := t.file.ReadAt(p, off)
|
|
|
|
t.bytesSent += int64(readed)
|
|
|
|
t.handleThrottle()
|
|
|
|
return readed, e
|
|
|
|
}
|
|
|
|
|
2019-07-30 18:51:29 +00:00
|
|
|
// WriteAt writes len(p) bytes to the uploaded file starting at byte offset off and updates the bytes received.
|
|
|
|
// It handles upload bandwidth throttling too
|
2019-07-20 10:26:52 +00:00
|
|
|
func (t *Transfer) WriteAt(p []byte, off int64) (n int, err error) {
|
2019-07-27 07:38:09 +00:00
|
|
|
t.lastActivity = time.Now()
|
2019-07-20 10:26:52 +00:00
|
|
|
written, e := t.file.WriteAt(p, off)
|
|
|
|
t.bytesReceived += int64(written)
|
|
|
|
t.handleThrottle()
|
|
|
|
return written, e
|
|
|
|
}
|
|
|
|
|
2019-07-30 18:51:29 +00:00
|
|
|
// Close it is called when the transfer is completed.
|
|
|
|
// It closes the underlying file, log the transfer info, update the user quota, for uploads, and execute any defined actions.
|
2019-07-20 10:26:52 +00:00
|
|
|
func (t *Transfer) Close() error {
|
2019-07-27 09:17:32 +00:00
|
|
|
err := t.file.Close()
|
2019-07-20 10:26:52 +00:00
|
|
|
elapsed := time.Since(t.start).Nanoseconds() / 1000000
|
|
|
|
if t.transferType == transferDownload {
|
|
|
|
logger.TransferLog(sftpdDownloadLogSender, t.path, elapsed, t.bytesSent, t.user.Username, t.connectionID)
|
2019-07-27 07:38:09 +00:00
|
|
|
executeAction(operationDownload, t.user.Username, t.path, "")
|
2019-07-20 10:26:52 +00:00
|
|
|
} else {
|
|
|
|
logger.TransferLog(sftpUploadLogSender, t.path, elapsed, t.bytesReceived, t.user.Username, t.connectionID)
|
2019-07-27 07:38:09 +00:00
|
|
|
executeAction(operationUpload, t.user.Username, t.path, "")
|
2019-07-20 10:26:52 +00:00
|
|
|
}
|
|
|
|
removeTransfer(t)
|
2019-07-28 11:24:46 +00:00
|
|
|
if t.transferType == transferUpload {
|
2019-07-20 10:26:52 +00:00
|
|
|
numFiles := 0
|
2019-07-28 11:24:46 +00:00
|
|
|
if t.isNewFile {
|
|
|
|
numFiles = 1
|
|
|
|
}
|
2019-07-28 20:04:50 +00:00
|
|
|
dataprovider.UpdateUserQuota(dataProvider, t.user, numFiles, t.bytesReceived, false)
|
2019-07-20 10:26:52 +00:00
|
|
|
}
|
2019-07-27 09:17:32 +00:00
|
|
|
return err
|
2019-07-20 10:26:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Transfer) handleThrottle() {
|
|
|
|
var wantedBandwidth int64
|
|
|
|
var trasferredBytes int64
|
|
|
|
if t.transferType == transferDownload {
|
|
|
|
wantedBandwidth = t.user.DownloadBandwidth
|
|
|
|
trasferredBytes = t.bytesSent
|
|
|
|
} else {
|
|
|
|
wantedBandwidth = t.user.UploadBandwidth
|
|
|
|
trasferredBytes = t.bytesReceived
|
|
|
|
}
|
|
|
|
if wantedBandwidth > 0 {
|
|
|
|
// real and wanted elapsed as milliseconds, bytes as kilobytes
|
|
|
|
realElapsed := time.Since(t.start).Nanoseconds() / 1000000
|
2019-07-27 07:38:09 +00:00
|
|
|
// trasferredBytes / 1000 = KB/s, we multiply for 1000 to get milliseconds
|
2019-07-20 10:26:52 +00:00
|
|
|
wantedElapsed := 1000 * (trasferredBytes / 1000) / wantedBandwidth
|
|
|
|
if wantedElapsed > realElapsed {
|
|
|
|
toSleep := time.Duration(wantedElapsed - realElapsed)
|
|
|
|
time.Sleep(toSleep * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|