fix for the feed, and added sorting support!

This commit is contained in:
Bora M. Alper 2017-07-04 18:51:46 +03:00
parent 07fe0d3eb4
commit 2b99fb3675
3 changed files with 146 additions and 108 deletions

View 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()

View File

@ -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)

View File

@ -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>