diff --git a/cmd/magneticod/dht/mainline/transport.go b/cmd/magneticod/dht/mainline/transport.go index 8bc7321..2d97187 100644 --- a/cmd/magneticod/dht/mainline/transport.go +++ b/cmd/magneticod/dht/mainline/transport.go @@ -5,11 +5,11 @@ import ( "github.com/anacrolix/torrent/bencode" "go.uber.org/zap" - "strings" + "golang.org/x/sys/unix" ) type Transport struct { - conn *net.UDPConn + fd int laddr *net.UDPAddr started bool @@ -46,16 +46,18 @@ func (t *Transport) Start() { t.started = true var err error - t.conn, err = net.ListenUDP("udp", t.laddr) + t.fd, err = unix.Socket(unix.SOCK_DGRAM, unix.AF_INET, 0) if err != nil { zap.L().Fatal("Could NOT create a UDP socket!", zap.Error(err)) } + unix.Bind(t.fd, unix.SockaddrInet4{Addr: t.laddr.IP, Port: t.laddr.Port}) + go t.readMessages() } func (t *Transport) Terminate() { - t.conn.Close() + unix.Close(t.fd); } // readMessages is a goroutine! @@ -63,14 +65,10 @@ func (t *Transport) readMessages() { buffer := make([]byte, 65536) for { - n, addr, err := t.conn.ReadFrom(buffer) + n, from, err := unix.Recvfrom(t.fd, buffer, 0) if err != nil { // TODO: isn't there a more reliable way to detect if UDPConn is closed? - if strings.HasSuffix(err.Error(), "use of closed network connection") { - break - } else { - zap.L().Debug("Could NOT read an UDP packet!", zap.Error(err)) - } + zap.L().Debug("Could NOT read an UDP packet!", zap.Error(err)) } var msg Message @@ -79,7 +77,7 @@ func (t *Transport) readMessages() { zap.L().Debug("Could NOT unmarshal packet data!", zap.Error(err)) } - t.onMessage(&msg, addr) + t.onMessage(&msg, from) } } @@ -89,9 +87,9 @@ func (t *Transport) WriteMessages(msg *Message, addr net.Addr) { zap.L().Panic("Could NOT marshal an outgoing message! (Programmer error.)") } - _, err = t.conn.WriteTo(data, addr) + err = unix.Sendto(t.fd, data, 0, addr) // TODO: isn't there a more reliable way to detect if UDPConn is closed? - if err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { + if err != nil { zap.L().Debug("Could NOT write an UDP packet!", zap.Error(err)) } } diff --git a/cmd/magneticow/data/static/scripts/torrents.js b/cmd/magneticow/data/static/scripts/torrents.js new file mode 100644 index 0000000..66c2d83 --- /dev/null +++ b/cmd/magneticow/data/static/scripts/torrents.js @@ -0,0 +1,4 @@ +function loadMore() { + console.log("lastX", canLoadMore, lastID, lastOrderedValue); + +} diff --git a/cmd/magneticow/data/templates/homepage.html b/cmd/magneticow/data/templates/homepage.html index 123bc95..cc49dab 100644 --- a/cmd/magneticow/data/templates/homepage.html +++ b/cmd/magneticow/data/templates/homepage.html @@ -12,12 +12,12 @@
magneticow(pre-alpha)
- +
diff --git a/cmd/magneticow/data/templates/torrent.html b/cmd/magneticow/data/templates/torrent.html index fec83cf..58acc36 100644 --- a/cmd/magneticow/data/templates/torrent.html +++ b/cmd/magneticow/data/templates/torrent.html @@ -12,7 +12,7 @@
magneticow(pre-alpha)
- +
diff --git a/cmd/magneticow/data/templates/torrents.html b/cmd/magneticow/data/templates/torrents.html index 2f2386c..96fb6fa 100644 --- a/cmd/magneticow/data/templates/torrents.html +++ b/cmd/magneticow/data/templates/torrents.html @@ -2,20 +2,25 @@ - {% if search %}"{{search}}"{% else %}Most recent torrents{% endif %} - magneticow + {{ if .Query }}"{{ .Query }}"{{ else }}Most recent torrents{{ end }} - magneticow - + +
magneticow(pre-alpha)
- +
- feed icon subscribe
@@ -24,67 +29,29 @@ - - {% if sorted_by == "name ASC" %} - Name ▲ - {% elif sorted_by == "name DESC" %} - Name ▼ - {% else %} - Name - {% endif %} - - - {% if sorted_by == "total_size ASC" %} - Size ▲ - {% elif sorted_by == "total_size DESC" %} - Size ▼ - {% else %} - Size - {% endif %} - - - {% if sorted_by == "discovered_on ASC" %} - Discovered on ▲ - {% elif sorted_by == "discovered_on DESC" %} - Discovered on ▼ - {% else %} - Discovered on - {% endif %} - + Name + Size + Discovered on - {% for torrent in torrents %} + {{ range .Torrents }} - - Magnet link + Magnet link - {{ torrent.name }} - {{ torrent.size }} - {{ torrent.discovered_on }} + {{ .Name }} + {{ humanizeSize .Size }} + {{ unixTimeToYearMonthDay .DiscoveredOn }} - {% endfor %} + {{ end }}
\ No newline at end of file diff --git a/cmd/magneticow/main.go b/cmd/magneticow/main.go index 906b113..8e90c35 100644 --- a/cmd/magneticow/main.go +++ b/cmd/magneticow/main.go @@ -2,16 +2,20 @@ package main import ( "encoding/hex" + "fmt" "html/template" "log" "net/http" "os" "strconv" - "strings" "time" - "unsafe" + + //"strconv" + "strings" + // "time" "github.com/dustin/go-humanize" + // "github.com/dustin/go-humanize" "github.com/gorilla/mux" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -26,17 +30,20 @@ var database persistence.Database // ========= TD: TemplateData ========= type HomepageTD struct { - Count uint + NTorrents uint } type TorrentsTD struct { - Search string - SubscriptionURL string - Torrents []persistence.TorrentMetadata - Before int64 - After int64 - SortedBy string - NextPageExists bool + CanLoadMore bool + Query string + SubscriptionURL string + Torrents []persistence.TorrentMetadata + SortedBy string + NextPageExists bool + Epoch int64 + LastOrderedValue uint64 + LastID uint64 + } type TorrentTD struct { @@ -100,17 +107,21 @@ func main() { "humanizeSize": func(s uint64) string { return humanize.IBytes(s) }, + + "comma": func(s uint) string { + return humanize.Comma(int64(s)) + }, } templates = make(map[string]*template.Template) - templates["feed"] = template.Must(template.New("feed").Parse(string(mustAsset("templates/feed.xml")))) - templates["homepage"] = template.Must(template.New("homepage").Parse(string(mustAsset("templates/homepage.html")))) - templates["statistics"] = template.Must(template.New("statistics").Parse(string(mustAsset("templates/statistics.html")))) - templates["torrent"] = template.Must(template.New("torrent").Funcs(templateFunctions).Parse(string(mustAsset("templates/torrent.html")))) + // templates["feed"] = template.Must(template.New("feed").Parse(string(mustAsset("templates/feed.xml")))) + templates["homepage"] = template.Must(template.New("homepage").Funcs(templateFunctions).Parse(string(mustAsset("templates/homepage.html")))) + // templates["statistics"] = template.Must(template.New("statistics").Parse(string(mustAsset("templates/statistics.html")))) + // templates["torrent"] = template.Must(template.New("torrent").Funcs(templateFunctions).Parse(string(mustAsset("templates/torrent.html")))) templates["torrents"] = template.Must(template.New("torrents").Funcs(templateFunctions).Parse(string(mustAsset("templates/torrents.html")))) var err error - database, err = persistence.MakeDatabase("sqlite3:///home/bora/.local/share/magneticod/database.sqlite3", unsafe.Pointer(logger)) + database, err = persistence.MakeDatabase("sqlite3:///home/bora/.local/share/magneticod/database.sqlite3", logger) if err != nil { panic(err.Error()) } @@ -121,135 +132,85 @@ func main() { // DONE func rootHandler(w http.ResponseWriter, r *http.Request) { - count, err := database.GetNumberOfTorrents() + nTorrents, err := database.GetNumberOfTorrents() if err != nil { panic(err.Error()) } templates["homepage"].Execute(w, HomepageTD{ - Count: count, + NTorrents: nTorrents, }) } +func respondError(w http.ResponseWriter, statusCode int, format string, a ...interface{}) { + w.WriteHeader(statusCode) + w.Write([]byte(fmt.Sprintf(format, a...))) +} + func torrentsHandler(w http.ResponseWriter, r *http.Request) { + // TODO: Parsing URL Query is tedious and looks stupid... can we do better? queryValues := r.URL.Query() - // Parses `before` and `after` parameters in the URL query following the conditions below: - // * `before` and `after` cannot be both supplied at the same time. - // * `before` -if supplied- cannot be less than or equal to zero. - // * `after` -if supplied- cannot be greater than the current Unix time. - // * if `before` is not supplied, it is set to the current Unix time. - qBefore, qAfter := (int64)(-1), (int64)(-1) - var err error - if queryValues.Get("before") != "" { - qBefore, err = strconv.ParseInt(queryValues.Get("before"), 10, 64) - if err != nil { - panic(err.Error()) - } - if qBefore <= 0 { - panic("before parameter is less than or equal to zero!") - } - } else if queryValues.Get("after") != "" { - if qBefore != -1 { - panic("both before and after supplied") - } - qAfter, err = strconv.ParseInt(queryValues.Get("after"), 10, 64) - if err != nil { - panic(err.Error()) - } - if qAfter > time.Now().Unix() { - panic("after parameter is greater than the current Unix time!") - } - } else { - qBefore = time.Now().Unix() + var query string + epoch := time.Now().Unix() // epoch, if not supplied, is NOW. + var lastOrderedValue, lastID *uint64 + + if query = queryValues.Get("query"); query == "" { + respondError(w, 400, "query is missing") + return } - var torrents []persistence.TorrentMetadata - if qBefore != -1 { - torrents, err = database.GetNewestTorrents(N_TORRENTS, qBefore) - } else { - torrents, err = database.QueryTorrents( - queryValues.Get("search"), - persistence.BY_DISCOVERED_ON, - true, - false, - N_TORRENTS, - qAfter, - true, - ) + if queryValues.Get("epoch") != "" && queryValues.Get("lastOrderedValue") != "" && queryValues.Get("lastID") != "" { + var err error + + epoch, err = strconv.ParseInt(queryValues.Get("epoch"), 10, 64) + if err != nil { + respondError(w, 400, "error while parsing epoch: %s", err.Error()) + return + } + if epoch <= 0 { + respondError(w, 400, "epoch has to be greater than zero") + return + } + + *lastOrderedValue, err = strconv.ParseUint(queryValues.Get("lastOrderedValue"), 10, 64) + if err != nil { + respondError(w, 400, "error while parsing lastOrderedValue: %s", err.Error()) + return + } + if *lastOrderedValue <= 0 { + respondError(w, 400, "lastOrderedValue has to be greater than zero") + return + } + + *lastID, err = strconv.ParseUint(queryValues.Get("lastID"), 10, 64) + if err != nil { + respondError(w, 400, "error while parsing lastID: %s", err.Error()) + return + } + if *lastID <= 0 { + respondError(w, 400, "lastID has to be greater than zero") + return + } + } else if !(queryValues.Get("epoch") == "" && queryValues.Get("lastOrderedValue") == "" && queryValues.Get("lastID") == "") { + respondError(w, 400, "`epoch`, `lastOrderedValue`, `lastID` must be supplied altogether, if supplied.") + return } + + torrents, err := database.QueryTorrents(query, epoch, persistence.ByRelevance, true, 20, nil, nil) if err != nil { - panic(err.Error()) + respondError(w, 400, "query error: %s", err.Error()) + return } - // TODO: for testing, REMOVE - torrents[2].HasReadme = true - - templates["torrents"].Execute(w, TorrentsTD{ - Search: "", - SubscriptionURL: "borabora", - Torrents: torrents, - Before: torrents[len(torrents)-1].DiscoveredOn, - After: torrents[0].DiscoveredOn, - SortedBy: "anan", - NextPageExists: true, - }) - -} - -func newestTorrentsHandler(w http.ResponseWriter, r *http.Request) { - queryValues := r.URL.Query() - - qBefore, qAfter := (int64)(-1), (int64)(-1) - var err error - if queryValues.Get("before") != "" { - qBefore, err = strconv.ParseInt(queryValues.Get("before"), 10, 64) - if err != nil { - panic(err.Error()) - } - } else if queryValues.Get("after") != "" { - if qBefore != -1 { - panic("both before and after supplied") - } - qAfter, err = strconv.ParseInt(queryValues.Get("after"), 10, 64) - if err != nil { - panic(err.Error()) - } - } else { - qBefore = time.Now().Unix() - } - - var torrents []persistence.TorrentMetadata - if qBefore != -1 { - torrents, err = database.QueryTorrents( - "", - persistence.BY_DISCOVERED_ON, - true, - false, - N_TORRENTS, - qBefore, - false, - ) - } else { - torrents, err = database.QueryTorrents( - "", - persistence.BY_DISCOVERED_ON, - false, - false, - N_TORRENTS, - qAfter, - true, - ) - } - if err != nil { - panic(err.Error()) + if torrents == nil { + panic("torrents is nil!!!") } templates["torrents"].Execute(w, TorrentsTD{ - Search: "", + CanLoadMore: true, + Query: query, SubscriptionURL: "borabora", Torrents: torrents, - Before: torrents[len(torrents)-1].DiscoveredOn, - After: torrents[0].DiscoveredOn, SortedBy: "anan", NextPageExists: true, }) diff --git a/pkg/persistence/interface.go b/pkg/persistence/interface.go index 206122a..5439084 100644 --- a/pkg/persistence/interface.go +++ b/pkg/persistence/interface.go @@ -27,8 +27,8 @@ type Database interface { orderBy orderingCriteria, ascending bool, limit uint, - lastOrderedValue *uint, - lastID *uint, + lastOrderedValue *uint64, + lastID *uint64, ) ([]TorrentMetadata, error) // GetTorrents returns the TorrentExtMetadata for the torrent of the given InfoHash. Will return // nil, nil if the torrent does not exist in the database. diff --git a/pkg/persistence/sqlite3.go b/pkg/persistence/sqlite3.go index 858b592..f002563 100644 --- a/pkg/persistence/sqlite3.go +++ b/pkg/persistence/sqlite3.go @@ -166,8 +166,8 @@ func (db *sqlite3Database) QueryTorrents( orderBy orderingCriteria, ascending bool, limit uint, - lastOrderedValue *uint, - lastID *uint, + lastOrderedValue *uint64, + lastID *uint64, ) ([]TorrentMetadata, error) { if query == "" && orderBy == ByRelevance { return nil, fmt.Errorf("torrents cannot be ordered by relevance when the query is empty") @@ -176,8 +176,8 @@ func (db *sqlite3Database) QueryTorrents( return nil, fmt.Errorf("lastOrderedValue and lastID should be supplied together, if supplied") } - doJoin := query != "" - firstPage := lastID != nil + doJoin := query != "" + firstPage := true // lastID != nil // executeTemplate is used to prepare the SQL query, WITH PLACEHOLDERS FOR USER INPUT. sqlQuery := executeTemplate(` @@ -196,11 +196,11 @@ func (db *sqlite3Database) QueryTorrents( ) AS idx USING(id) {{ end }} WHERE modified_on <= ? - {{ if not FirstPage }} + {{ if not .FirstPage }} AND id > ? - AND {{ .OrderOn }} {{ GTEorLTE(.Ascending) }} ? + AND {{ .OrderOn }} {{ GTEorLTE .Ascending }} ? {{ end }} - ORDER BY {{ .OrderOn }} {{ AscOrDesc(.Ascending) }}, id ASC + ORDER BY {{ .OrderOn }} {{ AscOrDesc .Ascending }}, id ASC LIMIT ?; `, struct { DoJoin bool @@ -208,17 +208,17 @@ func (db *sqlite3Database) QueryTorrents( OrderOn string Ascending bool }{ - DoJoin: doJoin, // if there is a query, do join - FirstPage: firstPage, // lastID != nil implies that lastOrderedValue != nil as well + DoJoin: doJoin, + FirstPage: firstPage, OrderOn: orderOn(orderBy), Ascending: ascending, }, template.FuncMap{ "GTEorLTE": func(ascending bool) string { // TODO: or maybe vice versa idk if ascending { - return "<" - } else { return ">" + } else { + return "<" } }, "AscOrDesc": func(ascending bool) string { @@ -236,7 +236,7 @@ func (db *sqlite3Database) QueryTorrents( queryArgs = append(queryArgs, query) } queryArgs = append(queryArgs, epoch) - if firstPage { + if !firstPage { queryArgs = append(queryArgs, lastID) queryArgs = append(queryArgs, lastOrderedValue) } @@ -247,8 +247,7 @@ func (db *sqlite3Database) QueryTorrents( return nil, fmt.Errorf("error while querying torrents: %s", err.Error()) } - - var torrents []TorrentMetadata + torrents := make([]TorrentMetadata, 0) for rows.Next() { var torrent TorrentMetadata if err = rows.Scan(&torrent.InfoHash, &torrent.Name, &torrent.Size, &torrent.DiscoveredOn, &torrent.NFiles); err != nil { @@ -338,12 +337,14 @@ func (db *sqlite3Database) GetFiles(infoHash []byte) ([]File, error) { } func (db *sqlite3Database) GetStatistics(n uint, to string) (*Statistics, error) { + /* to_time, granularity, err := ParseISO8601(to) if err != nil { return nil, fmt.Errorf("parsing @to error: %s", err.Error()) } // TODO + */ return nil, nil } @@ -497,9 +498,9 @@ func (db *sqlite3Database) setupDatabase() error { // // * Added `n_files` column to the `torrents` table. zap.L().Warn("Updating database schema from 2 to 3... (this might take a while)") - tx.Exec(` - CREATE VIRTUAL TABLE torrents_idx USING fts5(name, content='torrents', content_rowid='id', tokenize="porter unicode61 separators ' !""#$%&''()*+,-./:;<=>?@[\]^_` + "`" + `{|}~'"); - + _, err = tx.Exec(` + CREATE VIRTUAL TABLE IF NOT EXISTS torrents_idx USING fts5(name, content='torrents', content_rowid='id', tokenize="porter unicode61 separators ' !""#$%&''()*+,-./:;<=>?@[\]^_` + "`" + `{|}~'"); + -- Populate the index INSERT INTO torrents_idx(rowid, name) SELECT id, name FROM torrents; @@ -517,8 +518,8 @@ func (db *sqlite3Database) setupDatabase() error { -- Add column modified_on ALTER TABLE torrents ADD COLUMN modified_on INTEGER; + UPDATE torrents SET modified_on = (SELECT discovered_on); CREATE INDEX modified_on_index ON torrents (modified_on); - UPDATE torrents SET torrents.modified_on = (SELECT discovered_on); PRAGMA user_version = 3; `)