]> git.r.bdr.sh - rbdr/mobius/blob - hotline/file_transfer.go
Read banner once at startup
[rbdr/mobius] / hotline / file_transfer.go
1 package hotline
2
3 import (
4 "encoding/binary"
5 "fmt"
6 "math"
7 "math/rand"
8 "path/filepath"
9 "sync"
10 )
11
12 // File transfer types
13 const (
14 FileDownload = 0
15 FileUpload = 1
16 FolderDownload = 2
17 FolderUpload = 3
18 bannerDownload = 4
19 )
20
21 type FileTransfer struct {
22 FileName []byte
23 FilePath []byte
24 ReferenceNumber []byte
25 refNum [4]byte
26 Type int
27 TransferSize []byte
28 FolderItemCount []byte
29 fileResumeData *FileResumeData
30 options []byte
31 bytesSentCounter *WriteCounter
32 ClientConn *ClientConn
33 }
34
35 // WriteCounter counts the number of bytes written to it.
36 type WriteCounter struct {
37 mux sync.Mutex
38 Total int64 // Total # of bytes written
39 }
40
41 // Write implements the io.Writer interface.
42 //
43 // Always completes and never returns an error.
44 func (wc *WriteCounter) Write(p []byte) (int, error) {
45 wc.mux.Lock()
46 defer wc.mux.Unlock()
47 n := len(p)
48 wc.Total += int64(n)
49 return n, nil
50 }
51
52 func (cc *ClientConn) newFileTransfer(transferType int, fileName, filePath, size []byte) *FileTransfer {
53 var transactionRef [4]byte
54 rand.Read(transactionRef[:])
55
56 ft := &FileTransfer{
57 FileName: fileName,
58 FilePath: filePath,
59 ReferenceNumber: transactionRef[:],
60 refNum: transactionRef,
61 Type: transferType,
62 TransferSize: size,
63 ClientConn: cc,
64 bytesSentCounter: &WriteCounter{},
65 }
66
67 cc.transfersMU.Lock()
68 defer cc.transfersMU.Unlock()
69 cc.transfers[transferType][transactionRef] = ft
70
71 cc.Server.mux.Lock()
72 defer cc.Server.mux.Unlock()
73 cc.Server.fileTransfers[transactionRef] = ft
74
75 return ft
76 }
77
78 // String returns a string representation of a file transfer and its progress for display in the GetInfo window
79 // Example:
80 // MasterOfOrionII1.4.0. 0% 197.9M
81 func (ft *FileTransfer) String() string {
82 trunc := fmt.Sprintf("%.21s", ft.FileName)
83 return fmt.Sprintf("%-21s %.3s%% %6s\n", trunc, ft.percentComplete(), ft.formattedTransferSize())
84 }
85
86 func (ft *FileTransfer) percentComplete() string {
87 ft.bytesSentCounter.mux.Lock()
88 defer ft.bytesSentCounter.mux.Unlock()
89 return fmt.Sprintf(
90 "%v",
91 math.RoundToEven(float64(ft.bytesSentCounter.Total)/float64(binary.BigEndian.Uint32(ft.TransferSize))*100),
92 )
93 }
94
95 func (ft *FileTransfer) formattedTransferSize() string {
96 sizeInKB := float32(binary.BigEndian.Uint32(ft.TransferSize)) / 1024
97 if sizeInKB > 1024 {
98 return fmt.Sprintf("%.1fM", sizeInKB/1024)
99 } else {
100 return fmt.Sprintf("%.0fK", sizeInKB)
101 }
102 }
103
104 func (ft *FileTransfer) ItemCount() int {
105 return int(binary.BigEndian.Uint16(ft.FolderItemCount))
106 }
107
108 type folderUpload struct {
109 DataSize [2]byte
110 IsFolder [2]byte
111 PathItemCount [2]byte
112 FileNamePath []byte
113 }
114
115 func (fu *folderUpload) FormattedPath() string {
116 pathItemLen := binary.BigEndian.Uint16(fu.PathItemCount[:])
117
118 var pathSegments []string
119 pathData := fu.FileNamePath
120
121 // TODO: implement scanner interface instead?
122 for i := uint16(0); i < pathItemLen; i++ {
123 segLen := pathData[2]
124 pathSegments = append(pathSegments, string(pathData[3:3+segLen]))
125 pathData = pathData[3+segLen:]
126 }
127
128 return filepath.Join(pathSegments...)
129 }