diff --git a/README.md b/README.md index 9cce8aa7..a82e6459 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,19 @@ Image of the frontend UI - Automatic stop after seeding ratio reached - Pushbullet notification on torrent complete - Automatic move of completed torrent to new directory (leave symlink behind for seeding) - - Doesn't work on Windows yet, have to copy file for now + - Symlinks don't work on Windows yet, have to copy file for now ## Roadmap - Early-Mid 2018 + - [X] Ability to modify storage path of torrent after it has been added + + - [X] Backend to frontend notification messages + + - [X] Global Rate Limiting for Upload/Download Speed + + - [X] Add torrents from watch folder (cron job every 5 minutes) + - [ ] Unit testing completed for a large portion of the package - [ ] Stability/bug fixing/Optimization rewrite of some of the core structures of the WebUI and base server @@ -40,8 +48,8 @@ Image of the frontend UI - [ ] Ability to set priority for individual files (just added to anacrolix/torrent so coming soon, already added to my UI) - [ ] Ability to view TOML settings from WebUI (and perhaps change a few as well) - - - [ ] Ability to modify storage path of torrent after it has been added + + - [ ] Authentication from client to server - Late 2018 @@ -96,6 +104,12 @@ The `config.toml` file contains all of the settings for the server part of the a SeedRatioStop = 1.50 #automatically stops the torrent after it reaches this seeding ratio #Relative or absolute path accepted, the server will convert any relative path to an absolute path. DefaultMoveFolder = 'downloaded' #default path that a finished torrent is symlinked to after completion. Torrents added via RSS will default here + TorrentWatchFolder = 'torrentUpload' #folder path that is watched for .torrent files and adds them automatically every 5 minutes + + #Limits your upload and download speed globally, all are averages and not burst protected (usually burst on start). + #Low = ~.05MB/s, Medium = ~.5MB/s, High = ~1.5MB/s + UploadRateLimit = "Unlimited" #Options are "Low", "Medium", "High", "Unlimited" #Unlimited is default + DownloadRateLimit = "Unlimited" [notifications] diff --git a/config.toml b/config.toml index c736fc36..72bcdb96 100644 --- a/config.toml +++ b/config.toml @@ -3,7 +3,7 @@ ServerPort = ":8000" #leave format as is it expects a string with colon ServerAddr = "" #blank will bind to default IP address, usually fine to leave be - LogLevel = "Info" # Options = Debug, Info, Warn, Error, Fatal, Panic + LogLevel = "Warn" # Options = Debug, Info, Warn, Error, Fatal, Panic LogOutput = "file" #Options = file, stdout #file will print it to logs/server.log SeedRatioStop = 1.50 #automatically stops the torrent after it reaches this seeding ratio @@ -19,7 +19,7 @@ [notifications] - PushBulletToken = "o.QW6G7F6FUOKXCUKmw948fBceCUn0msFi" #add your pushbullet api token here to notify of torrent completion to pushbullet + PushBulletToken = "" #add your pushbullet api token here to notify of torrent completion to pushbullet [EncryptionPolicy] diff --git a/documentation/images/frontend.png b/documentation/images/frontend.png index d56087e2..901adf9a 100644 Binary files a/documentation/images/frontend.png and b/documentation/images/frontend.png differ diff --git a/engine/cronJobs.go b/engine/cronJobs.go index 291f0a54..80752a42 100644 --- a/engine/cronJobs.go +++ b/engine/cronJobs.go @@ -2,6 +2,7 @@ package engine import ( "io/ioutil" + "os" "path/filepath" "github.com/anacrolix/torrent" @@ -46,9 +47,9 @@ func CheckTorrentWatchFolder(c *cron.Cron, db *storm.DB, tclient *torrent.Client continue } - //os.Remove(fullFilePathAbs) //delete the torrent after adding it and copying it over + os.Remove(fullFilePathAbs) //delete the torrent after adding it and copying it over Logger.WithFields(logrus.Fields{"Source Folder": fullFilePathAbs, "Destination Folder": fullNewFilePathAbs, "Torrent": file.Name()}).Info("Added torrent from watch folder, and moved torrent file") - StartTorrent(clientTorrent, torrentLocalStorage, db, config.TorrentConfig.DataDir, "file", file.Name(), config.DefaultMoveFolder, "default", config.TFileUploadFolder) + StartTorrent(clientTorrent, torrentLocalStorage, db, "file", fullNewFilePathAbs, config.DefaultMoveFolder, "default", config) } } @@ -84,7 +85,7 @@ func RefreshRSSCron(c *cron.Cron, db *storm.DB, tclient *torrent.Client, torrent Logger.WithFields(logrus.Fields{"err": err, "Torrent": RSSTorrent.Title}).Warn("Unable to add torrent to torrent client!") break //break out of the loop entirely for this message since we hit an error } - StartTorrent(clientTorrent, torrentLocalStorage, db, config.TorrentConfig.DataDir, "magnet", "", config.DefaultMoveFolder, "RSS", config.TFileUploadFolder) //TODO let user specify torrent default storage location and let change on fly + StartTorrent(clientTorrent, torrentLocalStorage, db, "magnet", "", config.DefaultMoveFolder, "RSS", config) //TODO let user specify torrent default storage location and let change on fly singleFeed.Torrents = append(singleFeed.Torrents, singleRSSTorrent) } diff --git a/engine/doneTorrentActions.go b/engine/doneTorrentActions.go index 05dfbcb2..167a8b76 100644 --- a/engine/doneTorrentActions.go +++ b/engine/doneTorrentActions.go @@ -1,8 +1,6 @@ package engine import ( - "fmt" - "io" "os" "path/filepath" "runtime" @@ -20,22 +18,21 @@ func MoveAndLeaveSymlink(config FullClientSettings, tHash string, db *storm.DB, tStorage := Storage.FetchTorrentFromStorage(db, tHash) Logger.WithFields(logrus.Fields{"Torrent Name": tStorage.TorrentName}).Info("Move and Create symlink started for torrent") var oldFilePath string - if moveDone { + if moveDone { //only occurs on manual move oldFilePathTemp := filepath.Join(oldPath, tStorage.TorrentName) - oldFilePath, err := filepath.Abs(oldFilePathTemp) + var err error + oldFilePath, err = filepath.Abs(oldFilePathTemp) if err != nil { Logger.WithFields(logrus.Fields{"Torrent Name": tStorage.TorrentName, "Filepath": oldFilePath}).Error("Cannot create absolute file path!") } - - fmt.Println("oldfilepath", oldFilePath) } else { oldFilePathTemp := filepath.Join(config.TorrentConfig.DataDir, tStorage.TorrentName) - oldFilePath, err := filepath.Abs(oldFilePathTemp) + var err error + oldFilePath, err = filepath.Abs(oldFilePathTemp) if err != nil { Logger.WithFields(logrus.Fields{"Torrent Name": tStorage.TorrentName, "Filepath": oldFilePath}).Error("Cannot create absolute file path!") } } - newFilePathTemp := filepath.Join(tStorage.StoragePath, tStorage.TorrentName) newFilePath, err := filepath.Abs(newFilePathTemp) if err != nil { @@ -55,70 +52,27 @@ func MoveAndLeaveSymlink(config FullClientSettings, tHash string, db *storm.DB, } if oldFilePath != newFilePath { - if runtime.GOOS == "windows" { //TODO the windows symlink is broken on windows 10 creator edition, so doing a copy for now until Go 1.11 - if oldFileInfo.IsDir() { - os.Mkdir(newFilePath, 0755) - if moveDone { - err := folderCopy.Copy(config.TorrentConfig.DataDir, newFilePath) - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": config.TorrentConfig.DataDir, "New File Path": newFilePath, "error": err}).Error("Error Copying Folder!") - } - } else { - err := folderCopy.Copy(oldFilePath, newFilePath) //copy the folder to the new location - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Error Copying Folder!") - } - - } - os.Chmod(newFilePath, 0777) - notifyUser(tStorage, config, db) - return - } - srcFile, err := os.Open(oldFilePath) - defer srcFile.Close() - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "error": err}).Error("Windows: Cannot open old file for copy") - return - } - destFile, err := os.Create(newFilePath) - defer destFile.Close() - if err != nil { - Logger.WithFields(logrus.Fields{"New File Path": newFilePath, "error": err}).Error("Windows: Cannot open new file for copying into") - return - } - - bytesWritten, err := io.Copy(destFile, srcFile) - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Windows: Cannot copy old file into new") - return - } - err = destFile.Sync() - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Windows: Error syncing new file to disk") - } - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "bytesWritten": bytesWritten}).Info("Windows Torrent Copy Completed") - notifyUser(tStorage, config, db) - } else { - - folderCopy.Copy(oldFilePath, newFilePath) - os.Chmod(newFilePath, 0777) //changing permissions on the new file to be permissive - os.RemoveAll(oldFilePath) - if moveDone { - err := os.Symlink(newFilePath, config.TorrentConfig.DataDir) - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": config.TorrentConfig.DataDir, "New File Path": newFilePath, "error": err}).Error("Error creating symlink") - return - } - } else { - err := os.Symlink(newFilePath, oldFilePath) //For all other OS's create a symlink - if err != nil { - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Error creating symlink") - return - } - } - notifyUser(tStorage, config, db) - Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath}).Info("Moving completed torrent") + newFilePathDir := filepath.Dir(newFilePath) + os.Mkdir(newFilePathDir, 0755) + err := folderCopy.Copy(oldFilePath, newFilePath) //copy the folder to the new location + if err != nil { + Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Error Copying Folder!") } + os.Chmod(newFilePath, 0777) + if runtime.GOOS != "windows" { //TODO the windows symlink is broken on windows 10 creator edition, so on the other platforms create symlink (windows will copy) until Go1.11 + os.RemoveAll(oldFilePath) + err = os.Symlink(newFilePath, oldFilePath) + if err != nil { + Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Error creating symlink") + } + } + if moveDone == false { + tStorage.TorrentMoved = true //TODO error handling instead of just saying torrent was moved when it was not + notifyUser(tStorage, config, db) //Only notify if we haven't moved yet, don't want to push notify user every time user uses change storage button + } + Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath}).Info("Moving completed torrent") + tStorage.StoragePath = filepath.Dir(newFilePath) + Storage.UpdateStorageTick(db, tStorage) } } diff --git a/engine/engine.go b/engine/engine.go index de3cb96c..15515fc9 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "path/filepath" "strconv" "strings" "time" @@ -128,7 +127,7 @@ func readTorrentFileFromDB(element *Storage.TorrentLocal, tclient *torrent.Clien } //StartTorrent creates the storage.db entry and starts A NEW TORRENT and adds to the running torrent array -func StartTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage Storage.TorrentLocal, torrentDbStorage *storm.DB, dataDir, torrentType, torrentFileName, torrentStoragePath, labelValue, tFileUploadFolder string) { +func StartTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage Storage.TorrentLocal, torrentDbStorage *storm.DB, torrentType, torrentFilePathAbs, torrentStoragePath, labelValue string, config FullClientSettings) { timedOut := timeOutInfo(clientTorrent, 45) //seeing if adding the torrent times out (giving 45 seconds) if timedOut { //if we fail to add the torrent return return @@ -147,6 +146,7 @@ func StartTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage Storage.To torrentLocalStorage.Label = labelValue torrentLocalStorage.DateAdded = time.Now().Format("Jan _2 2006") torrentLocalStorage.StoragePath = torrentStoragePath + torrentLocalStorage.TempStoragePath = config.TorrentConfig.DataDir torrentLocalStorage.TorrentName = clientTorrent.Name() torrentLocalStorage.TorrentUploadLimit = true //by default all of the torrents will stop uploading after the global rate is set. torrentLocalStorage.TorrentMoved = false //by default the torrent has no been moved. @@ -154,8 +154,6 @@ func StartTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage Storage.To torrentLocalStorage.TorrentType = torrentType //either "file" or "magnet" maybe more in the future torrentLocalStorage.TorrentSize = clientTorrent.Length() //Length will change as we cancel files so store it in DB if torrentType == "file" { //if it is a file read the entire file into the database for us to spit out later - torrentFilePath := filepath.Join(tFileUploadFolder, torrentFileName) - torrentFilePathAbs, err := filepath.Abs(torrentFilePath) torrentfile, err := ioutil.ReadFile(torrentFilePathAbs) torrentLocalStorage.TorrentFileName = torrentFilePathAbs if err != nil { @@ -236,11 +234,10 @@ func CreateRunningTorrentArray(tclient *torrent.Client, TorrentLocalArray []*Sto PercentDone := fmt.Sprintf("%.2f", float32(singleTorrent.BytesCompleted())/float32(singleTorrentFromStorage.TorrentSize)) fullClientDB.TorrentHash = TempHash fullClientDB.PercentDone = PercentDone - fullClientDB.DataBytesRead = fullStruct.ConnStats.DataBytesRead //used for calculations not passed to client calculating up/down speed - fullClientDB.DataBytesWritten = fullStruct.ConnStats.DataBytesWritten //used for calculations not passed to client calculating up/down speed + fullClientDB.DataBytesRead = fullStruct.ConnStats.BytesReadData //used for calculations not passed to client calculating up/down speed + fullClientDB.DataBytesWritten = fullStruct.ConnStats.BytesWrittenData //used for calculations not passed to client calculating up/down speed fullClientDB.ActivePeers = activePeersString + " / (" + totalPeersString + ")" fullClientDB.TorrentHashString = TempHash.String() - fullClientDB.StoragePath = singleTorrentFromStorage.StoragePath fullClientDB.TorrentName = singleTorrentFromStorage.TorrentName fullClientDB.DateAdded = singleTorrentFromStorage.DateAdded fullClientDB.TorrentLabel = singleTorrentFromStorage.Label @@ -252,7 +249,7 @@ func CreateRunningTorrentArray(tclient *torrent.Client, TorrentLocalArray []*Sto TempHash := singleTorrent.InfoHash() if previousElement.TorrentHashString == TempHash.String() { //matching previous to new CalculateTorrentSpeed(singleTorrent, fullClientDB, previousElement) - fullClientDB.TotalUploadedBytes = singleTorrentFromStorage.UploadedBytes + (fullStruct.ConnStats.DataBytesWritten - previousElement.DataBytesWritten) + fullClientDB.TotalUploadedBytes = singleTorrentFromStorage.UploadedBytes + (fullStruct.ConnStats.BytesWrittenData - previousElement.DataBytesWritten) } } } @@ -276,7 +273,7 @@ func CreateRunningTorrentArray(tclient *torrent.Client, TorrentLocalArray []*Sto } //CreateFileListArray creates a file list for a single torrent that is selected and sent to the server -func CreateFileListArray(tclient *torrent.Client, selectedHash string, db *storm.DB) TorrentFileList { +func CreateFileListArray(tclient *torrent.Client, selectedHash string, db *storm.DB, config FullClientSettings) TorrentFileList { runningTorrents := tclient.Torrents() //don't need running torrent array since we aren't adding or deleting from storage torrentFileListStorage := Storage.FetchTorrentFromStorage(db, selectedHash) TorrentFileListSelected := TorrentFileList{} @@ -289,11 +286,7 @@ func CreateFileListArray(tclient *torrent.Client, selectedHash string, db *storm for _, singleFile := range torrentFilesRaw { TorrentFileStruct.TorrentHashString = tempHash TorrentFileStruct.FileName = singleFile.DisplayPath() - absFilePath, err := filepath.Abs(singleFile.Path()) - if err != nil { - Logger.WithFields(logrus.Fields{"file": singleFile.Path()}).Debug("Unable to create absolute path") - } - TorrentFileStruct.FilePath = absFilePath + TorrentFileStruct.FilePath = singleFile.Path() PieceState := singleFile.State() var downloadedBytes int64 for _, piece := range PieceState { diff --git a/engine/engineHelpers.go b/engine/engineHelpers.go index 08036f62..84c3c137 100644 --- a/engine/engineHelpers.go +++ b/engine/engineHelpers.go @@ -62,7 +62,7 @@ func CopyFile(srcFile string, destFile string) { func CalculateTorrentSpeed(t *torrent.Torrent, c *ClientDB, oc ClientDB) { now := time.Now() bytes := t.BytesCompleted() - bytesUpload := t.Stats().DataBytesWritten + bytesUpload := t.Stats().BytesWrittenData dt := float32(now.Sub(oc.UpdatedAt)) // get the delta time length between now and last updated db := float32(bytes - oc.BytesCompleted) //getting the delta bytes rate := db * (float32(time.Second) / dt) // converting into seconds diff --git a/goTorrentWebUI/src/TopMenu/Modals/RSSModal/RSSModalLayout.js b/goTorrentWebUI/src/TopMenu/Modals/RSSModal/RSSModalLayout.js index c94b1965..83fd0194 100644 --- a/goTorrentWebUI/src/TopMenu/Modals/RSSModal/RSSModalLayout.js +++ b/goTorrentWebUI/src/TopMenu/Modals/RSSModal/RSSModalLayout.js @@ -89,7 +89,10 @@ const inlineStyle = { {i: 'b', x: 0, y: 1, w: 1, h: 5, static: true}, {i: 'c', x: 1, y: 1, w: 5, h: 5, minW: 5, minH: 3, static: true}, ]; - this.state = { layout }; + this.state = { + layout , + textValue: "", + }; }; @@ -103,7 +106,6 @@ const inlineStyle = { } handleAddRSSFeed = () => { - this.setState({ textValue: "Clear"}) //clearing out the text submitted let RSSURLSubmit = { MessageType: "addRSSFeed", Payload: [this.state.textValue] @@ -113,20 +115,13 @@ const inlineStyle = { MessageType: "rssFeedRequest", } ws.send(JSON.stringify(RSSRequest)) //Immediatly request an update of the feed when you add a new URL + this.setState({textValue: ""}) } setTextValue = (event) => { this.setState({ textValue: event.target.value }); } - - componentWillReceiveProps (nextProps) { - console.log("nextprops", nextProps, "Modal", nextProps.RSSModalOpen) - } - componentWillMount () { - console.log("Mounting grid") - } - render() { return (