2017-11-03 00:15:13 +01:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2018-04-16 17:40:54 +02:00
|
|
|
|
"encoding/hex"
|
2017-11-03 00:15:13 +01:00
|
|
|
|
"html/template"
|
|
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
2018-04-16 17:40:54 +02:00
|
|
|
|
"strconv"
|
2017-11-03 00:15:13 +01:00
|
|
|
|
"strings"
|
2018-04-16 17:40:54 +02:00
|
|
|
|
"time"
|
|
|
|
|
"unsafe"
|
2017-11-03 00:15:13 +01:00
|
|
|
|
|
|
|
|
|
"github.com/dustin/go-humanize"
|
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
|
|
2018-04-16 17:40:54 +02:00
|
|
|
|
"github.com/boramalper/magnetico/pkg/persistence"
|
2017-11-03 00:15:13 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const N_TORRENTS = 20
|
|
|
|
|
|
|
|
|
|
var templates map[string]*template.Template
|
|
|
|
|
var database persistence.Database
|
|
|
|
|
|
|
|
|
|
// ========= TD: TemplateData =========
|
|
|
|
|
type HomepageTD struct {
|
|
|
|
|
Count uint
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TorrentsTD struct {
|
|
|
|
|
Search string
|
|
|
|
|
SubscriptionURL string
|
|
|
|
|
Torrents []persistence.TorrentMetadata
|
|
|
|
|
Before int64
|
|
|
|
|
After int64
|
|
|
|
|
SortedBy string
|
|
|
|
|
NextPageExists bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TorrentTD struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FeedTD struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type StatisticsTD struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
loggerLevel := zap.NewAtomicLevel()
|
|
|
|
|
// Logging levels: ("debug", "info", "warn", "error", "dpanic", "panic", and "fatal").
|
|
|
|
|
logger := zap.New(zapcore.NewCore(
|
|
|
|
|
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
|
|
|
|
|
zapcore.Lock(os.Stderr),
|
|
|
|
|
loggerLevel,
|
|
|
|
|
))
|
|
|
|
|
defer logger.Sync()
|
|
|
|
|
zap.ReplaceGlobals(logger)
|
|
|
|
|
|
|
|
|
|
zap.L().Info("magneticow v0.7.0 has been started.")
|
|
|
|
|
zap.L().Info("Copyright (C) 2017 Mert Bora ALPER <bora@boramalper.org>.")
|
|
|
|
|
zap.L().Info("Dedicated to Cemile Binay, in whose hands I thrived.")
|
|
|
|
|
|
|
|
|
|
router := mux.NewRouter()
|
|
|
|
|
router.HandleFunc("/", rootHandler)
|
|
|
|
|
router.HandleFunc("/torrents", torrentsHandler)
|
|
|
|
|
router.HandleFunc("/torrents/{infohash:[a-z0-9]{40}}", torrentsInfohashHandler)
|
|
|
|
|
router.HandleFunc("/statistics", statisticsHandler)
|
|
|
|
|
router.PathPrefix("/static").HandlerFunc(staticHandler)
|
|
|
|
|
|
|
|
|
|
router.HandleFunc("/feed", feedHandler)
|
|
|
|
|
|
|
|
|
|
templateFunctions := template.FuncMap{
|
|
|
|
|
"add": func(augend int, addends int) int {
|
|
|
|
|
return augend + addends
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"subtract": func(minuend int, subtrahend int) int {
|
|
|
|
|
return minuend - subtrahend
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"bytesToHex": func(bytes []byte) string {
|
|
|
|
|
return hex.EncodeToString(bytes)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"unixTimeToYearMonthDay": func(s int64) string {
|
|
|
|
|
tm := time.Unix(s, 0)
|
|
|
|
|
// > Format and Parse use example-based layouts. Usually you’ll use a constant from time
|
|
|
|
|
// > for these layouts, but you can also supply custom layouts. Layouts must use the
|
|
|
|
|
// > reference time Mon Jan 2 15:04:05 MST 2006 to show the pattern with which to
|
|
|
|
|
// > format/parse a given time/string. The example time must be exactly as shown: the
|
|
|
|
|
// > year 2006, 15 for the hour, Monday for the day of the week, etc.
|
|
|
|
|
// https://gobyexample.com/time-formatting-parsing
|
|
|
|
|
// Why you gotta be so weird Go?
|
|
|
|
|
return tm.Format("02/01/2006")
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"humanizeSize": func(s uint64) string {
|
|
|
|
|
return humanize.IBytes(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["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))
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zap.L().Info("magneticow is ready to serve!")
|
|
|
|
|
http.ListenAndServe(":8080", router)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DONE
|
|
|
|
|
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
count, err := database.GetNumberOfTorrents()
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err.Error())
|
|
|
|
|
}
|
|
|
|
|
templates["homepage"].Execute(w, HomepageTD{
|
|
|
|
|
Count: count,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func torrentsHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
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 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 err != nil {
|
|
|
|
|
panic(err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 torrentsInfohashHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
// show torrents/{infohash}
|
|
|
|
|
infoHash, err := hex.DecodeString(mux.Vars(r)["infohash"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
torrent, err := database.GetTorrent(infoHash)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
templates["torrent"].Execute(w, torrent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func statisticsHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func feedHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func staticHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
data, err := Asset(r.URL.Path[1:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.NotFound(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var contentType string
|
|
|
|
|
if strings.HasSuffix(r.URL.Path, ".css") {
|
|
|
|
|
contentType = "text/css; charset=utf-8"
|
|
|
|
|
} else { // fallback option
|
|
|
|
|
contentType = http.DetectContentType(data)
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Content-Type", contentType)
|
|
|
|
|
w.Write(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mustAsset(name string) []byte {
|
|
|
|
|
data, err := Asset(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Panicf("Could NOT access the requested resource `%s`: %s (please inform us, this is a BUG!)", name, err.Error())
|
|
|
|
|
}
|
|
|
|
|
return data
|
|
|
|
|
}
|