]>
Commit | Line | Data |
---|---|---|
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 | } |