magneticow: display readme!

This commit is contained in:
Bora M. Alper 2020-02-22 15:42:14 +00:00
parent 9069506acc
commit 1ca410774d
No known key found for this signature in database
GPG Key ID: 8F1A9504E1BD114D
9 changed files with 219 additions and 11 deletions

View File

@ -1,18 +1,163 @@
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/boramalper/magnetico/pkg/persistence"
"golang.org/x/text/encoding/charmap"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/storage"
"github.com/gorilla/mux"
"go.uber.org/zap"
"github.com/boramalper/magnetico/pkg/persistence"
)
type ApiReadmeHandler struct {
client *torrent.Client
tempdir string
}
func NewApiReadmeHandler() (*ApiReadmeHandler, error) {
h := new(ApiReadmeHandler)
var err error
h.tempdir, err = ioutil.TempDir("", "magneticod_")
if err != nil {
return nil, err
}
config := torrent.NewDefaultClientConfig()
config.ListenPort = 0
config.DefaultStorage = storage.NewFileByInfoHash(h.tempdir)
h.client, err = torrent.NewClient(config)
if err != nil {
_ = os.RemoveAll(h.tempdir)
return nil, err
}
return h, nil
}
func (h *ApiReadmeHandler) Close() {
h.client.Close()
_ = os.RemoveAll(h.tempdir)
}
func (h *ApiReadmeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
infohashHex := mux.Vars(r)["infohash"]
infohash, err := hex.DecodeString(infohashHex)
if err != nil {
respondError(w, http.StatusBadRequest, "couldn't decode infohash: %s", err.Error())
return
}
files, err := database.GetFiles(infohash)
if err != nil {
zap.L().Error("GetFiles error", zap.Error(err))
respondError(w, http.StatusInternalServerError, "Internal Server Error")
}
ok := false
for _, file := range files {
if strings.HasSuffix(file.Path, ".nfo") {
ok = true
break
} else if strings.Contains(file.Path, "read") {
ok = true
break
}
}
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
zap.L().Warn("README")
t, err := h.client.AddMagnet("magnet:?xt=urn:btih:" + infohashHex)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
defer t.Drop()
zap.L().Warn("WAITING FOR INFO")
select {
case <- t.GotInfo():
case <- time.After(30 * time.Second):
respondError(w, http.StatusInternalServerError, "Timeout")
return
}
zap.L().Warn("GOT INFO!")
t.CancelPieces(0, t.NumPieces())
var file *torrent.File
for _, file = range t.Files() {
filePath := file.Path()
if strings.HasSuffix(filePath, ".nfo") {
break
} else if strings.Contains(filePath, "read") {
break
}
}
if file == nil {
w.WriteHeader(http.StatusNotFound)
return
}
// Cancel if the file is larger than 50 KiB
if file.Length() > 50 * 1024 {
w.WriteHeader(http.StatusRequestEntityTooLarge)
return
}
file.Download()
reader := file.NewReader()
// BEWARE:
// ioutil.ReadAll(reader)
// returns some adjancent garbage too, for reasons unknown...
content := make([]byte, file.Length())
_, err = io.ReadFull(reader, content)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer reader.Close()
if strings.HasSuffix(file.Path(), ".nfo") {
content, err = charmap.CodePage437.NewDecoder().Bytes(content)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
// Because .nfo files are right padded with \x00'es.
content = bytes.TrimRight(content, "\x00")
w.Header().Set("Content-Type", "text/plain;charset=UTF-8")
_, _ = w.Write(content)
}
func apiTorrents(w http.ResponseWriter, r *http.Request) {
// @lastOrderedValue AND @lastID are either both supplied or neither of them should be supplied
// at all; and if that is NOT the case, then return an error.

View File

@ -0,0 +1,7 @@
# PxPlus_IBM_VGA8
**Source:** https://int10h.org/oldschool-pc-fonts/ released under the
[CC BY-SA 4.0 license](http://creativecommons.org/licenses/by-sa/4.0/).
.woff is generated by [Font Squirrel](https://www.fontsquirrel.com/tools/webfont-generator) from
`PxPlus_IBM_VGA8.ttf`.

View File

@ -32,6 +32,22 @@ function humaniseDate(unixTime) {
});
}
// a fetch() that errs on anything but HTTP 2XX
// Source: https://github.com/github/fetch/issues/155#issuecomment-108288863
function myFetch(url, options) {
if (options == null) options = {}
if (options.credentials == null) options.credentials = 'same-origin'
return fetch(url, options).then(function(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
var error = new Error(response.statusText || response.status)
error.response = response
return Promise.reject(error)
}
})
}
/**
* Returns the ISO 8601 week number for this date.
*

View File

@ -8,7 +8,7 @@
"use strict";
window.onload = function() {
window.onload = function () {
let infoHash = window.location.pathname.split("/")[2];
fetch("/api/v0.1/torrents/" + infoHash).then(x => x.json()).then(x => {
@ -16,7 +16,7 @@ window.onload = function() {
const template = document.getElementById("main-template").innerHTML;
document.querySelector("main").innerHTML = Mustache.render(template, {
name: x.name,
name: x.name,
infoHash: x.infoHash,
sizeHumanised: fileSize(x.size),
discoveredOnHumanised: humaniseDate(x.discoveredOn),
@ -35,11 +35,24 @@ window.onload = function() {
tree.add({
id: pathElems.slice(0, i + 1).join("/"),
parent: i >= 1 ? pathElems.slice(0, i).join("/") : undefined,
label: pathElems[i] + ( i === pathElems.length - 1 ? "&emsp;<tt>" + fileSize(e.size) + "</tt>" : ""),
label: pathElems[i] + (i === pathElems.length - 1 ? "&emsp;<tt>" + fileSize(e.size) + "</tt>" : ""),
opened: true,
});
}
}
});
myFetch("/api/v0.1/torrents/" + infoHash + "/readme")
.then(response => {
return response.text();
})
.then(x => {
const readme = document.getElementById("readme");
readme.innerText = x;
})
.catch(err => {
const readme = document.getElementById("readme");
readme.innerText = err;
});
});
};

View File

@ -77,4 +77,19 @@ th {
tt {
font-style: italic;
font-size: smaller;
}
}
@font-face {
font-family: 'PxPlus-IBM-VGA8';
src: URL('/static/fonts/PxPlus_IBM_VGA8/pxplus_ibm_vga8-webfont.woff') format('woff');
}
#readme {
padding: 1em;
display: inline-block;
background-color: black;
color: white;
font-family: 'PxPlus-IBM-VGA8', monospace;
line-height: 1em;
letter-spacing: -0.5px;
}

View File

@ -13,8 +13,6 @@
<script src="/static/scripts/naturalSort-v0.8.1.js"></script>
<script src="/static/scripts/mustache-v2.3.0.min.js"></script>
<script src="/static/scripts/vanillatree-v0.0.3.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/torrent.js"></script>
<!-- Goes into <main> -->
<script id="main-template" type="text/x-handlebars-template">
@ -22,7 +20,7 @@
<h2>{{ name }}</h2>
<a href="magnet:?xt=urn:btih:{{ infoHash }}&amp;dn={{ name }}">
<img src="/static/assets/magnet.gif" alt="Magnet link"
title="Download this torrent using magnet" />
title="Download this torrent using magnet"/>
<small>{{ infoHash }}</small>
</a>
</div>
@ -42,8 +40,11 @@
</tr>
</table>
<h3>Better Files</h3>
<h3>Files</h3>
<div id="fileTree"></div>
<h3>Readme</h3>
<pre id="readme">Loading...</pre>
</script>
</head>
<body>
@ -56,5 +57,8 @@
<main>
</main>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/torrent.js"></script>
</body>
</html>

View File

@ -102,6 +102,12 @@ func main() {
}
}()
apiReadmeHandler, err := NewApiReadmeHandler()
if err != nil {
zap.L().Fatal("Could not initialise readme handler", zap.Error(err))
}
defer apiReadmeHandler.Close()
router := mux.NewRouter()
router.HandleFunc("/",
BasicAuth(rootHandler, "magneticow"))
@ -114,6 +120,8 @@ func main() {
BasicAuth(apiTorrent, "magneticow"))
router.HandleFunc("/api/v0.1/torrents/{infohash:[a-f0-9]{40}}/filelist",
BasicAuth(apiFilelist, "magneticow"))
router.Handle("/api/v0.1/torrents/{infohash:[a-f0-9]{40}}/readme",
apiReadmeHandler)
router.HandleFunc("/feed",
BasicAuth(feedHandler, "magneticow"))
@ -171,7 +179,6 @@ func main() {
templates["feed"] = template.Must(template.New("feed").Funcs(templateFunctions).Parse(string(mustAsset("templates/feed.xml"))))
templates["homepage"] = template.Must(template.New("homepage").Funcs(templateFunctions).Parse(string(mustAsset("templates/homepage.html"))))
var err error
database, err = persistence.MakeDatabase(opts.Database, logger)
if err != nil {
zap.L().Fatal("could not access to database", zap.Error(err))
@ -190,7 +197,7 @@ func main() {
// 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...)))
_, _ = w.Write([]byte(fmt.Sprintf(format, a...)))
}
func mustAsset(name string) []byte {

1
go.mod
View File

@ -22,6 +22,7 @@ require (
go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
golang.org/x/sys v0.0.0-20190516110030-61b9204099cb
golang.org/x/text v0.3.0
)
go 1.13