magnetico/cmd/magneticow/main.go
Bora Alper 0501fc3e3c magneticow: search now works perfectly!
- support for ordering is yet to be implemented
2018-06-19 18:49:46 +03:00

232 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/hex"
"fmt"
"html/template"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/boramalper/magnetico/pkg/persistence"
)
const N_TORRENTS = 20
// Set a Decoder instance as a package global, because it caches
// meta-data about structs, and an instance can be shared safely.
var decoder = schema.NewDecoder()
var templates map[string]*template.Template
var database persistence.Database
// ======= Q: Query =======
type TorrentsQ struct {
Epoch *int64 `schema:"epoch"`
Query *string `schema:"query"`
OrderBy *string `schema:"orderBy"`
Ascending *bool `schema:"ascending"`
LastOrderedValue *float64 `schema:"lastOrderedValue"`
LastID *uint64 `schema:"lastID"`
}
// ========= TD: TemplateData =========
type HomepageTD struct {
NTorrents uint
}
type TorrentsTD struct {
CanLoadMore bool
Query string
SubscriptionURL string
Torrents []persistence.TorrentMetadata
SortedBy string
NextPageExists bool
Epoch int64
LastOrderedValue float64
LastID uint64
}
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("/api/v0.1/torrents", apiTorrentsHandler)
router.HandleFunc("/api/v0.1/torrents/{infohash:[a-z0-9]{40}}", apiTorrentsInfohashHandler)
router.HandleFunc("/api/v0.1/files/{infohash:[a-z0-9]{40}}", apiFilesInfohashHandler)
router.HandleFunc("/api/v0.1/statistics", apiStatisticsHandler)
router.HandleFunc("/torrents", torrentsHandler)
router.HandleFunc("/torrents/{infohash:[a-z0-9]{40}}", torrentsInfohashHandler)
router.HandleFunc("/statistics", statisticsHandler)
router.HandleFunc("/feed", feedHandler)
router.PathPrefix("/static").HandlerFunc(staticHandler)
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 youll 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)
},
"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").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", logger)
if err != nil {
panic(err.Error())
}
decoder.IgnoreUnknownKeys(false)
decoder.ZeroEmpty(true)
zap.L().Info("magneticow is ready to serve!")
err = http.ListenAndServe(":10101", router)
if err != nil {
zap.L().Error("ListenAndServe error", zap.Error(err))
}
}
// DONE
func rootHandler(w http.ResponseWriter, r *http.Request) {
nTorrents, err := database.GetNumberOfTorrents()
if err != nil {
panic(err.Error())
}
err = templates["homepage"].Execute(w, HomepageTD{
NTorrents: nTorrents,
})
if err != nil {
panic(err.Error())
}
}
// TODO: I think there is a standard lib. function for this
func respondError(w http.ResponseWriter, statusCode int, format string, a ...interface{}) {
w.WriteHeader(statusCode)
w.Write([]byte(fmt.Sprintf(format, a...)))
}
// TODO: we might as well move torrents.html into static...
func torrentsHandler(w http.ResponseWriter, r *http.Request) {
data := mustAsset("templates/torrents.html")
w.Header().Set("Content-Type", http.DetectContentType(data))
w.Write(data)
}
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
}