From 7d9b3117f2c99d018d9383cafc8a7402992b4d89 Mon Sep 17 00:00:00 2001 From: deranjer Date: Fri, 27 Oct 2017 20:37:32 -0400 Subject: [PATCH] BRANCH: Torrent with Vue and pure JS. --- client.go | 43 --------- config.toml | 59 ++++++++++++ main.go | 158 +++++++++++++++++++++++++++++++- public/static/js/addTorrents.js | 49 ++++++++++ public/static/js/grid.js | 31 +++++++ public/static/js/tabControl.js | 13 +++ public/static/js/websocket.js | 65 +++++++++++++ settings.go | 32 +++++++ storage.go | 95 +++++++++++++++++++ templates/home.tmpl | 154 ++++++++++--------------------- 10 files changed, 545 insertions(+), 154 deletions(-) create mode 100644 config.toml create mode 100644 public/static/js/addTorrents.js create mode 100644 public/static/js/grid.js create mode 100644 public/static/js/tabControl.js create mode 100644 public/static/js/websocket.js create mode 100644 settings.go create mode 100644 storage.go diff --git a/client.go b/client.go index fb0f5c7a..81710551 100644 --- a/client.go +++ b/client.go @@ -3,9 +3,6 @@ package main import ( "fmt" "github.com/anacrolix/torrent" - "log" - "os" - "path/filepath" "time" ) @@ -29,17 +26,6 @@ type ClientConfig struct { DownloadDir string } -// NewClientConfig creates a new default configuration. -func NewClientConfig() ClientConfig { - return ClientConfig{ - Port: 8080, - TorrentPort: 50007, - Seed: false, - TCP: true, - MaxConnections: 200, - DownloadDir: "Download", - } -} type Client struct { Client *torrent.Client @@ -57,33 +43,4 @@ type Client struct { Config ClientConfig } -func NewClient(cfg ClientConfig) (client Client, err error) { - var t *torrent.Torrent - var c *torrent.Client - - client.Config = cfg - - //create the download directory - _, err := os.Create(cfg.DownloadDir) - if err != nil { - log.Println(err) - return - } - - c, err = torrent.NewClient(&torrent.Config{ - DataDir: cfg.DownloadDir, - NoUpload: !cfg.Seed, - Seed: cfg.Seed, - DisableTCP: !cfg.TCP, - ListenAddr: fmt.Sprintf("%d", cfg.TorrentPort), - }) - if err != nil { - return client, ClientError{Type: "creating torrent client", Origin: err} - } - - client.Client = c - - //adding torrents - -} diff --git a/config.toml b/config.toml new file mode 100644 index 00000000..e804c313 --- /dev/null +++ b/config.toml @@ -0,0 +1,59 @@ +[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 + +#encryption policy +IPBlocklist = "" #iplist.Ranger +DisableIPv6 = false #boolean +Debug = false #boolean + + +[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 + +#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" \ No newline at end of file diff --git a/main.go b/main.go index 6bbfda57..8c94f0f5 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,11 @@ import ( "os" "strings" "time" + "github.com/anacrolix/torrent" + //"github.com/anacrolix/dht" + "github.com/anacrolix/torrent/metainfo" + "github.com/boltdb/bolt" + "encoding/json" ) var ( @@ -26,40 +31,185 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 1024, } +type torrentList struct { //helps create the JSON structure that vuetable expects to recieve + Totaltorrents int `json:"total"` + ClientDBstruct []clientDB `json:"data"` + +} + +type clientDB struct{ + TorrentName string `json:"Torrent Name"` + ChunksWritten int64 + ChunksRead int64 + BytesWritten int64 + BytesRead int64 + DataBytesWritten int64 + DataBytesRead int64 + ActivePeers int + TotalPeers int `json:"Total Peers"` + TorrentHashString string + TorrentHash metainfo.Hash + StoragePath string `json:"Storage Location"` + DateAdded string +} + + func serveHome(w http.ResponseWriter, r *http.Request) { s1, _ := template.ParseFiles("templates/home.tmpl") s1.ExecuteTemplate(w, "base", map[string]string{"APP_ID": APP_ID}) } +func startTorrent(clientTorrent *torrent.Torrent, torrentLocalStorage *TorrentLocal, Config torrent.Config, torrentDbStorage *bolt.DB){ + <-clientTorrent.GotInfo() //waiting for all of the torrent info to be downloaded + + torrentLocalStorage.Hash = clientTorrent.InfoHash() // we will store the infohash to add it back later on client restart (if needed) + torrentLocalStorage.DateAdded = time.Now().Format("Jan _2 2006") + torrentLocalStorage.StoragePath = Config.DataDir //TODO check full path information for torrent storage + torrentLocalStorage.TorrentName = clientTorrent.Name() + + fmt.Printf("%+v\n", torrentLocalStorage) + addTorrentLocalStorage(torrentDbStorage, torrentLocalStorage) //writing all of the data to the database + + clientTorrent.DownloadAll()//starting the download +} + +func createRunningTorrentArray (tclient *torrent.Client, TorrentLocalArray []*TorrentLocal) (RunningTorrentArray []clientDB) { + for _, element := range TorrentLocalArray { //re-adding all the torrents we had stored from last shutdown + + singleTorrent, _ := tclient.AddTorrentInfoHash(element.Hash) //adding back in the torrents by hash + + fullClientDB := new(clientDB) + + fullStruct := singleTorrent.Stats() + + fullClientDB.TorrentHash = element.Hash + fullClientDB.ChunksWritten = fullStruct.ConnStats.ChunksWritten + fullClientDB.ChunksRead = fullStruct.ConnStats.ChunksRead + fullClientDB.BytesWritten = fullStruct.ConnStats.BytesWritten + fullClientDB.BytesRead = fullStruct.ConnStats.BytesRead + fullClientDB.DataBytesWritten = fullStruct.ConnStats.DataBytesWritten + fullClientDB.DataBytesRead = fullStruct.ConnStats.DataBytesRead + fullClientDB.ActivePeers = fullStruct.ActivePeers + fullClientDB.TotalPeers = fullStruct.TotalPeers + fullClientDB.TorrentHashString = element.Hash.String() + fullClientDB.StoragePath = element.StoragePath + fullClientDB.TorrentName = element.TorrentName + fullClientDB.DateAdded = element.DateAdded + + + RunningTorrentArray = append(RunningTorrentArray, *fullClientDB) + + } + return RunningTorrentArray +} + +func updateClient(torrentstats []clientDB, conn *websocket.Conn){ //get the torrent client and the websocket connection to write msg + //first get the list of torrents in the client + + conn.WriteJSON(torrentstats) //converting to JSON and writing to the client + +} + + func main() { + //setting up the torrent client + Config := fullClientSettingsNew() //grabbing from settings.go - r := mux.NewRouter() + torrentLocalStorage := new(TorrentLocal) //creating a new struct that stores all of our local storage info + fmt.Printf("%+v\n", Config) + + tclient, err := torrent.NewClient(&Config.Config) //pulling out the torrent specific config to use + if err != nil { + log.Fatalf("error creating client: %s", err) + } + + //torrentDbStorage := initializeStorage //initializing the boltDB store that contains all the added torrents + + db, err := bolt.Open("storage.db", 0600, nil) //initializing the boltDB store that contains all the added torrents + if err !=nil { + log.Fatal(err) + } + defer db.Close() //defering closing the database until the program closes + + + //defer torrentDbStorage().Close() //defering closing the database until the program closes + + var TorrentLocalArray = []*TorrentLocal{} //this is an array of ALL of the local storage torrents, they will be added back in via hash + var RunningTorrentArray = []clientDB{} //this stores ALL of the torrents that are running, used for client update pushes combines Local Storage and Running tclient info + + + TorrentLocalArray = readInTorrents(db)//pulling in all the already added torrents + + if TorrentLocalArray != nil { + RunningTorrentArray = createRunningTorrentArray(tclient, TorrentLocalArray) //Updates the RunningTorrentArray with the current client data as well + } else { + fmt.Println("Database is empty!") + } + + + + r := mux.NewRouter() //setting up the handler for the web backend r.HandleFunc("/", serveHome) http.Handle("/static/", http.FileServer(http.Dir("public"))) http.Handle("/", r) + http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request){ //exposing the data to the + if len(RunningTorrentArray) > 0 { + RunningTorrentArray = createRunningTorrentArray(tclient, TorrentLocalArray) //Updates the RunningTorrentArray with the current client data as well + var torrentlistArray = new(torrentList) + torrentlistArray.ClientDBstruct = RunningTorrentArray + torrentlistArray.Totaltorrents = len(RunningTorrentArray) + torrentlistArrayJson, _:= json.Marshal(torrentlistArray) + w.Header().Set("Content-Type", "application/json") + w.Write(torrentlistArrayJson) + //updateClient(RunningTorrentArray, conn) // sending the client update information over the websocket + } + }) http.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { log.Println(err) return } + for { msgType, msg, err := conn.ReadMessage() if err != nil { fmt.Println(err) return } - if string(msg) == "ping" { + if string(msg) == "ping" { //6 second update ping fmt.Println("ping") - time.Sleep(2 * time.Second) + time.Sleep(6 * time.Second) err = conn.WriteMessage(msgType, []byte("pong")) if err != nil { - fmt.Println(err) + fmt.Println("Websocket err", err) return } + + if len(RunningTorrentArray) > 0 { + RunningTorrentArray = createRunningTorrentArray(tclient, TorrentLocalArray) //Updates the RunningTorrentArray with the current client data as well + var torrentlistArray = new(torrentList) + torrentlistArray.ClientDBstruct = RunningTorrentArray + torrentlistArray.Totaltorrents = len(RunningTorrentArray) + fmt.Printf("%+v\n", torrentlistArray) + conn.WriteJSON(torrentlistArray) + //updateClient(RunningTorrentArray, conn) // sending the client update information over the websocket + } + + } else if strings.HasPrefix(string(msg), "magnet:") { fmt.Println(string(msg)) + clientTorrent, err := tclient.AddMagnet(string(msg)) + if err !=nil{ + fmt.Println("Magnet Error", err) + } + fmt.Println(clientTorrent) + fmt.Printf("Adding") + + startTorrent(clientTorrent, torrentLocalStorage, Config.Config, db) //starting the torrent and creating local DB entry + } else { conn.Close() fmt.Println(string(msg)) diff --git a/public/static/js/addTorrents.js b/public/static/js/addTorrents.js new file mode 100644 index 00000000..dc392aa5 --- /dev/null +++ b/public/static/js/addTorrents.js @@ -0,0 +1,49 @@ +// Get the modal +var modal = document.getElementById('addTorrentModal'); + +// Get the button that opens the modal +var btn = document.getElementById("addTorrentLink"); + +// Get the element that closes the modal +var span = document.getElementsByClassName("addTorrentModalClose")[0]; + + +// When the user clicks the button, open the modal +btn.onclick = function() { + modal.style.display = "block"; +} + +// When the user clicks on (x), close the modal +span.onclick = function() { + modal.style.display = "none"; +} + +// When the user clicks anywhere outside of the modal, close it +window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + } +} + +var torrentFileModal = document.getElementById('addTorrentFileModal'); + +var btnTorrentFile = document.getElementById("addTorrentFile"); + +var spanTorrentFile = document.getElementsByClassName("addTorrentFileModalClose")[0]; + + +btnTorrentFile.onclick = function() { + torrentFileModal.style.display = "block"; +} + +// When the user clicks on (x), close the modal +spanTorrentFile.onclick = function() { + torrentFileModal.style.display = "none"; +} + +// When the user clicks anywhere outside of the modal, close it +window.onclick = function(event) { + if (event.target == torrentFileModal) { + torrentFileModal.style.display = "none"; + } +} diff --git a/public/static/js/grid.js b/public/static/js/grid.js new file mode 100644 index 00000000..39c2ea03 --- /dev/null +++ b/public/static/js/grid.js @@ -0,0 +1,31 @@ +Vue.use(Vuetable); + +var demo = new Vue({ + delimiters: ['((', '))'], + el: '#torrentlist', + components:{ + 'vuetable-pagination': Vuetable.VuetablePagination + }, + data: { + fields: ['Torrent Name', 'Status','Percent Complete','Size','Total Peers','Storage Location'] + }, + computed:{ + /*httpOptions(){ + return {headers: {'Authorization': "my-token"}} //table props -> :http-options="httpOptions" + },*/ + }, + methods: { + onPaginationData (paginationData) { + this.$refs.pagination.setPaginationData(paginationData) + }, + onChangePage (page) { + this.$refs.vuetable.changePage(page) + }, + editRow(rowData){ + alert("You clicked edit on"+ JSON.stringify(rowData)) + }, + deleteRow(rowData){ + alert("You clicked delete on"+ JSON.stringify(rowData)) + } + } +}) \ No newline at end of file diff --git a/public/static/js/tabControl.js b/public/static/js/tabControl.js new file mode 100644 index 00000000..e3d2416b --- /dev/null +++ b/public/static/js/tabControl.js @@ -0,0 +1,13 @@ +function openTab(evt, tabName) { + var i, x, tablinks; + x = document.getElementsByClassName("tab"); + for (i = 0; i < x.length; i++) { + x[i].style.display = "none"; + } + tablinks = document.getElementsByClassName("tablink"); + for (i = 0; i < x.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" activeButton", ""); + } + document.getElementById(tabName).style.display = "block"; + evt.currentTarget.className += " activeButton"; +} diff --git a/public/static/js/websocket.js b/public/static/js/websocket.js new file mode 100644 index 00000000..6993bd68 --- /dev/null +++ b/public/static/js/websocket.js @@ -0,0 +1,65 @@ +function myWebsocketStart() +{ + + var torrentLinkSubmit = document.getElementById('torrentLinkSubmit'); + var magnetLink = document.getElementById('magnetLink'); + var modal = document.getElementById('addTorrentModal'); + var myTextArea = document.getElementById("loggerData"); + var torrentHash = document.getElementById("hash"); + + + var ws = new WebSocket("ws://192.168.1.141:8000/websocket"); + + ws.onopen = function() + { + // Web Socket is connected, send data using send() + ws.send("ping"); + + myTextArea.innerHTML = myTextArea.innerHTML + "
" + "First message sent"; + }; + + ws.onmessage = function (evt) + { + var myTextArea = document.getElementById("loggerData"); + if(evt.data == "pong") { + setTimeout(function(){ws.send("ping");}, 2000); + } else { + + var clientUpdate = JSON.parse(evt.data); + myTextArea.innerHTML = myTextArea.innerHTML + "
" + "Client Update Event..."; + //myTextArea.innerHTML = myTextArea.innerHTML + "
" + clientUpdate.LocalTorrentInfo.DateAdded; + myTextArea.innerHTML = myTextArea.innerHTML + "
" + evt.data; + //myTextArea.innerHTML = myTextArea.innerHTML + "
" + clientUpdate[0].TorrentHashString; + + //torrentHash.innerHTML = "Hash: " + clientUpdate[0].TorrentHashString; + + + + } + }; + + ws.onclose = function() + { + var myTextArea = document.getElementById("loggerData"); + myTextArea.innerHTML = myTextArea.innerHTML + "
" + "Connection closed"; + }; + + + torrentLinkSubmit.onclick = function(e) { + e.preventDefault(); + + var magnetLinkjs = magnetLink.value; + + ws.send(magnetLinkjs); + myTextArea.innerHTML = myTextArea.innerHTML + "
Send:" + magnetLinkjs + modal.style.display = "none"; + magnetLink.value = ''; + } + + +} + +function sendEvent(message) +{ + ws.send(message); +} diff --git a/settings.go b/settings.go new file mode 100644 index 00000000..1c4f832d --- /dev/null +++ b/settings.go @@ -0,0 +1,32 @@ +package main + +import ( + + "github.com/anacrolix/torrent" + "github.com/anacrolix/dht" +) + + +type fullClientSettings struct { + version int + torrent.Config + +} + + +func fullClientSettingsNew()(fullClientSettings){ + //Config := fullClientSettings //generate a new struct + + var Config fullClientSettings + + + Config.version = 1.0 + Config.DataDir = "downloads" //the full OR relative path of the default download directory for torrents + + Config.DHTConfig = dht.ServerConfig{ + StartingNodes: dht.GlobalBootstrapAddrs, + } + + return Config + +} \ No newline at end of file diff --git a/storage.go b/storage.go new file mode 100644 index 00000000..8e46af35 --- /dev/null +++ b/storage.go @@ -0,0 +1,95 @@ +package main + + + +import ( + "github.com/boltdb/bolt" + "fmt" + "github.com/anacrolix/torrent/metainfo" + "time" +) + +type TorrentLocal struct { + Hash metainfo.Hash + DateAdded string + StoragePath string + TorrentName string +} + + +func readInTorrents (torrentStorage *bolt.DB) (TorrentLocalArray []*TorrentLocal){ + + TorrentLocalArray = []*TorrentLocal{} + + torrentStorage.View(func(tx *bolt.Tx) error { + + tx.ForEach(func(name []byte, b *bolt.Bucket) error { + torrentLocal := new(TorrentLocal) //create a struct to store to an array + var Dateadded []byte + var StoragePath []byte + var Hash []byte + var TorrentName []byte + Dateadded = b.Get([]byte("Date")) + if Dateadded == nil { + fmt.Println("Date added error!") + Dateadded = []byte(time.Now().Format("Jan _2 2006")) + } + StoragePath = b.Get([]byte("StoragePath")) + if StoragePath == nil { + fmt.Println("StoragePath error!") + StoragePath = []byte("downloads") + } + Hash = b.Get([]byte("InfoHash")) + if Hash == nil { + fmt.Println("Hash error!") + } + TorrentName = b.Get([]byte("TorrentName")) + if TorrentName == nil { + fmt.Println("Torrent Name not found") + TorrentName = []byte("Not Found!") + } + + torrentLocal.DateAdded = string(Dateadded) + torrentLocal.StoragePath = string(StoragePath) + torrentLocal.Hash = metainfo.HashBytes(Hash) //Converting the byte slice back into the full hash + torrentLocal.TorrentName = string(TorrentName) + + fmt.Println("Torrentlocal list: ", torrentLocal) + TorrentLocalArray = append(TorrentLocalArray, torrentLocal) //dumping it into the array + return nil + }) + return nil + }) + + return TorrentLocalArray //all done, return the entire Array to add to the torrent client +} + + +func addTorrentLocalStorage (torrentStorage *bolt.DB, local *TorrentLocal){ + println("Adding Local storage information") + + torrentStorage.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte(local.Hash.Bytes()))//translating hash into bytes for storage + if err != nil { + return fmt.Errorf("create bucket %s", err) + } + err = b.Put([]byte("Date"), []byte(local.DateAdded))//TODO error checking marshall into JSON + if err != nil { + return err + } + err = b.Put([]byte("StoragePath"), []byte(local.StoragePath)) + if err != nil { + return err + } + err = b.Put([]byte("InfoHash"), []byte(local.Hash.Bytes())) + if err != nil { + return err + } + err = b.Put([]byte("TorrentName"), []byte(local.TorrentName)) + if err != nil { + return err + } + + return nil + }) +} \ No newline at end of file diff --git a/templates/home.tmpl b/templates/home.tmpl index 64286b90..e476510a 100644 --- a/templates/home.tmpl +++ b/templates/home.tmpl @@ -1,81 +1,17 @@ {{define "base"}} - - - torrent-project - - + - ws.onmessage = function (evt) - { - var myTextArea = document.getElementById("loggerData"); - myTextArea.innerHTML = myTextArea.innerHTML + "\n" + evt.data - if(evt.data == "pong") { - setTimeout(function(){ws.send("ping");}, 2000); - } - }; + - ws.onclose = function() - { - var myTextArea = document.getElementById("loggerData"); - myTextArea.innerHTML = myTextArea.innerHTML + "\n" + "Connection closed"; - }; - - - torrentLinkSubmit.onclick = function(e) { - e.preventDefault(); - - var magnetLinkjs = magnetLink.value; - - ws.send(magnetLinkjs); - - modal.style.display = "none"; - } - + - } - - function sendEvent(message) - { - ws.send(message); - } - - -
@@ -100,49 +36,46 @@
-
-
- - - - - - - - - - - - - - -
NameSizeProgressStatusSeedsPeersDown SpeedUp SpeedETARatioAvail.
+
  • Upload Torrent File
  • + +
  • Delete Torrent
  • +
  • Start Torrent
  • +
  • Pause Torrent
  • +
  • Stop Torrent
  • +
  • Move Torrent Up
  • +
  • Down Torrent
  • +
  • RSS Torrent
  • +
  • Settings
  • +
    + +
    +
    + + + +
    + +
    @@ -157,6 +90,7 @@

    General

    +

    General Information

    @@ -203,9 +137,15 @@
    - + + + + + + + + + - -