[magneticow] fully client-side rendering + better file tree + API fixes

This commit is contained in:
Bora M. Alper 2019-05-21 13:31:51 +01:00
parent 75abc0ee02
commit 2cc4c4930a
No known key found for this signature in database
GPG Key ID: 8F1A9504E1BD114D
12 changed files with 444 additions and 180 deletions

View File

@ -13,7 +13,7 @@ import (
"go.uber.org/zap"
)
func apiTorrentsHandler(w http.ResponseWriter, r *http.Request) {
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.
if q := r.URL.Query(); !((q.Get("lastOrderedValue") != "" && q.Get("lastID") != "") ||
@ -29,6 +29,7 @@ func apiTorrentsHandler(w http.ResponseWriter, r *http.Request) {
Ascending *bool `schema:"ascending"`
LastOrderedValue *float64 `schema:"lastOrderedValue"`
LastID *uint64 `schema:"lastID"`
Limit *uint `schema:"limit"`
}
if err := decoder.Decode(&tq, r.URL.Query()); err != nil {
respondError(w, 400, "error while parsing the URL: %s", err.Error())
@ -69,27 +70,26 @@ func apiTorrentsHandler(w http.ResponseWriter, r *http.Request) {
}
}
if tq.Limit == nil {
tq.Limit = new(uint)
*tq.Limit = 20
}
torrents, err := database.QueryTorrents(
*tq.Query, *tq.Epoch, orderBy,
*tq.Ascending, N_TORRENTS, tq.LastOrderedValue, tq.LastID)
*tq.Ascending, *tq.Limit, tq.LastOrderedValue, tq.LastID)
if err != nil {
respondError(w, 400, "query error: %s", err.Error())
return
}
// TODO: use plain Marshal
jm, err := json.MarshalIndent(torrents, "", " ")
if err != nil {
respondError(w, 500, "json marshalling error: %s", err.Error())
return
}
if _, err = w.Write(jm); err != nil {
zap.L().Warn("couldn't write http.ResponseWriter", zap.Error(err))
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err = json.NewEncoder(w).Encode(torrents); err != nil {
zap.L().Warn("JSON encode error", zap.Error(err))
}
}
func apiTorrentsInfohashHandler(w http.ResponseWriter, r *http.Request) {
func apiTorrent(w http.ResponseWriter, r *http.Request) {
infohashHex := mux.Vars(r)["infohash"]
infohash, err := hex.DecodeString(infohashHex)
@ -107,19 +107,13 @@ func apiTorrentsInfohashHandler(w http.ResponseWriter, r *http.Request) {
return
}
// TODO: use plain Marshal
jm, err := json.MarshalIndent(torrent, "", " ")
if err != nil {
respondError(w, 500, "json marshalling error: %s", err.Error())
return
}
if _, err = w.Write(jm); err != nil {
zap.L().Warn("couldn't write http.ResponseWriter", zap.Error(err))
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err = json.NewEncoder(w).Encode(torrent); err != nil {
zap.L().Warn("JSON encode error", zap.Error(err))
}
}
func apiFilesInfohashHandler(w http.ResponseWriter, r *http.Request) {
func apiFilelist(w http.ResponseWriter, r *http.Request) {
infohashHex := mux.Vars(r)["infohash"]
infohash, err := hex.DecodeString(infohashHex)
@ -137,19 +131,13 @@ func apiFilesInfohashHandler(w http.ResponseWriter, r *http.Request) {
return
}
// TODO: use plain Marshal
jm, err := json.MarshalIndent(files, "", " ")
if err != nil {
respondError(w, 500, "json marshalling error: %s", err.Error())
return
}
if _, err = w.Write(jm); err != nil {
zap.L().Warn("couldn't write http.ResponseWriter", zap.Error(err))
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err = json.NewEncoder(w).Encode(files); err != nil {
zap.L().Warn("JSON encode error", zap.Error(err))
}
}
func apiStatisticsHandler(w http.ResponseWriter, r *http.Request) {
func apiStatistics(w http.ResponseWriter, r *http.Request) {
from := r.URL.Query().Get("from")
// TODO: use gorilla?
@ -175,15 +163,9 @@ func apiStatisticsHandler(w http.ResponseWriter, r *http.Request) {
return
}
// TODO: use plain Marshal
jm, err := json.MarshalIndent(stats, "", " ")
if err != nil {
respondError(w, 500, "json marshalling error: %s", err.Error())
return
}
if _, err = w.Write(jm); err != nil {
zap.L().Warn("couldn't write http.ResponseWriter", zap.Error(err))
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err = json.NewEncoder(w).Encode(stats); err != nil {
zap.L().Warn("JSON encode error", zap.Error(err))
}
}

View File

@ -24,6 +24,14 @@ function fileSize(fileSizeInBytes) {
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
}
function humaniseDate(unixTime) {
return (new Date(unixTime * 1000)).toLocaleDateString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
}
/**
* Returns the ISO 8601 week number for this date.
*

View File

@ -9,52 +9,37 @@
window.onload = function() {
var pre_element = document.getElementsByTagName("pre")[0];
var paths = pre_element.textContent.replace(/\s+$/, "").split("\n");
paths.sort(naturalSort);
paths = paths.map(function(path) { return path.split('/'); });
pre_element.textContent = stringify(structurise(paths)).join("\n");
let infoHash = window.location.pathname.split("/")[2];
fetch("/api/v0.1/torrents/" + infoHash).then(x => x.json()).then(x => {
document.querySelector("title").innerText = x.name + " - magneticow";
const template = document.getElementById("main-template").innerHTML;
document.querySelector("main").innerHTML = Mustache.render(template, {
name: x.name,
infoHash: x.infoHash,
sizeHumanised: fileSize(x.size),
discoveredOnHumanised: humaniseDate(x.discoveredOn),
nFiles: x.nFiles,
});
fetch("/api/v0.1/torrents/" + infoHash + "/filelist").then(x => x.json()).then(x => {
const tree = new VanillaTree('#fileTree', {
placeholder: 'Loading...',
});
for (let e of x) {
let pathElems = e.path.split("/");
for (let i = 0; i < pathElems.length; i++) {
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>" : ""),
opened: true,
});
}
}
});
});
};
function structurise(paths) {
var items = [];
for(var i = 0, l = paths.length; i < l; i++) {
var path = paths[i];
var name = path[0];
var rest = path.slice(1);
var item = null;
for(var j = 0, m = items.length; j < m; j++) {
if(items[j].name === name) {
item = items[j];
break;
}
}
if(item === null) {
item = {name: name, children: []};
items.push(item);
}
if(rest.length > 0) {
item.children.push(rest);
}
}
for(i = 0, l = items.length; i < l; i++) {
item = items[i];
item.children = structurise(item.children);
}
return items;
}
function stringify(items) {
var lines = [];
for(var i = 0, l = items.length; i < l; i++) {
var item = items[i];
lines.push(item.name);
var subLines = stringify(item.children);
for(var j = 0, m = subLines.length; j < m; j++) {
lines.push(" " + subLines[j]);
}
}
return lines;
}

View File

@ -104,11 +104,7 @@ function load() {
for (let t of torrents) {
t.size = fileSize(t.size);
t.discoveredOn = (new Date(t.discoveredOn * 1000)).toLocaleDateString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
t.discoveredOn = humaniseDate(t.discoveredOn);
ul.innerHTML += Mustache.render(template, t);
}

View File

@ -0,0 +1,214 @@
(function (root, factory) {
if (typeof define == 'function' && define.amd) {
define( factory );
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.VanillaTree = factory();
}
}(this, function () {
"use strict";
// Look at the Balalaika https://github.com/finom/balalaika
var $=function(n,e,k,h,p,m,l,b,d,g,f,c){c=function(a,b){return new c.i(a,b)};c.i=function(a,d){k.push.apply(this,a?a.nodeType||a==n?[a]:""+a===a?/</.test(a)?((b=e.createElement(d||"q")).innerHTML=a,b.children):(d&&c(d)[0]||e).querySelectorAll(a):/f/.test(typeof a)?/c/.test(e.readyState)?a():c(e).on("DOMContentLoaded",a):a:k)};c.i[f="prototype"]=(c.extend=function(a){g=arguments;for(b=1;b<g.length;b++)if(f=g[b])for(d in f)a[d]=f[d];return a})(c.fn=c[f]=k,{on:function(a,d){a=a.split(h);this.map(function(c){(h[b=a[0]+(c.b$=c.b$||++p)]=h[b]||[]).push([d,a[1]]);c["add"+m](a[0],d)});return this},off:function(a,c){a=a.split(h);f="remove"+m;this.map(function(e){if(b=(g=h[a[0]+e.b$])&&g.length)for(;d=g[--b];)c&&c!=d[0]||a[1]&&a[1]!=d[1]||(e[f](a[0],d[0]),g.splice(b,1));else!a[1]&&e[f](a[0],c)});return this},is:function(a){d=(b=this[0])&&(b.matches||b["webkit"+l]||b["moz"+l]||b["ms"+l]);return!!d&&d.call(b,a)}});return c}(window,document,[],/\.(.+)/,0,"EventListener","MatchesSelector");
var create = function( tagName, props ) {
return $.extend( document.createElement( tagName ), props );
},
Tree = function( s, options ) {
var _this = this,
container = _this.container = $( s )[ 0 ],
tree = _this.tree = container.appendChild( create( 'ul', {
className: 'vtree'
}) );
_this.placeholder = options && options.placeholder;
_this._placeholder();
_this.leafs = {};
tree.addEventListener( 'click', function( evt ) {
if( $( evt.target ).is( '.vtree-leaf-label' ) ) {
_this.select( evt.target.parentNode.getAttribute('data-vtree-id') );
} else if( $( evt.target ).is( '.vtree-toggle' ) ) {
_this.toggle( evt.target.parentNode.getAttribute('data-vtree-id') );
}
});
if( options && options.contextmenu ) {
tree.addEventListener( 'contextmenu', function( evt ) {
var menu;
$( '.vtree-contextmenu' ).forEach( function( menu ) {
menu.parentNode.removeChild( menu );
});
if( $( evt.target ).is( '.vtree-leaf-label' ) ) {
evt.preventDefault();
evt.stopPropagation();
menu = create( 'menu', {
className: 'vtree-contextmenu'
});
var rect = evt.target.getBoundingClientRect();
$.extend(menu.style, {
top: (evt.target.offsetTop + rect.height).toString() + "px",
left: evt.target.offsetLeft.toString() + "px",
display: 'block'
});
options.contextmenu.forEach( function( item ) {
menu.appendChild( create( 'li', {
className: 'vtree-contextmenu-item',
innerHTML: item.label
}) ).addEventListener( 'click', item.action.bind( item, evt.target.parentNode.getAttribute('data-vtree-id') ) );
});
evt.target.parentNode.appendChild( menu );
}
});
document.addEventListener( 'click', function( evt ) {
if(evt.button === 2) return;
$( '.vtree-contextmenu' ).forEach( function( menu ) {
menu.parentNode.removeChild( menu );
});
});
}
};
Tree.prototype = {
constructor: Tree,
_dispatch: function( name, id ) {
var event;
try {
event = new CustomEvent( 'vtree-' + name, {
bubbles: true,
cancelable: true,
detail: {
id: id
}
});
} catch(e) {
event = document.createEvent( 'CustomEvent' );
event.initCustomEvent( 'vtree-' + name, true, true, { id: id });
}
( this.getLeaf( id, true ) || this.tree )
.dispatchEvent( event );
return this;
},
_placeholder: function() {
var p;
if( !this.tree.children.length && this.placeholder ) {
this.tree.innerHTML = '<li class="vtree-placeholder">' + this.placeholder + '</li>'
} else if( p = this.tree.querySelector( '.vtree-placeholder' ) ) {
this.tree.removeChild( p );
}
return this;
},
getLeaf: function( id, notThrow ) {
var leaf = $( '[data-vtree-id="' + id + '"]', this.tree )[ 0 ];
if( !notThrow && !leaf ) throw Error( 'No VanillaTree leaf with id "' + id + '"' )
return leaf;
},
getChildList: function( id ) {
var list,
parent;
if( id ) {
parent = this.getLeaf( id );
if( !( list = $( 'ul', parent )[ 0 ] ) ) {
list = parent.appendChild( create( 'ul', {
className: 'vtree-subtree'
}) );
}
} else {
list = this.tree;
}
return list;
},
add: function( options ) {
// see https://github.com/finom/vanillatree/issues/8
if(this.getLeaf( options.id,true )){return;}// don't add leaves that already exist
var id,
leaf = create( 'li', {
className: 'vtree-leaf'
}),
parentList = this.getChildList( options.parent );
leaf.setAttribute( 'data-vtree-id', id = options.id || Math.random() );
leaf.appendChild( create( 'span', {
className: 'vtree-toggle'
}) );
leaf.appendChild( create( 'a', {
className: 'vtree-leaf-label',
innerHTML: options.label
}) );
parentList.appendChild( leaf );
if( parentList !== this.tree ) {
parentList.parentNode.classList.add( 'vtree-has-children' );
}
this.leafs[ id ] = options;
if( !options.opened ) {
this.close( id );
}
if( options.selected ) {
this.select( id );
}
return this._placeholder()._dispatch( 'add', id );
},
move: function( id, parentId ) {
var leaf = this.getLeaf( id ),
oldParent = leaf.parentNode,
newParent = this.getLeaf( parentId, true );
if( newParent ) {
newParent.classList.add( 'vtree-has-children' );
}
this.getChildList( parentId ).appendChild( leaf );
oldParent.parentNode.classList.toggle( 'vtree-has-children', !!oldParent.children.length );
return this._dispatch( 'move', id );
},
remove: function( id ) {
var leaf = this.getLeaf( id ),
oldParent = leaf.parentNode;
oldParent.removeChild( leaf );
oldParent.parentNode.classList.toggle( 'vtree-has-children', !!oldParent.children.length );
return this._placeholder()._dispatch( 'remove', id );
},
open: function( id ) {
this.getLeaf( id ).classList.remove( 'closed' );
return this._dispatch( 'open', id );
},
close: function( id ) {
this.getLeaf( id ).classList.add( 'closed' );
return this._dispatch( 'close', id );
},
toggle: function( id ) {
return this[ this.getLeaf( id ).classList.contains( 'closed' ) ? 'open' : 'close' ]( id );
},
select: function( id ) {
var leaf = this.getLeaf( id );
if( !leaf.classList.contains( 'vtree-selected' ) ) {
$( 'li.vtree-leaf', this.tree ).forEach( function( leaf ) {
leaf.classList.remove( 'vtree-selected' );
});
leaf.classList.add( 'vtree-selected' );
this._dispatch( 'select', id );
}
return this;
}
};
return Tree;
// Look at the Balalaika https://github.com/finom/balalaika
}));

View File

@ -37,7 +37,8 @@ header > div {
}
#title h2 {
margin-bottom: 0px;
margin-bottom: 0;
word-break: break-all;
}
#title {
@ -49,12 +50,6 @@ header > div {
color: inherit;
}
pre {
background-color: black;
color: white;
overflow: auto;
}
table {
max-width: 700px;
width: 700px;
@ -78,3 +73,8 @@ th {
width: 1%;
}
tt {
font-style: italic;
font-size: smaller;
}

View File

@ -79,6 +79,7 @@ ul li div {
ul li div h3 {
margin: 0;
word-break: break-all;
}
a {

View File

@ -0,0 +1,96 @@
.vtree ul.vtree-subtree, .vtree li.vtree-leaf {
margin: 0;
padding: 0;
list-style-type: none;
position: relative;
}
.vtree li.vtree-leaf {
background-position: -90px 0;
background-repeat: repeat-y;
min-height: 18px;
line-height: 18px;
}
.vtree li.vtree-leaf::before {
content: '';
width: 18px;
height: 18px;
position: absolute;
background-position: -36px 0;
}
.vtree li.vtree-leaf li.vtree-leaf {
margin-left: 18px;
}
.vtree li.vtree-leaf:last-child {
background-image: none;
}
.vtree li.vtree-leaf.closed ul.vtree-subtree {
display: none;
}
.vtree li.vtree-leaf.vtree-has-children > span.vtree-toggle {
display: block;
width: 18px;
height: 18px;
background-position: -72px 0;
position: absolute;
left: 0;
top: 0;
}
.vtree li.vtree-leaf.vtree-has-children.closed > span.vtree-toggle {
background-position: -54px 0;
}
.vtree a.vtree-leaf-label {
line-height: 18px;
display: inline-block;
vertical-align: top;
cursor: pointer;
max-width: 100%;
margin-left: 18px;
padding: 0 2px;
}
.vtree li.vtree-leaf a.vtree-leaf-label:hover {
background-color: #e7f4f9;
outline: 1px solid #d8f0fa;
}
.vtree li.vtree-leaf.vtree-selected > a.vtree-leaf-label {
background-color: #beebff;
outline: 1px solid #99defd;
}
.vtree-contextmenu {
position: absolute;
z-index: 9999999;
border: solid 1px #ccc;
background: #eee;
padding: 0px;
margin: 0px;
display: none;
}
.vtree-contextmenu li {
list-style: none;
padding: 1px 5px;
margin: 0px;
color: #333;
line-height: 20px;
height: 20px;
cursor: default;
}
.vtree-contextmenu li:hover {
color: #fff;
background-color: #3399ff;
}
.vtree li.vtree-leaf, .vtree li.vtree-leaf::before, .vtree li.vtree-leaf.vtree-has-children > span.vtree-toggle {
background-image: url(data:image/png;base64,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);
}

View File

@ -3,12 +3,48 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ .T.Name }} - magnetico</title>
<title>Loading... - magneticow</title>
<link rel="stylesheet" href="/static/styles/reset.css">
<link rel="stylesheet" href="/static/styles/vanillatree-v0.0.3.css">
<link rel="stylesheet" href="/static/styles/essential.css">
<link rel="stylesheet" href="/static/styles/torrent.css">
<script defer src="/static/scripts/naturalSort-v0.8.1.js"></script>
<script defer src="/static/scripts/torrent.js"></script>
<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">
<div id="title">
<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" />
<small>{{ infoHash }}</small>
</a>
</div>
<table>
<tr>
<th scope="row">Size</th>
<td>{{ sizeHumanised }}</td>
</tr>
<tr>
<th scope="row">Discovered on</th>
<td>{{ discoveredOnHumanised }}</td>
</tr>
<tr>
<th scope="row">Files</th>
<td>{{ nFiles }}</td>
</tr>
</table>
<h3>Better Files</h3>
<div id="fileTree"></div>
</script>
</head>
<body>
<header>
@ -18,33 +54,7 @@
</form>
</header>
<main>
<div id="title">
<h2>{{ .T.Name }}</h2>
<a href="magnet:?xt=urn:btih:{{ bytesToHex .T.InfoHash }}&amp;dn={{ .T.Name }}">
<img src="/static/assets/magnet.gif" alt="Magnet link"
title="Download this torrent using magnet" />
<small>{{ bytesToHex .T.InfoHash }}</small>
</a>
</div>
<table>
<tr>
<th scope="row">Size</th>
<td>{{ humanizeSize .T.Size }}</td>
</tr>
<tr>
<th scope="row">Discovered on</th>
<td>{{ unixTimeToYearMonthDay .T.DiscoveredOn }}</td>
</tr>
<tr>
<th scope="row">Files</th>
<td>{{ .T.NFiles }}</td>
</tr>
</table>
<h3>Files</h3>
<!-- Content of this element will be overwritten by the script -->
<pre>{{ range .F }}{{ .Path }}{{ "\t" }}{{ humanizeSizeF .Size }}{{ "\n" }}{{ end }}</pre>
</main>
</body>
</html>

View File

@ -5,13 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Search - magneticow</title>
<link rel="stylesheet" href="static/styles/reset.css">
<link rel="stylesheet" href="static/styles/essential.css">
<link rel="stylesheet" href="static/styles/torrents.css">
<link rel="stylesheet" href="/static/styles/reset.css">
<link rel="stylesheet" href="/static/styles/essential.css">
<link rel="stylesheet" href="/static/styles/torrents.css">
<script src="static/scripts/mustache-v2.3.0.min.js"></script>
<script src="static/scripts/common.js"></script>
<script src="static/scripts/torrents.js"></script>
<script src="/static/scripts/mustache-v2.3.0.min.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/torrents.js"></script>
<script id="item-template" type="text/x-handlebars-template">
<li>

View File

@ -1,15 +1,13 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/pkg/errors"
"net/http"
"strings"
"time"
"github.com/pkg/errors"
"github.com/boramalper/magnetico/pkg/persistence"
"github.com/gorilla/mux"
)
// DONE
@ -36,39 +34,12 @@ func torrentsHandler(w http.ResponseWriter, r *http.Request) {
}
func torrentsInfohashHandler(w http.ResponseWriter, r *http.Request) {
infoHash, err := hex.DecodeString(mux.Vars(r)["infohash"])
if err != nil {
handlerError(errors.Wrap(err, "cannot decode infohash"), w)
data := mustAsset("templates/torrent.html")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// Cache static resources for a day
w.Header().Set("Cache-Control", "max-age=86400")
_, _ = w.Write(data)
return
}
torrent, err := database.GetTorrent(infoHash)
if err != nil {
handlerError(errors.Wrap(err, "cannot get torrent"), w)
return
}
if torrent == nil {
respondError(w, http.StatusNotFound, "torrent not found!")
return
}
files, err := database.GetFiles(infoHash)
if err != nil {
handlerError(errors.Wrap(err, "could not get files"), w)
return
}
if files == nil {
handlerError(fmt.Errorf("could not get files"), w)
return
}
_ = templates["torrent"].Execute(w, struct {
T *persistence.TorrentMetadata
F []persistence.File
}{
T: torrent,
F: files,
})
}
func statisticsHandler(w http.ResponseWriter, r *http.Request) {
@ -102,7 +73,7 @@ func feedHandler(w http.ResponseWriter, r *http.Request) {
time.Now().Unix(),
persistence.ByDiscoveredOn,
false,
N_TORRENTS,
20,
nil,
nil,
)
@ -137,6 +108,8 @@ func staticHandler(w http.ResponseWriter, r *http.Request) {
var contentType string
if strings.HasSuffix(r.URL.Path, ".css") {
contentType = "text/css; charset=utf-8"
} else if strings.HasSuffix(r.URL.Path, ".js") {
contentType = "text/javascript; charset=utf-8"
} else { // fallback option
contentType = http.DetectContentType(data)
}

View File

@ -29,8 +29,6 @@ import (
"github.com/boramalper/magnetico/pkg/persistence"
)
const N_TORRENTS = 20
var compiledOn string
// Set a Decoder instance as a package global, because it caches
@ -106,14 +104,16 @@ func main() {
router := mux.NewRouter()
router.HandleFunc("/",
BasicAuth(rootHandler, "magneticow"))
router.HandleFunc("/api/v0.1/files/{infohash:[a-f0-9]{40}}",
BasicAuth(apiFilesInfohashHandler, "magneticow"))
router.HandleFunc("/api/v0.1/statistics",
BasicAuth(apiStatisticsHandler, "magneticow"))
BasicAuth(apiStatistics, "magneticow"))
router.HandleFunc("/api/v0.1/torrents",
BasicAuth(apiTorrentsHandler, "magneticow"))
BasicAuth(apiTorrents, "magneticow"))
router.HandleFunc("/api/v0.1/torrents/{infohash:[a-f0-9]{40}}",
BasicAuth(apiTorrentsInfohashHandler, "magneticow"))
BasicAuth(apiTorrent, "magneticow"))
router.HandleFunc("/api/v0.1/torrents/{infohash:[a-f0-9]{40}}/filelist",
BasicAuth(apiFilelist, "magneticow"))
router.HandleFunc("/feed",
BasicAuth(feedHandler, "magneticow"))
router.PathPrefix("/static").HandlerFunc(
@ -169,7 +169,6 @@ func main() {
templates = make(map[string]*template.Template)
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"))))
templates["torrent"] = template.Must(template.New("torrent").Funcs(templateFunctions).Parse(string(mustAsset("templates/torrent.html"))))
var err error
database, err = persistence.MakeDatabase(opts.Database, logger)