Files
goTorrent/main.go

626 lines
33 KiB
Go

package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
_ "net/http/pprof"
"github.com/anacrolix/torrent"
"github.com/asdine/storm"
Engine "github.com/deranjer/goTorrent/engine"
Settings "github.com/deranjer/goTorrent/settings"
Storage "github.com/deranjer/goTorrent/storage"
jwt "github.com/dgrijalva/jwt-go"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/mmcdole/gofeed"
"github.com/sirupsen/logrus"
)
var (
//Logger does logging for the entire project
Logger = logrus.New()
//Authenticated stores the value of the result of the client that connects to the server
Authenticated = false
APP_ID = os.Getenv("APP_ID")
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func serveHome(w http.ResponseWriter, r *http.Request) {
s1, _ := template.ParseFiles("templates/home.tmpl")
s1.ExecuteTemplate(w, "base", map[string]string{"APP_ID": APP_ID})
}
func handleAuthentication(conn *websocket.Conn, db *storm.DB) {
msg := Engine.Message{}
err := conn.ReadJSON(&msg)
conn.WriteJSON(msg) //TODO just for testing, remove
payloadData, ok := msg.Payload.(map[string]interface{})
clientAuthToken, tokenOk := payloadData["ClientAuthString"].(string)
fmt.Println("ClientAuthToken:", clientAuthToken, "TokenOkay", tokenOk, "PayloadData", payloadData, "PayloadData Okay?", ok)
if ok == false || tokenOk == false {
authFail := Engine.AuthResponse{MessageType: "authResponse", Payload: "Message Payload in AuthRequest was malformed, closing connection"}
conn.WriteJSON(authFail)
conn.Close()
return
}
if err != nil {
Logger.WithFields(logrus.Fields{"error": err, "SuppliedToken": clientAuthToken}).Error("Unable to read authentication message")
}
fmt.Println("Authstring", clientAuthToken)
signingKeyStruct := Storage.FetchJWTTokens(db)
singingKey := signingKeyStruct.SigningKey
token, err := jwt.Parse(clientAuthToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return singingKey, nil
})
if err != nil {
authFail := Engine.AuthResponse{MessageType: "authResponse", Payload: "Parsing of Token failed, ensure you have the correct token! Closing Connection"}
conn.WriteJSON(authFail)
Logger.WithFields(logrus.Fields{"error": err, "SuppliedToken": token}).Error("Unable to parse token!")
conn.Close()
return
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
authTrue := Engine.AuthResponse{MessageType: "authResponse", Payload: "Authentication Verified, proceed with commands."}
conn.WriteJSON(authTrue)
fmt.Println("Claims", claims["ClientName"], claims["Issuer"])
Authenticated = true
} else {
Logger.WithFields(logrus.Fields{"error": err}).Error("Authentication Error occured, cannot complete!")
}
}
func main() {
Engine.Logger = Logger //Injecting the logger into all the packages
Storage.Logger = Logger
Settings.Logger = Logger
Config := Settings.FullClientSettingsNew() //grabbing from settings.go
Engine.Config = Config
if Config.LoggingOutput == "file" {
_, err := os.Stat("logs")
if os.IsNotExist(err) {
err := os.Mkdir("logs", 0755)
if err != nil {
fmt.Println("Unable to create 'log' folder for logging.... please check permissions.. forcing output to stdout", err)
Logger.Out = os.Stdout
}
} else {
os.Remove("logs/server.log") //cleanup the old log on every restart
file, err := os.OpenFile("logs/server.log", os.O_CREATE|os.O_WRONLY, 0755) //creating the log file
//defer file.Close() //TODO.. since we write to this constantly how does close work?
if err != nil {
fmt.Println("Unable to create file for logging.... please check permissions.. forcing output to stdout")
Logger.Out = os.Stdout
}
fmt.Println("Logging to file logs/server.log")
Logger.Out = file
}
} else {
Logger.Out = os.Stdout
}
Logger.SetLevel(Config.LoggingLevel)
httpAddr := Config.HTTPAddr
os.MkdirAll(Config.TFileUploadFolder, 0755) //creating a directory to store uploaded torrent files
os.MkdirAll(Config.TorrentWatchFolder, 0755) //creating a directory to watch for added .torrent files
//Logger.WithFields(logrus.Fields{"Config": Config}).Info("Torrent Client Config has been generated...")
tclient, err := torrent.NewClient(&Config.TorrentConfig) //pulling out the torrent specific config to use
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Fatalf("Error creating torrent client: %s")
}
fmt.Printf("%+v\n", Config.TorrentConfig)
db, err := storm.Open("storage.db") //initializing the boltDB store that contains all the added torrents
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Error opening/creating storage.db")
}
defer db.Close() //defering closing the database until the program closes
tokens := Storage.IssuedTokensList{} //if first run setting up the authentication tokens
var signingKey []byte
err = db.One("ID", 3, &tokens)
if err != nil {
Logger.WithFields(logrus.Fields{"RSSFeedStore": tokens, "error": err}).Info("No Tokens database found, assuming first run, generating token...")
fmt.Println("Error", err)
fmt.Println("MAIN TOKEN: %+v\n", tokens)
tokens.ID = 3 //creating the initial store
claims := Settings.GoTorrentClaims{
"goTorrentWebUI",
jwt.StandardClaims{
Issuer: "goTorrentServer",
},
}
signingKey = Settings.GenerateSigningKey() //Running this will invalidate any certs you already issued!!
authString := Settings.GenerateToken(claims, signingKey)
tokens.SigningKey = signingKey
tokens.FirstToken = authString
tokens.TokenNames = append(tokens.TokenNames, Storage.SingleToken{"firstClient"})
err := ioutil.WriteFile("clientAuth.txt", []byte(authString), 0755)
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Warn("Unable to write client auth to file..")
}
db.Save(&tokens) //Writing all of that to the database
} else { //Already have a signing key so pulling that signing key out of the database to sign any key requests
tokens := Storage.FetchJWTTokens(db)
signingKey = tokens.SigningKey
}
oldConfig, err := Storage.FetchConfig(db)
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Info("Assuming first run as no config found in database, client config being generated")
Settings.GenerateClientConfigFile(Config, tokens.FirstToken) //if first run generate the client config file
} else {
if reflect.DeepEqual(oldConfig.ClientConnectSettings, Config.ClientConnectSettings) {
Logger.WithFields(logrus.Fields{"error": err}).Info("Configs are the same, not regenerating client config")
} else {
Logger.WithFields(logrus.Fields{"error": err}).Info("Config has changed, re-writting config")
Settings.GenerateClientConfigFile(Config, tokens.FirstToken)
}
}
Storage.SaveConfig(db, Config) //Save the config to the database
cronEngine := Engine.InitializeCronEngine() //Starting the cron engine for tasks
Logger.Debug("Cron Engine Initialized...")
torrentLocalStorage := Storage.TorrentLocal{} //creating a new struct that stores all of our local storage info
var RunningTorrentArray = []Engine.ClientDB{} //this stores ALL of the torrents that are running, used for client update pushes combines Local Storage and Running tclient info
var PreviousTorrentArray = []Engine.ClientDB{}
TorrentLocalArray := Storage.FetchAllStoredTorrents(db) //pulling in all the already added torrents - this is an array of ALL of the local storage torrents, they will be added back in via hash
if TorrentLocalArray != nil { //the first creation of the running torrent array //since we are adding all of them in we use a coroutine... just allows the web ui to load then it will load in the torrents
go Engine.CreateInitialTorrentArray(tclient, TorrentLocalArray, db) //adding all of the stored torrents into the torrent client
} else {
Logger.Info("Database is empty, no torrents loaded")
}
Engine.CheckTorrentWatchFolder(cronEngine, db, tclient, torrentLocalStorage, Config) //Every 5 minutes the engine will check the specified folder for new .torrent files
Engine.RefreshRSSCron(cronEngine, db, tclient, torrentLocalStorage, Config) // Refresing the RSS feeds on an hourly basis to add torrents that show up in the RSS feed
router := mux.NewRouter() //setting up the handler for the web backend
router.HandleFunc("/", serveHome) //Serving the main page for our SPA
router.PathPrefix("/static/").Handler(http.FileServer(http.Dir("public")))
http.Handle("/", router)
router.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { //TODO, remove this
TorrentLocalArray = Storage.FetchAllStoredTorrents(db)
RunningTorrentArray = Engine.CreateRunningTorrentArray(tclient, TorrentLocalArray, PreviousTorrentArray, Config, db) //Updates the RunningTorrentArray with the current client data as well
var torrentlistArray = new(Engine.TorrentList)
torrentlistArray.MessageType = "torrentList" //setting the type of message
torrentlistArray.ClientDBstruct = RunningTorrentArray //the full JSON that includes the number of torrents as the root
torrentlistArray.Totaltorrents = len(RunningTorrentArray)
torrentlistArrayJSON, _ := json.Marshal(torrentlistArray)
w.Header().Set("Content-Type", "application/json")
w.Write(torrentlistArrayJSON)
})
router.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) { //websocket is the main data pipe to the frontend
conn, err := upgrader.Upgrade(w, r, nil)
fmt.Println("Websocket connection established, awaiting authentication")
connResponse := Engine.ServerPushMessage{MessageType: "connectResponse", MessageLevel: "Message", Payload: "Websocket Connection Established, awaiting Authentication"}
conn.WriteJSON(&connResponse)
defer conn.Close() //defer closing the websocket until done.
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to create websocket!")
return
}
if Authenticated != true {
handleAuthentication(conn, db)
} else { //If we are authenticated inject the connection into the other packages
connResponse := Engine.ServerPushMessage{MessageType: "authResponse", MessageLevel: "Message", Payload: "Already Authenticated... Awaiting Commands"}
conn.WriteJSON(&connResponse)
Logger.Info("Authenticated, websocket connection available!")
}
Engine.Conn = conn
Storage.Conn = conn
MessageLoop: //Tagging this so we can continue out of it with any errors we encounter that are failing
for {
runningTorrents := tclient.Torrents() //getting running torrents here since multiple cases ask for the running torrents
msg := Engine.Message{}
err := conn.ReadJSON(&msg)
if err != nil {
Logger.WithFields(logrus.Fields{"error": err, "message": msg}).Error("Unable to read JSON client message")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Malformed JSON request made to server.. ignoring"}, conn)
break MessageLoop
}
var payloadData map[string]interface{}
if msg.Payload != nil && msg.Payload != "" {
payloadData = msg.Payload.(map[string]interface{})
}
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Message From Client")
switch msg.MessageType { //first handling data requests
case "authRequest":
if Authenticated {
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Client already authenticated... skipping authentication method")
} else {
handleAuthentication(conn, db)
}
case "newAuthToken":
claims := Settings.GoTorrentClaims{
payloadData["ClientName"].(string),
jwt.StandardClaims{
Issuer: "goTorrentServer",
},
}
Logger.WithFields(logrus.Fields{"clientName": payloadData["ClientName"].(string)}).Info("New Auth Token creation request")
fmt.Println("Signing Key", signingKey)
token := Settings.GenerateToken(claims, signingKey)
tokenReturn := Settings.TokenReturn{TokenReturn: token}
tokensDB := Storage.FetchJWTTokens(db)
tokensDB.TokenNames = append(tokens.TokenNames, Storage.SingleToken{payloadData["ClientName"].(string)})
db.Update(&tokensDB) //adding the new token client name to the database
conn.WriteJSON(tokenReturn)
case "torrentListRequest":
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Client Requested TorrentList Update")
go func() { //running updates in separate thread so can still accept commands
TorrentLocalArray = Storage.FetchAllStoredTorrents(db) //Required to re-read th database since we write to the DB and this will pull the changes from it
RunningTorrentArray = Engine.CreateRunningTorrentArray(tclient, TorrentLocalArray, PreviousTorrentArray, Config, db) //Updates the RunningTorrentArray with the current client data as well
PreviousTorrentArray = RunningTorrentArray
torrentlistArray := Engine.TorrentList{MessageType: "torrentList", ClientDBstruct: RunningTorrentArray, Totaltorrents: len(RunningTorrentArray)}
Logger.WithFields(logrus.Fields{"torrentList": torrentlistArray, "previousTorrentList": PreviousTorrentArray}).Debug("Previous and Current Torrent Lists for sending to client")
conn.WriteJSON(torrentlistArray)
}()
case "torrentFileListRequest": //client requested a filelist update
Logger.WithFields(logrus.Fields{"message": msg}).Info("Client Requested FileList Update")
fileListArrayRequest := payloadData["FileListHash"].(string)
FileListArray := Engine.CreateFileListArray(tclient, fileListArrayRequest, db, Config)
conn.WriteJSON(FileListArray) //writing the JSON to the client
case "torrentPeerListRequest":
Logger.WithFields(logrus.Fields{"message": msg}).Info("Client Requested PeerList Update")
peerListArrayRequest := payloadData["PeerListHash"].(string)
torrentPeerList := Engine.CreatePeerListArray(tclient, peerListArrayRequest)
conn.WriteJSON(torrentPeerList)
case "fetchTorrentsByLabel":
Logger.WithFields(logrus.Fields{"message": msg}).Info("Client Requested Torrents by Label")
label := payloadData["Label"].(string)
torrentsByLabel := Storage.FetchTorrentsByLabel(db, label)
RunningTorrentArray = Engine.CreateRunningTorrentArray(tclient, TorrentLocalArray, PreviousTorrentArray, Config, db)
labelRunningArray := []Engine.ClientDB{}
for _, torrent := range RunningTorrentArray { //Ranging over the running torrents and if the hashes match we have torrents by label
for _, label := range torrentsByLabel {
if torrent.TorrentHashString == label.Hash {
labelRunningArray = append(labelRunningArray, torrent)
}
}
}
conn.WriteJSON(labelRunningArray)
case "changeStorageValue":
Logger.WithFields(logrus.Fields{"message": msg}).Info("Client Requested Storage Location Update")
newStorageLocation := payloadData["StorageValue"].(string)
hashes := payloadData["ChangeStorageHashes"].([]interface{})
for _, singleHash := range hashes {
singleTorrent := Storage.FetchTorrentFromStorage(db, singleHash.(string))
oldPath := singleTorrent.StoragePath
newStorageLocationAbs, err := filepath.Abs(filepath.ToSlash(newStorageLocation))
if err != nil {
Logger.WithFields(logrus.Fields{"patherr": err, "path": newStorageLocation}).Warn("Unable to create absolute path for storage location, using default")
singleTorrent.StoragePath = Config.TorrentConfig.DataDir
} else {
singleTorrent.StoragePath = newStorageLocationAbs
}
Storage.UpdateStorageTick(db, singleTorrent) //push torrent to storage
if singleTorrent.TorrentMoved == true { //If torrent has already been moved and I change path then move it again... TODO, does this work with symlinks?
Logger.WithFields(logrus.Fields{"message": msg}).Info("Change Storage Value called")
Engine.MoveAndLeaveSymlink(Config, singleHash.(string), db, true, oldPath)
}
}
case "settingsFileRequest":
Logger.WithFields(logrus.Fields{"message": msg}).Info("Client Requested Settings File")
clientSettingsFile := Engine.SettingsFile{MessageType: "settingsFile", Config: Config}
conn.WriteJSON(clientSettingsFile)
case "rssFeedRequest":
Logger.WithFields(logrus.Fields{"message": msg}).Info("Client Requested RSS Update")
RSSList := Storage.FetchRSSFeeds(db)
RSSJSONFeed := Engine.RSSJSONList{MessageType: "rssList", TotalRSSFeeds: len(RSSList.RSSFeeds)}
RSSsingleFeed := Engine.RSSFeedsNames{}
for _, singleFeed := range RSSList.RSSFeeds {
RSSsingleFeed.RSSName = singleFeed.Name
RSSsingleFeed.RSSFeedURL = singleFeed.URL
RSSJSONFeed.RSSFeeds = append(RSSJSONFeed.RSSFeeds, RSSsingleFeed)
}
conn.WriteJSON(RSSJSONFeed)
case "addRSSFeed":
newRSSFeed := payloadData["RSSURL"].(string)
Logger.WithFields(logrus.Fields{"message": newRSSFeed}).Info("Client Added RSS Feed")
fullRSSFeeds := Storage.FetchRSSFeeds(db)
Logger.WithFields(logrus.Fields{"RSSFeeds": fullRSSFeeds}).Info("Pulled Full RSS Feeds")
for _, singleFeed := range fullRSSFeeds.RSSFeeds {
if newRSSFeed == singleFeed.URL || newRSSFeed == "" {
Logger.WithFields(logrus.Fields{"RSSFeed": newRSSFeed}).Warn("Empty URL or Duplicate RSS URL to one already in database! Rejecting submission")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Empty URL or Duplicate RSS URL to one already in database!"}, conn)
continue MessageLoop
}
}
fp := gofeed.NewParser()
feed, err := fp.ParseURL(newRSSFeed)
if err != nil {
Logger.WithFields(logrus.Fields{"RSSFeed": newRSSFeed}).Warn("Unable to parse the URL as valid RSS.. cannot add RSS...")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to parse the URL as valid RSS.. cannot add RSS..."}, conn)
continue MessageLoop
}
Logger.WithFields(logrus.Fields{"RSSFeedTitle": feed.Title}).Info("Have feed from URL...")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "success", Payload: "Added RSS Feed"}, conn)
newRSSFeedFull := Storage.SingleRSSFeed{}
newRSSFeedFull.Name = feed.Title
newRSSFeedFull.URL = newRSSFeed
fullRSSFeeds.RSSFeeds = append(fullRSSFeeds.RSSFeeds, newRSSFeedFull) // add the new RSS feed to the stack
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Adding RSS feed..."}, conn)
Engine.ForceRSSRefresh(db, fullRSSFeeds)
//forcing an RSS refresh to fully populate all rss feeds
case "deleteRSSFeed":
deleteRSSFeed := payloadData["RSSURL"].(string)
Logger.WithFields(logrus.Fields{"message": deleteRSSFeed}).Info("Deleting RSS Feed")
Storage.DeleteRSSFeed(db, deleteRSSFeed)
fullRSSFeeds := Storage.FetchRSSFeeds(db)
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Deleting RSS feed..."}, conn)
Engine.ForceRSSRefresh(db, fullRSSFeeds)
case "rssTorrentsRequest":
RSSFeedURL := payloadData["RSSURL"].(string)
Logger.WithFields(logrus.Fields{"RSSFeed": RSSFeedURL}).Info("Requesting torrentList for feed..")
UpdatedRSSFeed := Engine.RefreshSingleRSSFeed(db, Storage.FetchSpecificRSSFeed(db, RSSFeedURL))
TorrentRSSList := Engine.SingleRSSFeedMessage{MessageType: "rssTorrentList", URL: RSSFeedURL, Name: UpdatedRSSFeed.Name, TotalTorrents: len(UpdatedRSSFeed.Torrents), Torrents: UpdatedRSSFeed.Torrents}
Logger.WithFields(logrus.Fields{"TorrentRSSList": TorrentRSSList}).Info("Returning Torrent list from RSSFeed to client")
conn.WriteJSON(TorrentRSSList)
case "magnetLinkSubmit": //if we detect a magnet link we will be adding a magnet torrent
storageValue, ok := payloadData["StorageValue"].(string)
if storageValue == "" || ok == false {
storageValue, err = filepath.Abs(filepath.ToSlash(Config.DefaultMoveFolder))
if err != nil {
Logger.WithFields(logrus.Fields{"err": err, "MagnetLink": Config.DefaultMoveFolder}).Error("Unable to add default Storage Path")
}
} else {
storageValue, err = filepath.Abs(filepath.ToSlash(storageValue))
if err != nil {
Logger.WithFields(logrus.Fields{"err": err, "MagnetLink": storageValue}).Error("Unable to add Storage Path")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add Storage path..."}, conn)
storageValue, _ = filepath.Abs(filepath.ToSlash(Config.DefaultMoveFolder))
}
}
labelValue, ok := payloadData["Label"].(string)
if labelValue == "" || ok == false {
labelValue = "None"
}
magnetLinks := payloadData["MagnetLinks"].([]interface{})
for _, magnetLink := range magnetLinks {
clientTorrent, err := tclient.AddMagnet(magnetLink.(string)) //reading the payload into the torrent client
if err != nil {
Logger.WithFields(logrus.Fields{"err": err, "MagnetLink": magnetLink}).Error("Unable to add magnetlink to client!")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add magnetlink to client!"}, conn)
continue MessageLoop //continue out of the loop entirely for this message since we hit an error
}
Logger.WithFields(logrus.Fields{"clientTorrent": clientTorrent, "magnetLink": magnetLink}).Info("Adding torrent to client!")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Received MagnetLink"}, conn)
go Engine.StartTorrent(clientTorrent, torrentLocalStorage, db, "magnet", "", storageValue, labelValue, Config) //starting the torrent and creating local DB entry
}
case "torrentFileSubmit":
base64encoded := payloadData["FileData"].(string)
fileName := payloadData["FileName"].(string)
storageValue, ok := payloadData["StorageValue"].(string)
if storageValue == "" || ok == false {
storageValue, err = filepath.Abs(filepath.ToSlash(Config.DefaultMoveFolder))
if err != nil {
Logger.WithFields(logrus.Fields{"err": err, "MagnetLink": Config.DefaultMoveFolder}).Error("Unable to add Storage Path")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add default Storage Path"}, conn)
} else {
storageValue, err = filepath.Abs(filepath.ToSlash(storageValue))
if err != nil {
Logger.WithFields(logrus.Fields{"err": err, "MagnetLink": storageValue}).Error("Unable to add Storage Path")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add Storage Path"}, conn)
storageValue, _ = filepath.Abs(filepath.ToSlash(Config.DefaultMoveFolder))
}
}
}
labelValue, ok := payloadData["Label"].(string)
if labelValue == "" || ok == false {
labelValue = "None"
}
base64file := strings.Split(base64encoded, ",") //Mozilla and Chrome have different payloads, but both start the file after the comma
file, err := base64.StdEncoding.DecodeString(base64file[1]) //grabbing the second half of the string after the split
if err != nil {
Logger.WithFields(logrus.Fields{"Error": err, "file": file}).Info("Unable to decode base64 string to file")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to decode base64 string to file"}, conn)
}
filePath := filepath.Join(Config.TFileUploadFolder, fileName)
filePathAbs, err := filepath.Abs(filePath) //creating a full filepath to store the .torrent files
err = ioutil.WriteFile(filePathAbs, file, 0755) //Dumping our received file into the filename
if err != nil {
Logger.WithFields(logrus.Fields{"filepath": filePathAbs, "file Name": fileName, "Error": err}).Error("Unable to write torrent data to file")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to write torrent data to file"}, conn)
}
clientTorrent, err := tclient.AddTorrentFromFile(filePathAbs)
if err != nil {
Logger.WithFields(logrus.Fields{"filepath": filePathAbs, "Error": err}).Error("Unable to add Torrent to torrent server")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add Torrent to torrent server"}, conn)
}
Logger.WithFields(logrus.Fields{"clienttorrent": clientTorrent.Name(), "filename": filePathAbs}).Info("Added torrent")
go Engine.StartTorrent(clientTorrent, torrentLocalStorage, db, "file", filePathAbs, storageValue, labelValue, Config)
case "stopTorrents":
torrentHashes := payloadData["TorrentHashes"].([]interface{})
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Received Stop Request"}, conn)
for _, singleTorrent := range tclient.Torrents() {
for _, singleSelection := range torrentHashes {
if singleTorrent.InfoHash().String() == singleSelection {
Logger.WithFields(logrus.Fields{"selection": singleSelection}).Info("Matched for stopping torrents")
oldTorrentInfo := Storage.FetchTorrentFromStorage(db, singleTorrent.InfoHash().String())
oldTorrentInfo.TorrentStatus = "Stopped"
oldTorrentInfo.MaxConnections = 0
Storage.UpdateStorageTick(db, oldTorrentInfo) //Updating the torrent status
}
}
}
case "deleteTorrents":
torrentHashes := payloadData["TorrentHashes"].([]interface{})
withData := payloadData["WithData"].(bool) //Checking if torrents should be deleted with data
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Received Delete Request"}, conn)
Logger.WithFields(logrus.Fields{"deleteTorrentsPayload": msg.Payload, "torrentlist": msg.Payload, "deleteWithData?": withData}).Info("message for deleting torrents")
for _, singleTorrent := range runningTorrents {
for _, singleSelection := range torrentHashes {
if singleTorrent.InfoHash().String() == singleSelection {
oldTorrentInfo := Storage.FetchTorrentFromStorage(db, singleTorrent.InfoHash().String())
Logger.WithFields(logrus.Fields{"selection": singleSelection}).Info("Matched for deleting torrents")
if withData {
oldTorrentInfo.TorrentStatus = "DroppedData" //Will be cleaned up the next engine loop since deleting a torrent mid loop can cause issues
} else {
oldTorrentInfo.TorrentStatus = "Dropped"
}
Storage.UpdateStorageTick(db, oldTorrentInfo)
}
}
}
case "startTorrents":
torrentHashes := payloadData["TorrentHashes"].([]interface{})
Logger.WithFields(logrus.Fields{"selection": msg.Payload}).Info("Matched for starting torrents")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Received Start Request"}, conn)
for _, singleTorrent := range runningTorrents {
for _, singleSelection := range torrentHashes {
if singleTorrent.InfoHash().String() == singleSelection {
Logger.WithFields(logrus.Fields{"infoHash": singleTorrent.InfoHash().String()}).Info("Found matching torrent to start")
oldTorrentInfo := Storage.FetchTorrentFromStorage(db, singleTorrent.InfoHash().String())
oldTorrentInfo.TorrentStatus = "Running"
oldTorrentInfo.MaxConnections = 80
singleTorrent.DownloadAll() //set all of the pieces to download (piece prio is NE to file prio)
NumPieces := singleTorrent.NumPieces() //find the number of pieces
singleTorrent.CancelPieces(1, NumPieces) //cancel all of the pieces to use file priority
for _, file := range singleTorrent.Files() {
for _, sentFile := range oldTorrentInfo.TorrentFilePriority {
if file.DisplayPath() == sentFile.TorrentFilePath {
switch sentFile.TorrentFilePriority {
case "High":
file.SetPriority(torrent.PiecePriorityHigh)
case "Normal":
file.SetPriority(torrent.PiecePriorityNormal)
case "Cancel":
file.SetPriority(torrent.PiecePriorityNone)
default:
file.SetPriority(torrent.PiecePriorityNormal)
}
}
}
}
Logger.WithFields(logrus.Fields{"Torrent": oldTorrentInfo.TorrentName}).Info("Changing database to torrent running with 80 max connections")
Storage.UpdateStorageTick(db, oldTorrentInfo) //Updating the torrent status
}
}
}
case "forceUploadTorrents":
torrentHashes := payloadData["TorrentHashes"].([]interface{})
Logger.WithFields(logrus.Fields{"selection": msg.Payload}).Info("Matched for force Uploading Torrents")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Received Force Start Request"}, conn)
for _, singleTorrent := range runningTorrents {
for _, singleSelection := range torrentHashes {
if singleTorrent.InfoHash().String() == singleSelection {
Logger.WithFields(logrus.Fields{"infoHash": singleTorrent.InfoHash().String()}).Debug("Found matching torrent to force start")
oldTorrentInfo := Storage.FetchTorrentFromStorage(db, singleTorrent.InfoHash().String())
oldTorrentInfo.TorrentUploadLimit = false // no upload limit for this torrent
oldTorrentInfo.TorrentStatus = "Running"
oldTorrentInfo.MaxConnections = 80
fmt.Println("OldtorrentinfoName", oldTorrentInfo.TorrentName)
Logger.WithFields(logrus.Fields{"NewMax": oldTorrentInfo.MaxConnections, "Torrent": oldTorrentInfo.TorrentName}).Info("Setting max connection from zero to 80")
Storage.UpdateStorageTick(db, oldTorrentInfo) //Updating the torrent status
}
}
}
case "setFilePriority": //TODO disable if the file is already at 100%?
priorityRequested := payloadData["FilePriority"].(string)
torrentHash := payloadData["TorrentHash"].(string)
fileList := payloadData["FilePaths"].([]interface{})
Logger.WithFields(logrus.Fields{"selection": torrentHash}).Info("Matched for setting file priority")
Engine.CreateServerPushMessage(Engine.ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "info", Payload: "Received Set Priority Request"}, conn)
Logger.WithFields(logrus.Fields{"filelist": fileList}).Debug("Full filelist for setting file priority")
for _, singleTorrent := range runningTorrents {
if singleTorrent.InfoHash().String() == torrentHash {
activeTorrentStruct := Storage.FetchTorrentFromStorage(db, torrentHash) //fetching all the data from the db to update certain fields then write it all back
Logger.WithFields(logrus.Fields{"singleTorrent": singleTorrent}).Debug("Matched for changing file prio torrents")
for _, file := range singleTorrent.Files() {
for _, sentFile := range fileList {
var priorityString string
if file.Path() == sentFile {
switch priorityRequested {
case "High":
priorityString = "High"
file.SetPriority(torrent.PiecePriorityHigh)
case "Normal":
priorityString = "Normal"
file.SetPriority(torrent.PiecePriorityNormal)
case "Cancel":
priorityString = "Cancel"
file.SetPriority(torrent.PiecePriorityNone)
default:
priorityString = "Normal"
file.SetPriority(torrent.PiecePriorityNormal)
}
for i, specificFile := range activeTorrentStruct.TorrentFilePriority { //searching for that specific file
if specificFile.TorrentFilePath == file.DisplayPath() {
activeTorrentStruct.TorrentFilePriority[i].TorrentFilePriority = priorityString //writing just that field to the current struct
}
}
Logger.WithFields(logrus.Fields{"singleTorrent": file.DisplayPath()}).Debug("Setting priority for ", priorityString)
Storage.UpdateStorageTick(db, activeTorrentStruct) //re-writting essentially that entire struct right back into the database
}
}
}
}
}
default:
//conn.Close()
Logger.WithFields(logrus.Fields{"message": msg}).Info("Unrecognized Message from client... ignoring")
return
}
}
})
if Config.UseProxy {
err := http.ListenAndServe(httpAddr, handlers.ProxyHeaders(router))
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to listen on the http Server!")
}
} else {
err := http.ListenAndServe(httpAddr, nil) //Can't send proxy headers if not used since that can be a security issue
if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to listen on the http Server with no proxy headers!")
}
}
}