notify/file_receive_fs.go

148 lines
3.2 KiB
Go
Raw Permalink Normal View History

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
}