-
Notifications
You must be signed in to change notification settings - Fork 0
/
transfer.go
178 lines (158 loc) · 4.99 KB
/
transfer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package main
import (
"database/sql"
"log"
"os"
"path"
"strconv"
"time"
)
const (
maxFileUploadSizeMB = 5000
freeFileUploadBytes = 250000000
freeBandwidthBytes = freeFileUploadBytes * 10
creditSteps = 0.5
userDirLen = 50
)
var fileStoreDirectory = os.Getenv("file_dir")
// Transfer structure
type Transfer struct {
ID int64 `json:"-"`
FilePath string `json:"file_path"`
Size int `json:"file_size"`
from User `json:"-"`
to User `json:"-"`
hash string `json:"-"`
password string `json:"-"`
expiry time.Time `json:"-"`
}
// GetPasswordAndUUID fetches the password for the transfer and the UUID of the sending user
// based on the UUID of the destination user the filepath of the transfer and the file hash
func (transfer *Transfer) GetPasswordAndUUID(db *sql.DB) {
result := db.QueryRow(`
SELECT password, from_UUID
FROM transfer
WHERE finished_dttm IS NULL
AND to_UUID = ?
AND file_path = ?
AND file_hash = ?`, Hash(transfer.to.UUID), transfer.FilePath, transfer.hash)
Handle(result.Scan(&transfer.password, &transfer.from.UUID))
}
// AlreadyToUser returns true if already transferring between two users
func (transfer *Transfer) AlreadyToUser(db *sql.DB) bool {
var id int64
result := db.QueryRow(`
SELECT id
FROM transfer
WHERE from_UUID = ?
AND to_UUID = ?
AND finished_dttm IS NULL`, Hash(transfer.from.UUID), Hash(transfer.to.UUID))
_ = result.Scan(&id)
return id > 0
}
// InitialStore stores the from_UUID and to_UUID in the transfer table as placeholders
func (transfer Transfer) InitialStore(db *sql.DB) int64 {
res, err := db.Exec(`
INSERT into transfer (from_UUID, to_UUID)
VALUES (?, ?)`, Hash(transfer.from.UUID), Hash(transfer.to.UUID))
Handle(err)
ID, err := res.LastInsertId()
Handle(err)
return ID
}
// Store stores the full information of the transfer based on the ID from InitialStore
func (transfer Transfer) Store(db *sql.DB) error {
return UpdateErr(db.Exec(`
UPDATE transfer
SET size=?, file_hash=?, file_path=?, password=?, expiry_dttm=?, updated_dttm=NOW()
WHERE id=?`, transfer.Size, transfer.hash, transfer.FilePath, transfer.password, transfer.expiry, transfer.ID))
}
// KeepAliveTransfer will update the updated_dttm of the transfer to prevent the cleanup CleanExpiredTransfers()
// from executing while still downloading
func KeepAliveTransfer(db *sql.DB, user User, path string) {
Handle(UpdateErr(db.Exec(`
UPDATE transfer
SET updated_dttm=NOW()
WHERE file_path=?
AND to_UUID
OR from_UUID`, path, Hash(user.UUID), Hash(user.UUID))))
}
// Completed will mark a transfer as completed and return the state back to the user over socket message.
func (transfer Transfer) Completed(db *sql.DB, failed bool, expired bool) {
err := UpdateErr(db.Exec(`
UPDATE transfer
SET file_path = NULL, finished_dttm = NOW(), password = NULL, failed = ?
WHERE from_UUID = ?
AND to_UUID = ?
AND file_path = ?`, failed, Hash(transfer.from.UUID), Hash(transfer.to.UUID), transfer.FilePath))
Handle(err)
go deleteUploadDir(transfer.FilePath)
message := DesktopMessage{}
if expired {
message.Title = "Expired Transfer!"
message.Message = "Your file was not downloaded in time!"
} else if failed {
message.Title = "Cancelled Transfer"
message.Message = "Your friend may have ignored the transfer!"
} else {
message.Title = "Successful Transfer"
message.Message = "Your friend has received your file!"
// send user stats update to sender
fromUser := User{UUID: transfer.from.UUID}
fromUser.SetStats(db)
WSConns.Write(SocketMessage{
User: &fromUser,
}, transfer.from.UUID, true)
}
WSConns.Write(SocketMessage{Message: &message}, transfer.from.UUID, true)
}
// AllowedToDownload verifies that the download request is legitimate
func AllowedToDownload(db *sql.DB, user User, filePath string) bool {
var id int
result := db.QueryRow(`
SELECT id
FROM transfer
WHERE to_UUID = ?
AND file_path = ?
AND finished_dttm IS NULL`, Hash(user.UUID), filePath)
_ = result.Scan(&id)
if id > 0 {
return true
}
return false
}
// CleanExpiredTransfers removes transfers which have exceeded the length of time they are allowed to be hosted on the
// server
func (s *Server) CleanExpiredTransfers() {
// find and remove all expired uploads
rows, err := s.db.Query(`
SELECT id, file_path, to_UUID, from_UUID
FROM transfer
WHERE finished_dttm IS NULL
AND expiry_dttm IS NOT NULL
AND expiry_dttm < NOW()
AND (updated_dttm IS NULL OR updated_dttm + interval 1 minute > NOW())`)
Handle(err)
cnt := 0
for rows.Next() {
var transfer Transfer
err := rows.Scan(&transfer.ID, &transfer.FilePath, &transfer.to.UUID, &transfer.from.UUID)
Handle(err)
go transfer.Completed(s.db, true, true)
cnt += 1
}
rows.Close()
if cnt > 0 {
log.Println("Deleted " + strconv.Itoa(cnt) + " transfers")
}
}
func deleteUploadDir(filePath string) bool {
dir := path.Dir(fileStoreDirectory + filePath)
if err := os.RemoveAll(dir); err != nil {
Handle(err)
err = os.Remove(dir)
Handle(err)
return false
}
return true
}