fix for the feed, and added sorting support!
This commit is contained in:
parent
07fe0d3eb4
commit
2b99fb3675
60
magneticow/magneticow/authorization.py
Normal file
60
magneticow/magneticow/authorization.py
Normal file
@ -0,0 +1,60 @@
|
||||
# magneticow - Lightweight web interface for magnetico.
|
||||
# Copyright (C) 2017 Mert Bora ALPER <bora@boramalper.org>
|
||||
# Dedicated to Cemile Binay, in whose hands I thrived.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
import functools
|
||||
import hashlib
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
# Adapted from: http://flask.pocoo.org/snippets/8/
|
||||
# (c) Copyright 2010 - 2017 by Armin Ronacher
|
||||
# BEGINNING OF THE 3RD PARTY COPYRIGHTED CONTENT
|
||||
def is_authorized(supplied_username, supplied_password):
|
||||
""" This function is called to check if a username / password combination is valid. """
|
||||
# Because we do monkey-patch! [in magneticow.__main__.py:main()]
|
||||
app = flask.current_app
|
||||
for username, password in app.arguments.user: # pylint: disable=maybe-no-member
|
||||
if supplied_username == username and supplied_password == password:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def authenticate():
|
||||
""" Sends a 401 response that enables basic auth. """
|
||||
return flask.Response(
|
||||
"Could not verify your access level for that URL.\n"
|
||||
"You have to login with proper credentials",
|
||||
401,
|
||||
{"WWW-Authenticate": 'Basic realm="Login Required"'}
|
||||
)
|
||||
|
||||
|
||||
def requires_auth(f):
|
||||
@functools.wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
auth = flask.request.authorization
|
||||
if not auth or not is_authorized(auth.username, auth.password):
|
||||
return authenticate()
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
# END OF THE 3RD PARTY COPYRIGHTED CONTENT
|
||||
|
||||
|
||||
def generate_feed_hash(username: str, password: str, filter_: str) -> str:
|
||||
"""
|
||||
Deterministically generates the feed hash from given username, password, and filter.
|
||||
Hash is the hex encoding of the SHA256 sum.
|
||||
"""
|
||||
return hashlib.sha256((username + "\0" + password + "\0" + filter_).encode()).digest().hex()
|
@ -13,10 +13,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
import collections
|
||||
import functools
|
||||
import datetime as dt
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
import sqlite3
|
||||
import os
|
||||
@ -25,6 +23,7 @@ import appdirs
|
||||
import flask
|
||||
|
||||
from magneticow import utils
|
||||
from magneticow.authorization import requires_auth, generate_feed_hash
|
||||
|
||||
|
||||
File = collections.namedtuple("file", ["path", "size"])
|
||||
@ -38,51 +37,6 @@ app.config.from_object(__name__)
|
||||
# this. Investigate the cause and fix it (I suspect of Gevent).
|
||||
magneticod_db = None
|
||||
|
||||
|
||||
# Adapted from: http://flask.pocoo.org/snippets/8/
|
||||
# (c) Copyright 2010 - 2017 by Armin Ronacher
|
||||
# BEGINNING OF THE COPYRIGHTED CONTENT
|
||||
def is_authorized(supplied_username, supplied_password):
|
||||
""" This function is called to check if a username / password combination is valid. """
|
||||
# Because we do monkey-patch! [in magneticow.__main__.py:main()]
|
||||
for username, password in app.arguments.user: # pylint: disable=maybe-no-member
|
||||
if supplied_username == username and supplied_password == password:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def authenticate():
|
||||
""" Sends a 401 response that enables basic auth. """
|
||||
return flask.Response(
|
||||
"Could not verify your access level for that URL.\n"
|
||||
"You have to login with proper credentials",
|
||||
401,
|
||||
{"WWW-Authenticate": 'Basic realm="Login Required"'}
|
||||
)
|
||||
|
||||
|
||||
def requires_auth(f):
|
||||
@functools.wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
auth = flask.request.authorization
|
||||
if not auth or not is_authorized(auth.username, auth.password):
|
||||
return authenticate()
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
# END OF THE COPYRIGHTED CONTENT
|
||||
|
||||
|
||||
def generate_feed_hash(username: str, password: str, filter_: str) -> str:
|
||||
"""
|
||||
Deterministically generates the feed hash from given username, password, and filter.
|
||||
Hash is the hex encoding of the SHA256 sum.
|
||||
:param username:
|
||||
:param password:
|
||||
:param filter_:
|
||||
:return:
|
||||
"""
|
||||
return hashlib.sha256((username + "\0" + password + "\0" + filter_).encode()).digest().hex()
|
||||
|
||||
@app.route("/")
|
||||
@requires_auth
|
||||
def home_page():
|
||||
@ -97,16 +51,7 @@ def home_page():
|
||||
@app.route("/torrents/")
|
||||
@requires_auth
|
||||
def torrents():
|
||||
if flask.request.args:
|
||||
if flask.request.args["search"] == "":
|
||||
return newest_torrents()
|
||||
return search_torrents()
|
||||
else:
|
||||
return newest_torrents()
|
||||
|
||||
|
||||
def search_torrents():
|
||||
search = flask.request.args["search"]
|
||||
search = flask.request.args.get("search")
|
||||
page = int(flask.request.args.get("page", 0))
|
||||
|
||||
context = {
|
||||
@ -114,23 +59,56 @@ def search_torrents():
|
||||
"page": page
|
||||
}
|
||||
|
||||
SQL_query = """
|
||||
SELECT
|
||||
info_hash,
|
||||
name,
|
||||
total_size,
|
||||
discovered_on
|
||||
FROM torrents
|
||||
"""
|
||||
if search:
|
||||
SQL_query += """
|
||||
INNER JOIN (
|
||||
SELECT docid AS id, rank(matchinfo(fts_torrents, 'pcnxal')) AS rank
|
||||
FROM fts_torrents
|
||||
WHERE name MATCH ?
|
||||
) AS ranktable USING(id)
|
||||
"""
|
||||
SQL_query += """
|
||||
ORDER BY {}
|
||||
LIMIT 20 OFFSET ?
|
||||
"""
|
||||
|
||||
sort_by = flask.request.args.get("sort_by")
|
||||
allowed_sorts = [
|
||||
None,
|
||||
"name ASC",
|
||||
"name DESC",
|
||||
"total_size ASC",
|
||||
"total_size DESC",
|
||||
"discovered_on ASC",
|
||||
"discovered_on DESC"
|
||||
]
|
||||
if sort_by not in allowed_sorts:
|
||||
return flask.Response("Invalid value for `sort_by! (Allowed values are %s)" % (allowed_sorts, ), 400)
|
||||
|
||||
if search:
|
||||
if sort_by:
|
||||
SQL_query = SQL_query.format(sort_by + ", " + "rank ASC")
|
||||
else:
|
||||
SQL_query = SQL_query.format("rank ASC")
|
||||
else:
|
||||
if sort_by:
|
||||
SQL_query = SQL_query.format(sort_by + ", " + "id DESC")
|
||||
else:
|
||||
SQL_query = SQL_query.format("id DESC")
|
||||
|
||||
with magneticod_db:
|
||||
cur = magneticod_db.execute(
|
||||
"SELECT"
|
||||
" info_hash, "
|
||||
" name, "
|
||||
" total_size, "
|
||||
" discovered_on "
|
||||
"FROM torrents "
|
||||
"INNER JOIN ("
|
||||
" SELECT docid AS id, rank(matchinfo(fts_torrents, 'pcnxal')) AS rank "
|
||||
" FROM fts_torrents "
|
||||
" WHERE name MATCH ? "
|
||||
" ORDER BY rank ASC"
|
||||
" LIMIT 20 OFFSET ?"
|
||||
") AS ranktable USING(id);",
|
||||
(search, 20 * page)
|
||||
)
|
||||
if search:
|
||||
cur = magneticod_db.execute(SQL_query, (search, 20 * page))
|
||||
else:
|
||||
cur = magneticod_db.execute(SQL_query, (20 * page, ))
|
||||
context["torrents"] = [Torrent(t[0].hex(), t[1], utils.to_human_size(t[2]),
|
||||
datetime.fromtimestamp(t[3]).strftime("%d/%m/%Y"), [])
|
||||
for t in cur.fetchall()]
|
||||
@ -143,38 +121,8 @@ def search_torrents():
|
||||
username, password = flask.request.authorization.username, flask.request.authorization.password
|
||||
context["subscription_url"] = "/feed?filter=%s&hash=%s" % (search, generate_feed_hash(username, password, search))
|
||||
|
||||
return flask.render_template("torrents.html", **context)
|
||||
|
||||
|
||||
def newest_torrents():
|
||||
page = int(flask.request.args.get("page", 0))
|
||||
|
||||
context = {
|
||||
"page": page
|
||||
}
|
||||
|
||||
with magneticod_db:
|
||||
cur = magneticod_db.execute(
|
||||
"SELECT "
|
||||
" info_hash, "
|
||||
" name, "
|
||||
" total_size, "
|
||||
" discovered_on "
|
||||
"FROM torrents "
|
||||
"ORDER BY id DESC LIMIT 20 OFFSET ?",
|
||||
(20 * page,)
|
||||
)
|
||||
context["torrents"] = [Torrent(t[0].hex(), t[1], utils.to_human_size(t[2]), datetime.fromtimestamp(t[3]).strftime("%d/%m/%Y"), [])
|
||||
for t in cur.fetchall()]
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
if len(context["torrents"]) < 20:
|
||||
context["next_page_exists"] = False
|
||||
else:
|
||||
context["next_page_exists"] = True
|
||||
|
||||
username, password = flask.request.authorization.username, flask.request.authorization.password
|
||||
context["subscription_url"] = "/feed?filter=&hash=%s" % (generate_feed_hash(username, password, ""),)
|
||||
if sort_by:
|
||||
context["sorted_by"] = sort_by
|
||||
|
||||
return flask.render_template("torrents.html", **context)
|
||||
|
||||
|
@ -24,9 +24,33 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th><!-- Magnet link --></th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Discovered on</th>
|
||||
<th>
|
||||
{% if sorted_by == "name ASC" %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=name+DESC">Name ▲</a>
|
||||
{% elif sorted_by == "name DESC" %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=name+ASC">Name ▼</a>
|
||||
{% else %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=name+ASC">Name</a>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>
|
||||
{% if sorted_by == "total_size ASC" %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=total_size+DESC">Size ▲</a>
|
||||
{% elif sorted_by == "total_size DESC" %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=total_size+ASC">Size ▼</a>
|
||||
{% else %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=total_size+ASC">Size</a>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>
|
||||
{% if sorted_by == "discovered_on ASC" %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=discovered_on+DESC">Discovered on ▲</a>
|
||||
{% elif sorted_by == "discovered_on DESC" %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=discovered_on+ASC">Discovered on ▼</a>
|
||||
{% else %}
|
||||
<a href="/torrents/?search={{ search }}&sort_by=discovered_on+DESC">Discovered on</a>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -48,11 +72,17 @@
|
||||
<form action="/torrents" method="get">
|
||||
<button {% if page == 0 %}disabled{% endif %}>Previous</button>
|
||||
<input type="text" name="search" value="{{ search }}" hidden>
|
||||
{% if sorted_by %}
|
||||
<input type="text" name="sort_by" value="{{ sorted_by }}" hidden>
|
||||
{% endif %}
|
||||
<input type="number" name="page" value="{{ page - 1 }}" hidden>
|
||||
</form>
|
||||
<form action="/torrents" method="get">
|
||||
<button {% if not next_page_exists %}disabled{% endif %}>Next</button>
|
||||
<input type="text" name="search" value="{{ search }}" hidden>
|
||||
{% if sorted_by %}
|
||||
<input type="text" name="sort_by" value="{{ sorted_by }}" hidden>
|
||||
{% endif %}
|
||||
<input type="number" name="page" value="{{ page + 1 }}" hidden>
|
||||
</form>
|
||||
</footer>
|
||||
|
Loading…
Reference in New Issue
Block a user