47 Commits

Author SHA1 Message Date
840a965877 Added Settings Webui (view only), rewrite of API, Fixes #14, Fixes #2, now Testing 2018-02-20 21:51:49 -05:00
d4966f597b Fixes #15, started seperating Settings into their own package 2018-02-17 11:52:38 -05:00
ba0f076c66 cleaning up an issue with client config generation 2018-02-16 20:41:09 -05:00
3978be8a40 Adding ReverseProxy settings File 2018-02-15 22:55:47 -05:00
c5b86597cb File prio code added, API rewrite completed, some core features rewritten for clarity 2018-02-15 22:49:11 -05:00
b843cfc11b adding frontend authentication, starting file priority code 2018-02-10 09:53:02 -05:00
42f4ecc81b Reverse Proxy with SSL support, Generated client Configs, JWT client to server auth, closes #13 2018-02-07 21:42:35 -05:00
d6288f4aaa Reverse Proxy with SSL support, Generated client Configs, JWT client to server auth, closes #13 2018-02-07 21:41:00 -05:00
0abe1620c6 closes #9, closes #8, closes #3, closes #4, added new notification features, search torrents, change directory, force seed torrent, updated Readme 2018-02-03 14:22:21 -05:00
3ab66456a1 cleaning up old files 2018-01-31 22:29:51 -05:00
8db9a43b0f testing rate limiting, making API changes 2018-01-31 22:28:45 -05:00
6af49b317d Adding logic to change torrent storage path 2018-01-25 23:08:10 -05:00
f58ca5bb09 Finished Frontend notifications, added file prio (needs test), started Settings Button work 2018-01-23 23:22:25 -05:00
52e245d11f Finished Frontend notifications, added file prio (needs test), started Settings Button work 2018-01-23 23:21:25 -05:00
5856052f82 Started adding frontend notifications, fixing firefox file upload bug 2018-01-22 19:03:06 -05:00
f14e96c490 Update README.md
Fixing special thanks once again...
2018-01-21 12:59:47 -05:00
4f90f0a69d Update README.md
Fixing special thanks issue
2018-01-21 12:58:58 -05:00
6878f41888 adding documentation folder and image 2018-01-21 12:55:50 -05:00
9868ce55a4 Update README.md
Added image and badges to readme
2018-01-21 12:52:26 -05:00
8111858d8d Update README.md
Fixing some list issues.
2018-01-21 11:50:31 -05:00
28d7dfb021 Update README.md
fixing roadmap a second time..
2018-01-21 11:49:03 -05:00
2a91953638 Update README.md
Fixing roadmap.
2018-01-21 11:48:36 -05:00
16e8c6399e Update README.md
Added a Readme
2018-01-21 11:47:04 -05:00
8432c1b5b3 Adding favicon, optimizing Torrent Status checking 2018-01-20 22:52:21 -05:00
83a03b3ef6 Changing how filepath is handled for Windows 2018-01-20 17:16:13 -05:00
c87443ca40 Adding more logging options, changing file permissions on move torrent 2018-01-20 11:26:46 -05:00
67ddec7cda Changing folder/file creation permissions for linux issue 2018-01-19 23:21:09 -05:00
988a6517fc Adding a systemd service file for linux distros 2018-01-19 21:51:17 -05:00
aa9082f598 attempting to fix the symlink issue being the wrong direction, fixing the log folder not existing error 2018-01-19 21:49:19 -05:00
c4b86bcf1d Adding dists to .gitignore 2018-01-19 20:31:31 -05:00
7ce0adae4d Purging config file to prepare for release test 2018-01-19 20:21:21 -05:00
1467c3d003 Closing a file that was left open, working on profiling app for memory leak on Windows 2018-01-19 19:59:23 -05:00
a310d64ce4 Bug fixing added moving torrents after download, getting ready for alpha release 2018-01-19 17:54:50 -05:00
06e9317c9a working on making the file upload work over websocket and json 2018-01-17 23:27:27 -05:00
8e72ffb917 Added logging, changed some directory structure 2018-01-13 21:33:40 -05:00
f079a5f067 working on pulling settings from file using viper, finished basic RSS feed and cron job 2018-01-10 20:07:00 -05:00
08b3a14576 started adding the logic for RSS feeds, started storing metainfo and torrent files in database directly 2018-01-05 23:02:54 -05:00
7411638c95 Added the Files tab, fixed peer tab, started adding functionality for the buttons, cleaned up general tab 2017-12-30 23:24:17 -05:00
a9315a4b54 Started working on the tabs, added selection background for filters, started expanding Storage options 2017-12-29 11:17:26 -05:00
8a5f7eaa09 started adding the back to front api via websocket and json 2017-12-24 10:42:12 -05:00
2e1eb8e4e1 Started seperating the go files into seperate packages for cleaner code organization 2017-12-17 23:22:04 -05:00
b7c5032c37 Started seperating the main.go package into multiple packages 2017-12-17 21:49:32 -05:00
c408801447 seperating react files, starting to fix top menu. 2017-12-17 21:40:00 -05:00
1904a6ec24 adding more fields to torrentlist, ul speed/dl speed
enter the commit message for your changes. Lines starting
2017-12-14 20:42:55 -05:00
2de6ba11a5 Adding progress bar, fixing progress code, preparing for splitting files. 2017-12-06 20:16:38 -05:00
f43107be2b Merge branch 'master' of https://derajnet.duckdns.org/git/deranjer/goTorrent 2017-11-30 20:42:53 -05:00
f6140e34c5 Initial commit 2017-11-30 20:55:45 +00:00
83153 changed files with 901839 additions and 309387 deletions

