148 lines
3.2 KiB
Go
148 lines
3.2 KiB
Go
|
|
package notify
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/hex"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func computeFileChecksum(path string) (string, error) {
|
||
|
|
fd, err := os.Open(path)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
defer fd.Close()
|
||
|
|
h := sha256.New()
|
||
|
|
if _, err := io.Copy(h, fd); err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func filePacketModTime(packet FilePacket) time.Time {
|
||
|
|
if packet.ModTime <= 0 {
|
||
|
|
return time.Time{}
|
||
|
|
}
|
||
|
|
return time.Unix(0, packet.ModTime)
|
||
|
|
}
|
||
|
|
|
||
|
|
func applyReceivedFileMeta(path string, mode os.FileMode, modTime time.Time) {
|
||
|
|
if mode != 0 {
|
||
|
|
_ = os.Chmod(path, mode.Perm())
|
||
|
|
}
|
||
|
|
if !modTime.IsZero() {
|
||
|
|
_ = os.Chtimes(path, modTime, modTime)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func sanitizeFileName(name string) string {
|
||
|
|
trimmed := strings.TrimSpace(name)
|
||
|
|
if trimmed == "" {
|
||
|
|
return "unnamed"
|
||
|
|
}
|
||
|
|
trimmed = strings.ReplaceAll(trimmed, "/", "_")
|
||
|
|
trimmed = strings.ReplaceAll(trimmed, "\\", "_")
|
||
|
|
trimmed = strings.ReplaceAll(trimmed, ":", "_")
|
||
|
|
return trimmed
|
||
|
|
}
|
||
|
|
|
||
|
|
func shortFileIDSuffix(fileID string) string {
|
||
|
|
cleaned := sanitizeFileName(fileID)
|
||
|
|
if len(cleaned) > 12 {
|
||
|
|
return cleaned[:12]
|
||
|
|
}
|
||
|
|
if cleaned == "" {
|
||
|
|
return "copy"
|
||
|
|
}
|
||
|
|
return cleaned
|
||
|
|
}
|
||
|
|
|
||
|
|
func pathExists(path string) bool {
|
||
|
|
_, err := os.Stat(path)
|
||
|
|
return err == nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *fileReceivePool) receiveDirLocked() string {
|
||
|
|
if p.dir != "" {
|
||
|
|
return p.dir
|
||
|
|
}
|
||
|
|
return os.TempDir()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *fileReceivePool) uniqueFinalPathLocked(baseDir string, name string, fileID string) string {
|
||
|
|
cleanName := sanitizeFileName(filepath.Base(name))
|
||
|
|
if cleanName == "" {
|
||
|
|
cleanName = "unnamed.bin"
|
||
|
|
}
|
||
|
|
ext := filepath.Ext(cleanName)
|
||
|
|
base := strings.TrimSuffix(cleanName, ext)
|
||
|
|
candidate := filepath.Join(baseDir, cleanName)
|
||
|
|
if !p.pathReservedLocked(candidate) && !pathExists(candidate) {
|
||
|
|
return candidate
|
||
|
|
}
|
||
|
|
suffix := shortFileIDSuffix(fileID)
|
||
|
|
candidate = filepath.Join(baseDir, fmt.Sprintf("%s.%s%s", base, suffix, ext))
|
||
|
|
if !p.pathReservedLocked(candidate) && !pathExists(candidate) {
|
||
|
|
return candidate
|
||
|
|
}
|
||
|
|
for i := 1; ; i++ {
|
||
|
|
candidate = filepath.Join(baseDir, fmt.Sprintf("%s.%s.%d%s", base, suffix, i, ext))
|
||
|
|
if !p.pathReservedLocked(candidate) && !pathExists(candidate) {
|
||
|
|
return candidate
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *fileReceivePool) pathReservedLocked(path string) bool {
|
||
|
|
for _, session := range p.sessions {
|
||
|
|
if session.finalPath == path || session.tmpPath == path {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *fileReceivePool) trimCompletedLocked() {
|
||
|
|
if p.completedLimit <= 0 || len(p.completed) <= p.completedLimit {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
for len(p.completed) > p.completedLimit {
|
||
|
|
oldestKey := ""
|
||
|
|
oldestTime := time.Time{}
|
||
|
|
for key, session := range p.completed {
|
||
|
|
candidateTime := completedFileReceiveTime(session)
|
||
|
|
if oldestKey == "" || candidateTime.Before(oldestTime) || (candidateTime.Equal(oldestTime) && key < oldestKey) {
|
||
|
|
oldestKey = key
|
||
|
|
oldestTime = candidateTime
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if oldestKey == "" {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
delete(p.completed, oldestKey)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func completedFileReceiveTime(session *fileReceiveSession) time.Time {
|
||
|
|
if session == nil {
|
||
|
|
return time.Time{}
|
||
|
|
}
|
||
|
|
if !session.updatedAt.IsZero() {
|
||
|
|
return session.updatedAt
|
||
|
|
}
|
||
|
|
return session.startedAt
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *fileReceiveSession) copy() *fileReceiveSession {
|
||
|
|
if s == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
dup := *s
|
||
|
|
return &dup
|
||
|
|
}
|