Repository: deranjer/goTorrent Branch: master Commit: ae88043eaabf Files: 196 Total size: 4.9 MB Directory structure: gitextract_ys94f86f/ ├── .gitignore ├── .vscode/ │ ├── settings.json │ └── tasks.json ├── Dockerfile ├── LICENSE ├── README.md ├── config.toml ├── config.toml.bk ├── dist-specific-files/ │ ├── Linux-systemd/ │ │ └── goTorrent.service │ └── ReverseProxy/ │ └── nginx.conf ├── engine/ │ ├── clientStructs.go │ ├── cronJobs.go │ ├── doneTorrentActions.go │ ├── engine.go │ └── engineHelpers.go ├── go.mod ├── go.sum ├── goTorrentWebUI/ │ ├── .babelrc │ ├── acorn │ ├── acorn.cmd │ ├── ansi-html │ ├── ansi-html.cmd │ ├── atob │ ├── atob.cmd │ ├── babel │ ├── babel-doctor │ ├── babel-doctor.cmd │ ├── babel-external-helpers │ ├── babel-external-helpers.cmd │ ├── babel-node │ ├── babel-node.cmd │ ├── babel.cmd │ ├── babylon │ ├── babylon.cmd │ ├── browserslist │ ├── browserslist.cmd │ ├── cssesc │ ├── cssesc.cmd │ ├── csso │ ├── csso.cmd │ ├── detect │ ├── detect-port │ ├── detect-port.cmd │ ├── detect.cmd │ ├── errno │ ├── errno.cmd │ ├── escodegen │ ├── escodegen.cmd │ ├── esgenerate │ ├── esgenerate.cmd │ ├── eslint │ ├── eslint.cmd │ ├── esparse │ ├── esparse.cmd │ ├── esvalidate │ ├── esvalidate.cmd │ ├── fonts/ │ │ └── index.css │ ├── handlebars │ ├── handlebars.cmd │ ├── he │ ├── he.cmd │ ├── html-minifier │ ├── html-minifier.cmd │ ├── import-local-fixture │ ├── import-local-fixture.cmd │ ├── internal-ip │ ├── internal-ip.cmd │ ├── is-ci │ ├── is-ci.cmd │ ├── jest │ ├── jest-runtime │ ├── jest-runtime.cmd │ ├── jest.cmd │ ├── js-yaml │ ├── js-yaml.cmd │ ├── jsesc │ ├── jsesc.cmd │ ├── json5 │ ├── json5.cmd │ ├── lib/ │ │ ├── BackendComm/ │ │ │ └── backendWebsocket.js │ │ ├── BottomMenu/ │ │ │ ├── Tabs/ │ │ │ │ ├── fileTab.js │ │ │ │ ├── generalTab.js │ │ │ │ └── peerTab.js │ │ │ └── bottomMenu.js │ │ ├── CustomCells/ │ │ │ └── progressBarCell.js │ │ ├── TopMenu/ │ │ │ ├── Modals/ │ │ │ │ ├── RSSModal/ │ │ │ │ │ ├── RSSFeedList.js │ │ │ │ │ ├── RSSModalLayout.js │ │ │ │ │ ├── RSSTorrentList.js │ │ │ │ │ └── addRSSModal.js │ │ │ │ ├── addTorrentFileModal.js │ │ │ │ ├── addTorrentLinkModal.js │ │ │ │ └── deleteTorrentModal.js │ │ │ └── topMenu.js │ │ ├── app.js │ │ ├── index.html │ │ ├── leftMenu/ │ │ │ └── leftMenu.js │ │ ├── modals.js │ │ ├── store/ │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ └── torrentlist.js │ ├── loose-envify │ ├── loose-envify.cmd │ ├── miller-rabin │ ├── miller-rabin.cmd │ ├── mime │ ├── mime.cmd │ ├── mkdirp │ ├── mkdirp.cmd │ ├── multicast-dns │ ├── multicast-dns.cmd │ ├── package.json │ ├── parcel │ ├── parcel.cmd │ ├── rc │ ├── rc.cmd │ ├── react-scripts │ ├── react-scripts.cmd │ ├── regjsparser │ ├── regjsparser.cmd │ ├── rimraf │ ├── rimraf.cmd │ ├── sane │ ├── sane.cmd │ ├── semver │ ├── semver.cmd │ ├── sha.js │ ├── sha.js.cmd │ ├── src/ │ │ ├── BackendComm/ │ │ │ └── backendWebsocket.js │ │ ├── BottomMenu/ │ │ │ ├── Tabs/ │ │ │ │ ├── fileTab.js │ │ │ │ ├── generalTab.js │ │ │ │ └── peerTab.js │ │ │ └── bottomMenu.js │ │ ├── CSS/ │ │ │ └── topMenu.css │ │ ├── CustomCells/ │ │ │ └── progressBarCell.js │ │ ├── TopMenu/ │ │ │ ├── Modals/ │ │ │ │ ├── RSSModal/ │ │ │ │ │ ├── RSSFeedList.js │ │ │ │ │ ├── RSSModalLayout.js │ │ │ │ │ ├── RSSTorrentList.js │ │ │ │ │ └── addRSSModal.js │ │ │ │ ├── SettingsModal/ │ │ │ │ │ ├── SettingsModalContentTabs/ │ │ │ │ │ │ ├── apiSettingsTab.js │ │ │ │ │ │ ├── clientSettingsTab.js │ │ │ │ │ │ ├── loggingSettingsTab.js │ │ │ │ │ │ ├── notesTab.js │ │ │ │ │ │ └── serverSettingsTab.js │ │ │ │ │ ├── addSettingsModal.js │ │ │ │ │ ├── settingsModalContent.js │ │ │ │ │ ├── settingsModalLayout.js │ │ │ │ │ └── settingsModalList.js │ │ │ │ ├── addTorrentFileModal.js │ │ │ │ ├── addTorrentLinkModal.js │ │ │ │ ├── changeStorageModal.js │ │ │ │ └── deleteTorrentModal.js │ │ │ ├── topMenu.js │ │ │ └── torrentSearch.js │ │ ├── app.jsx │ │ ├── leftMenu/ │ │ │ └── leftMenu.js │ │ ├── login.js │ │ ├── notifications.js │ │ ├── store/ │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── torrentlist-test.js │ │ └── torrentlist.js │ ├── sshpk-conv │ ├── sshpk-conv.cmd │ ├── sshpk-sign │ ├── sshpk-sign.cmd │ ├── sshpk-verify │ ├── sshpk-verify.cmd │ ├── strip-indent │ ├── strip-indent.cmd │ ├── svgo │ ├── svgo.cmd │ ├── sw-precache │ ├── sw-precache.cmd │ ├── uglifyjs │ ├── uglifyjs.cmd │ ├── user-home │ ├── user-home.cmd │ ├── uuid │ ├── uuid.cmd │ ├── webpack │ ├── webpack-dev-server │ ├── webpack-dev-server.cmd │ ├── webpack.cmd │ ├── webpack.config.js │ ├── which │ └── which.cmd ├── main.go ├── public/ │ └── static/ │ ├── css/ │ │ └── gridbase.css │ └── js/ │ ├── bundle.js │ └── kickwebsocket-manual.js ├── settings/ │ ├── authentication_helper.go │ ├── clientConnectGenerate.go │ └── settings.go ├── storage/ │ └── storage.go ├── templates/ │ └── home.tmpl └── torrentUpload/ └── desktop.ini ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ downloads/ downloading/ downloaded/ uploadedTorrents/ boltBrowser/ storage.db.lock storage.db storage.db.old .torrent.bolt.db.lock .torrent.bolt.db .idea/torrent-project.iml .idea/modules.xml .idea/misc.xml output.json .idea/workspace.xml .idea/vcs.xml *.torrent boltbrowser.win64.exe logs/server.log .goreleaser.yml config.toml.backup config.1.toml config.toml.old /public/static/js/kickwebsocket.js.backup /public/static/js/kickwebsocket-generated.js clientAuth.txt dist debScripts/ ================================================ FILE: .vscode/settings.json ================================================ { "git.ignoreLimitWarning": true, "cSpell.words": [ "anacrolix", "asdine", "btih", "gofeed", "logrus", "mmcdole", "otiai" ] } ================================================ FILE: .vscode/tasks.json ================================================ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "taskName": "Run Program", "type": "shell", "command": "go run main.go storage.go settings.go", "problemMatcher": [ "$go" ] }, { "taskName": "goReleaser Snapshot", "type": "shell", "command": "C:/Users/deranjer/go/bin/goreleaser.exe -rm-dist -snapshot", "problemMatcher": [ "$go" ] } ] } ================================================ FILE: Dockerfile ================================================ FROM scratch COPY goTorrent / ENTRYPOINT [ "/goTorrent" ] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 deranjer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # goTorrent [](https://gitter.im/goTorrent-project/Lobby) [](https://goreportcard.com/report/github.com/deranjer/goTorrent) goTorrent is a torrenting server built with Go (Golang) with websocket API that comes with a React web frontend. The current release is an alpha release which means there may be bugs, please open issues to help me improve this software! Image of the frontend UI  ## Supported Platforms: - Windows - Linux - MacOS - (untested as I do not have a Mac) ### Supported Arch: - x64 ## Features: - Responsive React based WebUI - Download torrents from File upload or Magnet Link - Start/Stop/Delete Multiple Torrents - Add RSS feeds and automatically download new torrents from feed - Detailed information for each torrent - Automatic stop after seeding ratio reached - Pushbullet notification on torrent complete - Automatic move of completed torrent to new directory (leave symlink behind for seeding) - 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) - [X] Authentication from client to server (done via JWT, will add functionality for 3rd party clients later) - [X] Reverse Proxy Support with SSL upgrade added (with provided config for nginx) - [X] Mostly generated client config from toml.config on first run - [X] Ability to view TOML settings from WebUI (and perhaps change a few as well) - [X] Ability to set priority for individual files (needs more testing!) - [ ] 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 - [ ] Put the "Move torrent after download" into own goroutine with checks so the WebUI doesn't freeze when moving torrent - Late 2018 - [X] Define the websocket API for users to write their own clients/extensions - [ ] React-native Android app (I don't own any Mac products so there will be no iPhone version) # Documentation All the documentation is available [here](https://deranjer.github.io/goTorrentDocs/) # Special Thanks I viewed cloud-torrent source to construct my project: [Cloud-Torent:Cloud torrent is a a self-hosted remote torrent client, written in Go (golang)](https://github.com/jpillora/cloud-torrent) [Anacrolix BitTorrent client package and utilities](https://github.com/anacrolix/torrent) [goreleaser: Deliver Go binaries as fast and easily as possible](https://github.com/goreleaser/goreleaser) [Viper: Go configuration with fangs](https://github.com/spf13/viper) [logrus: Structured, pluggable logging for Go.](https://github.com/sirupsen/logrus) [boltdb: An embedded key/value database for Go.](https://github.com/boltdb/bolt) [storm: Simple and powerful toolkit for BoltDB](https://github.com/asdine/storm) [Gorilla: web toolkit for the Go programming language](http://www.gorillatoolkit.org/) [gofeed: Parse RSS and Atom feeds in Go](https://github.com/mmcdole/gofeed) [pushbullet-go: A library to call Pushbullet HTTP API for Golang](https://github.com/mitsuse/pushbullet-go) ================================================ FILE: config.toml ================================================ [serverConfig] ServerPort = "8000" #Required to input as string ServerAddr = "192.168.1.8" #Put in the IP address you want to bind to as string LogLevel = "Debug" # 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 #Relative or absolute path accepted, the server will convert any relative path to an absolute path. DefaultMoveFolder = 'downloads' #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" #Maximum number of allowed active torrents, the rest will be queued MaxActiveTorrents = 5 [goTorrentWebUI] #Basic goTorrentWebUI authentication (not terribly secure, implemented in JS, password is hashed to SHA256, not salted, basically don't depend on this if you require very good security) WebUIAuth = false # bool, if false no authentication is required for the webUI WebUIUser = "admin" WebUIPassword = "Password1" [notifications] PushBulletToken = "" #add your pushbullet api token here to notify of torrent completion to pushbullet # execute the command when torrent completes NotifyCommand = "" [reverseProxy] #This is for setting up goTorrent behind a reverse Proxy (with SSL, reverse proxy with no SSL will require editing the WSS connection to a WS connection manually) ProxyEnabled = false #bool, either false or true #URL is CASE SENSITIVE BaseURL = "domain.com/subroute/" # MUST be in the format (if you have a subdomain, and must have trailing slash) "yoursubdomain.domain.org/subroute/" [socksProxy] SocksProxyEnabled = false #bool, either false or true # Sets usage of Socks5 Proxy. Authentication should be included in the url if needed. # Examples: socks5://demo:demo@192.168.99.100:1080 # http://proxy.domain.com:3128 SocksProxyURL = "" [EncryptionPolicy] DisableEncryption = false ForceEncryption = false PreferNoEncryption = true [torrentClientConfig] DownloadDir = 'downloading' #the full OR relative path where the torrent server stores in-progress torrents Seed = true #boolean #seed after download # Never send chunks to peers. NoUpload = false #boolean #User-provided Client peer ID. If not present, one is generated automatically. PeerID = "" #string #The address to listen for new uTP and TCP bittorrent protocol connections. DHT shares a UDP socket with uTP unless configured otherwise. ListenAddr = "" #Leave Blank for default, syntax "HOST:PORT" #Don't announce to trackers. This only leaves DHT to discover peers. DisableTrackers = false #boolean DisablePEX = false # boolean # Don't create a DHT. NoDHT = false #boolean #For the bittorrent protocol. DisableUTP = false #bool #For the bittorrent protocol. DisableTCP = false #bool #Called to instantiate storage for each added torrent. Builtin backends # are in the storage package. If not set, the "file" implementation is used. DefaultStorage = "storage.ClientImpl" #encryption policy IPBlocklist = "" #of type iplist.Ranger DisableIPv6 = false #boolean Debug = false #boolean #HTTP *http.Client HTTPUserAgent = "" # HTTPUserAgent changes default UserAgent for HTTP requests ExtendedHandshakeClientVersion = "" Bep20 = "" # Overrides the default DHT configuration, see dhtServerConfig #advanced.. so be careful DHTConfig = "" # default is "dht.ServerConfig" [dhtServerConfig] # Set NodeId Manually. Caller must ensure that if NodeId does not conform to DHT Security Extensions, that NoSecurity is also set. NodeId = "" #[20]byte Conn = "" # https:#godoc.org/net#PacketConn #not implemented # Don't respond to queries from other nodes. Passive = false # boolean # the default addresses are "router.utorrent.com:6881","router.bittorrent.com:6881","dht.transmissionbt.com:6881","dht.aelitis.com:6881", #https:#github.com/anacrolix/dht/blob/master/dht.go StartingNodes = "dht.GlobalBootstrapAddrs" #Disable the DHT security extension: http:#www.libtorrent.org/dht_sec.html. NoSecurity = false #Initial IP blocklist to use. Applied before serving and bootstrapping begins. IPBlocklist = "" #of type iplist.Ranger #Used to secure the server's ID. Defaults to the Conn's LocalAddr(). Set to the IP that remote nodes will see, #as that IP is what they'll use to validate our ID. PublicIP = "" #net.IP #Hook received queries. Return true if you don't want to propagate to the default handlers. OnQuery = "func(query *krpc.Msg, source net.Addr) (propagate bool)" #Called when a peer successfully announces to us. OnAnnouncePeer = "func(infoHash metainfo.Hash, peer Peer)" #How long to wait before re-sending queries that haven't received a response. Defaults to a random value between 4.5 and 5.5s. QueryResendDelay = "func() time.Duration" ================================================ FILE: config.toml.bk ================================================ [serverConfig] ServerPort = ":8000" #leave format as is it expects a string with colon ServerAddr = "192.168.1.8" #Put in the IP address you want to bind to LogLevel = "Info" # Options = Debug, Info, Warn, Error, Fatal, Panic LogOutput = "stdout" #Options = file, stdout #file will print it to logs/server.log 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 = 'Z:\downloads' #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" [goTorrentWebUI] #Basic goTorrentWebUI authentication (not terribly secure, implemented in JS, password is hashed to SHA256, not salted, basically don't depend on this if you require very good security) WebUIAuth = false # bool, if false no authentication is required for the webUI WebUIUser = "admin" WebUIPassword = "Password1" [notifications] PushBulletToken = "o.8sUHemPkTCaty3u7KnyvEBN19EkeT63g" #add your pushbullet api token here to notify of torrent completion to pushbullet [reverseProxy] #This is for setting up goTorrent behind a reverse Proxy (with SSL, reverse proxy with no SSL will require editing the WSS connection to a WS connection manually) ProxyEnabled = false #bool, either false or true #URL is CASE SENSITIVE BaseURL = "derajnet.duckdns.org/gopher/" # MUST be in the format (if you have a subdomain, and must have trailing slash) "yoursubdomain.domain.org/subroute/" [EncryptionPolicy] DisableEncryption = false ForceEncryption = false PreferNoEncryption = false [torrentClientConfig] DownloadDir = 'downloading' #the full OR relative path where the torrent server stores in-progress torrents Seed = false #boolean #seed after download # Never send chunks to peers. NoUpload = false #boolean #User-provided Client peer ID. If not present, one is generated automatically. PeerID = "" #string #The address to listen for new uTP and TCP bittorrent protocol connections. DHT shares a UDP socket with uTP unless configured otherwise. ListenAddr = "" #Leave Blank for default, syntax "HOST:PORT" #Don't announce to trackers. This only leaves DHT to discover peers. DisableTrackers = false #boolean DisablePEX = false # boolean # Don't create a DHT. NoDHT = false #boolean #For the bittorrent protocol. DisableUTP = false #bool #For the bittorrent protocol. DisableTCP = false #bool #Called to instantiate storage for each added torrent. Builtin backends # are in the storage package. If not set, the "file" implementation is used. DefaultStorage = "storage.ClientImpl" #encryption policy IPBlocklist = "" #of type iplist.Ranger DisableIPv6 = false #boolean Debug = false #boolean #HTTP *http.Client HTTPUserAgent = "" # HTTPUserAgent changes default UserAgent for HTTP requests ExtendedHandshakeClientVersion = "" Bep20 = "" # Overrides the default DHT configuration, see dhtServerConfig #advanced.. so be careful DHTConfig = "" # default is "dht.ServerConfig" [dhtServerConfig] # Set NodeId Manually. Caller must ensure that if NodeId does not conform to DHT Security Extensions, that NoSecurity is also set. NodeId = "" #[20]byte Conn = "" # https:#godoc.org/net#PacketConn #not implemented # Don't respond to queries from other nodes. Passive = false # boolean # the default addresses are "router.utorrent.com:6881","router.bittorrent.com:6881","dht.transmissionbt.com:6881","dht.aelitis.com:6881", #https:#github.com/anacrolix/dht/blob/master/dht.go StartingNodes = "dht.GlobalBootstrapAddrs" #Disable the DHT security extension: http:#www.libtorrent.org/dht_sec.html. NoSecurity = false #Initial IP blocklist to use. Applied before serving and bootstrapping begins. IPBlocklist = "" #of type iplist.Ranger #Used to secure the server's ID. Defaults to the Conn's LocalAddr(). Set to the IP that remote nodes will see, #as that IP is what they'll use to validate our ID. PublicIP = "" #net.IP #Hook received queries. Return true if you don't want to propagate to the default handlers. OnQuery = "func(query *krpc.Msg, source net.Addr) (propagate bool)" #Called when a peer successfully announces to us. OnAnnouncePeer = "func(infoHash metainfo.Hash, peer Peer)" #How long to wait before re-sending queries that haven't received a response. Defaults to a random value between 4.5 and 5.5s. QueryResendDelay = "func() time.Duration" ================================================ FILE: dist-specific-files/Linux-systemd/goTorrent.service ================================================ [Unit] Description=goTorrent Server After=network.target [Service] type=simple User=goTorrent WorkingDirectory=/opt/goTorrent ExecStart=/opt/goTorrent/goTorrent Restart=on-abort [Install] WantedBy=multi-user.target ================================================ FILE: dist-specific-files/ReverseProxy/nginx.conf ================================================ location ^~ /gotorrent/ { proxy_pass http://192.168.1.100:8000/; proxy_redirect http:// https://; proxy_pass_header Server; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $http_address; proxy_set_header X-Scheme $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } ================================================ FILE: engine/clientStructs.go ================================================ package engine import ( "time" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" Settings "github.com/deranjer/goTorrent/settings" Storage "github.com/deranjer/goTorrent/storage" ) //All the message types are first, first the server handling messages from the client //Message contains the JSON messages from the client, we first unmarshal to get the messagetype, then pass it on to each module type Message struct { MessageType string Payload interface{} } //Next are the messages the server sends to the client //AuthResponse is sent when the client fails to perform authentication correctly type AuthResponse struct { MessageType string Payload string } //ServerPushMessage is information (usually logs and status messages) that the server pushes to the client type ServerPushMessage struct { MessageType string MessageLevel string //can be "success", "error", "warn", "info" Payload string //the actual message } //RSSJSONList is a slice of gofeed.Feeds sent to the client type RSSJSONList struct { MessageType string TotalRSSFeeds int RSSFeeds []RSSFeedsNames //strings of the full rss feed } //RSSFeedsNames stores all of the feeds by name and with URL type RSSFeedsNames struct { RSSName string RSSFeedURL string } //SingleRSSFeedMessage contains the torrents/name/etc of a single torrent feed type SingleRSSFeedMessage struct { //TODO had issues with getting this to work with Storage or Engine MessageType string URL string //the URL of the individual RSS feed Name string TotalTorrents int Torrents []Storage.SingleRSSTorrent //name of the torrents } //TorrentList struct contains the torrent list that is sent to the client type TorrentList struct { //helps create the JSON structure that react expects to receive MessageType string `json:"MessageType"` Totaltorrents int `json:"total"` ClientDBstruct []ClientDB `json:"data"` } //TorrentFileList supplies a list of files attached to a single torrent along with some additional information type TorrentFileList struct { MessageType string TotalFiles int `json:"TotalFiles"` FileList []TorrentFile `json:"FileList"` } //PeerFileList returns a slice of peers type PeerFileList struct { MessageType string TotalPeers int PeerList []torrent.Peer } //TorrentFile describes a single file that a torrent client is downloading for a single torrent type TorrentFile struct { TorrentHashString string //Used to tie the file to a torrent //TODO not sure if needed FileName string //The name of the file FilePath string //The relative filepath to the file FileSize string //Humanized file size display FilePercent string //String value of percent of individual file percent done FilePriority string //Currently "High", "Normal", or "Cancel" } type SettingsFile struct { MessageType string Config Settings.FullClientSettings } //ClientDB struct contains the struct that is used to compose the torrentlist type ClientDB struct { //TODO maybe separate out the internal bits into another client struct TorrentHashString string //Passed to client for displaying hash and is used to uniquely identify all torrents TorrentName string //String of the name of the torrent DownloadedSize string //how much the client has downloaded total Size string //total size of the torrent DownloadSpeed string //the dl speed of the torrent Status string //Passed to client for display PercentDone string //Passed to client to show percent done ActivePeers string //passed to client UploadSpeed string //passed to client to show Uploadspeed StoragePath string //Passed to client (and stored in stormdb) DateAdded string //Passed to client (and stored in stormdb) ETA string //Passed to client TorrentLabel string //Passed to client and stored in stormdb SourceType string //Stores whether the torrent came from a torrent file or a magnet link KnownSwarm []torrent.Peer //Passed to client for Peer Tab UploadRatio string //Passed to client, stores the string for uploadratio stored in stormdb TotalUploadedSize string //Humanized version of TotalUploadedBytes to pass to the client TotalUploadedBytes int64 `json:"-"` //includes bytes that happened before reboot (from stormdb) downloadSpeedInt int64 //Internal used for calculating dl speed BytesCompleted int64 `json:"-"` //Internal used for calculating the dl speed DataBytesWritten int64 `json:"-"` //Internal used for calculating dl speed DataBytesRead int64 `json:"-"` //Internal used for calculating dl speed UpdatedAt time.Time `json:"-"` //Internal used for calculating speeds of upload and download TorrentHash metainfo.Hash `json:"-"` //Used to create string for TorrentHashString... not sure why I have it... make that a TODO I guess NumberofFiles int //Number of files in the torrent NumberofPieces int //Total number of pieces in the torrent (Not currently used) MaxConnections int //Used to stop the torrent by limiting the max allowed connections } ================================================ FILE: engine/cronJobs.go ================================================ package engine import ( "io/ioutil" "os" "path/filepath" "github.com/anacrolix/torrent" "github.com/asdine/storm" Settings "github.com/deranjer/goTorrent/settings" Storage "github.com/deranjer/goTorrent/storage" "github.com/mmcdole/gofeed" "github.com/robfig/cron" "github.com/sirupsen/logrus" ) //InitializeCronEngine initializes and starts the cron engine so we can add tasks as needed, returns pointer to the engine func InitializeCronEngine() *cron.Cron { c := cron.New() c.Start() return c } //CheckTorrentWatchFolder adds torrents from a watch folder //TODO see if you can use filepath.Abs instead of changing directory func CheckTorrentWatchFolder(c *cron.Cron, db *storm.DB, tclient *torrent.Client, torrentLocalStorage Storage.TorrentLocal, config Settings.FullClientSettings, torrentQueues Storage.TorrentQueues) { c.AddFunc("@every 5m", func() { Logger.WithFields(logrus.Fields{"Watch Folder": config.TorrentWatchFolder}).Info("Running the watch folder cron job") torrentFiles, err := ioutil.ReadDir(config.TorrentWatchFolder) if err != nil { Logger.WithFields(logrus.Fields{"Folder": config.TorrentWatchFolder, "Error": err}).Error("Unable to read from the torrent upload folder") return } for _, file := range torrentFiles { if filepath.Ext(file.Name()) != ".torrent" { Logger.WithFields(logrus.Fields{"File": file.Name(), "error": err}).Error("Not a torrent file..") continue } else { fullFilePath := filepath.Join(config.TorrentWatchFolder, file.Name()) fullFilePathAbs, err := filepath.Abs(fullFilePath) fullNewFilePath := filepath.Join(config.TFileUploadFolder, file.Name()) fullNewFilePathAbs, err := filepath.Abs(fullNewFilePath) Logger.WithFields(logrus.Fields{"Name": file.Name(), "FullFilePath": fullFilePathAbs, "newFullFilePath": fullNewFilePathAbs}).Info("Attempting to add the following file... and copy to") CopyFile(fullFilePathAbs, fullNewFilePathAbs) clientTorrent, err := tclient.AddTorrentFromFile(fullNewFilePathAbs) if err != nil { Logger.WithFields(logrus.Fields{"err": err, "Torrent": file.Name()}).Warn("Unable to add torrent to torrent client!") continue } 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") AddTorrent(clientTorrent, torrentLocalStorage, db, "file", fullNewFilePathAbs, config.DefaultMoveFolder, "default", config) } } }) } //CheckTorrentsCron runs a upload ratio check, a queue check (essentially anything that should not be frontend dependent) func CheckTorrentsCron(c *cron.Cron, db *storm.DB, tclient *torrent.Client, config Settings.FullClientSettings) { c.AddFunc("@every 30s", func() { Logger.Debug("Running a torrent Ratio and Queue Check") torrentLocalArray := Storage.FetchAllStoredTorrents(db) torrentQueues := Storage.FetchQueues(db) for _, singleTorrentFromStorage := range torrentLocalArray { var singleTorrent *torrent.Torrent for _, liveTorrent := range tclient.Torrents() { //matching the torrent from storage to the live torrent if singleTorrentFromStorage.Hash == liveTorrent.InfoHash().String() { singleTorrent = liveTorrent } } calculatedCompletedSize := CalculateCompletedSize(singleTorrentFromStorage, singleTorrent) bytesCompleted := CalculateCompletedSize(singleTorrentFromStorage, singleTorrent) if float64(singleTorrentFromStorage.UploadedBytes)/float64(bytesCompleted) >= config.SeedRatioStop && singleTorrentFromStorage.TorrentUploadLimit == true { //If storage shows torrent stopped or if it is over the seeding ratio AND is under the global limit Logger.WithFields(logrus.Fields{"Action: Stopping torrent due to seed Ratio": singleTorrentFromStorage.TorrentName}).Info() StopTorrent(singleTorrent, singleTorrentFromStorage, db) } if len(torrentQueues.ActiveTorrents) < config.MaxActiveTorrents && singleTorrentFromStorage.TorrentStatus == "Queued" { Logger.WithFields(logrus.Fields{"Action: Adding Torrent to Active Queue": singleTorrentFromStorage.TorrentName}).Info() AddTorrentToActive(singleTorrentFromStorage, singleTorrent, db) } if (calculatedCompletedSize == singleTorrentFromStorage.TorrentSize) && (singleTorrentFromStorage.TorrentMoved == false) { //if we are done downloading and haven't moved torrent yet Logger.WithFields(logrus.Fields{"singleTorrent": singleTorrentFromStorage.TorrentName}).Info("Torrent Completed, moving...") tStorage := Storage.FetchTorrentFromStorage(db, singleTorrent.InfoHash().String()) //Todo... find a better way to do this in the go-routine currently just to make sure it doesn't trigger multiple times tStorage.TorrentMoved = true Storage.UpdateStorageTick(db, tStorage) go func() { //moving torrent in separate go-routine then verifying that the data is still there and correct err := MoveAndLeaveSymlink(config, singleTorrent.InfoHash().String(), db, false, "") //can take some time to move file so running this in another thread TODO make this a goroutine and skip this block if the routine is still running if err != nil { //If we fail, print the error and attempt a retry Logger.WithFields(logrus.Fields{"singleTorrent": singleTorrentFromStorage.TorrentName, "error": err}).Error("Failed to move Torrent!") VerifyData(singleTorrent) tStorage.TorrentMoved = false Storage.UpdateStorageTick(db, tStorage) } }() } } ValidateQueues(db, config, tclient) //Ensure we don't have too many in activeQueue }) } //RefreshRSSCron refreshes all of the RSS feeds on an hourly basis func RefreshRSSCron(c *cron.Cron, db *storm.DB, tclient *torrent.Client, torrentLocalStorage Storage.TorrentLocal, config Settings.FullClientSettings, torrentQueues Storage.TorrentQueues) { c.AddFunc("@hourly", func() { torrentHashHistory := Storage.FetchHashHistory(db) RSSFeedStore := Storage.FetchRSSFeeds(db) singleRSSTorrent := Storage.SingleRSSTorrent{} newFeedStore := Storage.RSSFeedStore{ID: RSSFeedStore.ID} //creating a new feed store just using old one to parse for new torrents fp := gofeed.NewParser() for _, singleFeed := range RSSFeedStore.RSSFeeds { feed, err := fp.ParseURL(singleFeed.URL) if err != nil { Logger.WithFields(logrus.Fields{"err": err, "url": singleFeed.URL}).Error("Failed to parse RSS URL") } for _, RSSTorrent := range feed.Items { Logger.WithFields(logrus.Fields{"Torrent": RSSTorrent.Title}).Info("Found new torrent") singleRSSTorrent.Link = RSSTorrent.Link singleRSSTorrent.Title = RSSTorrent.Title singleRSSTorrent.PubDate = RSSTorrent.Published for _, hash := range torrentHashHistory.HashList { linkHash := singleRSSTorrent.Link[20:60] //cutting the infohash out of the link if linkHash == hash { Logger.WithFields(logrus.Fields{"Torrent": RSSTorrent.Title}).Warn("Torrent already added for this RSS item, skipping torrent") } } clientTorrent, err := tclient.AddMagnet(RSSTorrent.Link) if err != nil { 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 } AddTorrent(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) } newFeedStore.RSSFeeds = append(newFeedStore.RSSFeeds, singleFeed) } Storage.UpdateRSSFeeds(db, newFeedStore) //Calling this to fully update storage will all rss feeds }) } //LogCronStatus prints out the status of the cron jobs to the log func LogCronStatus(c *cron.Cron) { //TODO add a cron to inspect cron jobs and log the outputs } ================================================ FILE: engine/doneTorrentActions.go ================================================ package engine import ( "fmt" "os" "os/exec" "path/filepath" "github.com/asdine/storm" Settings "github.com/deranjer/goTorrent/settings" Storage "github.com/deranjer/goTorrent/storage" pushbullet "github.com/mitsuse/pushbullet-go" "github.com/mitsuse/pushbullet-go/requests" folderCopy "github.com/otiai10/copy" "github.com/sirupsen/logrus" ) //MoveAndLeaveSymlink takes the file from the default download dir and moves it to the user specified directory and then leaves a symlink behind. func MoveAndLeaveSymlink(config Settings.FullClientSettings, tHash string, db *storm.DB, moveDone bool, oldPath string) error { //moveDone and oldPath are for moving a completed torrent 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 { //only occurs on manual move oldFilePathTemp := filepath.Join(oldPath, tStorage.TorrentName) 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!") moveDone = false return err } } else { oldFilePathTemp := filepath.Join(config.TorrentConfig.DataDir, tStorage.TorrentName) 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!") moveDone = false return err } } newFilePathTemp := filepath.Join(tStorage.StoragePath, tStorage.TorrentName) newFilePath, err := filepath.Abs(newFilePathTemp) if err != nil { Logger.WithFields(logrus.Fields{"Torrent Name": tStorage.TorrentName, "Filepath": newFilePath}).Error("Cannot create absolute file path for new file path!") moveDone = false return err } _, err = os.Stat(tStorage.StoragePath) if os.IsNotExist(err) { err := os.MkdirAll(tStorage.StoragePath, 0777) if err != nil { Logger.WithFields(logrus.Fields{"New File Path": newFilePath, "error": err}).Error("Cannot create new directory") moveDone = false return err } } oldFileInfo, err := os.Stat(oldFilePath) if err != nil { Logger.WithFields(logrus.Fields{"Old File info": oldFileInfo, "Old File Path": oldFilePath, "error": err}).Error("Cannot find the old file to copy/symlink!") moveDone = false return err } if oldFilePath != newFilePath { newFilePathDir := filepath.Dir(newFilePath) os.Mkdir(newFilePathDir, 0777) 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!") return err } err = filepath.Walk(newFilePath, func(path string, info os.FileInfo, err error) error { //Walking the file path to change the permissions if err != nil { Logger.WithFields(logrus.Fields{"file": path, "error": err}).Error("Potentially non-critical error, continuing..") } os.Chmod(path, 0777) return nil }) /* 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") moveDone = false return err } } */ 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) } return nil } func notifyUser(tStorage Storage.TorrentLocal, config Settings.FullClientSettings, db *storm.DB) { Logger.WithFields(logrus.Fields{"New File Path": tStorage.StoragePath, "Torrent Name": tStorage.TorrentName}).Info("Attempting to notify user..") tStorage.TorrentMoved = true //Storage.AddTorrentLocalStorage(db, tStorage) //Updating the fact that we moved the torrent Storage.UpdateStorageTick(db, tStorage) if config.PushBulletToken != "" { pb := pushbullet.New(config.PushBulletToken) n := requests.NewNote() n.Title = tStorage.TorrentName n.Body = "Completed and moved to " + tStorage.StoragePath if _, err := pb.PostPushesNote(n); err != nil { Logger.WithFields(logrus.Fields{"Torrent": tStorage.TorrentName, "New File Path": tStorage.StoragePath, "error": err}).Error("Error pushing PushBullet Note") return } Logger.WithFields(logrus.Fields{"Torrent": tStorage.TorrentName, "New File Path": tStorage.StoragePath}).Info("Pushbullet note sent") } else { Logger.WithFields(logrus.Fields{"New File Path": tStorage.StoragePath, "Torrent Name": tStorage.TorrentName}).Info("No pushbullet API key set, not notifying") } if config.NotifyCommand != "" { cmd := exec.Command(config.NotifyCommand) cmd.Env = append(os.Environ(), fmt.Sprintf("DIR=%s", tStorage.StoragePath), fmt.Sprintf("PATH=%s", tStorage.TorrentFileName), fmt.Sprintf("SIZE=%d", tStorage.TorrentSize), fmt.Sprintf("FILECNT=%d", len(tStorage.TorrentFile))) stdoutStderr, err := cmd.CombinedOutput() if err != nil { Logger.WithFields(logrus.Fields{"Torrent Name": tStorage.TorrentName, "New File Path": tStorage.StoragePath}).Error("NotifyCommand called error:", err) } Logger.WithFields(logrus.Fields{"Torrent Name": tStorage.TorrentName, "New File Path": tStorage.StoragePath}).Info("NotifyCommand called output:", stdoutStderr) } } ================================================ FILE: engine/engine.go ================================================ package engine //main file for all the calculations and data gathering needed for creating the running torrent arrays import ( "fmt" "io/ioutil" "os" "strconv" "strings" "time" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" "github.com/asdine/storm" Settings "github.com/deranjer/goTorrent/settings" Storage "github.com/deranjer/goTorrent/storage" "github.com/gorilla/websocket" "github.com/mmcdole/gofeed" "github.com/sirupsen/logrus" ) //Logger is the injected variable for global logger var Logger *logrus.Logger //Config is the injected variable for the torrent config var Config Settings.FullClientSettings //Conn is the injected variable for the websocket connection var Conn *websocket.Conn //CreateServerPushMessage Pushes a message from the server to the client func CreateServerPushMessage(message ServerPushMessage, conn *websocket.Conn) { conn.WriteJSON(message) } //RefreshSingleRSSFeed refreshing a single RSS feed to send to the client (so no updating database) mainly by updating the torrent list to display any changes func RefreshSingleRSSFeed(db *storm.DB, RSSFeed Storage.SingleRSSFeed) Storage.SingleRSSFeed { //Todo.. duplicate as cron job... any way to merge these to reduce duplication? singleRSSFeed := Storage.SingleRSSFeed{URL: RSSFeed.URL, Name: RSSFeed.Name} singleRSSTorrent := Storage.SingleRSSTorrent{} fp := gofeed.NewParser() feed, err := fp.ParseURL(RSSFeed.URL) if err != nil { Logger.WithFields(logrus.Fields{"RSSFeedURL": RSSFeed.URL, "error": err}).Error("Unable to parse URL") CreateServerPushMessage(ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add Storage Path"}, Conn) } for _, RSSTorrent := range feed.Items { singleRSSTorrent.Link = RSSTorrent.Link singleRSSTorrent.Title = RSSTorrent.Title singleRSSTorrent.PubDate = RSSTorrent.Published singleRSSFeed.Torrents = append(singleRSSFeed.Torrents, singleRSSTorrent) } return singleRSSFeed } //ForceRSSRefresh forces a refresh (in addition to the cron schedule) to add the new RSS feed func ForceRSSRefresh(db *storm.DB, RSSFeedStore Storage.RSSFeedStore) { //Todo.. duplicate as cron job... any way to merge these to reduce duplication? singleRSSTorrent := Storage.SingleRSSTorrent{} newFeedStore := Storage.RSSFeedStore{ID: RSSFeedStore.ID} //creating a new feed store just using old one to parse for new torrents fp := gofeed.NewParser() Logger.WithFields(logrus.Fields{"RSSFeedStoreLength": len(RSSFeedStore.RSSFeeds)}).Debug("Length of RSS feeds (should be ONE)") for _, singleFeed := range RSSFeedStore.RSSFeeds { feed, err := fp.ParseURL(singleFeed.URL) if err != nil { Logger.WithFields(logrus.Fields{"RSSFeedURL": singleFeed.URL, "error": err}).Error("Unable to parse RSS URL") CreateServerPushMessage(ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to parse RSS URL"}, Conn) } for _, RSSTorrent := range feed.Items { singleRSSTorrent.Link = RSSTorrent.Link singleRSSTorrent.Title = RSSTorrent.Title singleRSSTorrent.PubDate = RSSTorrent.Published singleFeed.Torrents = append(singleFeed.Torrents, singleRSSTorrent) } newFeedStore.RSSFeeds = append(newFeedStore.RSSFeeds, singleFeed) } Storage.UpdateRSSFeeds(db, newFeedStore) //Calling this to fully update storage will all rss feeds } //timeOutInfo forcing a timeout of the torrent if it doesn't load from program restart func timeOutInfo(clientTorrent *torrent.Torrent, seconds time.Duration) (deleted bool) { Logger.WithFields(logrus.Fields{"Seconds to wait for info...": seconds}).Info("Attempting to download info for torrent") timeout := make(chan bool, 1) //creating a timeout channel for our gotinfo go func() { time.Sleep(seconds * time.Second) timeout <- true }() select { case <-clientTorrent.GotInfo(): //attempting to retrieve info for torrent Logger.WithFields(logrus.Fields{"clientTorrentName": clientTorrent.Name()}).Debug("Received torrent info for torrent") return false case <-timeout: // getting info for torrent has timed out so purging the torrent Logger.WithFields(logrus.Fields{"clientTorrentName": clientTorrent.Name()}).Error("Forced to drop torrent from timeout waiting for info") CreateServerPushMessage(ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Timout waiting for torrent info... dropping"}, Conn) clientTorrent.Drop() return true } } func readTorrentFileFromDB(element *Storage.TorrentLocal, tclient *torrent.Client, db *storm.DB) (singleTorrent *torrent.Torrent, err error) { tempFile, err := ioutil.TempFile("", "TorrentFileTemp") if err != nil { Logger.WithFields(logrus.Fields{"tempfile": tempFile, "err": err}).Error("Unable to create tempfile") return nil, err } //defer tempFile.Close() //Todo.. if we remove this do we need to close it? defer os.Remove(tempFile.Name()) if _, err := tempFile.Write(element.TorrentFile); err != nil { //writing out out the entire file back into the temp dir from boltdb Logger.WithFields(logrus.Fields{"tempfile": tempFile, "err": err}).Error("Unable to write to tempfile") return nil, err } if err := tempFile.Close(); err != nil { //close the tempfile so that we can add it back into the torrent client Logger.WithFields(logrus.Fields{"tempfile": tempFile, "err": err}).Error("Unable to close tempfile") } _, err = os.Stat(element.TorrentFileName) //if we CAN find the torrent, add it if err != nil { Logger.WithFields(logrus.Fields{"tempfile": tempFile, "err": err}).Error("Unable to find file") Storage.DelTorrentLocalStorage(db, element.Hash) //purge the torrent return nil, err } singleTorrent, err = tclient.AddTorrentFromFile(element.TorrentFileName) if err != nil { Logger.WithFields(logrus.Fields{"tempfile": element.TorrentFileName, "err": err}).Error("Unable to add Torrent from file!") CreateServerPushMessage(ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "error", Payload: "Unable to add Torrent from file!"}, Conn) Storage.DelTorrentLocalStorage(db, element.Hash) //purge the torrent return nil, err } return singleTorrent, nil } //AddTorrent creates the storage.db entry and starts A NEW TORRENT and adds to the running torrent array func AddTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage Storage.TorrentLocal, db *storm.DB, torrentType, torrentFilePathAbs, torrentStoragePath, labelValue string, config Settings.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 } var TempHash metainfo.Hash TempHash = clientTorrent.InfoHash() fmt.Println("GOT INFOHASH", TempHash.String()) allStoredTorrents := Storage.FetchAllStoredTorrents(db) for _, runningTorrentHashes := range allStoredTorrents { if runningTorrentHashes.Hash == TempHash.String() { Logger.WithFields(logrus.Fields{"Hash": TempHash.String()}).Info("Torrent has duplicate hash to already running torrent... will not add to storage") return } } torrentLocalStorage.Hash = TempHash.String() // we will store the infohash to add it back later on client restart (if needed) torrentLocalStorage.InfoBytes = clientTorrent.Metainfo().InfoBytes 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. torrentLocalStorage.TorrentStatus = "Running" //by default start all the torrents as downloading. 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 torrentfile, err := ioutil.ReadFile(torrentFilePathAbs) torrentLocalStorage.TorrentFileName = torrentFilePathAbs if err != nil { Logger.WithFields(logrus.Fields{"torrentFile": torrentfile, "error": err}).Error("Unable to read the torrent file") } torrentLocalStorage.TorrentFile = torrentfile //storing the entire file in to database } Logger.WithFields(logrus.Fields{"Storage Path": torrentStoragePath, "Torrent Name": clientTorrent.Name()}).Info("Adding Torrent with following storage path, to active Queue") torrentFiles := clientTorrent.Files() //storing all of the files in the database along with the priority var TorrentFilePriorityArray = []Storage.TorrentFilePriority{} for _, singleFile := range torrentFiles { //creating the database setup for the file array var torrentFilePriority = Storage.TorrentFilePriority{} torrentFilePriority.TorrentFilePath = singleFile.DisplayPath() torrentFilePriority.TorrentFilePriority = "Normal" torrentFilePriority.TorrentFileSize = singleFile.Length() TorrentFilePriorityArray = append(TorrentFilePriorityArray, torrentFilePriority) } torrentLocalStorage.TorrentFilePriority = TorrentFilePriorityArray //torrentQueues := Storage.FetchQueues(db) AddTorrentToActive(&torrentLocalStorage, clientTorrent, db) Storage.AddTorrentLocalStorage(db, torrentLocalStorage) //writing all of the data to the database } //CreateInitialTorrentArray adds all the torrents on program start from the database func CreateInitialTorrentArray(tclient *torrent.Client, TorrentLocalArray []*Storage.TorrentLocal, db *storm.DB, config Settings.FullClientSettings) { for _, singleTorrentFromStorage := range TorrentLocalArray { var singleTorrent *torrent.Torrent var err error if singleTorrentFromStorage.TorrentType == "file" { //if it is a file pull it from the uploaded torrent folder singleTorrent, err = readTorrentFileFromDB(singleTorrentFromStorage, tclient, db) if err != nil { continue } } else { singleTorrentFromStorageMagnet := "magnet:?xt=urn:btih:" + singleTorrentFromStorage.Hash //For magnet links just need to prepend the magnet part to the hash to readd singleTorrent, err = tclient.AddMagnet(singleTorrentFromStorageMagnet) if err != nil { continue } } if len(singleTorrentFromStorage.InfoBytes) == 0 { //TODO.. kind of a fringe scenario.. not sure if needed since the db should always have the infobytes timeOut := timeOutInfo(singleTorrent, 45) if timeOut == true { // if we did timeout then drop the torrent from the bolt.db database Storage.DelTorrentLocalStorage(db, singleTorrentFromStorage.Hash) //purging torrent from the local database continue } singleTorrentFromStorage.InfoBytes = singleTorrent.Metainfo().InfoBytes } err = singleTorrent.SetInfoBytes(singleTorrentFromStorage.InfoBytes) //setting the infobytes back into the torrent if err != nil { Logger.WithFields(logrus.Fields{"torrentFile": singleTorrent.Name(), "error": err}).Error("Unable to add infobytes to the torrent!") } torrentQueues := Storage.FetchQueues(db) if singleTorrentFromStorage.TorrentStatus == "Stopped" { singleTorrent.SetMaxEstablishedConns(0) continue } if singleTorrentFromStorage.TorrentStatus == "ForceStart" { AddTorrentToForceStart(singleTorrentFromStorage, singleTorrent, db) } if len(torrentQueues.ActiveTorrents) == 0 && len(torrentQueues.QueuedTorrents) == 0 { // If empty, run through all the torrents and assign them if len(torrentQueues.ActiveTorrents) < Config.MaxActiveTorrents { if singleTorrentFromStorage.TorrentStatus == "Completed" || singleTorrentFromStorage.TorrentStatus == "Seeding" { Logger.WithFields(logrus.Fields{"Torrent Name": singleTorrentFromStorage.TorrentName}).Info("Completed Torrents have lower priority, adding to Queued") AddTorrentToQueue(singleTorrentFromStorage, singleTorrent, db) } else { Logger.WithFields(logrus.Fields{"Torrent Name": singleTorrentFromStorage.TorrentName}).Info("Adding Torrent to Active Queue (Initial Torrent Load)") AddTorrentToActive(singleTorrentFromStorage, singleTorrent, db) } } else { Logger.WithFields(logrus.Fields{"Torrent Name": singleTorrentFromStorage.TorrentName}).Info("Last resort for torrent, adding to Queued") AddTorrentToQueue(singleTorrentFromStorage, singleTorrent, db) } } else { //If we already have a queue set up then assign torrents to queue if singleTorrentFromStorage.TorrentStatus == "Queued" { AddTorrentToQueue(singleTorrentFromStorage, singleTorrent, db) } else { if len(torrentQueues.ActiveTorrents) < Config.MaxActiveTorrents { Logger.WithFields(logrus.Fields{"Torrent Name": singleTorrentFromStorage.TorrentName}).Info("Adding Torrent to Active Queue (Initial Torrent Load Second)") AddTorrentToActive(singleTorrentFromStorage, singleTorrent, db) } else { AddTorrentToQueue(singleTorrentFromStorage, singleTorrent, db) } } RemoveDuplicatesFromQueues(db) } Storage.UpdateStorageTick(db, *singleTorrentFromStorage) } torrentQueues := Storage.FetchQueues(db) if len(torrentQueues.ActiveTorrents) < config.MaxActiveTorrents && len(torrentQueues.QueuedTorrents) > 0 { //after all the torrents are added, see if out active torrent list isn't full, then add from the queue Logger.WithFields(logrus.Fields{"Max Active: ": config.MaxActiveTorrents, "Current : ": torrentQueues.ActiveTorrents}).Info("Adding Torrents from queue to active to fill...") maxCanSend := config.MaxActiveTorrents - len(torrentQueues.ActiveTorrents) if maxCanSend > len(torrentQueues.QueuedTorrents) { maxCanSend = len(torrentQueues.QueuedTorrents) } torrentsToStart := make([]string, maxCanSend) copy(torrentsToStart, torrentQueues.QueuedTorrents[len(torrentsToStart)-1:]) for _, torrentStart := range torrentsToStart { for _, singleTorrent := range tclient.Torrents() { if singleTorrent.InfoHash().String() == torrentStart { singleTorrentFromStorage := Storage.FetchTorrentFromStorage(db, torrentStart) AddTorrentToActive(&singleTorrentFromStorage, singleTorrent, db) } } } } SetFilePriority(tclient, db) //Setting the desired file priority from storage Logger.WithFields(logrus.Fields{"Max Active: ": config.MaxActiveTorrents, "Current : ": torrentQueues.ActiveTorrents}).Debug("Queue after all initial torrents have been added") } //CreateRunningTorrentArray creates the entire torrent list to pass to client func CreateRunningTorrentArray(tclient *torrent.Client, TorrentLocalArray []*Storage.TorrentLocal, PreviousTorrentArray []ClientDB, config Settings.FullClientSettings, db *storm.DB) (RunningTorrentArray []ClientDB) { torrentQueues := Storage.FetchQueues(db) Logger.WithFields(logrus.Fields{"Max Active: ": config.MaxActiveTorrents, "TorrentQueues": torrentQueues}).Debug("Current TorrentQueues") for _, singleTorrentFromStorage := range TorrentLocalArray { torrentQueues := Storage.FetchQueues(db) var singleTorrent *torrent.Torrent for _, liveTorrent := range tclient.Torrents() { //matching the torrent from storage to the live torrent if singleTorrentFromStorage.Hash == liveTorrent.InfoHash().String() { singleTorrent = liveTorrent } } tickUpdateStruct := Storage.TorrentLocal{} //we are shoving the tick updates into a torrentlocal struct to pass to storage happens at the end of the routine fullClientDB := new(ClientDB) //Handling deleted torrents here if singleTorrentFromStorage.TorrentStatus == "Dropped" { Logger.WithFields(logrus.Fields{"selection": singleTorrentFromStorage.TorrentName}).Info("Deleting just the torrent") DeleteTorrentFromQueues(singleTorrentFromStorage.Hash, db) singleTorrent.Drop() Storage.DelTorrentLocalStorage(db, singleTorrentFromStorage.Hash) } if singleTorrentFromStorage.TorrentStatus == "DroppedData" { Logger.WithFields(logrus.Fields{"selection": singleTorrentFromStorage.TorrentName}).Info("Deleting torrent and data") singleTorrent.Drop() DeleteTorrentFromQueues(singleTorrentFromStorage.Hash, db) Storage.DelTorrentLocalStorageAndFiles(db, singleTorrentFromStorage.Hash, Config.TorrentConfig.DataDir) } if singleTorrentFromStorage.TorrentType == "file" { //if it is a file pull it from the uploaded torrent folder fullClientDB.SourceType = "Torrent File" } else { fullClientDB.SourceType = "Magnet Link" } var TempHash metainfo.Hash TempHash = singleTorrent.InfoHash() calculatedTotalSize := CalculateDownloadSize(singleTorrentFromStorage, singleTorrent) calculatedCompletedSize := CalculateCompletedSize(singleTorrentFromStorage, singleTorrent) fullStruct := singleTorrent.Stats() activePeersString := strconv.Itoa(fullStruct.ActivePeers) //converting to strings totalPeersString := fmt.Sprintf("%v", fullStruct.TotalPeers) fullClientDB.StoragePath = singleTorrentFromStorage.StoragePath downloadedSizeHumanized := HumanizeBytes(float32(calculatedCompletedSize)) //convert size to GB if needed totalSizeHumanized := HumanizeBytes(float32(calculatedTotalSize)) fullClientDB.DownloadedSize = downloadedSizeHumanized fullClientDB.Size = totalSizeHumanized PercentDone := fmt.Sprintf("%.2f", float32(calculatedCompletedSize)/float32(calculatedTotalSize)) fullClientDB.TorrentHash = TempHash fullClientDB.PercentDone = PercentDone fullClientDB.DataBytesRead = fullStruct.ConnStats.BytesReadData.Int64() //used for calculations not passed to client calculating up/down speed fullClientDB.DataBytesWritten = fullStruct.ConnStats.BytesWrittenData.Int64() //used for calculations not passed to client calculating up/down speed fullClientDB.ActivePeers = activePeersString + " / (" + totalPeersString + ")" fullClientDB.TorrentHashString = TempHash.String() fullClientDB.TorrentName = singleTorrentFromStorage.TorrentName fullClientDB.DateAdded = singleTorrentFromStorage.DateAdded fullClientDB.TorrentLabel = singleTorrentFromStorage.Label fullClientDB.BytesCompleted = calculatedCompletedSize fullClientDB.NumberofFiles = len(singleTorrent.Files()) if len(PreviousTorrentArray) > 0 { //if we actually have a previous array //ranging over the previous torrent array to calculate the speed for each torrent for _, previousElement := range PreviousTorrentArray { TempHash := singleTorrent.InfoHash() if previousElement.TorrentHashString == TempHash.String() { //matching previous to new CalculateTorrentSpeed(singleTorrent, fullClientDB, previousElement, calculatedCompletedSize) fullClientDB.TotalUploadedBytes = singleTorrentFromStorage.UploadedBytes + (fullStruct.ConnStats.BytesWrittenData.Int64() - previousElement.DataBytesWritten) } } } CalculateTorrentETA(singleTorrentFromStorage.TorrentSize, calculatedCompletedSize, fullClientDB) //needs to be here since we need the speed calculated before we can estimate the eta. fullClientDB.TotalUploadedSize = HumanizeBytes(float32(fullClientDB.TotalUploadedBytes)) fullClientDB.UploadRatio = CalculateUploadRatio(singleTorrent, fullClientDB) //calculate the upload ratio CalculateTorrentStatus(singleTorrent, fullClientDB, config, singleTorrentFromStorage, calculatedCompletedSize, calculatedTotalSize, torrentQueues, db) //add torrents to the queue, remove from queue, etc tickUpdateStruct.UploadRatio = fullClientDB.UploadRatio tickUpdateStruct.TorrentSize = calculatedTotalSize tickUpdateStruct.UploadedBytes = fullClientDB.TotalUploadedBytes tickUpdateStruct.TorrentStatus = fullClientDB.Status tickUpdateStruct.Hash = fullClientDB.TorrentHashString //needed for index Storage.UpdateStorageTick(db, tickUpdateStruct) RunningTorrentArray = append(RunningTorrentArray, *fullClientDB) } return RunningTorrentArray } //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, config Settings.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{} TorrentFileStruct := TorrentFile{} for _, singleTorrent := range runningTorrents { tempHash := singleTorrent.InfoHash().String() if tempHash == selectedHash { // if our selection hash equals our torrent hash torrentFilesRaw := singleTorrent.Files() Logger.WithFields(logrus.Fields{"torrentFiles": torrentFilesRaw}).Debug("Unable to close tempfile") for _, singleFile := range torrentFilesRaw { TorrentFileStruct.TorrentHashString = tempHash TorrentFileStruct.FileName = singleFile.DisplayPath() TorrentFileStruct.FilePath = singleFile.Path() PieceState := singleFile.State() var downloadedBytes int64 for _, piece := range PieceState { if piece.Complete { downloadedBytes = downloadedBytes + piece.Bytes //adding up the bytes in the completed pieces } } TorrentFileStruct.FilePercent = fmt.Sprintf("%.2f", float32(downloadedBytes)/float32(singleFile.Length())) for i, specificFile := range torrentFileListStorage.TorrentFilePriority { //searching for that specific file in storage if specificFile.TorrentFilePath == singleFile.DisplayPath() { TorrentFileStruct.FilePriority = torrentFileListStorage.TorrentFilePriority[i].TorrentFilePriority } } TorrentFileStruct.FileSize = HumanizeBytes(float32(singleFile.Length())) TorrentFileListSelected.FileList = append(TorrentFileListSelected.FileList, TorrentFileStruct) } TorrentFileListSelected.MessageType = "torrentFileList" TorrentFileListSelected.TotalFiles = len(singleTorrent.Files()) Logger.WithFields(logrus.Fields{"selectedFiles": TorrentFileListSelected}).Debug("Selected Torrent Files") return TorrentFileListSelected } } return TorrentFileListSelected } //CreatePeerListArray create a list of peers for the torrent and displays them func CreatePeerListArray(tclient *torrent.Client, selectedHash string) PeerFileList { runningTorrents := tclient.Torrents() TorrentPeerList := PeerFileList{} for _, singleTorrent := range runningTorrents { tempHash := singleTorrent.InfoHash().String() if (strings.Compare(tempHash, selectedHash)) == 0 { TorrentPeerList.MessageType = "torrentPeerList" TorrentPeerList.PeerList = singleTorrent.KnownSwarm() TorrentPeerList.TotalPeers = len(TorrentPeerList.PeerList) return TorrentPeerList } } return TorrentPeerList } //CreateTorrentDetailJSON creates the json response for a request for more torrent information func CreateTorrentDetailJSON(tclient *torrent.Client, selectedHash string, torrentStorage *storm.DB) ClientDB { localTorrentInfo := Storage.FetchTorrentFromStorage(torrentStorage, selectedHash) runningTorrents := tclient.Torrents() TorrentDetailStruct := ClientDB{} for _, singleTorrent := range runningTorrents { //ranging through the running torrents to find the one we are looking for tempHash := singleTorrent.InfoHash().String() if tempHash == selectedHash { Logger.WithFields(logrus.Fields{"torrentHash": tempHash, "detailedInfo": localTorrentInfo}).Info("Creating detailed torrent list") return TorrentDetailStruct } } return TorrentDetailStruct } ================================================ FILE: engine/engineHelpers.go ================================================ package engine import ( "fmt" "io" "os" "time" "github.com/anacrolix/torrent" "github.com/asdine/storm" Settings "github.com/deranjer/goTorrent/settings" "github.com/deranjer/goTorrent/storage" Storage "github.com/deranjer/goTorrent/storage" "github.com/sirupsen/logrus" ) func secondsToMinutes(inSeconds int64) string { minutes := inSeconds / 60 seconds := inSeconds % 60 minutesString := fmt.Sprintf("%d", minutes) secondsString := fmt.Sprintf("%d", seconds) str := minutesString + " Min/ " + secondsString + " Sec" return str } //VerifyData just verifies the data of a torrent by hash func VerifyData(singleTorrent *torrent.Torrent) { singleTorrent.VerifyData() } //MakeRange creates a range of pieces to set their priority based on a file func MakeRange(min, max int) []int { a := make([]int, max-min+1) for i := range a { a[i] = min + i } return a } //HumanizeBytes returns a nice humanized version of bytes in either GB or MB func HumanizeBytes(bytes float32) string { if bytes < 1000000 { //if we have less than 1MB in bytes convert to KB pBytes := fmt.Sprintf("%.2f", bytes/1024) pBytes = pBytes + " KB" return pBytes } bytes = bytes / 1024 / 1024 //Converting bytes to a useful measure if bytes > 1024 { pBytes := fmt.Sprintf("%.2f", bytes/1024) pBytes = pBytes + " GB" return pBytes } pBytes := fmt.Sprintf("%.2f", bytes) //If not too big or too small leave it as MB pBytes = pBytes + " MB" return pBytes } //CopyFile takes a source file string and a destination file string and copies the file func CopyFile(srcFile string, destFile string) { //TODO move this to our imported copy repo fileContents, err := os.Open(srcFile) defer fileContents.Close() if err != nil { Logger.WithFields(logrus.Fields{"File": srcFile, "Error": err}).Error("Cannot open source file") } outfileContents, err := os.Create(destFile) defer outfileContents.Close() if err != nil { Logger.WithFields(logrus.Fields{"File": destFile, "Error": err}).Error("Cannot open destination file") } _, err = io.Copy(outfileContents, fileContents) if err != nil { Logger.WithFields(logrus.Fields{"Source File": srcFile, "Destination File": destFile, "Error": err}).Error("Cannot write contents to destination file") } } //SetFilePriority sets the priorities for all of the files in all of the torrents func SetFilePriority(t *torrent.Client, db *storm.DB) { storedTorrents := Storage.FetchAllStoredTorrents(db) for _, singleTorrent := range t.Torrents() { for _, storedTorrent := range storedTorrents { if storedTorrent.Hash == singleTorrent.InfoHash().String() { for _, file := range singleTorrent.Files() { for _, storedFile := range storedTorrent.TorrentFilePriority { if storedFile.TorrentFilePath == file.DisplayPath() { switch storedFile.TorrentFilePriority { case "High": file.SetPriority(torrent.PiecePriorityHigh) case "Normal": file.SetPriority(torrent.PiecePriorityNormal) case "Cancel": file.SetPriority(torrent.PiecePriorityNone) default: file.SetPriority(torrent.PiecePriorityNormal) } } } } } } } } //CalculateTorrentSpeed is used to calculate the torrent upload and download speed over time c is current clientdb, oc is last client db to calculate speed over time func CalculateTorrentSpeed(t *torrent.Torrent, c *ClientDB, oc ClientDB, completedSize int64) { now := time.Now() bytes := completedSize 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 dbU := float32(bytesUpload.Int64() - oc.DataBytesWritten) rateUpload := dbU * (float32(time.Second) / dt) if rate >= 0 { rateMB := rate / 1024 / 1024 //creating MB to calculate ETA c.DownloadSpeed = fmt.Sprintf("%.2f", rateMB) c.DownloadSpeed = c.DownloadSpeed + " MB/s" c.downloadSpeedInt = int64(rate) } if rateUpload >= 0 { rateUpload = rateUpload / 1024 / 1024 c.UploadSpeed = fmt.Sprintf("%.2f", rateUpload) c.UploadSpeed = c.UploadSpeed + " MB/s" } c.UpdatedAt = now } //CalculateDownloadSize will calculate the download size once file priorities are sorted out func CalculateDownloadSize(tFromStorage *Storage.TorrentLocal, activeTorrent *torrent.Torrent) int64 { var totalLength int64 for _, file := range tFromStorage.TorrentFilePriority { if file.TorrentFilePriority != "Cancel" { totalLength = totalLength + file.TorrentFileSize } } return totalLength } //CalculateCompletedSize will be used to calculate how much of the actual torrent we have completed minus the canceled files (even if they have been partially downloaded) func CalculateCompletedSize(tFromStorage *Storage.TorrentLocal, activeTorrent *torrent.Torrent) int64 { var discardByteLength int64 for _, storageFile := range tFromStorage.TorrentFilePriority { if storageFile.TorrentFilePriority == "Cancel" { //If the file is canceled don't count it as downloaded for _, activeFile := range activeTorrent.Files() { if activeFile.DisplayPath() == storageFile.TorrentFilePath { //match the file from storage to active for _, piece := range activeFile.State() { if piece.Partial || piece.Complete { discardByteLength = discardByteLength + piece.Bytes } } } } } } downloadedLength := activeTorrent.BytesCompleted() - discardByteLength if downloadedLength < 0 { downloadedLength = 0 } return downloadedLength } //CalculateTorrentETA is used to estimate the remaining dl time of the torrent based on the speed that the MB are being downloaded func CalculateTorrentETA(tSize int64, tBytesCompleted int64, c *ClientDB) { missingBytes := tSize - tBytesCompleted if missingBytes == 0 { c.ETA = "Done" } else if c.downloadSpeedInt == 0 { c.ETA = "N/A" } else { ETASeconds := missingBytes / c.downloadSpeedInt str := secondsToMinutes(ETASeconds) //converting seconds to minutes + seconds c.ETA = str } } //CalculateUploadRatio calculates the download to upload ratio so you can see if you are being a good seeder func CalculateUploadRatio(t *torrent.Torrent, c *ClientDB) string { if c.TotalUploadedBytes > 0 && t.BytesCompleted() > 0 { //If we have actually started uploading and downloading stuff start calculating our ratio uploadRatio := fmt.Sprintf("%.2f", float64(c.TotalUploadedBytes)/float64(t.BytesCompleted())) return uploadRatio } uploadRatio := "0.00" //we haven't uploaded anything so no upload ratio just pass a string directly return uploadRatio } //StopTorrent stops the torrent, updates the database and sends a message. Since stoptorrent is called by each loop (individually) no need to call an array func StopTorrent(singleTorrent *torrent.Torrent, torrentLocalStorage *Storage.TorrentLocal, db *storm.DB) { if torrentLocalStorage.TorrentStatus == "Stopped" { //if we are already stopped Logger.WithFields(logrus.Fields{"Torrent Name": torrentLocalStorage.TorrentName}).Info("Torrent Already Stopped, returning...") return } torrentLocalStorage.TorrentStatus = "Stopped" torrentLocalStorage.MaxConnections = 0 singleTorrent.SetMaxEstablishedConns(0) DeleteTorrentFromQueues(singleTorrent.InfoHash().String(), db) Storage.UpdateStorageTick(db, *torrentLocalStorage) CreateServerPushMessage(ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "success", Payload: "Torrent Stopped!"}, Conn) Logger.WithFields(logrus.Fields{"Torrent Name": torrentLocalStorage.TorrentName}).Info("Torrent Stopped Success!") } //AddTorrentToForceStart forces torrent to be high priority on start func AddTorrentToForceStart(torrentLocalStorage *Storage.TorrentLocal, singleTorrent *torrent.Torrent, db *storm.DB) { torrentQueues := Storage.FetchQueues(db) for index, torrentHash := range torrentQueues.ActiveTorrents { if torrentHash == singleTorrent.InfoHash().String() { //If torrent already in active remove from active torrentQueues.ActiveTorrents = append(torrentQueues.ActiveTorrents[:index], torrentQueues.ActiveTorrents[index+1:]...) } } for index, queuedTorrentHash := range torrentQueues.QueuedTorrents { //Removing from the queued torrents if in queued torrents if queuedTorrentHash == singleTorrent.InfoHash().String() { torrentQueues.QueuedTorrents = append(torrentQueues.QueuedTorrents[:index], torrentQueues.QueuedTorrents[index+1:]...) } } singleTorrent.NewReader() singleTorrent.SetMaxEstablishedConns(80) torrentQueues.ActiveTorrents = append(torrentQueues.ActiveTorrents, singleTorrent.InfoHash().String()) torrentLocalStorage.TorrentStatus = "ForceStart" torrentLocalStorage.MaxConnections = 80 for _, file := range singleTorrent.Files() { for _, sentFile := range torrentLocalStorage.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 Name": torrentLocalStorage.TorrentName}).Info("Adding Torrent to ForceStart Queue") Storage.UpdateStorageTick(db, *torrentLocalStorage) Storage.UpdateQueues(db, torrentQueues) } //AddTorrentToActive adds a torrent to the active slice func AddTorrentToActive(torrentLocalStorage *Storage.TorrentLocal, singleTorrent *torrent.Torrent, db *storm.DB) { torrentQueues := Storage.FetchQueues(db) if torrentLocalStorage.TorrentStatus == "Stopped" { Logger.WithFields(logrus.Fields{"Torrent Name": torrentLocalStorage.TorrentName}).Info("Torrent set as stopped, skipping add") return } for _, torrentHash := range torrentQueues.ActiveTorrents { if torrentHash == singleTorrent.InfoHash().String() { //If torrent already in active skip return } } for index, queuedTorrentHash := range torrentQueues.QueuedTorrents { //Removing from the queued torrents if in queued torrents if queuedTorrentHash == singleTorrent.InfoHash().String() { torrentQueues.QueuedTorrents = append(torrentQueues.QueuedTorrents[:index], torrentQueues.QueuedTorrents[index+1:]...) } } singleTorrent.NewReader() singleTorrent.SetMaxEstablishedConns(80) torrentQueues.ActiveTorrents = append(torrentQueues.ActiveTorrents, singleTorrent.InfoHash().String()) torrentLocalStorage.TorrentStatus = "Running" torrentLocalStorage.MaxConnections = 80 for _, file := range singleTorrent.Files() { for _, sentFile := range torrentLocalStorage.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 Name": torrentLocalStorage.TorrentName}).Info("Adding Torrent to Active Queue (Manual Call)") Storage.UpdateStorageTick(db, *torrentLocalStorage) Storage.UpdateQueues(db, torrentQueues) } //RemoveTorrentFromActive forces a torrent to be removed from the active list if the max limit is already there and user forces a new torrent to be added func RemoveTorrentFromActive(torrentLocalStorage *Storage.TorrentLocal, singleTorrent *torrent.Torrent, db *storm.DB) { torrentQueues := Storage.FetchQueues(db) for x, torrentHash := range torrentQueues.ActiveTorrents { if torrentHash == singleTorrent.InfoHash().String() { torrentQueues.ActiveTorrents = append(torrentQueues.ActiveTorrents[:x], torrentQueues.ActiveTorrents[x+1:]...) torrentQueues.QueuedTorrents = append(torrentQueues.QueuedTorrents, torrentHash) torrentLocalStorage.TorrentStatus = "Queued" torrentLocalStorage.MaxConnections = 0 singleTorrent.SetMaxEstablishedConns(0) Storage.UpdateQueues(db, torrentQueues) //AddTorrentToQueue(torrentLocalStorage, singleTorrent, db) //Adding the lasttorrent from active to queued Storage.UpdateStorageTick(db, *torrentLocalStorage) } } } //DeleteTorrentFromQueues deletes the torrent from all queues (for a stop or delete action) func DeleteTorrentFromQueues(torrentHash string, db *storm.DB) { torrentQueues := Storage.FetchQueues(db) for x, torrentHashActive := range torrentQueues.ActiveTorrents { //FOR EXTRA CAUTION deleting it from both queues in case a mistake occurred. if torrentHash == torrentHashActive { torrentQueues.ActiveTorrents = append(torrentQueues.ActiveTorrents[:x], torrentQueues.ActiveTorrents[x+1:]...) Logger.Info("Removing Torrent from Active: ", torrentHash) } } for x, torrentHashQueued := range torrentQueues.QueuedTorrents { //FOR EXTRA CAUTION deleting it from both queues in case a mistake occurred. if torrentHash == torrentHashQueued { torrentQueues.QueuedTorrents = append(torrentQueues.QueuedTorrents[:x], torrentQueues.QueuedTorrents[x+1:]...) Logger.Info("Removing Torrent from Queued", torrentHash) } } for x, torrentHashActive := range torrentQueues.ForcedTorrents { //FOR EXTRA CAUTION deleting it from all queues in case a mistake occurred. if torrentHash == torrentHashActive { torrentQueues.ForcedTorrents = append(torrentQueues.ForcedTorrents[:x], torrentQueues.ForcedTorrents[x+1:]...) Logger.Info("Removing Torrent from Forced: ", torrentHash) } } Storage.UpdateQueues(db, torrentQueues) Logger.WithFields(logrus.Fields{"Torrent Hash": torrentHash, "TorrentQueues": torrentQueues}).Info("Removing Torrent from all Queues") } //AddTorrentToQueue adds a torrent to the queue func AddTorrentToQueue(torrentLocalStorage *Storage.TorrentLocal, singleTorrent *torrent.Torrent, db *storm.DB) { torrentQueues := Storage.FetchQueues(db) for _, torrentHash := range torrentQueues.QueuedTorrents { if singleTorrent.InfoHash().String() == torrentHash { //don't add duplicate to que but do everything else (TODO, maybe find a better way?) singleTorrent.SetMaxEstablishedConns(0) torrentLocalStorage.MaxConnections = 0 torrentLocalStorage.TorrentStatus = "Queued" Logger.WithFields(logrus.Fields{"TorrentName": torrentLocalStorage.TorrentName}).Info("Adding torrent to the queue, not active") Storage.UpdateStorageTick(db, *torrentLocalStorage) return } } torrentQueues.QueuedTorrents = append(torrentQueues.QueuedTorrents, singleTorrent.InfoHash().String()) singleTorrent.SetMaxEstablishedConns(0) torrentLocalStorage.MaxConnections = 0 torrentLocalStorage.TorrentStatus = "Queued" Logger.WithFields(logrus.Fields{"TorrentName": torrentLocalStorage.TorrentName}).Info("Adding torrent to the queue, not active") Storage.UpdateQueues(db, torrentQueues) Storage.UpdateStorageTick(db, *torrentLocalStorage) } //RemoveDuplicatesFromQueues removes any duplicates from torrentQueues.QueuedTorrents (which will happen if it is read in from DB) func RemoveDuplicatesFromQueues(db *storm.DB) { torrentQueues := Storage.FetchQueues(db) for _, torrentHash := range torrentQueues.ActiveTorrents { for i, queuedHash := range torrentQueues.QueuedTorrents { if torrentHash == queuedHash { torrentQueues.QueuedTorrents = append(torrentQueues.QueuedTorrents[:i], torrentQueues.QueuedTorrents[i+1:]...) } } } Storage.UpdateQueues(db, torrentQueues) } //ValidateQueues is a sanity check that runs every tick to make sure the queues are in order... tried to avoid this but seems to be required func ValidateQueues(db *storm.DB, config Settings.FullClientSettings, tclient *torrent.Client) { torrentQueues := Storage.FetchQueues(db) for len(torrentQueues.ActiveTorrents) > config.MaxActiveTorrents { removeTorrent := torrentQueues.ActiveTorrents[:1] for _, singleTorrent := range tclient.Torrents() { if singleTorrent.InfoHash().String() == removeTorrent[0] { singleTorrentFromStorage := Storage.FetchTorrentFromStorage(db, removeTorrent[0]) RemoveTorrentFromActive(&singleTorrentFromStorage, singleTorrent, db) } } } torrentQueues = Storage.FetchQueues(db) for _, singleTorrent := range tclient.Torrents() { singleTorrentFromStorage := Storage.FetchTorrentFromStorage(db, singleTorrent.InfoHash().String()) if singleTorrentFromStorage.TorrentStatus == "Stopped" { continue } for _, queuedTorrent := range torrentQueues.QueuedTorrents { //If we have a queued torrent that is missing data, and an active torrent that is seeding, then prioritize the missing data one if singleTorrent.InfoHash().String() == queuedTorrent { if singleTorrent.BytesMissing() > 0 { for _, activeTorrent := range torrentQueues.ActiveTorrents { for _, singleActiveTorrent := range tclient.Torrents() { if activeTorrent == singleActiveTorrent.InfoHash().String() { if singleActiveTorrent.Seeding() == true { singleActiveTFS := Storage.FetchTorrentFromStorage(db, activeTorrent) Logger.WithFields(logrus.Fields{"TorrentName": singleActiveTFS.TorrentName}).Info("Seeding, Removing from active to add queued") RemoveTorrentFromActive(&singleActiveTFS, singleActiveTorrent, db) singleQueuedTFS := Storage.FetchTorrentFromStorage(db, queuedTorrent) Logger.WithFields(logrus.Fields{"TorrentName": singleQueuedTFS.TorrentName}).Info("Adding torrent to the queue, not active") AddTorrentToActive(&singleQueuedTFS, singleTorrent, db) } } } } } } } } } //CalculateTorrentStatus is used to determine what the STATUS column of the frontend will display ll2 func CalculateTorrentStatus(t *torrent.Torrent, c *ClientDB, config Settings.FullClientSettings, tFromStorage *storage.TorrentLocal, bytesCompleted int64, totalSize int64, torrentQueues Storage.TorrentQueues, db *storm.DB) { if tFromStorage.TorrentStatus == "Stopped" { c.Status = "Stopped" return } //Only has 2 states in storage, stopped or running, so we know it should be running, and the websocket request handled updating the database with connections and status for _, torrentHash := range torrentQueues.QueuedTorrents { if tFromStorage.Hash == torrentHash { c.Status = "Queued" return } } bytesMissing := totalSize - bytesCompleted c.MaxConnections = 80 t.SetMaxEstablishedConns(80) if t.Seeding() && t.Stats().ActivePeers > 0 && bytesMissing == 0 { c.Status = "Seeding" } else if t.Stats().ActivePeers > 0 && bytesMissing > 0 { c.Status = "Downloading" } else if t.Stats().ActivePeers == 0 && bytesMissing == 0 { c.Status = "Completed" } else if t.Stats().ActivePeers == 0 && bytesMissing > 0 { c.Status = "Awaiting Peers" } else { c.Status = "Unknown" } } ================================================ FILE: go.mod ================================================ module github.com/deranjer/goTorrent go 1.12 require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/DataDog/zstd v1.3.5 // indirect github.com/PuerkitoBio/goquery v1.5.0 // indirect github.com/Sereal/Sereal v0.0.0-20190226181601-237c2cca198f // indirect github.com/anacrolix/dht v1.0.1 github.com/anacrolix/torrent v1.1.1 github.com/asdine/storm v2.1.2+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/golang/protobuf v1.3.1 // indirect github.com/gorilla/handlers v1.4.0 github.com/gorilla/mux v1.7.0 github.com/gorilla/websocket v1.4.0 github.com/mitsuse/pushbullet-go v0.1.0 github.com/mmcdole/gofeed v1.0.0-beta2 github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect github.com/otiai10/copy v1.0.1 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 // indirect github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 github.com/sirupsen/logrus v1.4.0 github.com/spf13/viper v1.3.2 github.com/vmihailenco/msgpack v4.0.3+incompatible // indirect go.etcd.io/bbolt v1.3.2 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 google.golang.org/appengine v1.5.0 // indirect ) ================================================ FILE: go.sum ================================================ bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bou.ke/monkey v1.0.1 h1:zEMLInw9xvNakzUUPjfS4Ds6jYPqCFx3m7bRmG5NH2U= bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.17 h1:oCYFIFEMSQZrLHpywH7919esI1VSrQZ0pJXkZPGIJ78= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= github.com/Sereal/Sereal v0.0.0-20190226181601-237c2cca198f h1:99C4f5FJQChWyzMSpZPU4eUv3kjFmjxyWy8t2rlbUcs= github.com/Sereal/Sereal v0.0.0-20190226181601-237c2cca198f/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= github.com/anacrolix/dht v0.0.0-20180412060941-24cbf25b72a4/go.mod h1:hQfX2BrtuQsLQMYQwsypFAab/GvHg8qxwVi4OJdR1WI= github.com/anacrolix/dht v0.0.0-20181129074040-b09db78595aa/go.mod h1:Ayu4t+5TsHQ07/P8XzRJqVofv7lU4R1ZTT7KW5+SPFA= github.com/anacrolix/dht v1.0.1 h1:a7zVMiZWfPiToAUbjMZYeI3UvmsDP3j8vH5EDIAjM9c= github.com/anacrolix/dht v1.0.1/go.mod h1:dtcIktBFD8YD/7ZcE5nQuuGGfLxcwa8+18mHl+GU+KA= github.com/anacrolix/dht/v2 v2.0.1 h1:gOHJ+OKqJ4Eb48OYStZm4AlWr1/nSA2TWlzb/+t36SA= github.com/anacrolix/dht/v2 v2.0.1/go.mod h1:GbTT8BaEtfqab/LPd5tY41f3GvYeii3mmDUK300Ycyo= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa h1:xCaATLKmn39QqLs3tUZYr6eKvezJV+FYvVOLTklxK6U= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/go-libutp v0.0.0-20180522111405-6baeb806518d/go.mod h1:beQSaSxwH2d9Eeu5ijrEnHei5Qhk+J6cDm1QkWFru4E= github.com/anacrolix/go-libutp v0.0.0-20180808010927-aebbeb60ea05 h1:Zoniih3jyqtr3I0xFoMvw1USWpg+CbI/zOrcLudr0lc= github.com/anacrolix/go-libutp v0.0.0-20180808010927-aebbeb60ea05/go.mod h1:POY/GPlrFKRxnOKH1sGAB+NBWMoP+sI+hHJxgcgWbWw= github.com/anacrolix/log v0.0.0-20180412014343-2323884b361d/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew= github.com/anacrolix/log v0.1.0/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew= github.com/anacrolix/log v0.2.0 h1:LzaW6XTEk2zcmLZkcZPkJ2mDdnZkOdOTeBH7Kt81ouU= github.com/anacrolix/log v0.2.0/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew= github.com/anacrolix/missinggo v0.0.0-20180522035225-b4a5853e62ff/go.mod h1:b0p+7cn+rWMIphK1gDH2hrDuwGOcbB6V4VXeSsEfHVk= github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s= github.com/anacrolix/missinggo v0.0.0-20181129073415-3237bf955fed/go.mod h1:IN+9GUe7OxKMIs/XeXEbT/rMUolmJzmlZiXHS7FwD/Y= github.com/anacrolix/missinggo v0.2.1-0.20190310234110-9fbdc9f242a8/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/missinggo v1.1.0 h1:0lZbaNa6zTR1bELAIzCNmRGAtkHuLDPJqTiTtXoAIx8= github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw= github.com/anacrolix/mmsg v0.0.0-20180808012353-5adb2c1127c0 h1:Fa1XqqLW62lQzEDlNA+QcdJbkfJcxQN0YC8983kj5tU= github.com/anacrolix/mmsg v0.0.0-20180808012353-5adb2c1127c0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc= github.com/anacrolix/sync v0.0.0-20171108081538-eee974e4f8c1/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w= github.com/anacrolix/sync v0.0.0-20180611022320-3c4cb11f5a01/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w= github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778 h1:XpCDEixzXOB8yaTW/4YBzKrJdMcFI0DzpPTYNv75wzk= github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v0.0.0-20180605133421-f477c8c2f14c/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v0.0.0-20180803105420-3a8ff5428f76/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/torrent v0.0.0-20180622074351-fefeef4ee9eb/go.mod h1:3vcFVxgOASslNXHdivT8spyMRBanMCenHRpe0u5vpBs= github.com/anacrolix/torrent v1.0.1/go.mod h1:ZYV1Z2Wx3jXYSh26mDvneAbk8XIUxfvoVil2GW962zY= github.com/anacrolix/torrent v1.1.1 h1:f54cvN3950x72hOB8UvzRwEbF5AY3VMj4vPyntgt24Q= github.com/anacrolix/torrent v1.1.1/go.mod h1:XdYEuC3KuxFQZrQ6iUBXnwKr3IyxeyUlVH6RT8FhyaU= github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572 h1:kpt6TQTVi6gognY+svubHfxxpq0DLU9AfTQyZVc3UOc= github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kbKTHGrXYt/Q= github.com/asdine/storm v2.1.2+incompatible/go.mod h1:RarYDc9hq1UPLImuiXK3BIWPJLdIygvV3PsInK0FbVQ= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c h1:FUUopH4brHNO2kJoNN3pV+OBEYmgraLT/KHZrMM69r0= github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a h1:2Zw3pxDRTs4nX1WCLAEm27UN0hvjZSge7EaUUQexRZw= github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a/go.mod h1:afkYpY8JAIL4341N7Zj9xJ5yTovsg6BkWfBFlCzIoF4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e h1:SiEs4J3BKVIeaWrH3tKaz3QLZhJ68iJ/A4xrzIoE5+Y= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9 h1:Z0f701LpR4dqO92bP6TnIe3ZURClzJtBhds8R8u1HBE= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/ipfs/go-ipfs v0.4.18/go.mod h1:iXzbK+Wa6eePj3jQg/uY6Uoq5iOwY+GToD/bgaRadto= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-sqlite3 v1.7.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitsuse/pushbullet-go v0.1.0 h1:W9izHOpz8uilRBgbYSnqb+LZK/l8Ad4slRTCBFpItG0= github.com/mitsuse/pushbullet-go v0.1.0/go.mod h1:sJ6Y3IROSfSQNLY/8gtYjq4Gs49DFnrxaqxQA6DVgnM= github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E= github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA= github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/mint v1.2.3 h1:PsrRBmrxR68kyNu6YlqYHbNlItc5vOkuS6LBEsNttVA= github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE= github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/syncthing/syncthing v0.14.48-rc.4/go.mod h1:nw3siZwHPA6M8iSfjDCWQ402eqvEIasMQOE8nFOxy7M= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vmihailenco/msgpack v4.0.3+incompatible h1:g+G529Dqo4BY2Gxn5GKENa/3NVK+mu/6hM7G3jEWszQ= github.com/vmihailenco/msgpack v4.0.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bloom v0.0.0-20170505221640-54e3b963ee16/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA= github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190318221613-d196dffd7c2b h1:ZWpVMTsK0ey5WJCu+vVdfMldWq7/ezaOcjnKWIHWVkE= golang.org/x/net v0.0.0-20190318221613-d196dffd7c2b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca h1:o2TLx1bGN3W+Ei0EMU5fShLupLmTOU95KvJJmfYhAzM= golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: goTorrentWebUI/.babelrc ================================================ { "presets": [ "react", "env", "stage-2", ], "plugins": ["transform-class-properties"] } ================================================ FILE: goTorrentWebUI/acorn ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/acorn/bin/acorn" "$@" ret=$? else node "$basedir/node_modules/acorn/bin/acorn" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/acorn.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\acorn\bin\acorn" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\acorn\bin\acorn" %* ) ================================================ FILE: goTorrentWebUI/ansi-html ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/ansi-html/bin/ansi-html" "$@" ret=$? else node "$basedir/node_modules/ansi-html/bin/ansi-html" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/ansi-html.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\ansi-html\bin\ansi-html" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\ansi-html\bin\ansi-html" %* ) ================================================ FILE: goTorrentWebUI/atob ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/atob/bin/atob.js" "$@" ret=$? else node "$basedir/node_modules/atob/bin/atob.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/atob.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\atob\bin\atob.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\atob\bin\atob.js" %* ) ================================================ FILE: goTorrentWebUI/babel ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/babel-cli/bin/babel.js" "$@" ret=$? else node "$basedir/node_modules/babel-cli/bin/babel.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/babel-doctor ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/babel-cli/bin/babel-doctor.js" "$@" ret=$? else node "$basedir/node_modules/babel-cli/bin/babel-doctor.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/babel-doctor.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\babel-cli\bin\babel-doctor.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\babel-cli\bin\babel-doctor.js" %* ) ================================================ FILE: goTorrentWebUI/babel-external-helpers ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/babel-cli/bin/babel-external-helpers.js" "$@" ret=$? else node "$basedir/node_modules/babel-cli/bin/babel-external-helpers.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/babel-external-helpers.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\babel-cli\bin\babel-external-helpers.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\babel-cli\bin\babel-external-helpers.js" %* ) ================================================ FILE: goTorrentWebUI/babel-node ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/babel-cli/bin/babel-node.js" "$@" ret=$? else node "$basedir/node_modules/babel-cli/bin/babel-node.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/babel-node.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\babel-cli\bin\babel-node.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\babel-cli\bin\babel-node.js" %* ) ================================================ FILE: goTorrentWebUI/babel.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\babel-cli\bin\babel.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\babel-cli\bin\babel.js" %* ) ================================================ FILE: goTorrentWebUI/babylon ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/babylon/bin/babylon.js" "$@" ret=$? else node "$basedir/node_modules/babylon/bin/babylon.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/babylon.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\babylon\bin\babylon.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\babylon\bin\babylon.js" %* ) ================================================ FILE: goTorrentWebUI/browserslist ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/browserslist/cli.js" "$@" ret=$? else node "$basedir/node_modules/browserslist/cli.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/browserslist.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\browserslist\cli.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\browserslist\cli.js" %* ) ================================================ FILE: goTorrentWebUI/cssesc ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/cssesc/bin/cssesc" "$@" ret=$? else node "$basedir/node_modules/cssesc/bin/cssesc" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/cssesc.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\cssesc\bin\cssesc" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\cssesc\bin\cssesc" %* ) ================================================ FILE: goTorrentWebUI/csso ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/csso/bin/csso" "$@" ret=$? else node "$basedir/node_modules/csso/bin/csso" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/csso.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\csso\bin\csso" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\csso\bin\csso" %* ) ================================================ FILE: goTorrentWebUI/detect ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/detect-port-alt/bin/detect-port" "$@" ret=$? else node "$basedir/node_modules/detect-port-alt/bin/detect-port" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/detect-port ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/detect-port-alt/bin/detect-port" "$@" ret=$? else node "$basedir/node_modules/detect-port-alt/bin/detect-port" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/detect-port.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\detect-port-alt\bin\detect-port" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\detect-port-alt\bin\detect-port" %* ) ================================================ FILE: goTorrentWebUI/detect.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\detect-port-alt\bin\detect-port" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\detect-port-alt\bin\detect-port" %* ) ================================================ FILE: goTorrentWebUI/errno ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/errno/cli.js" "$@" ret=$? else node "$basedir/node_modules/errno/cli.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/errno.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\errno\cli.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\errno\cli.js" %* ) ================================================ FILE: goTorrentWebUI/escodegen ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/escodegen/bin/escodegen.js" "$@" ret=$? else node "$basedir/node_modules/escodegen/bin/escodegen.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/escodegen.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\escodegen\bin\escodegen.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\escodegen\bin\escodegen.js" %* ) ================================================ FILE: goTorrentWebUI/esgenerate ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/escodegen/bin/esgenerate.js" "$@" ret=$? else node "$basedir/node_modules/escodegen/bin/esgenerate.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/esgenerate.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\escodegen\bin\esgenerate.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\escodegen\bin\esgenerate.js" %* ) ================================================ FILE: goTorrentWebUI/eslint ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/eslint/bin/eslint.js" "$@" ret=$? else node "$basedir/node_modules/eslint/bin/eslint.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/eslint.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\eslint\bin\eslint.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\eslint\bin\eslint.js" %* ) ================================================ FILE: goTorrentWebUI/esparse ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/esprima/bin/esparse.js" "$@" ret=$? else node "$basedir/node_modules/esprima/bin/esparse.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/esparse.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\esprima\bin\esparse.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\esprima\bin\esparse.js" %* ) ================================================ FILE: goTorrentWebUI/esvalidate ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/esprima/bin/esvalidate.js" "$@" ret=$? else node "$basedir/node_modules/esprima/bin/esvalidate.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/esvalidate.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\esprima\bin\esvalidate.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\esprima\bin\esvalidate.js" %* ) ================================================ FILE: goTorrentWebUI/fonts/index.css ================================================ * { font-family: Roboto, sans-serif; } @font-face { font-family: 'Roboto'; src: url('Roboto-Regular-webfont.eot'); src: url('Roboto-Regular-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-Regular-webfont.woff') format('woff'), url('Roboto-Regular-webfont.ttf') format('truetype'), url('Roboto-Regular-webfont.svg#RobotoRegular') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'Roboto'; src: url('Roboto-Italic-webfont.eot'); src: url('Roboto-Italic-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-Italic-webfont.woff') format('woff'), url('Roboto-Italic-webfont.ttf') format('truetype'), url('Roboto-Italic-webfont.svg#RobotoItalic') format('svg'); font-weight: normal; font-style: italic; } @font-face { font-family: 'Roboto'; src: url('Roboto-Bold-webfont.eot'); src: url('Roboto-Bold-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-Bold-webfont.woff') format('woff'), url('Roboto-Bold-webfont.ttf') format('truetype'), url('Roboto-Bold-webfont.svg#RobotoBold') format('svg'); font-weight: bold; font-style: normal; } @font-face { font-family: 'Roboto'; src: url('Roboto-BoldItalic-webfont.eot'); src: url('Roboto-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-BoldItalic-webfont.woff') format('woff'), url('Roboto-BoldItalic-webfont.ttf') format('truetype'), url('Roboto-BoldItalic-webfont.svg#RobotoBoldItalic') format('svg'); font-weight: bold; font-style: italic; } @font-face { font-family: 'Roboto'; src: url('Roboto-Thin-webfont.eot'); src: url('Roboto-Thin-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-Thin-webfont.woff') format('woff'), url('Roboto-Thin-webfont.ttf') format('truetype'), url('Roboto-Thin-webfont.svg#RobotoThin') format('svg'); font-weight: 200; font-style: normal; } @font-face { font-family: 'Roboto'; src: url('Roboto-ThinItalic-webfont.eot'); src: url('Roboto-ThinItalic-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-ThinItalic-webfont.woff') format('woff'), url('Roboto-ThinItalic-webfont.ttf') format('truetype'), url('Roboto-ThinItalic-webfont.svg#RobotoThinItalic') format('svg'); (under the Apache Software License). font-weight: 200; font-style: italic; } @font-face { font-family: 'Roboto'; src: url('Roboto-Light-webfont.eot'); src: url('Roboto-Light-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-Light-webfont.woff') format('woff'), url('Roboto-Light-webfont.ttf') format('truetype'), url('Roboto-Light-webfont.svg#RobotoLight') format('svg'); font-weight: 100; font-style: normal; } @font-face { font-family: 'Roboto'; src: url('Roboto-LightItalic-webfont.eot'); src: url('Roboto-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-LightItalic-webfont.woff') format('woff'), url('Roboto-LightItalic-webfont.ttf') format('truetype'), url('Roboto-LightItalic-webfont.svg#RobotoLightItalic') format('svg'); font-weight: 100; font-style: italic; } @font-face { font-family: 'Roboto'; src: url('Roboto-Medium-webfont.eot'); src: url('Roboto-Medium-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-Medium-webfont.woff') format('woff'), url('Roboto-Medium-webfont.ttf') format('truetype'), url('Roboto-Medium-webfont.svg#RobotoMedium') format('svg'); font-weight: 300; font-style: normal; } @font-face { font-family: 'Roboto'; src: url('Roboto-MediumItalic-webfont.eot'); src: url('Roboto-MediumItalic-webfont.eot?#iefix') format('embedded-opentype'), url('Roboto-MediumItalic-webfont.woff') format('woff'), url('Roboto-MediumItalic-webfont.ttf') format('truetype'), url('Roboto-MediumItalic-webfont.svg#RobotoMediumItalic') format('svg'); font-weight: 300; font-style: italic; } ================================================ FILE: goTorrentWebUI/handlebars ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/handlebars/bin/handlebars" "$@" ret=$? else node "$basedir/node_modules/handlebars/bin/handlebars" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/handlebars.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\handlebars\bin\handlebars" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\handlebars\bin\handlebars" %* ) ================================================ FILE: goTorrentWebUI/he ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/he/bin/he" "$@" ret=$? else node "$basedir/node_modules/he/bin/he" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/he.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\he\bin\he" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\he\bin\he" %* ) ================================================ FILE: goTorrentWebUI/html-minifier ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/html-minifier/cli.js" "$@" ret=$? else node "$basedir/node_modules/html-minifier/cli.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/html-minifier.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\html-minifier\cli.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\html-minifier\cli.js" %* ) ================================================ FILE: goTorrentWebUI/import-local-fixture ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/import-local/fixtures/cli.js" "$@" ret=$? else node "$basedir/node_modules/import-local/fixtures/cli.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/import-local-fixture.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\import-local\fixtures\cli.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\import-local\fixtures\cli.js" %* ) ================================================ FILE: goTorrentWebUI/internal-ip ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/internal-ip/cli.js" "$@" ret=$? else node "$basedir/node_modules/internal-ip/cli.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/internal-ip.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\internal-ip\cli.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\internal-ip\cli.js" %* ) ================================================ FILE: goTorrentWebUI/is-ci ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/is-ci/bin.js" "$@" ret=$? else node "$basedir/node_modules/is-ci/bin.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/is-ci.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\is-ci\bin.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\is-ci\bin.js" %* ) ================================================ FILE: goTorrentWebUI/jest ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/jest/bin/jest.js" "$@" ret=$? else node "$basedir/node_modules/jest/bin/jest.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/jest-runtime ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/jest-runtime/bin/jest-runtime.js" "$@" ret=$? else node "$basedir/node_modules/jest-runtime/bin/jest-runtime.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/jest-runtime.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\jest-runtime\bin\jest-runtime.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\jest-runtime\bin\jest-runtime.js" %* ) ================================================ FILE: goTorrentWebUI/jest.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\jest\bin\jest.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\jest\bin\jest.js" %* ) ================================================ FILE: goTorrentWebUI/js-yaml ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/js-yaml/bin/js-yaml.js" "$@" ret=$? else node "$basedir/node_modules/js-yaml/bin/js-yaml.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/js-yaml.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\js-yaml\bin\js-yaml.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\js-yaml\bin\js-yaml.js" %* ) ================================================ FILE: goTorrentWebUI/jsesc ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/jsesc/bin/jsesc" "$@" ret=$? else node "$basedir/node_modules/jsesc/bin/jsesc" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/jsesc.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\jsesc\bin\jsesc" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\jsesc\bin\jsesc" %* ) ================================================ FILE: goTorrentWebUI/json5 ================================================ #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac if [ -x "$basedir/node" ]; then "$basedir/node" "$basedir/node_modules/json5/lib/cli.js" "$@" ret=$? else node "$basedir/node_modules/json5/lib/cli.js" "$@" ret=$? fi exit $ret ================================================ FILE: goTorrentWebUI/json5.cmd ================================================ @IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\node_modules\json5\lib\cli.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\node_modules\json5\lib\cli.js" %* ) ================================================ FILE: goTorrentWebUI/lib/BackendComm/backendWebsocket.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _InfoOutline = require('material-ui-icons/InfoOutline'); var _InfoOutline2 = _interopRequireDefault(_InfoOutline); var _reactRedux = require('react-redux'); var _actions = require('../store/actions'); var actionTypes = _interopRequireWildcard(_actions); var _Select = require('material-ui/Select/Select'); var _Select2 = _interopRequireDefault(_Select); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var title = document.title; //Set the number of active torrents in the title var torrents = []; var peerList = []; var fileList = []; var RSSList = []; var RSSTorrentList = []; var torrentListRequest = { MessageType: "torrentListRequest" //websocket is started in kickwebsocket.js and is picked up here so "ws" is already defined 22 };ws.onmessage = function (evt) { //When we recieve a message from the websocket var serverMessage = JSON.parse(evt.data); //console.log("message", serverMessage.MessageType) switch (serverMessage.MessageType) { case "torrentList": //console.log("Recieved Client Update...", serverMessage) //var serverMessage = JSON.parse(evt.data); torrents = []; //clearing out the torrent array to make room for new (so that it does keep adding) for (var i = 0; i < serverMessage.total; i++) { var _torrents$push; torrents.push((_torrents$push = { TorrentHashString: serverMessage.data[i].TorrentHashString, TorrentName: serverMessage.data[i].TorrentName, DownloadedSize: serverMessage.data[i].DownloadedSize, Size: serverMessage.data[i].Size, DownloadSpeed: serverMessage.data[i].DownloadSpeed, UploadSpeed: serverMessage.data[i].UploadSpeed, PercentDone: serverMessage.data[i].PercentDone, StoragePath: serverMessage.data[i].StoragePath, DateAdded: serverMessage.data[i].DateAdded, SourceType: serverMessage.data[i].SourceType, Status: serverMessage.data[i].Status, BytesCompleted: serverMessage.data[i].BytesCompleted, ActivePeers: serverMessage.data[i].ActivePeers, ETA: serverMessage.data[i].ETA, TotalUploadedSize: serverMessage.data[i].TotalUploadedSize, Ratio: serverMessage.data[i].UploadRatio }, _defineProperty(_torrents$push, 'DateAdded', serverMessage.data[i].DateAdded), _defineProperty(_torrents$push, 'FileNumber', serverMessage.data[i].NumberofFiles), _defineProperty(_torrents$push, 'PieceNumber', serverMessage.data[i].NumberofPieces), _defineProperty(_torrents$push, 'MaxConnections', serverMessage.data[i].MaxConnections), _torrents$push)); } var newTitle = '(' + serverMessage.total + ')' + title; //updating the title document.title = newTitle; break; case "torrentPeerList": peerList = []; //clearing out the peerlist array to make room for new (so that it does keep adding) for (var i = 0; i < serverMessage.TotalPeers; i++) { peerList.push({ PeerID: serverMessage.PeerList[i].Id.toString(), IP: serverMessage.PeerList[i].IP, Port: serverMessage.PeerList[i].Port, Source: serverMessage.PeerList[i].Source, SupportsEncryption: serverMessage.PeerList[i].SupportsEncryption.toString() }); } break; case "torrentFileList": fileList = []; for (var i = 0; i < serverMessage.TotalFiles; i++) { fileList.push({ FileName: serverMessage.FileList[i].FileName, FilePath: serverMessage.FileList[i].FilePath, FileSize: serverMessage.FileList[i].FileSize, FilePercent: serverMessage.FileList[i].FilePercent, FilePriority: serverMessage.FileList[i].FilePriority }); } console.log("filelist", fileList); break; case "speedTab": console.log("Speedtab data requested"); break; case "loggerData": console.log("Logger data requested"); break; case "rssList": console.log("RSSListRequest recieved", evt.data); RSSList = []; for (var i = 0; i < serverMessage.TotalRSSFeeds; i++) { RSSList.push({ RSSURL: serverMessage.RSSFeeds[i].RSSFeedURL, RSSName: serverMessage.RSSFeeds[i].RSSName }); } console.log("RSSURLS", RSSList); console.log("FIRSTURL", RSSList[1]); console.log("FULLURL", RSSList[1].RSSURL); break; case "rssTorrentList": //console.log("RSSTorrentList recieved", evt.data) RSSTorrentList = []; for (var i = 0; i < serverMessage.TotalTorrents; i++) { RSSTorrentList.push({ TorrentName: serverMessage.Torrents[i].Title, TorrentLink: serverMessage.Torrents[i].Link, PublishDate: serverMessage.Torrents[i].PubDate }); } } }; ws.onclose = function () { console.log('Closing connection'); }; var divStyle = { display: 'inline-block', paddingTop: '10px', paddingLeft: '10px' }; var buttonStyle = { fontSize: '60px' }; var BackendSocket = function (_React$Component) { _inherits(BackendSocket, _React$Component); function BackendSocket() { var _ref; var _temp, _this, _ret; _classCallCheck(this, BackendSocket); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = BackendSocket.__proto__ || Object.getPrototypeOf(BackendSocket)).call.apply(_ref, [this].concat(args))), _this), _this.selectionHandler = function (selectionHashes, selectedTab) { switch (selectedTab) { case 0: console.log("general tab information requested"); break; case 1: var peerListHashes = { MessageType: "torrentPeerListRequest", Payload: {"PeerListHash": selectionHashes} }; console.log("Peers tab information requested", peerListHashes); ws.send(JSON.stringify(peerListHashes)); break; case 2: var fileListHashes = { MessageType: "torrentFileListRequest", Payload: {"FileListHash": selectionHashes[0]} }; console.log("Files tab information requested", fileListHashes); ws.send(JSON.stringify(fileListHashes)); break; case 3: console.log("Speed tab information requested"); break; case 4: console.log("Logger tab information requested"); break; default: console.log("default tab"); break; } }, _this.testSelectionLength = function (selection) { if (nextProps.selectionHashes.length > 1) { return true; } return false; }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(BackendSocket, [{ key: 'componentDidMount', value: function componentDidMount() { var _this2 = this; this.timerID = setInterval(function () { return _this2.tick(); }, 2000); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { clearInterval(this.timerID); } }, { key: 'tick', value: function tick() { // this tick is the main tick that updates ALL of the components that update on tick... which is a lot if (this.props.RSSList != RSSList & this.props.RSSModalOpen == true) { this.props.newRSSFeedStore(RSSList); //pushing the new RSSList to Redux } if (this.props.RSSTorrentList != RSSTorrentList & this.props.RSSModalOpen == true) { this.props.RSSTorrentList(RSSTorrentList); //pushing the new RSSTorrentList to Redux } ws.send(JSON.stringify(torrentListRequest)); //talking to the server to get the torrent list //console.log("Torrentlist", torrents) this.props.setButtonState(this.props.selection); //forcing an update to the buttons this.props.newTorrentList(torrents); //sending the list of torrents to torrentlist.js if (this.props.selectionHashes.length === 1) { switch (this.props.selectedTab) { case 1: var peerListHashes = { MessageType: "torrentPeerListRequest", Payload: {"PeerListHash": this.props.selectionHashes} }; ws.send(JSON.stringify(peerListHashes)); this.props.newPeerList(peerList); break; case 2: var fileListHashes = { MessageType: "torrentFileListRequest", Payload: {"FileListHash": this.props.selectionHashes[0]} }; ws.send(JSON.stringify(fileListHashes)); this.props.newFileList(fileList); break; } } } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { console.log("Lenght", nextProps.selectionHashes.length, "value", nextProps.selectionHashes); if (nextProps.selectionHashes.length === 1) { //if we have a selection pass it on for the tabs to verify this.selectionHandler(nextProps.selectionHashes, nextProps.selectedTab); } } }, { key: 'render', value: function render() { return _react2.default.createElement( 'div', { style: divStyle }, _react2.default.createElement(_InfoOutline2.default, { styles: buttonStyle, color: 'primary', 'data-tip': 'BackendStatus: Green=Good', 'aria-label': 'Settings' }) ); } }]); return BackendSocket; }(_react2.default.Component); var mapStateToProps = function mapStateToProps(state) { return { selectionHashes: state.selectionHashes, selectedTab: state.selectedTab, selection: state.selection, RSSModalOpen: state.RSSModalOpen, RSSTorrentList: state.RSSTorrentList }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { newTorrentList: function newTorrentList(torrentList) { return dispatch({ type: actionTypes.TORRENT_LIST, torrentList: torrentList }); }, newPeerList: function newPeerList(peerList) { return dispatch({ type: actionTypes.PEER_LIST, peerList: peerList }); }, newFileList: function newFileList(fileList) { return dispatch({ type: actionTypes.FILE_LIST, fileList: fileList }); }, setButtonState: function setButtonState(buttonState) { return dispatch({ type: actionTypes.SET_BUTTON_STATE, buttonState: buttonState }); }, newRSSFeedStore: function newRSSFeedStore(RSSList) { return dispatch({ type: actionTypes.NEW_RSS_FEED_STORE, RSSList: RSSList }); }, RSSTorrentList: function RSSTorrentList(_RSSTorrentList) { return dispatch({ type: actionTypes.RSS_TORRENT_LIST, RSSTorrentList: _RSSTorrentList }); } //changeSelection: (selection) => dispatch({type: actionTypes.CHANGE_SELECTION, selection}),//forcing an update to the buttons }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(BackendSocket); ================================================ FILE: goTorrentWebUI/lib/BottomMenu/Tabs/fileTab.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _progressBarCell = require('../../CustomCells/progressBarCell'); var _dxReactGrid = require('@devexpress/dx-react-grid'); var _dxReactGridMaterialUi = require('@devexpress/dx-react-grid-material-ui'); var _reactRedux = require('react-redux'); var _actions = require('../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var FileTab = function (_React$Component) { _inherits(FileTab, _React$Component); function FileTab(props) { _classCallCheck(this, FileTab); var _this = _possibleConstructorReturn(this, (FileTab.__proto__ || Object.getPrototypeOf(FileTab)).call(this, props)); _this.changeSelection = function (selection) { console.log("Filelist is changing selection now", selection); _this.setState({ selected: selection }); if (selection.length > 0) { //if selection is empty buttons will be default and selectionHashes will be blanked out and pushed to redux console.log("Getting the selected Rows"); var selectedRows = []; //array of all the selected Rows selection.forEach(function (element) { selectedRows.push(_this.props.fileList[element]); //pushing the selected rows out of torrentlist }); _this.setState({ fileSelection: selectedRows }); } }; _this.sendPriorityRequest = function (priority, sendfileNames) { _this.state.fileSelection.forEach(function (element) { console.log("element", element); sendFileNames.push(element.FilePath); }); var setFilePriority = { MessageType: "setFilePriority", Payload: sendFileNames }; console.log(JSON.stringify(setFilePriority)); ws.send(JSON.stringify(setFilePriority)); }; _this.setHighPriority = function () { var priorty = "High"; var selectionHash = _this.props.selectionHashes[0]; //getting the first element (should be the only one) var sendFileNames = [selectionHash, "High"]; // adding the selection hash as the first element will be stripped out by the server, second element is the prioty request }; _this.setNormalPriority = function () { var priorty = "Normal"; var selectionHash = _this.props.selectionHashes[0]; //getting the first element (should be the only one) var sendFileNames = [selectionHash, "Normal"]; // adding the selection hash as the first element will be stripped out by the server, second element is the prioty request }; _this.setCancelPriority = function () { var priorty = "Cancel"; var selectionHash = _this.props.selectionHashes[0]; //getting the first element (should be the only one) var sendFileNames = [selectionHash, "Cancel"]; // adding the selection hash as the first element will be stripped out by the server, second element is the prioty request }; _this.state = { //rows are stored in redux they are sent over from the server columns: [{ name: 'FileName', title: 'File Name' }, { name: 'FilePath', title: 'File Path' }, { name: 'FileSize', title: 'File Size' }, { name: 'FilePercent', title: 'File Percent' }, { name: 'FilePriority', title: 'File Priority' }], sorting: [], columnOrder: ['FileName', 'FilePath', 'FileSize', 'FilePercent', 'FilePriority'], columnWidths: { FileName: 450, FilePath: 650, FileSize: 100, FilePercent: 100, FilePriority: 75 }, fileSelection: [], selected: [] }; _this.changeColumnOrder = function (columnOrder) { return _this.setState({ columnOrder: columnOrder }); }; _this.changeColumnWidths = function (columnWidths) { return _this.setState({ columnWidths: columnWidths }); }; _this.changeSorting = function (sorting) { return _this.setState({ sorting: sorting }); }; return _this; } _createClass(FileTab, [{ key: 'render', value: function render() { return ( //Buttons here _react2.default.createElement( 'div', null, 'Set File Priority:', _react2.default.createElement( _Button2.default, { raised: true, color: 'primary', onClick: this.setHighPriority }, 'High' ), _react2.default.createElement( _Button2.default, { raised: true, color: 'primary', onClick: this.setNormalPriority }, 'Normal' ), _react2.default.createElement( _Button2.default, { raised: true, color: 'accent', onClick: this.setCancelPriority }, 'Do Not Download' ), _react2.default.createElement( _dxReactGridMaterialUi.Grid, { rows: this.props.fileList, columns: this.state.columns }, _react2.default.createElement(_dxReactGrid.SortingState, { sorting: this.state.sorting, onSortingChange: this.changeSorting }), _react2.default.createElement(_dxReactGrid.LocalSorting, null), _react2.default.createElement(_dxReactGridMaterialUi.DragDropContext, null), _react2.default.createElement(_dxReactGrid.SelectionState, { onSelectionChange: this.changeSelection, selection: this.state.selection }), _react2.default.createElement(_dxReactGridMaterialUi.VirtualTableView, { height: 300, tableCellTemplate: function tableCellTemplate(_ref) { var row = _ref.row, column = _ref.column, style = _ref.style; if (column.name === 'FilePercent') { return _react2.default.createElement(_progressBarCell.ProgressBarCell, { value: row.FilePercent * 100, style: style }); } return undefined; } }), '/>', _react2.default.createElement(_dxReactGridMaterialUi.TableColumnResizing, { columnWidths: this.state.columnWidths, onColumnWidthsChange: this.changeColumnWidths }), _react2.default.createElement(_dxReactGridMaterialUi.TableColumnReordering, { order: this.state.columnOrder, onOrderChange: this.changeColumnOrder }), _react2.default.createElement(_dxReactGridMaterialUi.TableSelection, { selectByRowClick: true, highlightSelected: true }), _react2.default.createElement(_dxReactGridMaterialUi.TableHeaderRow, { allowSorting: true, allowResizing: true, allowDragging: true }) ) ) ); } }]); return FileTab; }(_react2.default.Component); var mapStateToProps = function mapStateToProps(state) { return { selectionHashes: state.selectionHashes, fileList: state.fileList //fileSelectionNames: state.fileSelectionNames, }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { //changeFileSelection: (fileSelection) => dispatch({type: actionTypes.CHANGE_FILE_SELECTION, fileSelection}), sendSelectionHashes: function sendSelectionHashes(selectionHashes) { return dispatch({ type: actionTypes.SELECTION_HASHES, selectionHashes: selectionHashes }); } }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(FileTab); ================================================ FILE: goTorrentWebUI/lib/BottomMenu/Tabs/generalTab.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _styles = require('material-ui/styles'); var _Paper = require('material-ui/Paper'); var _Paper2 = _interopRequireDefault(_Paper); var _Grid = require('material-ui/Grid'); var _Grid2 = _interopRequireDefault(_Grid); var _reactRedux = require('react-redux'); var _actions = require('../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var styles = function styles(theme) { return { root: { flexGrow: 1, marginTop: 0 }, paper: { padding: 16, textAlign: 'left', color: theme.palette.text.primary }, floatRight: { float: 'right' } }; }; var GeneralTab = function (_React$Component) { _inherits(GeneralTab, _React$Component); function GeneralTab(props) { _classCallCheck(this, GeneralTab); var _this = _possibleConstructorReturn(this, (GeneralTab.__proto__ || Object.getPrototypeOf(GeneralTab)).call(this, props)); _this.componentWillReceiveProps = function () { //console.log("recieving props in generaltab", "TYPE", this.props.selectionHashes[Object.keys(this.props.selectionHashes)[0]]) if (_this.props.selectionHashes.length === 1) { //if one torrent is selected var selectionHashTemp = _this.props.selectionHashes[Object.keys(_this.props.selectionHashes)[0]]; // extract out the hash of the single selection var selectedTorrentTemp = []; _this.props.torrentList.forEach(function (singleTorrent) { if (singleTorrent.TorrentHashString === selectionHashTemp) { selectedTorrentTemp = singleTorrent; } }); //selectedTorrentTemp = this.props.torrentList.filter(torrent => torrent.TorrentHashString === this.props.selectionHashes) //console.log("SelectedTorrentTemp", selectedTorrentTemp) _this.setState({ selectedTorrent: selectedTorrentTemp }); } else { _this.setState({ selectedTorrent: [] }); } }; _this.state = { selectedTorrent: [] }; return _this; } _createClass(GeneralTab, [{ key: 'render', value: function render() { var classes = this.props.classes; return _react2.default.createElement( 'div', { className: classes.root }, _react2.default.createElement( _Grid2.default, { container: true, spacing: 8 }, _react2.default.createElement( _Grid2.default, { item: true, xs: 12, sm: 4 }, _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Torrent Name: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["TorrentName"], ' ' ) ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Torrent Size: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["Size"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Storage Path: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["StoragePath"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Date Added: ', _react2.default.createElement( 'span', { className: classes.floatRight }, ' ', this.state.selectedTorrent["DateAdded"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Source Type: ', _react2.default.createElement( 'span', { className: classes.floatRight }, ' ', this.state.selectedTorrent["SourceType"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Label: ', _react2.default.createElement( 'span', { className: classes.floatRight }, ' None ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Torrent Hash: ', _react2.default.createElement( 'span', { className: classes.floatRight }, ' ', this.state.selectedTorrent["TorrentHashString"], ' ' ), ' ' ) ), _react2.default.createElement( _Grid2.default, { item: true, xs: 12, sm: 4 }, _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Status: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["Status"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Percent Done: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["PercentDone"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Torrent DL Amount: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["DownloadedSize"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Total Upload Amount: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["TotalUploadedSize"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Seeding Ratio: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["Ratio"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'ETA: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["ETA"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Max Connections: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["MaxConnections"], ' ' ), ' ' ) ), _react2.default.createElement( _Grid2.default, { item: true, xs: 12, sm: 4 }, _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Number of Files: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["FileNumber"], ' ' ), ' ' ), _react2.default.createElement( _Paper2.default, { className: classes.paper }, 'Number of Pieces: ', _react2.default.createElement( 'span', { className: classes.floatRight }, this.state.selectedTorrent["PieceNumber"], ' ' ), ' ' ) ) ) ); } }]); return GeneralTab; }(_react2.default.Component); var mapStateToProps = function mapStateToProps(state) { return { selectionHashes: state.selectionHashes, torrentList: state.torrentList }; }; exports.default = (0, _styles.withStyles)(styles)((0, _reactRedux.connect)(mapStateToProps)(GeneralTab)); ================================================ FILE: goTorrentWebUI/lib/BottomMenu/Tabs/peerTab.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _reactBootstrapTable = require('react-bootstrap-table'); var _dxReactGrid = require('@devexpress/dx-react-grid'); var _dxReactGridMaterialUi = require('@devexpress/dx-react-grid-material-ui'); var _reactRedux = require('react-redux'); var _actions = require('../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var PeerTab = function (_React$Component) { _inherits(PeerTab, _React$Component); function PeerTab(props) { _classCallCheck(this, PeerTab); var _this = _possibleConstructorReturn(this, (PeerTab.__proto__ || Object.getPrototypeOf(PeerTab)).call(this, props)); _this.state = { //rows are stored in redux they are sent over from the server columns: [{ name: 'PeerID', title: 'Peer ID' }, { name: 'IP', title: 'IP Address' }, //{ name: 'Country', title: 'Country of Origin'}, //TODO map IP to country { name: 'Port', title: 'Port' }, { name: 'Source', title: 'Source' }, //T=Tracker, I=Incoming, Hg=DHTGetPeers, Ha=DHTAnnouncePeer, X=PEX { name: 'SupportsEncryption', title: 'Supports Encryption' }], sorting: [], columnOrder: ['PeerID', 'IP', 'Port', 'Source', 'SupportsEncryption'], columnWidths: { PeerID: 250, IP: 150, Port: 100, Source: 150, SupportsEncryption: 150 } }; _this.changeColumnOrder = function (columnOrder) { return _this.setState({ columnOrder: columnOrder }); }; _this.changeColumnWidths = function (columnWidths) { return _this.setState({ columnWidths: columnWidths }); }; _this.changeSorting = function (sorting) { return _this.setState({ sorting: sorting }); }; return _this; } _createClass(PeerTab, [{ key: 'render', value: function render() { return _react2.default.createElement( _dxReactGridMaterialUi.Grid, { rows: this.props.peerList, columns: this.state.columns }, _react2.default.createElement(_dxReactGrid.SortingState, { sorting: this.state.sorting, onSortingChange: this.changeSorting }), _react2.default.createElement(_dxReactGrid.LocalSorting, null), _react2.default.createElement(_dxReactGridMaterialUi.DragDropContext, null), _react2.default.createElement(_dxReactGridMaterialUi.VirtualTableView, { height: 350 }), _react2.default.createElement(_dxReactGridMaterialUi.TableColumnResizing, { columnWidths: this.state.columnWidths, onColumnWidthsChange: this.changeColumnWidths }), _react2.default.createElement(_dxReactGridMaterialUi.TableColumnReordering, { order: this.state.columnOrder, onOrderChange: this.changeColumnOrder }), _react2.default.createElement(_dxReactGridMaterialUi.TableHeaderRow, { allowSorting: true, allowResizing: true, allowDragging: true }) ); } }]); return PeerTab; }(_react2.default.Component); var mapStateToProps = function mapStateToProps(state) { return { selectionHashes: state.selectionHashes, peerList: state.peerList }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps)(PeerTab); ================================================ FILE: goTorrentWebUI/lib/BottomMenu/bottomMenu.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); require('typeface-roboto'); var _styles = require('material-ui/styles'); var _AppBar = require('material-ui/AppBar'); var _AppBar2 = _interopRequireDefault(_AppBar); var _Tabs = require('material-ui/Tabs'); var _Tabs2 = _interopRequireDefault(_Tabs); var _generalTab = require('./Tabs/generalTab'); var _generalTab2 = _interopRequireDefault(_generalTab); var _peerTab = require('./Tabs/peerTab'); var _peerTab2 = _interopRequireDefault(_peerTab); var _fileTab = require('./Tabs/fileTab'); var _fileTab2 = _interopRequireDefault(_fileTab); var _reactRedux = require('react-redux'); var _actions = require('../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // contains the font for material UI //Redux function TabContainer(props) { return _react2.default.createElement( 'div', { style: { padding: 8 * 3 } }, props.children ); } TabContainer.propTypes = { children: _propTypes2.default.node.isRequired }; var styles = function styles(theme) { return { root: { // flexGrow: 1, // marginTop: theme.spacing.unit * 3, //backgroundColor: theme.palette.background.paper, backgroundColor: '#e5e5e5', height: '100%', boxShadow: '0 0 20px #000' } }; }; var BasicTabs = function (_React$Component) { _inherits(BasicTabs, _React$Component); function BasicTabs() { var _ref; var _temp, _this, _ret; _classCallCheck(this, BasicTabs); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = BasicTabs.__proto__ || Object.getPrototypeOf(BasicTabs)).call.apply(_ref, [this].concat(args))), _this), _this.handleChange = function (event, value) { //this.setState({ value }); _this.props.changeTab(value); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(BasicTabs, [{ key: 'render', value: function render() { var classes = this.props.classes; return _react2.default.createElement( 'div', { className: classes.root }, _react2.default.createElement( 'div', { className: 'DragHandle' }, ' ', _react2.default.createElement( _AppBar2.default, { position: 'static' }, _react2.default.createElement( _Tabs2.default, { value: this.props.selectedTab, onChange: this.handleChange }, _react2.default.createElement(_Tabs.Tab, { label: 'General' }), _react2.default.createElement(_Tabs.Tab, { label: 'Peers' }), _react2.default.createElement(_Tabs.Tab, { label: 'Files' }), _react2.default.createElement(_Tabs.Tab, { label: 'Speed' }), _react2.default.createElement(_Tabs.Tab, { label: 'Logger', href: '#basic-tabs' }) ) ) ), this.props.selectedTab === 0 && _react2.default.createElement( TabContainer, null, _react2.default.createElement(_generalTab2.default, null) ), this.props.selectedTab === 1 && _react2.default.createElement( TabContainer, null, _react2.default.createElement(_peerTab2.default, null) ), this.props.selectedTab === 2 && _react2.default.createElement( TabContainer, null, _react2.default.createElement(_fileTab2.default, null) ), this.props.selectedTab === 3 && _react2.default.createElement( TabContainer, null, 'Speed' ), this.props.selectedTab === 4 && _react2.default.createElement( TabContainer, null, 'Logger' ) ); } }]); return BasicTabs; }(_react2.default.Component); BasicTabs.propTypes = { classes: _propTypes2.default.object.isRequired }; var mapStateToProps = function mapStateToProps(state) { return { selectedTab: state.selectedTab }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { changeTab: function changeTab(selectedTab) { return dispatch({ type: actionTypes.SELECTED_TAB, selectedTab: selectedTab }); } }; }; exports.default = (0, _styles.withStyles)(styles)((0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(BasicTabs)); ================================================ FILE: goTorrentWebUI/lib/CustomCells/progressBarCell.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProgressBarCell = exports.ProgressBarCellBase = undefined; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _materialUi = require('material-ui'); var _styles = require('material-ui/styles'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var styles = function styles(theme) { var _progressText; return { progressBarCell: { paddingLeft: theme.spacing.unit, paddingRight: theme.spacing.unit, borderBottom: '1px solid ' + theme.palette.text.lightDivider }, progressBar: { backgroundColor: theme.palette.primary[300], float: 'left', height: theme.spacing.unit, whiteSpace: 'nowrap' }, progressText: (_progressText = { display: 'inline-block', fontSize: '1em', textAlign: 'right', verticalAlign: 'text-top' }, _defineProperty(_progressText, 'fontSize', '12px'), _defineProperty(_progressText, 'fontWeight', 'bold'), _defineProperty(_progressText, 'margin', '5px'), _defineProperty(_progressText, 'whiteSpace', 'nowrap'), _progressText) }; }; var ProgressBarCellBase = exports.ProgressBarCellBase = function ProgressBarCellBase(_ref) { var value = _ref.value, classes = _ref.classes, style = _ref.style; return _react2.default.createElement( _materialUi.TableCell, { className: classes.progressBarCell, style: style }, _react2.default.createElement('div', { className: classes.progressBar, style: { width: value + '%' }, title: value.toFixed(1) + '%' }), _react2.default.createElement( 'div', { className: classes.progressText }, value ) ); }; ProgressBarCellBase.propTypes = { value: _propTypes2.default.number.isRequired, classes: _propTypes2.default.object.isRequired, style: _propTypes2.default.object }; ProgressBarCellBase.defaultProps = { style: {} }; var ProgressBarCell = exports.ProgressBarCell = (0, _styles.withStyles)(styles, { name: 'ProgressBarCell' })(ProgressBarCellBase); ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/RSSModal/RSSFeedList.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _TextField = require('material-ui/TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _styles = require('material-ui/styles'); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _List = require('material-ui/List'); var _List2 = _interopRequireDefault(_List); var _Dialog = require('material-ui/Dialog'); var _Dialog2 = _interopRequireDefault(_Dialog); var _Link = require('material-ui-icons/Link'); var _Link2 = _interopRequireDefault(_Link); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _Icon = require('material-ui/Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _RssFeed = require('material-ui-icons/RssFeed'); var _RssFeed2 = _interopRequireDefault(_RssFeed); var _AddCircle = require('material-ui-icons/AddCircle'); var _AddCircle2 = _interopRequireDefault(_AddCircle); var _Delete = require('material-ui-icons/Delete'); var _Delete2 = _interopRequireDefault(_Delete); var _reactRedux = require('react-redux'); var _actions = require('../../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //Redux var button = { fontSize: '60px', paddingRight: '20px', paddingLeft: '20px' }; var smallButton = { width: '36px', height: '36px', padding: '5px' }; var rssInput = { width: '90%', paddingRight: '10px' }; var inlineStyle = { display: 'inline-block', backdrop: 'static' }; var RSSFeedList = function (_React$Component) { _inherits(RSSFeedList, _React$Component); function RSSFeedList() { var _ref; var _temp, _this, _ret; _classCallCheck(this, RSSFeedList); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = RSSFeedList.__proto__ || Object.getPrototypeOf(RSSFeedList)).call.apply(_ref, [this].concat(args))), _this), _this.state = { testRSSFeeds: [], showList: false, selectedIndex: 0 }, _this.showRSSFiles = function (key) { var RSSTorrentsRequest = { messageType: "rssTorrentsRequest", Payload: [_this.props.RSSList[key].RSSURL] }; ws.send(JSON.stringify(RSSTorrentsRequest)); _this.setState({ selectedIndex: key }); //setting our selected index for styling console.log("RSSFEED", key, "sending message", JSON.stringify(RSSTorrentsRequest)); }, _this.getStyle = function (index) { console.log("SettingStye", selectedIndex, index); if (selectedIndex == index) { console.log("Returning activestyle"); style = "{{backgroundColor: '#80b3ff'}}"; return style; } style = "{{backgroundColor: '#f44295'}}"; return style; }, _this.deleteRSSFeed = function (key) { var RSSURLDelete = { messageType: "deleteRSSFeed", Payload: [_this.props.RSSList[key]] }; console.log("Deleting THIS", _this.props.RSSList[key]); //ws.send(JSON.stringify(RSSURLDelete)); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(RSSFeedList, [{ key: 'render', value: function render() { var _this2 = this; //const { classes, onRequestClose, handleRequestClose, handleSubmit } = this.props; if (this.props.RSSList.length > 0 && this.state.showList == false) { console.log("Setting list to show...."); this.setState({ showList: true }); } return _react2.default.createElement( 'div', { style: inlineStyle }, this.state.showList == true && //if we have any rss torrent feeds then display them in list } _react2.default.createElement( _List2.default, { dense: true }, this.props.RSSList.map(function (RSSFeed, index) { return _react2.default.createElement( _List.ListItem, { button: true, onClick: function onClick() { return _this2.showRSSFiles(index); }, key: index }, _react2.default.createElement(_List.ListItemText, { primary: RSSFeed.RSSName }), _react2.default.createElement( _List.ListItemSecondaryAction, null, _react2.default.createElement( _IconButton2.default, { key: index, onClick: function onClick() { return _this2.deleteRSSFeed(index); }, 'aria-label': 'Delete' }, _react2.default.createElement(_Delete2.default, null) ) ) ); }) ) ); } }]); return RSSFeedList; }(_react2.default.Component); ; var mapStateToProps = function mapStateToProps(state) { return { RSSList: state.RSSList }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { rssModalOpenState: function rssModalOpenState(RSSModalOpen) { return dispatch({ type: actionTypes.RSS_MODAL_OPEN_STATE, RSSModalOpen: RSSModalOpen }); } //sending modal state to backendwebsocket so we can update RSS lists }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(RSSFeedList); ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/RSSModal/RSSModalLayout.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); require('../../../../node_modules/react-grid-layout/css/styles.css'); require('../../../../node_modules/react-resizable/css/styles.css'); var _reactGridLayout = require('react-grid-layout'); var _reactGridLayout2 = _interopRequireDefault(_reactGridLayout); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _reactRedux = require('react-redux'); var _actions = require('../../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); var _TextField = require('material-ui/TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _styles = require('material-ui/styles'); var _Dialog = require('material-ui/Dialog'); var _Dialog2 = _interopRequireDefault(_Dialog); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _Icon = require('material-ui/Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _RssFeed = require('material-ui-icons/RssFeed'); var _RssFeed2 = _interopRequireDefault(_RssFeed); var _AddCircle = require('material-ui-icons/AddCircle'); var _AddCircle2 = _interopRequireDefault(_AddCircle); var _RSSFeedList = require('./RSSFeedList'); var _RSSFeedList2 = _interopRequireDefault(_RSSFeedList); var _RSSTorrentList = require('./RSSTorrentList'); var _RSSTorrentList2 = _interopRequireDefault(_RSSTorrentList); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //css for react grid //react-grid for layout //Redux //interior items var ReactGridLayout = (0, _reactGridLayout.WidthProvider)(_reactGridLayout2.default); var background = { backgroundColor: '#e5e5e5', boxShadow: '0 0 20px #000' }; var button = { fontSize: '60px', paddingRight: '20px', paddingLeft: '20px' }; var smallButton = { width: '36px', height: '36px', padding: '5px' }; var rssInput = { width: '90%', paddingRight: '10px' }; var inlineStyle = { display: 'inline-block', backdrop: 'static' }; var RSSModalLayout = function (_React$Component) { _inherits(RSSModalLayout, _React$Component); function RSSModalLayout(props) { _classCallCheck(this, RSSModalLayout); var _this = _possibleConstructorReturn(this, (RSSModalLayout.__proto__ || Object.getPrototypeOf(RSSModalLayout)).call(this, props)); _this.handleRSSModalClose = function () { var closeState = false; _this.props.rssModalOpenState(closeState); }; _this.handleAddRSSFeed = function () { _this.setState({ textValue: "Clear" }); //clearing out the text submitted var RSSURLSubmit = { messageType: "addRSSFeed", Payload: [_this.state.textValue] }; ws.send(JSON.stringify(RSSURLSubmit)); var RSSRequest = { messageType: "rssFeedRequest" }; ws.send(JSON.stringify(RSSRequest)); //Immediatly request an update of the feed when you add a new URL }; _this.setTextValue = function (event) { _this.setState({ textValue: event.target.value }); }; var layout = [{ i: 'a', x: 0, y: 0, w: 6, h: 1, static: true }, { 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: layout }; return _this; } _createClass(RSSModalLayout, [{ key: 'onLayoutChange', value: function onLayoutChange(layout) { this.props.onLayoutChange(layout); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { console.log("nextprops", nextProps, "Modal", nextProps.RSSModalOpen); } }, { key: 'componentWillMount', value: function componentWillMount() { console.log("Mounting grid"); } }, { key: 'render', value: function render() { return _react2.default.createElement( 'div', { style: inlineStyle }, _react2.default.createElement( _Dialog.DialogContent, null, _react2.default.createElement( ReactGridLayout, _extends({ layout: this.state.layout, onLayoutChange: this.onLayoutChange }, this.props), _react2.default.createElement( 'div', { key: 'a', sytle: background, className: 'DragHandle' }, _react2.default.createElement(_TextField2.default, { style: rssInput, autoFocus: true, margin: 'dense', id: 'name', label: 'Add New RSS URL', type: 'text', placeholder: 'Enter RSS URL Here..', onChange: this.setTextValue }), _react2.default.createElement( _IconButton2.default, { onClick: this.handleAddRSSFeed, color: 'primary', 'data-tip': 'Add RSS Feed', style: smallButton, 'aria-label': 'Add RSS Feeds' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_AddCircle2.default, null) ) ), _react2.default.createElement( 'div', { key: 'b', style: background, className: 'DragHandle' }, _react2.default.createElement(_RSSFeedList2.default, null) ), _react2.default.createElement( 'div', { key: 'c', style: background, className: 'DragHandle' }, _react2.default.createElement(_RSSTorrentList2.default, null) ) ) ), _react2.default.createElement( _Dialog.DialogActions, null, _react2.default.createElement( _Button2.default, { onClick: this.handleRSSModalClose, color: 'primary' }, 'Close' ) ) ); } }]); return RSSModalLayout; }(_react2.default.Component); RSSModalLayout.propTypes = { onLayoutChange: _propTypes2.default.func.isRequired }; RSSModalLayout.defaultProps = { className: "layout", items: 4, rowHeight: 100, onLayoutChange: function onLayoutChange() {}, cols: 6, draggableCancel: '.NoDrag', draggableHandle: '.DragHandle' }; ; //module.exports = RSSModalLayout; var mapStateToProps = function mapStateToProps(state) { return { RSSList: state.RSSList, RSSModalOpen: state.RSSModalOpen }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { rssModalOpenState: function rssModalOpenState(RSSModalOpen) { return dispatch({ type: actionTypes.RSS_MODAL_OPEN_STATE, RSSModalOpen: RSSModalOpen }); } }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(RSSModalLayout); ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/RSSModal/RSSTorrentList.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _dxReactGrid = require('@devexpress/dx-react-grid'); var _dxReactGridMaterialUi = require('@devexpress/dx-react-grid-material-ui'); var _reactRedux = require('react-redux'); var _actions = require('../../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var tableStyle = {}; var RSSTorrentList = function (_React$Component) { _inherits(RSSTorrentList, _React$Component); function RSSTorrentList(props) { _classCallCheck(this, RSSTorrentList); var _this = _possibleConstructorReturn(this, (RSSTorrentList.__proto__ || Object.getPrototypeOf(RSSTorrentList)).call(this, props)); _this.changeSelection = function (selection) { console.log("TorrentList is changing selection now", selection); _this.setState({ selected: selection }); if (selection.length > 0) { //if selection is empty buttons will be default and selectionHashes will be blanked out and pushed to redux console.log("Getting the selected Rows"); var selectedRows = []; //array of all the selected Rows selection.forEach(function (element) { selectedRows.push(_this.props.RSSTorrentList[element]); //pushing the selected rows out of torrentlist }); _this.setState({ fileSelection: selectedRows }); } }; _this.sendMagnetLinks = function () { var sendMagnetLinks = []; _this.state.fileSelection.forEach(function (element) { //fileselection contains the currently selected rows console.log("element", element); sendMagnetLinks.push(element.TorrentLink); }); var magnetLinkSubmit = { MessageType: "magnetLinkSubmit", Payload: sendMagnetLinks }; console.log(JSON.stringify(magnetLinkSubmit)); ws.send(JSON.stringify(magnetLinkSubmit)); }; _this.state = { //rows are stored in redux they are sent over from the server columns: [{ name: 'TorrentName', title: 'Title' }, { name: 'TorrentLink', title: 'Magnet Link' }, { name: 'PublishDate', title: 'Date Published' }], sorting: [], columnOrder: ['TorrentName', 'TorrentLink', 'PublishDate'], columnWidths: { TorrentName: 450, TorrentLink: 650, PublishDate: 200 }, fileSelection: [], selected: [] }; _this.changeColumnOrder = function (columnOrder) { return _this.setState({ columnOrder: columnOrder }); }; _this.changeColumnWidths = function (columnWidths) { return _this.setState({ columnWidths: columnWidths }); }; _this.changeSorting = function (sorting) { return _this.setState({ sorting: sorting }); }; return _this; } _createClass(RSSTorrentList, [{ key: 'componentWillReceiveProps', value: function componentWillReceiveProps() { console.log("New torrentlist", this.props.RSSTorrentList); } }, { key: 'render', value: function render() { return ( //Buttons here _react2.default.createElement( 'div', null, _react2.default.createElement( _Button2.default, { raised: true, color: 'primary', onClick: this.sendMagnetLinks }, 'Download Torrents' ), _react2.default.createElement( _dxReactGridMaterialUi.Grid, { rows: this.props.RSSTorrentList, columns: this.state.columns }, _react2.default.createElement(_dxReactGrid.SortingState, { sorting: this.state.sorting, onSortingChange: this.changeSorting }), _react2.default.createElement(_dxReactGrid.LocalSorting, null), _react2.default.createElement(_dxReactGridMaterialUi.DragDropContext, null), _react2.default.createElement(_dxReactGrid.SelectionState, { onSelectionChange: this.changeSelection, selection: this.state.selection }), _react2.default.createElement(_dxReactGridMaterialUi.VirtualTableView, { height: 500 }), _react2.default.createElement(_dxReactGridMaterialUi.TableColumnResizing, { columnWidths: this.state.columnWidths, onColumnWidthsChange: this.changeColumnWidths }), _react2.default.createElement(_dxReactGridMaterialUi.TableColumnReordering, { order: this.state.columnOrder, onOrderChange: this.changeColumnOrder }), _react2.default.createElement(_dxReactGridMaterialUi.TableSelection, { selectByRowClick: true, highlightSelected: true }), _react2.default.createElement(_dxReactGridMaterialUi.TableHeaderRow, { allowSorting: true, allowResizing: true, allowDragging: true }) ) ) ); } }]); return RSSTorrentList; }(_react2.default.Component); var mapStateToProps = function mapStateToProps(state) { return { selectionHashes: state.selectionHashes, RSSTorrentList: state.RSSTorrentList }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps)(RSSTorrentList); ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/RSSModal/addRSSModal.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _TextField = require('material-ui/TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _styles = require('material-ui/styles'); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _List = require('material-ui/List'); var _List2 = _interopRequireDefault(_List); var _Dialog = require('material-ui/Dialog'); var _Dialog2 = _interopRequireDefault(_Dialog); var _Link = require('material-ui-icons/Link'); var _Link2 = _interopRequireDefault(_Link); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _Icon = require('material-ui/Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _RssFeed = require('material-ui-icons/RssFeed'); var _RssFeed2 = _interopRequireDefault(_RssFeed); var _AddCircle = require('material-ui-icons/AddCircle'); var _AddCircle2 = _interopRequireDefault(_AddCircle); var _RSSModalLayout = require('./RSSModalLayout'); var _RSSModalLayout2 = _interopRequireDefault(_RSSModalLayout); var _reactRedux = require('react-redux'); var _actions = require('../../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //Redux var button = { fontSize: '60px', paddingRight: '20px', paddingLeft: '20px' }; var inlineStyle = { display: 'inline-block', backdrop: 'static' }; var AddRSSModal = function (_React$Component) { _inherits(AddRSSModal, _React$Component); function AddRSSModal() { var _ref; var _temp, _this, _ret; _classCallCheck(this, AddRSSModal); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = AddRSSModal.__proto__ || Object.getPrototypeOf(AddRSSModal)).call.apply(_ref, [this].concat(args))), _this), _this.rssModalOpenState = function () { console.log("Opening RSS Modal"); _this.props.rssModalOpenState(true); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(AddRSSModal, [{ key: 'componentDidMount', value: function componentDidMount() { //Immediatly request an update of the feed when loading app var RSSRequest = { messageType: "rssFeedRequest" }; ws.send(JSON.stringify(RSSRequest)); } }, { key: 'render', value: function render() { var _props = this.props, classes = _props.classes, onRequestClose = _props.onRequestClose, handleRequestClose = _props.handleRequestClose, handleSubmit = _props.handleSubmit; return _react2.default.createElement( 'div', { style: inlineStyle }, _react2.default.createElement( _IconButton2.default, { onClick: this.rssModalOpenState, color: 'primary', 'data-tip': 'Add RSS URL', style: button, 'aria-label': 'RSS Feeds' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_RssFeed2.default, null) ), _react2.default.createElement( _Dialog2.default, { fullScreen: true, open: this.props.RSSModalOpen, onRequestClose: this.handleRequestClose }, _react2.default.createElement( _Dialog.DialogTitle, null, 'Manage RSS Feeds' ), _react2.default.createElement(_RSSModalLayout2.default, null) ) ); } }]); return AddRSSModal; }(_react2.default.Component); ; var mapStateToProps = function mapStateToProps(state) { return { RSSModalOpen: state.RSSModalOpen }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { rssModalOpenState: function rssModalOpenState(RSSModalOpen) { return dispatch({ type: actionTypes.RSS_MODAL_OPEN_STATE, RSSModalOpen: RSSModalOpen }); } }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(AddRSSModal); ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/addTorrentFileModal.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _TextField = require('material-ui/TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _styles = require('material-ui/styles'); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _Dialog = require('material-ui/Dialog'); var _Dialog2 = _interopRequireDefault(_Dialog); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _AddBox = require('material-ui-icons/AddBox'); var _AddBox2 = _interopRequireDefault(_AddBox); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _reactDropzone = require('react-dropzone'); var _reactDropzone2 = _interopRequireDefault(_reactDropzone); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //import InsertLinkIcon from 'material-ui-icons/Link'; //import Icon from 'material-ui/Icon'; var button = { fontSize: '60px', paddingRight: '20px', paddingLeft: '20px' }; var uploadButton = { fontSize: '35px', paddingLeft: '0px' }; var inlineStyle = { display: 'inline-block' }; var input = { display: 'none' }; var addTorrentFilePopup = function (_React$Component) { _inherits(addTorrentFilePopup, _React$Component); function addTorrentFilePopup() { var _ref; var _temp, _this, _ret; _classCallCheck(this, addTorrentFilePopup); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = addTorrentFilePopup.__proto__ || Object.getPrototypeOf(addTorrentFilePopup)).call.apply(_ref, [this].concat(args))), _this), _this.state = { open: false, torrentFileName: "", torrentFileValue: [], storageValue: "", showDrop: true }, _this.handleClickOpen = function () { _this.setState({ open: true }); }, _this.handleRequestClose = function () { _this.setState({ open: false }); }, _this.handleSubmit = function () { _this.setState({ open: false }); //let magnetLinkSubmit = this.state.textValue; console.log("File", _this.state.torrentFileValue); var reader = new FileReader(); var torrentFileBlob = new Blob(_this.state.torrentFileValue); console.log("Blob", torrentFileBlob); reader.readAsDataURL(torrentFileBlob); reader.onloadend = function () { var base64data = reader.result; console.log("Base64", base64data); var torrentFileMessage = { MessageType: "torrentFileSubmit", MessageDetail: this.state.torrentFileName, MessageDetailTwo: this.state.storageValue, Payload: [base64data] }; console.log("Sending magnet link: ", torrentFileMessage); ws.send(JSON.stringify(torrentFileMessage)); }; }, _this.onFileLoad = function (file) { _this.setState({ torrentFileName: file[0].name }); _this.setState({ showDrop: false }); _this.setState({ torrentFileValue: file }); console.log("File Name", file[0].name); }, _this.setStorageValue = function (event) { _this.setState({ storageValue: event.target.value }); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(addTorrentFilePopup, [{ key: 'render', value: function render() { var _props = this.props, classes = _props.classes, onRequestClose = _props.onRequestClose, handleRequestClose = _props.handleRequestClose, handleSubmit = _props.handleSubmit; return _react2.default.createElement( 'div', { style: inlineStyle }, _react2.default.createElement( _IconButton2.default, { onClick: this.handleClickOpen, color: 'primary', 'data-tip': 'Add Torrent File', style: button, centerRipple: true, 'aria-label': 'Add Torrent File' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_AddBox2.default, null) ), _react2.default.createElement( _Dialog2.default, { open: this.state.open, onRequestClose: this.handleRequestClose, onEscapeKeyUp: this.handleRequestClose, maxWidth: 'md' }, _react2.default.createElement( _Dialog.DialogTitle, null, 'Add Torrent File' ), _react2.default.createElement( _Dialog.DialogContent, null, _react2.default.createElement(_Dialog.DialogContentText, null), this.state.showDrop && _react2.default.createElement( _reactDropzone2.default, { disablePreview: true, multiple: false, onDrop: this.onFileLoad }, 'Upload Torrent Here and Add Storage Path' ), this.state.torrentFileName != "" && this.state.torrentFileName, _react2.default.createElement(_TextField2.default, { id: 'storagePath', type: 'text', label: 'Storage Path', placeholder: 'Empty will be default torrent storage path', fullWidth: true, onChange: this.setStorageValue }) ), _react2.default.createElement( _Dialog.DialogActions, null, _react2.default.createElement( _Button2.default, { onClick: this.handleRequestClose, color: 'primary' }, 'Cancel' ), _react2.default.createElement( _Button2.default, { onClick: this.handleSubmit, color: 'primary' }, 'Submit' ) ) ) ); } }]); return addTorrentFilePopup; }(_react2.default.Component); exports.default = addTorrentFilePopup; ; ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/addTorrentLinkModal.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _TextField = require('material-ui/TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _styles = require('material-ui/styles'); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _Dialog = require('material-ui/Dialog'); var _Dialog2 = _interopRequireDefault(_Dialog); var _Link = require('material-ui-icons/Link'); var _Link2 = _interopRequireDefault(_Link); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _Icon = require('material-ui/Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var button = { fontSize: '60px', paddingRight: '20px', paddingLeft: '20px' }; var inlineStyle = { display: 'inline-block', backdrop: 'static' }; var addTorrentPopup = function (_React$Component) { _inherits(addTorrentPopup, _React$Component); function addTorrentPopup() { var _ref; var _temp, _this, _ret; _classCallCheck(this, addTorrentPopup); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = addTorrentPopup.__proto__ || Object.getPrototypeOf(addTorrentPopup)).call.apply(_ref, [this].concat(args))), _this), _this.state = { open: false, magnetLinkValue: "", storageValue: "" }, _this.handleClickOpen = function () { _this.setState({ open: true }); }, _this.handleRequestClose = function () { _this.setState({ open: false }); }, _this.handleSubmit = function () { _this.setState({ open: false }); //let magnetLinkSubmit = this.state.textValue; var magnetLinkMessage = { messageType: "magnetLinkSubmit", messageDetail: _this.state.storageValue, Payload: [_this.state.magnetLinkValue] }; console.log("Sending magnet link: ", magnetLinkMessage); ws.send(JSON.stringify(magnetLinkMessage)); }, _this.setMagnetLinkValue = function (event) { _this.setState({ magnetLinkValue: event.target.value }); }, _this.setStorageValue = function (event) { _this.setState({ storageValue: event.target.value }); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(addTorrentPopup, [{ key: 'render', value: function render() { var _props = this.props, classes = _props.classes, onRequestClose = _props.onRequestClose, handleRequestClose = _props.handleRequestClose, handleSubmit = _props.handleSubmit; return _react2.default.createElement( 'div', { style: inlineStyle }, _react2.default.createElement( _IconButton2.default, { onClick: this.handleClickOpen, color: 'primary', 'data-tip': 'Add Magnet Link', style: button, centerRipple: true, 'aria-label': 'Add Magnet Link' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_Link2.default, null) ), _react2.default.createElement( _Dialog2.default, { open: this.state.open, onRequestClose: this.handleRequestClose }, _react2.default.createElement( _Dialog.DialogTitle, null, 'Add Magnet Link' ), _react2.default.createElement( _Dialog.DialogContent, null, _react2.default.createElement( _Dialog.DialogContentText, null, 'Add a Magnet Link here and hit submit to add torrent...' ), _react2.default.createElement(_TextField2.default, { autoFocus: true, margin: 'dense', id: 'name', label: 'Magnet Link', type: 'text', placeholder: 'Enter Magnet Link Here', fullWidth: true, onChange: this.setMagnetLinkValue }), _react2.default.createElement(_TextField2.default, { id: 'storagePath', type: 'text', label: 'Storage Path', placeholder: 'Empty will be default torrent storage path', fullWidth: true, onChange: this.setStorageValue }) ), _react2.default.createElement( _Dialog.DialogActions, null, _react2.default.createElement( _Button2.default, { onClick: this.handleRequestClose, color: 'primary' }, 'Cancel' ), _react2.default.createElement( _Button2.default, { onClick: this.handleSubmit, color: 'primary' }, 'Submit' ) ) ) ); } }]); return addTorrentPopup; }(_react2.default.Component); exports.default = addTorrentPopup; ; ================================================ FILE: goTorrentWebUI/lib/TopMenu/Modals/deleteTorrentModal.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Button = require('material-ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _TextField = require('material-ui/TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _styles = require('material-ui/styles'); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _Dialog = require('material-ui/Dialog'); var _Dialog2 = _interopRequireDefault(_Dialog); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _AddBox = require('material-ui-icons/AddBox'); var _AddBox2 = _interopRequireDefault(_AddBox); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _Delete = require('material-ui-icons/Delete'); var _Delete2 = _interopRequireDefault(_Delete); var _reactRedux = require('react-redux'); var _actions = require('../../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //import InsertLinkIcon from 'material-ui-icons/Link'; //import Icon from 'material-ui/Icon'; //Redux var button = { fontSize: '60px', paddingRight: '20px', paddingLeft: '20px' }; var inlineStyle = { display: 'inline-block' }; var DeleteTorrentModal = function (_React$Component) { _inherits(DeleteTorrentModal, _React$Component); function DeleteTorrentModal() { var _ref; var _temp, _this, _ret; _classCallCheck(this, DeleteTorrentModal); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = DeleteTorrentModal.__proto__ || Object.getPrototypeOf(DeleteTorrentModal)).call.apply(_ref, [this].concat(args))), _this), _this.state = { open: false }, _this.handleDeleteTorrent = function () { var selection = []; var deleteTorrentHashes = { MessageType: "deleteTorrents", MessageDetail: "true", Payload: _this.props.selectionHashes }; console.log("Deleting Torrents", deleteTorrentHashes); ws.send(JSON.stringify(deleteTorrentHashes)); _this.props.setButtonState(_this.props.selection); //TODO this currently just forces a button refresh, should be a better way to do this _this.props.changeSelection(selection); //purging out our selection after deleting a torent _this.setState({ open: false }); }, _this.handleDeleteData = function () { var selection = []; var deleteTorrentHashes = { MessageType: "deleteTorrents", MessageDetail: "true", Payload: _this.props.selectionHashes }; console.log("Deleting Torrents and Data", deleteTorrentHashes); ws.send(JSON.stringify(deleteTorrentHashes)); _this.props.setButtonState(_this.props.selection); //TODO this currently just forces a button refresh, should be a better way to do this _this.props.changeSelection(selection); //purging out our selection after deleting a torent _this.setState({ open: false }); }, _this.handleClickOpen = function () { if (_this.props.selection.length > 0) { _this.setState({ open: true }); } else { console.log("Select a torrent to delete.."); } }, _this.handleRequestClose = function () { _this.setState({ open: false }); }, _this.setTextValue = function (event) { _this.setState({ textValue: event.target.value }); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(DeleteTorrentModal, [{ key: 'render', value: function render() { var _props = this.props, onRequestClose = _props.onRequestClose, handleRequestClose = _props.handleRequestClose, handleSubmit = _props.handleSubmit; return _react2.default.createElement( 'div', { style: inlineStyle }, _react2.default.createElement( _IconButton2.default, { color: this.props.buttonState[0].deleteButton, 'data-tip': 'Delete Torrent', style: button, onClick: this.handleClickOpen, 'aria-label': 'Delete Torrent' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'error', effect: 'float' }), _react2.default.createElement(_Delete2.default, null) ), _react2.default.createElement( _Dialog2.default, { open: this.state.open, onRequestClose: this.handleRequestClose, onEscapeKeyUp: this.handleRequestClose, maxWidth: 'md' }, _react2.default.createElement( _Dialog.DialogTitle, null, 'Delete Torrent' ), _react2.default.createElement( _Dialog.DialogContent, null, 'Are you sure you want to delete Torrent?' ), _react2.default.createElement( _Dialog.DialogActions, null, _react2.default.createElement( _Button2.default, { onClick: this.handleRequestClose, color: 'primary' }, 'Cancel' ), _react2.default.createElement( _Button2.default, { onClick: this.handleDeleteData, color: 'primary' }, 'Delete with Data' ), _react2.default.createElement( _Button2.default, { onClick: this.handleDeleteTorrent, color: 'primary' }, 'Delete just Torrent' ) ) ) ); } }]); return DeleteTorrentModal; }(_react2.default.Component); ; var mapStateToProps = function mapStateToProps(state) { return { buttonState: state.buttonState, selection: state.selection, selectionHashes: state.selectionHashes }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { setButtonState: function setButtonState(buttonState) { return dispatch({ type: actionTypes.SET_BUTTON_STATE, buttonState: buttonState }); }, changeSelection: function changeSelection(selection) { return dispatch({ type: actionTypes.CHANGE_SELECTION, selection: selection }); } //used to force a selection empty after deleting torrent }; }; exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(DeleteTorrentModal); ================================================ FILE: goTorrentWebUI/lib/TopMenu/topMenu.js ================================================ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); require('typeface-roboto'); var _styles = require('material-ui/styles'); var _Icon = require('material-ui/Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _IconButton = require('material-ui/IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _addTorrentLinkModal = require('./Modals/addTorrentLinkModal'); var _addTorrentLinkModal2 = _interopRequireDefault(_addTorrentLinkModal); var _addTorrentFileModal = require('./Modals/addTorrentFileModal'); var _addTorrentFileModal2 = _interopRequireDefault(_addTorrentFileModal); var _addRSSModal = require('./Modals/RSSModal/addRSSModal'); var _addRSSModal2 = _interopRequireDefault(_addRSSModal); var _deleteTorrentModal = require('./Modals/deleteTorrentModal'); var _deleteTorrentModal2 = _interopRequireDefault(_deleteTorrentModal); var _PlayArrow = require('material-ui-icons/PlayArrow'); var _PlayArrow2 = _interopRequireDefault(_PlayArrow); var _Stop = require('material-ui-icons/Stop'); var _Stop2 = _interopRequireDefault(_Stop); var _RssFeed = require('material-ui-icons/RssFeed'); var _RssFeed2 = _interopRequireDefault(_RssFeed); var _Settings = require('material-ui-icons/Settings'); var _Settings2 = _interopRequireDefault(_Settings); var _reactTooltip = require('react-tooltip'); var _reactTooltip2 = _interopRequireDefault(_reactTooltip); var _Delete = require('material-ui-icons/Delete'); var _Delete2 = _interopRequireDefault(_Delete); var _AddShoppingCart = require('material-ui-icons/AddShoppingCart'); var _AddShoppingCart2 = _interopRequireDefault(_AddShoppingCart); var _backendWebsocket = require('../BackendComm/backendWebsocket'); var _backendWebsocket2 = _interopRequireDefault(_backendWebsocket); var _reactRedux = require('react-redux'); var _actions = require('../store/actions'); var actionTypes = _interopRequireWildcard(_actions); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // contains the font for material UI //import PauseTorrentIcon from 'material-ui-icons/Pause'; //Redux var styles = function styles(theme) { return { button: { margin: theme.spacing.unit, fontSize: '60px' }, input: { display: 'none' }, paddingTest: { display: 'inline-block' }, padding: { paddingTop: '10px', paddingLeft: '10px' }, verticalDivider: { borderLeft: '2px solid grey', padding: '20px', height: '40px', position: 'absolute', display: 'inline-block', paddingRight: '30px', paddingLeft: '30px' }, background: { backgroundColor: theme.palette.background.paper } }; }; var IconButtons = function (_React$Component) { _inherits(IconButtons, _React$Component); function IconButtons(props) { _classCallCheck(this, IconButtons); var _this = _possibleConstructorReturn(this, (IconButtons.__proto__ || Object.getPrototypeOf(IconButtons)).call(this, props)); _this.startTorrent = function () { console.log("Starting Torrents", _this.props.selectionHashes); var startTorrentHashes = { MessageType: "startTorrents", Payload: _this.props.selectionHashes //console.log("Peers tab information requested", peerListHashes) };ws.send(JSON.stringify(startTorrentHashes)); _this.props.setButtonState(_this.props.selection); //TODO this currently just forces a button refresh, should be a better way to do this }; _this.stopTorrent = function () { var stopTorrentHashes = { MessageType: "stopTorrents", Payload: _this.props.selectionHashes }; console.log("Stopping Torrents", stopTorrentHashes); ws.send(JSON.stringify(stopTorrentHashes)); _this.props.setButtonState(_this.props.selection); //TODO this currently just forces a button refresh, should be a better way to do this }; return _this; } _createClass(IconButtons, [{ key: 'render', value: function render() { var classes = this.props.classes; return _react2.default.createElement( 'div', { className: classes.padding }, _react2.default.createElement(_addTorrentFileModal2.default, null), _react2.default.createElement(_addTorrentLinkModal2.default, null), _react2.default.createElement('div', { className: classes.verticalDivider }), _react2.default.createElement( _IconButton2.default, { color: this.props.buttonState[0].startButton, 'data-tip': 'Start Torrent', className: classes.button, 'aria-label': 'Start Torrent', onClick: this.startTorrent }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_PlayArrow2.default, null) ), _react2.default.createElement( _IconButton2.default, { color: this.props.buttonState[0].stopButton, 'data-tip': 'Stop Torrent', className: classes.button, onClick: this.stopTorrent, 'aria-label': 'Stop Torrent' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_Stop2.default, null) ), _react2.default.createElement(_deleteTorrentModal2.default, null), _react2.default.createElement('div', { className: classes.verticalDivider }), _react2.default.createElement(_addRSSModal2.default, null), _react2.default.createElement( _IconButton2.default, { color: 'primary', 'data-tip': 'Settings', className: classes.button, 'aria-label': 'Settings' }, _react2.default.createElement(_reactTooltip2.default, { place: 'top', type: 'light', effect: 'float' }), _react2.default.createElement(_Settings2.default, null) ), _react2.default.createElement('div', { className: classes.verticalDivider }), _react2.default.createElement(_backendWebsocket2.default, null) ); } }]); return IconButtons; }(_react2.default.Component); IconButtons.propTypes = { classes: _propTypes2.default.object.isRequired }; var mapStateToProps = function mapStateToProps(state) { return { buttonState: state.buttonState, selection: state.selection, selectionHashes: state.selectionHashes }; }; var mapDispatchToProps = function mapDispatchToProps(dispatch) { return { setButtonState: function setButtonState(buttonState) { return dispatch({ type: actionTypes.SET_BUTTON_STATE, buttonState: buttonState }); }, changeSelection: function changeSelection(selection) { return dispatch({ type: actionTypes.CHANGE_SELECTION, selection: selection }); } //used to force a selection empty after deleting torrent }; }; exports.default = (0, _styles.withStyles)(styles)((0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(IconButtons)); ================================================ FILE: goTorrentWebUI/lib/app.js ================================================ 'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); require('../node_modules/react-grid-layout/css/styles.css'); require('../node_modules/react-resizable/css/styles.css'); var _reactGridLayout = require('react-grid-layout'); var _reactGridLayout2 = _interopRequireDefault(_reactGridLayout); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _redux = require('redux'); var _reactRedux = require('react-redux'); var _reducer = require('./store/reducer'); var _reducer2 = _interopRequireDefault(_reducer); var _topMenu = require('./TopMenu/topMenu'); var _topMenu2 = _interopRequireDefault(_topMenu); var _bottomMenu = require('./BottomMenu/bottomMenu'); var _bottomMenu2 = _interopRequireDefault(_bottomMenu); var _leftMenu = require('./leftMenu/leftMenu'); var _leftMenu2 = _interopRequireDefault(_leftMenu); var _torrentlist = require('./torrentlist'); var _torrentlist2 = _interopRequireDefault(_torrentlist); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //css for react grid //react-grid for layout //Redux //Menu and torrentlist imports var reduxStore = (0, _redux.createStore)(_reducer2.default); var ReactGridLayout = (0, _reactGridLayout.WidthProvider)(_reactGridLayout2.default); var background = { backgroundColor: '#e5e5e5', boxShadow: '0 0 20px #000' }; var BasicLayout = function (_React$PureComponent) { _inherits(BasicLayout, _React$PureComponent); function BasicLayout(props) { _classCallCheck(this, BasicLayout); var _this = _possibleConstructorReturn(this, (BasicLayout.__proto__ || Object.getPrototypeOf(BasicLayout)).call(this, props)); var layout = [{ i: 'a', x: 0, y: 0, w: 6, h: 1, static: true }, { i: 'b', x: 0, y: 1, w: 1, h: 9, static: true }, { i: 'c', x: 1, y: 1, w: 5, h: 5, minW: 5, minH: 3, static: true }, { i: 'd', x: 1, y: 6, w: 5, h: 4, minW: 5, minH: 1, static: true }]; _this.state = { layout: layout }; return _this; } _createClass(BasicLayout, [{ key: 'onLayoutChange', value: function onLayoutChange(layout) { this.props.onLayoutChange(layout); } }, { key: 'render', value: function render() { return _react2.default.createElement( ReactGridLayout, _extends({ layout: this.state.layout, onLayoutChange: this.onLayoutChange }, this.props), _react2.default.createElement( 'div', { key: 'a', style: background, className: 'DragHandle' }, _react2.default.createElement(_topMenu2.default, null) ), _react2.default.createElement( 'div', { key: 'b', style: background, className: 'DragHandle' }, _react2.default.createElement(_leftMenu2.default, null) ), _react2.default.createElement( 'div', { key: 'c', style: background, className: 'DragHandle' }, _react2.default.createElement(_torrentlist2.default, null) ), _react2.default.createElement( 'div', { key: 'd' }, _react2.default.createElement(_bottomMenu2.default, null) ) ) //returning our 4 grids ; } }]); return BasicLayout; }(_react2.default.PureComponent); BasicLayout.propTypes = { onLayoutChange: _propTypes2.default.func.isRequired }; BasicLayout.defaultProps = { className: "layout", items: 4, rowHeight: 100, onLayoutChange: function onLayoutChange() {}, cols: 6, draggableCancel: '.NoDrag', draggableHandle: '.DragHandle' }; ; module.exports = BasicLayout; //if (require.main === module) { // require('../test-hook.jsx')(module.exports); //} _reactDom2.default.render(_react2.default.createElement( _reactRedux.Provider, { store: reduxStore }, _react2.default.createElement(BasicLayout, null) ), //wrapping redux around our app document.getElementById('app')); ================================================ FILE: goTorrentWebUI/lib/index.html ================================================
{{end}} ================================================ FILE: torrentUpload/desktop.ini ================================================ [LocalizedFileNames] Plan_9_from_Outer_Space_1959_archive.torrent=@Plan_9_from_Outer_Space_1959_archive.torrent,0 Return_of_the_Street_Fighter.avi.torrent=@Return_of_the_Street_Fighter.avi.torrent,0