19
.gitignore vendored
View File

@@ -1,17 +1,24 @@
downloads/
downloading/
downloaded/
uploadedTorrents/
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
configtest.toml
.idea/workspace.xml
.idea/vcs.xml
1905POD014.mp3.torrent
bolter.exe
mythbuntu-16.04.3-desktop-i386.iso.torrent
ubuntu-17.04-desktop-amd64.iso (1).torrent
ubuntu.torrent
*.torrent
boltbrowser.win64.exe
logs/server.log
.goreleaser.yml
config.toml.backup
/public/static/js/kickwebsocket.js.backup
/public/static/js/kickwebsocket-generated.js
clientAuth.txt
dist

12
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"git.ignoreLimitWarning": true,
"cSpell.words": [
"anacrolix",
"asdine",
"btih",
"gofeed",
"logrus",
"mmcdole",
"otiai"
]
}

6
.vscode/tasks.json vendored
View File

@@ -1,7 +1,7 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"version": "2.0.0",
"tasks": [
{
"taskName": "Run Program",
@@ -12,9 +12,9 @@
]
},
{
"taskName": "Build GopherJS",
"taskName": "goReleaser Snapshot",
"type": "shell",
"command": "C:/Users/deranjer/go/bin/gopherjs.exe build C:/Users/deranjer/GoglandProjects/torrent-project/public/static/js/frontend-websocket.go",
"command": "C:/Users/deranjer/go/bin/goreleaser.exe -rm-dist -snapshot",
"problemMatcher": [
"$go"
]

201
README.md
View File

@@ -1,3 +1,202 @@
# goTorrent
[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/goTorrent-project/Lobby) [![Go Report Card](https://goreportcard.com/badge/github.com/deranjer/goTorrent)](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
![alt text](/documentation/images/frontend.png "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
- [ ] 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
- [ ] Ability to set priority for individual files (just added to anacrolix/torrent so coming soon, already added to my UI)
- [ ] Ability to view TOML settings from WebUI (and perhaps change a few as well)
- Late 2018
- [ ] 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)
# Installation:
## Linux (tested on Debian)
You can watch a YouTube video of me setting it up:
<a href="http://www.youtube.com/watch?feature=player_embedded&v=G0gO_cm_Oks
" target="_blank"><img src="http://img.youtube.com/vi/G0gO_cm_Oks/0.jpg"
alt="goTorrent Alpha Setup Video" width="240" height="180" border="10" /></a>
### Configuring the backend
Download the latest release from the releases tab, it will be in a tar.gz format.
Create a directory where goTorrent will run from
sudo mkdir /opt/goTorrent
Put the tar.gz release into the folder, and extract it.
tar -zxvf goTorrent_release_64-git.tar.gz
You can then remove the tar.gz if you wish. You should have something similar to the following files:
drwxr-xr-x 5 root root 9 Jan 21 14:56 .
drwxr-xr-x 5 root root 5 Jan 21 14:54 ..
-rw-rw-rw- 1 root root 1086 Dec 1 01:42 LICENSE
-rw-rw-rw- 1 root root 69 Dec 1 01:01 README.md
-rw-rw-rw- 1 root root 4466 Jan 21 03:48 config.toml
drwxr-xr-x 3 root root 3 Jan 21 14:55 dist-specific-files
-rw-rw-rw- 1 root root 12503552 Jan 21 03:53 goTorrent
drwxr-xr-x 3 root root 3 Jan 21 14:55 public
drwxr-xr-x 2 root root 3 Jan 21 14:55 templates
The `config.toml` file contains all of the settings for the server part of the application. Most of the important settings are at the top of the file, so open it with your prefered text editor.
[serverConfig]
ServerPort = ":8000" #leave format as is it expects a string with colon
ServerAddr = "" #blank will bind to default IP address, usually fine to leave be
LogLevel = "Warn" # Options = Debug, Info, Warn, Error, Fatal, Panic
LogOutput = "file" #Options = file, stdout #file will print it to logs/server.log
SeedRatioStop = 1.50 #automatically stops the torrent after it reaches this seeding ratio
#Relative or absolute path accepted, the server will convert any relative path to an absolute path.
DefaultMoveFolder = 'downloaded' #default path that a finished torrent is symlinked to after completion. Torrents added via RSS will default here
TorrentWatchFolder = 'torrentUpload' #folder path that is watched for .torrent files and adds them automatically every 5 minutes
#Limits your upload and download speed globally, all are averages and not burst protected (usually burst on start).
#Low = ~.05MB/s, Medium = ~.5MB/s, High = ~1.5MB/s
UploadRateLimit = "Unlimited" #Options are "Low", "Medium", "High", "Unlimited" #Unlimited is default
DownloadRateLimit = "Unlimited"
[notifications]
PushBulletToken = "" #add your pushbullet api token here to notify of torrent completion to pushbullet
Usually you don't need to change anything in this file, goTorrent will use your default IP address and bind to it. You can change the port if you wish.
Next, we need to make sure that the executable runs, so run the following:
chmod +x goTorrent
This will make the program executable.
###Connecting the Frontend to the Backend
We need to connect our react frontend to our Golang backend, for this we only need to edit one JS file.
nano public/static/js/kickwebsocket.js
var ws = new WebSocket("ws://192.168.1.141:8000/websocket"); //creating websocket
Just change the IP address after ws:// to your server IP address, and change the port if you changed the port in the `config.toml` file.
Then save that file and return to `/opt/goTorrent`.
Now we can test the server. For testing I recommend going into the `config.toml` file and changing the `LogOutput` to `stdout`, and the `LogLevel` to `Info`.
Then start the server:
./goTorrent
If you have `LogLevel` set to `Info`, you should see the confirmation that the client config has been generated.
You can then open your browser and connect to IP:Port (http) and you should see the main page. You will see an error for retrieving RSS feeds in stdout, but this is expected for first load.
You can press `F12` if using Chrome to open the console and click around the UI to see the logging available for the frontend.
### Running goTorrent as a Service
If you are on a linux system that uses systemd, in the `dist-specific-files\Linux-systemd\` folder there is a `goTorrent.service` file that can be used to setup systemd for goTorrent. A quick overview of what is needed.
1. Edit the systemd file to specify your specific implementation
2. Copy the file to your systemd folder, i.e. `/etc/systemd/system`
3. Enable the service `systemctl enable goTorrent.service`
4. If using a new user, create that user and assign permissions:
a. `useradd goTorrent`
b. `sudo chown -R goTorrent:goTorrent /opt/goTorrent`
c. If you want to test server: `su goTorrent` then run the executable
5. Set your `config.toml` file to the values you want.
6. Start your server: `systemctl start goTorrent`
7. Check for errors: `systemctl status goTorrent`. You can also check `logs\server.log`.
### Windows
Please see the linux instructions as they are similar, for running it as a service I havn't tried out any of the programs that claim to do that, but perhaps try [NSSM](http://nssm.cc/download)
# 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)
Torrent server with web client written in Go and React

View File

@@ -1,46 +0,0 @@
package main
import (
"fmt"
"github.com/anacrolix/torrent"
"time"
)
// ClientError formats errors coming from the client.
type ClientError struct {
Type string
Origin error
}
func (clientError ClientError) Error() string {
return fmt.Sprintf("Error %s: %s\n", clientError.Type, clientError.Origin)
}
type ClientConfig struct {
TorrentPath string
Port int
TorrentPort int
Seed bool
TCP bool
MaxConnections int
DownloadDir string
}
type Client struct {
Client *torrent.Client
Torrent *torrent.Torrent
Name string
Progress int64
Status string
Seeds int
Peers int
DownloadSpeed int64
UploadSpeed int64
ETA time.Duration
Ratio int
Avail int
Config ClientConfig
}

View File

@@ -1,59 +1,122 @@
[serverConfig]
ServerPort = ":8000" #leave format as is it expects a string with colon
ServerAddr = "" #blank will bind to default IP address, usually fine to leave be
LogLevel = "Info" # Options = Debug, Info, Warn, Error, Fatal, Panic
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 = true #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 = true
[torrentClientConfig]
DataDir = "downloads" #the full OR relative path of the default download directory for torrents
#The address to listen for new uTP and TCP bittorrent protocolconnections. 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
# Overrides the default DHT configuration, see dhtServerConfig
DHTConfig = false # boolean, set to true and edit dhtServerConfig table to utilize
# Never send chunks to peers.
NoUpload = false #boolean
#seed after download
Seed = true #boolean
# Events are data bytes sent in pieces. The burst must be large enough to fit a whole chunk.
UploadRateLimiter = "" #*rate.Limiter
#The events are bytes read from connections. The burst must be biggerthan the largest Read performed on a Conn minus one. This is likely to
#be the larger of the main read loop buffer (~4096), and the requested chunk size (~16KiB).
DownloadRateLimiter = "" #*rate.Limiter
#User-provided Client peer ID. If not present, one is generated automatically.
PeerID = "" #string
#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 = dht.ServerConfig #storage.ClientImpl
DownloadDir = 'downloading' #the full OR relative path where the torrent server stores in-progress torrents
#encryption policy
IPBlocklist = "" #iplist.Ranger
DisableIPv6 = false #boolean
Debug = false #boolean
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 addressses 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 = "" #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
# Set NodeId Manually. Caller must ensure that if NodeId does not conform to DHT Security Extensions, that NoSecurity is also set.
NodeId = "" #[20]byte
#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 resending queries that haven't received a response. Defaults to a random value between 4.5 and 5.5s.
QueryResendDelay = "func() time.Duration"
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"

View File

@@ -0,0 +1,13 @@
[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

View File

@@ -0,0 +1,12 @@
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";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

100
engine/clientStructs.go Normal file
View File

@@ -0,0 +1,100 @@
package engine
import (
"time"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
)
//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
//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
}
//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"
}
//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
}

102
engine/cronJobs.go Normal file
View File

@@ -0,0 +1,102 @@
package engine
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/anacrolix/torrent"
"github.com/asdine/storm"
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 FullClientSettings) {
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")
StartTorrent(clientTorrent, torrentLocalStorage, db, "file", fullNewFilePathAbs, config.DefaultMoveFolder, "default", config)
}
}
})
}
//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 FullClientSettings) {
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
}
StartTorrent(clientTorrent, torrentLocalStorage, db, "magnet", "", config.DefaultMoveFolder, "RSS", config) //TODO let user specify torrent default storage location and let change on fly
singleFeed.Torrents = append(singleFeed.Torrents, singleRSSTorrent)
}
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
}

View File

@@ -0,0 +1,98 @@
package engine
import (
"os"
"path/filepath"
"runtime"
"github.com/asdine/storm"
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 FullClientSettings, tHash string, db *storm.DB, moveDone bool, oldPath string) { //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!")
}
} 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!")
}
}
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!")
}
_, err = os.Stat(tStorage.StoragePath)
if os.IsNotExist(err) {
err := os.MkdirAll(tStorage.StoragePath, 0755)
if err != nil {
Logger.WithFields(logrus.Fields{"New File Path": newFilePath, "error": err}).Error("Cannot create new directory")
}
}
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!")
return
}
if oldFilePath != newFilePath {
newFilePathDir := filepath.Dir(newFilePath)
os.Mkdir(newFilePathDir, 0755)
err := folderCopy.Copy(oldFilePath, newFilePath) //copy the folder to the new location
if err != nil {
Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Error Copying Folder!")
}
os.Chmod(newFilePath, 0777)
if runtime.GOOS != "windows" { //TODO the windows symlink is broken on windows 10 creator edition, so on the other platforms create symlink (windows will copy) until Go1.11
os.RemoveAll(oldFilePath)
err = os.Symlink(newFilePath, oldFilePath)
if err != nil {
Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath, "error": err}).Error("Error creating symlink")
}
}
if moveDone == false {
tStorage.TorrentMoved = true //TODO error handling instead of just saying torrent was moved when it was not
notifyUser(tStorage, config, db) //Only notify if we haven't moved yet, don't want to push notify user every time user uses change storage button
}
Logger.WithFields(logrus.Fields{"Old File Path": oldFilePath, "New File Path": newFilePath}).Info("Moving completed torrent")
tStorage.StoragePath = filepath.Dir(newFilePath)
Storage.UpdateStorageTick(db, tStorage)
}
}
func notifyUser(tStorage Storage.TorrentLocal, config 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")
}
}

View File

@@ -0,0 +1,46 @@
package engine
import (
"testing"
"github.com/asdine/storm"
Storage "github.com/deranjer/goTorrent/storage"
)
func TestMoveAndLeaveSymlink(t *testing.T) {
type args struct {
config FullClientSettings
tStorage Storage.TorrentLocal
db *storm.DB
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
MoveAndLeaveSymlink(tt.args.config, tt.args.tStorage, tt.args.db)
})
}
}
func Test_notifyUser(t *testing.T) {
type args struct {
tStorage Storage.TorrentLocal
config FullClientSettings
db *storm.DB
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
notifyUser(tt.args.tStorage, tt.args.config, tt.args.db)
})
}
}

350
engine/engine.go Normal file
View File

@@ -0,0 +1,350 @@
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"
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
//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")
clientTorrent.DownloadAll()
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
}
//StartTorrent creates the storage.db entry and starts A NEW TORRENT and adds to the running torrent array
func StartTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage Storage.TorrentLocal, torrentDbStorage *storm.DB, torrentType, torrentFilePathAbs, torrentStoragePath, labelValue string, config FullClientSettings) {
timedOut := timeOutInfo(clientTorrent, 45) //seeing if adding the torrent times out (giving 45 seconds)
if timedOut { //if we fail to add the torrent return
return
}
var TempHash metainfo.Hash
TempHash = clientTorrent.InfoHash()
allStoredTorrents := Storage.FetchAllStoredTorrents(torrentDbStorage)
for _, runningTorrentHashes := range allStoredTorrents {
if runningTorrentHashes.Hash == TempHash.String() {
Logger.WithFields(logrus.Fields{"Hash": TempHash.String()}).Error("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")
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
Storage.AddTorrentLocalStorage(torrentDbStorage, torrentLocalStorage) //writing all of the data to the database
clientTorrent.DownloadAll() //starting the download
CreateServerPushMessage(ServerPushMessage{MessageType: "serverPushMessage", MessageLevel: "success", Payload: "Torrent added!"}, Conn)
}
//CreateRunningTorrentArray creates the entire torrent list to pass to client
func CreateRunningTorrentArray(tclient *torrent.Client, TorrentLocalArray []*Storage.TorrentLocal, PreviousTorrentArray []ClientDB, config FullClientSettings, db *storm.DB) (RunningTorrentArray []ClientDB) {
for _, singleTorrentFromStorage := range TorrentLocalArray {
var singleTorrent *torrent.Torrent
var TempHash metainfo.Hash
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)
//singleTorrentStorageInfo := Storage.FetchTorrentFromStorage(db, TempHash.String()) //pulling the single torrent info from storage ()
if singleTorrentFromStorage.TorrentType == "file" { //if it is a file pull it from the uploaded torrent folder
var err error
singleTorrent, err = readTorrentFileFromDB(singleTorrentFromStorage, tclient, db)
if err != nil {
continue
}
fullClientDB.SourceType = "Torrent File"
} else {
singleTorrentFromStorageMagnet := "magnet:?xt=urn:btih:" + singleTorrentFromStorage.Hash //For magnet links just need to prepend the magnet part to the hash to readd
singleTorrent, _ = tclient.AddMagnet(singleTorrentFromStorageMagnet)
fullClientDB.SourceType = "Magnet Link"
}
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!")
}
calculatedTotalSize := CalculateDownloadSize(singleTorrentFromStorage, singleTorrent)
calculatedCompletedSize := CalculateCompletedSize(singleTorrentFromStorage, singleTorrent)
TempHash = singleTorrent.InfoHash()
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...")
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
}
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 //used for calculations not passed to client calculating up/down speed
fullClientDB.DataBytesWritten = fullStruct.ConnStats.BytesWrittenData //used for calculations not passed to client calculating up/down speed
fullClientDB.ActivePeers = activePeersString + " / (" + totalPeersString + ")"
fullClientDB.TorrentHashString = TempHash.String()
fullClientDB.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)
fullClientDB.TotalUploadedBytes = singleTorrentFromStorage.UploadedBytes + (fullStruct.ConnStats.BytesWrittenData - 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)
tickUpdateStruct.UploadRatio = fullClientDB.UploadRatio
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 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
}

164
engine/engineHelpers.go Normal file
View File

@@ -0,0 +1,164 @@
package engine
import (
"fmt"
"io"
"os"
"time"
"github.com/anacrolix/torrent"
"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
}
//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")
}
}
//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) {
now := time.Now()
bytes := t.BytesCompleted()
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 - 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
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
}
//CalculateTorrentStatus is used to determine what the STATUS column of the frontend will display ll2
func CalculateTorrentStatus(t *torrent.Torrent, c *ClientDB, config FullClientSettings, tFromStorage *storage.TorrentLocal, bytesCompleted int64, totalSize int64) {
if (tFromStorage.TorrentStatus == "Stopped") || (float64(c.TotalUploadedBytes)/float64(bytesCompleted) >= config.SeedRatioStop && tFromStorage.TorrentUploadLimit == true) { //If storage shows torrent stopped or if it is over the seeding ratio AND is under the global limit
c.Status = "Stopped"
c.MaxConnections = 0
t.SetMaxEstablishedConns(0)
} else { //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
bytesMissing := totalSize - bytesCompleted
c.MaxConnections = 80
t.SetMaxEstablishedConns(80) //TODO this should not be needed but apparently is needed
t.DownloadAll() //ensure that we are setting the torrent to download
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"
}
}
}

10
goTorrentWebUI/.babelrc Normal file
View File

@@ -0,0 +1,10 @@
{
"presets": [
"react",
"env",
"stage-2",
],
"plugins": ["transform-class-properties"]
}

View File

@@ -0,0 +1,334 @@
'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);

View File

@@ -0,0 +1,193 @@
'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);

View File

@@ -0,0 +1,325 @@
'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));

View File

@@ -0,0 +1,96 @@
'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);

View File

@@ -0,0 +1,183 @@
'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));

View File

@@ -0,0 +1,80 @@
'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);

View File

@@ -0,0 +1,215 @@
'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);

View File

@@ -0,0 +1,272 @@
'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);

View File

@@ -0,0 +1,149 @@
'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);

View File

@@ -0,0 +1,177 @@
'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);

View File

@@ -0,0 +1,198 @@
'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;
;

View File

@@ -0,0 +1,179 @@
'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;
;

View File

@@ -0,0 +1,216 @@
'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);

View File

@@ -0,0 +1,226 @@
'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));

159
goTorrentWebUI/lib/app.js Normal file
View File

@@ -0,0 +1,159 @@
'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'));

View File

@@ -0,0 +1,257 @@
'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 _List = require('material-ui/List');
var _List2 = _interopRequireDefault(_List);
var _Divider = require('material-ui/Divider');
var _Divider2 = _interopRequireDefault(_Divider);
var _FileDownload = require('material-ui-icons/FileDownload');
var _FileDownload2 = _interopRequireDefault(_FileDownload);
var _FileUpload = require('material-ui-icons/FileUpload');
var _FileUpload2 = _interopRequireDefault(_FileUpload);
var _SwapVert = require('material-ui-icons/SwapVert');
var _SwapVert2 = _interopRequireDefault(_SwapVert);
var _AllInclusive = require('material-ui-icons/AllInclusive');
var _AllInclusive2 = _interopRequireDefault(_AllInclusive);
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
//react redux
//TODO, clean up the goddamn variable names you are all over the place
var styles = function styles(theme) {
return {
root: {
width: '100%',
maxWidth: 360,
backgroundColor: '#e5e5e5'
},
icons: {
width: '40px',
height: '40px'
},
inactiveIcon: {
width: '40px',
height: '40px',
color: 'red'
},
active: {
backgroundColor: '#80b3ff'
}
};
};
var SimpleList = function (_React$Component) {
_inherits(SimpleList, _React$Component);
function SimpleList(props) {
_classCallCheck(this, SimpleList);
var _this = _possibleConstructorReturn(this, (SimpleList.__proto__ || Object.getPrototypeOf(SimpleList)).call(this, props));
_initialiseProps.call(_this);
var classes = _this.props.classes;
_this.state = {
allTorrentsClass: classes.active,
downloadingClass: '',
seedingClass: '',
activeTorrentsClass: '',
completedTorrentsClass: '',
allID: "All",
downloadingID: "Downloading",
seedingID: "Seeding",
activeID: "Active",
completedID: "Completed"
};
return _this;
}
_createClass(SimpleList, [{
key: 'render',
value: function render() {
var _this2 = this;
var classes = this.props.classes;
return _react2.default.createElement(
'div',
{ className: classes.root },
_react2.default.createElement(
_List2.default,
{ dense: true },
_react2.default.createElement(
_List.ListItem,
{ dense: true, className: this.state.allTorrentsClass, button: true, onClick: function onClick() {
return _this2.setFilter('', _this2.state.allID);
} },
_react2.default.createElement(
_List.ListItemIcon,
{ className: classes.icons },
_react2.default.createElement(_AllInclusive2.default, null)
),
_react2.default.createElement(_List.ListItemText, { primary: 'All Torrents' })
),
_react2.default.createElement(
_List.ListItem,
{ className: this.state.downloadingClass, button: true, onClick: function onClick() {
return _this2.setFilter('Downloading', _this2.state.downloadingID);
} },
_react2.default.createElement(
_List.ListItemIcon,
{ className: classes.icons },
_react2.default.createElement(_FileDownload2.default, null)
),
_react2.default.createElement(_List.ListItemText, { primary: 'Downloading Torrents' })
),
_react2.default.createElement(
_List.ListItem,
{ className: this.state.seedingClass, button: true, onClick: function onClick() {
return _this2.setFilter('Seeding', _this2.state.seedingID);
} },
_react2.default.createElement(
_List.ListItemIcon,
{ className: classes.icons },
_react2.default.createElement(_FileUpload2.default, null)
),
_react2.default.createElement(_List.ListItemText, { primary: 'Seeding Torrents' })
),
_react2.default.createElement(
_List.ListItem,
{ className: this.state.completedTorrentsClass, button: true, onClick: function onClick() {
return _this2.setFilter('Completed', _this2.state.completedID);
} },
_react2.default.createElement(
_List.ListItemIcon,
{ className: classes.inactiveIcon },
_react2.default.createElement(_SwapVert2.default, null)
),
_react2.default.createElement(_List.ListItemText, { primary: 'Completed Torrents' })
)
),
_react2.default.createElement(_Divider2.default, null)
);
}
}]);
return SimpleList;
}(_react2.default.Component);
var _initialiseProps = function _initialiseProps() {
var _this3 = this;
this.setActiveElement = function (listItem) {};
this.setFilter = function (filterState, id) {
var classes = _this3.props.classes;
filterState = [{ columnName: 'Status', value: filterState }];
_this3.props.changeFilter(filterState); //dispatch to redux
console.log("Switching filters classes", id);
switch (id) {//TODO.. there has to be a better fucking way to do this
case "All":
_this3.state.allTorrentsClass = classes.active;
_this3.state.downloadingClass = '';
_this3.state.seedingClass = '';
_this3.state.activeTorrentsClass = '';
_this3.state.completedTorrentsClass = '';
break;
case "Downloading":
console.log("Downloading...");
_this3.state.downloadingClass = classes.active;
_this3.state.allTorrentsClass = '';
_this3.state.seedingClass = '';
_this3.state.activeTorrentsClass = '';
_this3.state.completedTorrentsClass = '';
break;
case "Seeding":
_this3.state.seedingClass = classes.active;
_this3.state.allTorrentsClass = '';
_this3.state.downloadingClass = '';
_this3.state.activeTorrentsClass = '';
_this3.state.completedTorrentsClass = '';
break;
case "Active":
_this3.state.activeTorrentsClass = classes.active;
_this3.state.allTorrentsClass = '';
_this3.state.downloadingClass = '';
_this3.state.seedingClass = '';
_this3.state.completedTorrentsClass = '';
break;
case "Completed":
_this3.state.completedTorrentsClass = classes.active;
_this3.state.allTorrentsClass = '';
_this3.state.downloadingClass = '';
_this3.state.seedingClass = '';
_this3.state.activeTorrentsClass = '';
break;
}
};
};
SimpleList.propTypes = {
classes: _propTypes2.default.object.isRequired
};
var mapStateToProps = function mapStateToProps(state) {
return {
filter: state.filter
};
};
var mapDispatchToProps = function mapDispatchToProps(dispatch) {
return {
changeFilter: function changeFilter(filter) {
return dispatch({ type: actionTypes.CHANGE_FILTER, filter: filter });
}
};
};
exports.default = (0, _styles.withStyles)(styles)((0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(SimpleList));

View File

@@ -0,0 +1,18 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var SORTLIST = exports.SORTLIST = 'SORTLIST';
var CHANGE_SELECTION = exports.CHANGE_SELECTION = 'CHANGE_SELECTION';
var CHANGE_FILTER = exports.CHANGE_FILTER = 'CHANGE_FILTER';
var TORRENT_LIST = exports.TORRENT_LIST = 'TORRENT_LIST';
var SET_BUTTON_STATE = exports.SET_BUTTON_STATE = 'BUTTON_STATE';
var SELECTION_HASHES = exports.SELECTION_HASHES = 'SELECTION_HASHES';
var SELECTED_TAB = exports.SELECTED_TAB = 'SELECTED_TAB';
var PEER_LIST = exports.PEER_LIST = 'PEER_LIST';
var FILE_LIST = exports.FILE_LIST = 'FILE_LIST';
var CHANGE_FILE_SELECTION = exports.CHANGE_FILE_SELECTION = 'CHANGE_FILE_SELECTION';
var NEW_RSS_FEED_STORE = exports.NEW_RSS_FEED_STORE = 'NEW_RSS_FEED_STORE';
var RSS_MODAL_OPEN_STATE = exports.RSS_MODAL_OPEN_STATE = 'RSS_MODAL_OPEN_STATE';
var RSS_TORRENT_LIST = exports.RSS_TORRENT_LIST = 'RSS_TORRENT_LIST';

View File

@@ -0,0 +1,159 @@
"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 _actions = require("./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; } }
var initialState = {
buttonStateDefault: [{ startButton: "default", stopButton: "default", deleteButton: "default", fSeedButton: "default", fRecheckButton: "default" }],
buttonState: [{ startButton: "default", stopButton: "default", deleteButton: "default", fSeedButton: "default", fRecheckButton: "default" }],
sorting: [],
selection: [],
selectionHashes: [],
filter: ["Status", ""],
columnName: "Status",
torrentList: [],
peerList: [],
fileList: [],
torrentDetailInfo: [],
selectedTab: 0,
RSSList: [],
RSSTorrentList: [],
RSSModalOpen: false
};
var reducer = function reducer() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
var action = arguments[1];
switch (action.type) {
case actionTypes.CHANGE_SELECTION:
console.log("Change Selection", action.selection);
return _extends({}, state, {
peerList: [], //changing selection will purge out all of the old data
fileList: [],
selectionHashes: [],
selection: action.selection
});
case actionTypes.NEW_RSS_FEED_STORE:
console.log("New RSS Feed Store", action.RSSList);
return _extends({}, state, {
RSSList: action.RSSList
});
case actionTypes.RSS_TORRENT_LIST:
console.log("New RSS Torrent List IN REDUCER", action.RSSTorrentList);
return _extends({}, state, {
RSSTorrentList: action.RSSTorrentList
});
case actionTypes.SELECTION_HASHES:
console.log("Selection hashes REDUX", action.selectionHashes);
return _extends({}, state, {
selectionHashes: action.selectionHashes
});
case actionTypes.SORTLIST:
//TODO do I even need this in redux?
console.log("List Sort", action.sorting);
return state;
case actionTypes.CHANGE_FILTER:
return _extends({}, state, {
filter: action.filter
});
case actionTypes.TORRENT_LIST:
return _extends({}, state, {
torrentList: action.torrentList
});
case actionTypes.PEER_LIST:
return _extends({}, state, {
peerList: action.peerList
});
case actionTypes.FILE_LIST:
return _extends({}, state, {
fileList: action.fileList
});
case actionTypes.SELECTED_TAB:
return _extends({}, state, {
selectedTab: action.selectedTab
});
case actionTypes.RSS_MODAL_OPEN_STATE:
console.log("Setting RSS Modal State...", action.RSSModalOpen);
return _extends({}, state, {
RSSModalOpen: action.RSSModalOpen
});
case actionTypes.SET_BUTTON_STATE:
if (action.buttonState.length === 0) {
//if selection is empty buttons will be default and selectionHashes will be blanked out and pushed to redux
var _buttonStateFinal = state.buttonStateDefault; //if no selection dispatch that to redux
return _extends({}, state, {
buttonState: _buttonStateFinal
});
} else {
// if we have selection continue on with logic to determine button state
var selectedRows = []; //array of all the selected Rows
action.buttonState.forEach(function (element) {
selectedRows.push(state.torrentList[element]); //pushing the selected rows out of torrentlist
});
var buttonStateTest = selectedRows.filter(function (element) {
//TODO fix this bad mess... we literally just need to filter for stopped and go from there
var result = [];
if (element.Status === "Downloading" || element.Status === "Awaiting Peers" || element.Status === "Seeding" || element.Status === "Completed") {
result.push(element.Status);
return result;
}
});
var buttonStateTest2 = selectedRows.filter(function (element) {
return element.Status === "Stopped";
});
if (buttonStateTest.length > 0 && buttonStateTest2.length === 0) {
var _buttonStateFinal2 = [{ startButton: "default", stopButton: "primary", deleteButton: "accent", fSeedButton: "default", fRecheckButton: "primary" }];
return _extends({}, state, {
buttonState: _buttonStateFinal2
});
}
if (buttonStateTest.length === 0 && buttonStateTest2.length > 0) {
var _buttonStateFinal3 = [{ startButton: "primary", stopButton: "default", deleteButton: "accent", fSeedButton: "default", fRecheckButton: "primary" }];
return _extends({}, state, {
buttonState: _buttonStateFinal3
});
}
if (buttonStateTest.length > 0 && buttonStateTest2.length > 0) {
var _buttonStateFinal4 = [{ startButton: "primary", stopButton: "primary", deleteButton: "accent", fSeedButton: "default", fRecheckButton: "primary" }];
return _extends({}, state, {
buttonState: _buttonStateFinal4
});
}
}
return _extends({}, state, {
buttonState: buttonStateFinal
});
default:
return state;
};
console.log("no actiontypes found", action);
return state;
};
exports.default = reducer;

View File

@@ -0,0 +1,212 @@
'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 _reactBootstrapTableAllMin = require('../node_modules/react-bootstrap-table/dist/react-bootstrap-table-all.min.css');
var _reactBootstrapTableAllMin2 = _interopRequireDefault(_reactBootstrapTableAllMin);
var _reactBootstrapTable = require('react-bootstrap-table');
var _Paper = require('material-ui/Paper');
var _Paper2 = _interopRequireDefault(_Paper);
var _dxReactGrid = require('@devexpress/dx-react-grid');
var _dxReactGridMaterialUi = require('@devexpress/dx-react-grid-material-ui');
var _progressBarCell = require('./CustomCells/progressBarCell');
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; }
//react redux
/* var torrentLinkSubmit = document.getElementById('torrentLinkSubmit');
var magnetLink = document.getElementById('magnetLink');
var myTextArea = document.getElementById("loggerData");
var torrentHash = document.getElementById("hash");
initialize an empty torrents field before update.
var torrentLinkSubmit = document.getElementById('torrentLinkSubmit');
var magnetLink = document.getElementById('magnetLink');
var myTextArea = document.getElementById("loggerData");
var torrentHash = document.getElementById("hash");
var torrentLinkSubmit = document.getElementById('torrentLinkSubmit');
var torrentLinkModal = document.getElementById('addTorrentLinkModal');
var btnTorrentLink = document.getElementById("addTorrentLink");
*/
function sendEvent(message) {
ws.send(message);
console.log('Sending message... ', message);
}
var TorrentListTable = function (_React$Component) {
_inherits(TorrentListTable, _React$Component);
function TorrentListTable(props) {
_classCallCheck(this, TorrentListTable);
var _this = _possibleConstructorReturn(this, (TorrentListTable.__proto__ || Object.getPrototypeOf(TorrentListTable)).call(this, props));
_this.determineSelectionHashes = function (selectedRows) {
//console.log("CurrentSelectionHashes", this.props.selectionHashes)
var selectionHashes = []; //rebuilding our selection hashes from our currently selected rows
selectedRows.forEach(function (element) {
selectionHashes.push(element.TorrentHashString); //push the selection hash to the temp array
});
_this.props.sendSelectionHashes(selectionHashes); //push the result to redux
};
_this.changeSelection = function (selection) {
//console.log("TOrrentlist is changing selection now", selection)
_this.props.changeSelection(selection); //dispatch selection to redux, also clear out anything tied to the old selection (peerlists, filelists, etc)
if (selection.length === 0) {
//if selection is empty buttons will be default and selectionHashes will be blanked out and pushed to redux
_this.props.setButtonState(selection); //if no selection dispatch that to redux
} else {
// if we have selection continue on with logic to determine button state
var selectedRows = []; //array of all the selected Rows
selection.forEach(function (element) {
selectedRows.push(_this.props.torrentList[element]); //pushing the selected rows out of torrentlist
});
//console.log("Determining selection hashses")
_this.determineSelectionHashes(selectedRows); //pulling the torrent hashes for the selcted rows
_this.props.setButtonState(selection);
}
};
_this.filterHandler = function (filter) {
//TODO, figure out how to do multiple filter
//console.log("Changing FIlter", filter)
if (filter.value === "Active") {
console.log("This filter doesn't fucking work");
}
};
_this.state = { //rows are stored in redux they are sent over from the server
columns: [{ name: 'TorrentName', title: 'Torrent Name' }, { name: 'DownloadedSize', title: 'Dl Size' }, { name: 'Size', title: 'Size' }, { name: 'PercentDone', title: 'Percent Done' }, { name: 'Status', title: 'Status' }, { name: 'DownloadSpeed', title: 'DL Speed' }, { name: 'UploadSpeed', title: 'UL Speed' }, { name: 'ActivePeers', title: 'Active Peers' }, { name: 'ETA', title: 'ETA' }, { name: 'Ratio', title: 'Ratio' }, { name: 'DateAdded', title: 'Date Added' }, { name: 'Availability', title: 'Availability' }],
columnOrder: ['TorrentName', 'DownloadedSize', 'Size', 'PercentDone', 'Status', 'DownloadSpeed', 'UploadSpeed', 'ActivePeers', 'ETA', 'Ratio', 'DateAdded', 'Availability'],
columnWidths: { TorrentName: 250, DownloadedSize: 100, Size: 100, PercentDone: 175, Status: 150, DownloadSpeed: 100, UploadSpeed: 100, ActivePeers: 100, ETA: 100, Ratio: 75, DateAdded: 100, Availability: 75 },
prevSelection: [], //just used to pull data from cell (temp Prevcell holder), real selection is in Redux
pageSizes: [5, 10, 15, 0],
currentPage: 0
};
_this.changeColumnOrder = function (columnOrder) {
return _this.setState({ columnOrder: columnOrder });
};
_this.changeColumnWidths = function (columnWidths) {
return _this.setState({ columnWidths: columnWidths });
};
_this.changePageSize = function (pageSize) {
return _this.setState({ pageSize: pageSize });
};
_this.changeCurrentPage = function (currentPage) {
return _this.setState({ currentPage: currentPage });
};
return _this;
}
_createClass(TorrentListTable, [{
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
//this is for setting the filter when the left menu activates a new filter
if (this.props.filter != nextProps.filter) {
this.filterHandler(nextProps.filter);
}
//console.log("Recieving new props", nextProps.selection)
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement(
_Paper2.default,
null,
_react2.default.createElement(
_dxReactGridMaterialUi.Grid,
{ rows: this.props.torrentList, columns: this.state.columns },
_react2.default.createElement(_dxReactGrid.FilteringState, { filters: this.props.filter }),
_react2.default.createElement(_dxReactGrid.SortingState, { sorting: this.props.sorting, onSortingChange: this.props.changeSorting }),
_react2.default.createElement(_dxReactGrid.SelectionState, { onSelectionChange: this.changeSelection, selection: this.props.selection }),
_react2.default.createElement(_dxReactGrid.LocalFiltering, null),
_react2.default.createElement(_dxReactGrid.LocalSorting, null),
_react2.default.createElement(_dxReactGridMaterialUi.VirtualTableView, { height: 530, tableCellTemplate: function tableCellTemplate(_ref) {
var row = _ref.row,
column = _ref.column,
style = _ref.style;
if (column.name === 'PercentDone') {
return _react2.default.createElement(_progressBarCell.ProgressBarCell, { value: row.PercentDone * 100, style: style });
}
return undefined;
} }),
_react2.default.createElement(_dxReactGridMaterialUi.DragDropContext, null),
_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 TorrentListTable;
}(_react2.default.Component);
var mapStateToProps = function mapStateToProps(state) {
return {
filter: state.filter,
torrentList: state.torrentList,
buttonState: state.buttonState,
buttonStateDefault: state.buttonStateDefault, //all default
selectionHashes: state.selectionHashes,
selection: state.selection
};
};
var mapDispatchToProps = function mapDispatchToProps(dispatch) {
return {
changeSorting: function changeSorting(sorting) {
return dispatch({ type: actionTypes.SORTLIST, sorting: sorting });
},
changeSelection: function changeSelection(selection) {
return dispatch({ type: actionTypes.CHANGE_SELECTION, selection: selection });
},
setButtonState: function setButtonState(buttonState) {
return dispatch({ type: actionTypes.SET_BUTTON_STATE, buttonState: buttonState });
},
sendSelectionHashes: function sendSelectionHashes(selectionHashes) {
return dispatch({ type: actionTypes.SELECTION_HASHES, selectionHashes: selectionHashes });
}
};
};
exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(TorrentListTable);

Some files were not shown because too many files have changed in this diff Show More