magneticow: display readme!
This commit is contained in:
parent
9069506acc
commit
1ca410774d
@ -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.
|
||||
|
@ -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`.
|
Binary file not shown.
@ -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.
|
||||
*
|
||||
|
@ -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 ? " <tt>" + fileSize(e.size) + "</tt>" : ""),
|
||||
label: pathElems[i] + (i === pathElems.length - 1 ? " <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;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 }}&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>
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user