added the Games tab as well as Basic access authentication

This commit is contained in:
Matthew Welch 2020-05-06 14:34:38 -07:00
parent 2a5f7ae67f
commit 51c6b5e572
10 changed files with 326 additions and 35 deletions

View File

@ -142,6 +142,7 @@ def get_comic_page(comic_id, page_number):
response.headers["cache-control"] = "public" response.headers["cache-control"] = "public"
date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta.path))) date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta.path)))
response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z')
response.headers["Content-Disposition"] = "attachment; filename=\"{} {}_{}{}\"".format(str(meta.series), meta.issuetext, str(page_number), filetype.guess(byte_image).extension)
return response return response
@ -155,4 +156,5 @@ def get_comic_thumbnail(comic_id, page_number):
date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta.path))) date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta.path)))
response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z')
response.headers["content-type"] = thumb.type response.headers["content-type"] = thumb.type
response.headers["Content-Disposition"] = "attachment; filename=\"{} {}_{}_thumbnail\"".format(str(meta.series), meta.issuetext, str(page_number))
return response return response

91
games/games.py Normal file
View File

@ -0,0 +1,91 @@
from flask import Blueprint, render_template, request, send_file, current_app, jsonify, abort
from flask_login import login_required
from pathvalidate import sanitize_filename
import os
import inspect
import re
from scripts import database, func
Games = Blueprint("games", __name__, template_folder="templates")
@Games.route("/games")
@login_required
def index():
try:
page = request.args.get("page", 1, type=int)
max_items = request.args.get("max_items", 30, type=int)
games = database.get_all_games()
start = (max_items*(page-1))
end = len(games) if len(games) < max_items*page else max_items*page
return render_template("games/index.html", title="Games", games=games)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(type(e)) + " " + str(e)
@Games.route('/games/get_games')
@login_required
def get_games():
try:
games = database.get_all_games()
games_json = {}
for game in games:
games_json[game.game_id] = {
"id": game.game_id,
"title": game.title,
"windows": game.windows,
"mac": game.mac,
"linux": game.linux,
"description": game.description,
"poster_path": game.poster_path
}
return jsonify(games_json)
# return jsonify({game["id"]: game for game in games})
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(type(e)) + " " + str(e)
@Games.route('/games/get_game/<int:game_id>')
@login_required
def get_game(game_id):
try:
game = database.get_game(game_id)
if game:
game_json = {
"title": game.title,
"game_id": game.game_id,
"description": game.description,
"poster_path": game.poster_path,
"windows": game.windows,
"mac": game.mac,
"linux": game.linux
}
return jsonify(game_json)
abort(404)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(type(e)) + " " + str(e)
@Games.route("/games/download/<int:game_id>")
@login_required
def download_game(game_id):
try:
game = database.get_game(game_id)
if game:
files = game.windows["files"]
filename = sanitize_filename(files[0])
folder = re.match(r"(.+)_setup_win.(exe|msi)", filename).group(1)
if len(files) > 1:
filename = sanitize_filename(game.title+".zip")
path = os.path.join(func.GAMES_DIRECTORY, folder, filename)
return send_file(path, as_attachment=True, attachment_filename=filename)
else:
abort(404)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(type(e)) + " " + str(e)

View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
{% for game in games %}
<a class="btn btn-primary" href="{{ url_for("games.index") }}/download/{{ game.game_id }}">Download</a>
<p>{{ game.title }} - {{ game.game_id }}</p>
{% endfor %}
{% endblock %}

View File

@ -1,5 +1,5 @@
from flask import Flask from flask import Flask
from flask import render_template, request, g, redirect, url_for, flash, current_app from flask import render_template, request, g, redirect, url_for, flash, current_app, Response
from flask_login import LoginManager, current_user, login_user, logout_user, login_required from flask_login import LoginManager, current_user, login_user, logout_user, login_required
from oauthlib.oauth2 import WebApplicationClient from oauthlib.oauth2 import WebApplicationClient
@ -12,12 +12,14 @@ import datetime
import requests import requests
import json import json
import os import os
import base64
import scripts.func as func import scripts.func as func
from scripts import database from scripts import database
from admin import admin from admin import admin
from comics import comics from comics import comics
from tv_movies import tv_movies from tv_movies import tv_movies
from games import games
class NullHandler(logging.Handler): class NullHandler(logging.Handler):
@ -46,11 +48,14 @@ app = Flask(__name__)
app.register_blueprint(comics.Comics) app.register_blueprint(comics.Comics)
app.register_blueprint(admin.Admin) app.register_blueprint(admin.Admin)
app.register_blueprint(tv_movies.TV_Movies) app.register_blueprint(tv_movies.TV_Movies)
app.register_blueprint(games.Games)
app.config["SECRET_KEY"] = "***REMOVED***" app.config["SECRET_KEY"] = "***REMOVED***"
app.logger.setLevel("DEBUG") app.logger.setLevel("DEBUG")
# app.use_x_sendfile = True
login_manager = LoginManager(app) login_manager = LoginManager(app)
login_manager.login_view = "login" login_manager.login_view = "login"
app.config["REMEMBER_COOKIE_DOMAIN"] = "narnian.us"
MOBILE_DEVICES = ["android", "blackberry", "ipad", "iphone"] MOBILE_DEVICES = ["android", "blackberry", "ipad", "iphone"]
@ -91,9 +96,13 @@ def get_tv_shows():
func.get_tv_episode(file_path) func.get_tv_episode(file_path)
def get_games():
with app.app_context():
func.get_games()
with app.app_context(): with app.app_context():
current_app.logger.info("server start") current_app.logger.info("server start")
#database.initialize_db()
thread = threading.Thread(target=get_comics, args=()) thread = threading.Thread(target=get_comics, args=())
thread.daemon = True thread.daemon = True
thread.start() thread.start()
@ -103,6 +112,9 @@ with app.app_context():
thread3 = threading.Thread(target=get_tv_shows, args=()) thread3 = threading.Thread(target=get_tv_shows, args=())
thread3.daemon = True thread3.daemon = True
thread3.start() thread3.start()
thread4 = threading.Thread(target=get_games, args=())
thread4.daemon = True
thread4.start()
@app.teardown_appcontext @app.teardown_appcontext
@ -140,10 +152,18 @@ def update_tv_episodes_db(sender, **kw):
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e)) current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
def update_games_db(sender, **kw):
try:
database.add_games(kw["games"])
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
func.comic_loaded.connect(update_comic_db) func.comic_loaded.connect(update_comic_db)
func.movie_loaded.connect(update_movie_db) func.movie_loaded.connect(update_movie_db)
func.tv_show_loaded.connect(update_tv_show_db) func.tv_show_loaded.connect(update_tv_show_db)
func.tv_episodes_loaded.connect(update_tv_episodes_db) func.tv_episodes_loaded.connect(update_tv_episodes_db)
func.games_loaded.connect(update_games_db)
@login_manager.user_loader @login_manager.user_loader
@ -155,6 +175,27 @@ def load_user(email):
return str(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e)) return str(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
@login_manager.request_loader
def load_user_from_request(request):
try:
api_key = request.headers.get('Authorization')
if api_key:
api_key = api_key.replace('Basic ', '', 1)
try:
api_key = base64.b64decode(api_key).decode("utf-8")
except TypeError:
pass
email = api_key.split(":")[0]
password = api_key.split(":")[1]
user = load_user(email)
if user and user.check_password(password):
return user
return None
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
@app.route("/login", methods=["GET", "POST"]) @app.route("/login", methods=["GET", "POST"])
def login(): def login():
try: try:
@ -267,16 +308,47 @@ def home():
return str(e) return str(e)
@app.after_request
def apply_headers(response: Response):
try:
user_name = "anonymous"
if current_user:
user_name = getattr(current_user, "email", "anonymous")
response.set_cookie("rpi_name", user_name)
return response
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(e)
@app.route("/music") @app.route("/music")
@login_required @login_required
def music(): def music():
return "No music" return "No music"
@app.route("/games") @app.errorhandler(404)
@login_required def resource_not_found(e):
def games(): try:
return "No Games" return render_template("404.html"), 404
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(e)
@app.errorhandler(Exception)
def handle_exception(e):
return render_template("error.html", e=e), 500
games.Games.register_error_handler(404, resource_not_found)
tv_movies.TV_Movies.register_error_handler(404, resource_not_found)
comics.Comics.register_error_handler(404, resource_not_found)
admin.Admin.register_error_handler(404, resource_not_found)
games.Games.register_error_handler(Exception, handle_exception)
tv_movies.TV_Movies.register_error_handler(Exception, handle_exception)
comics.Comics.register_error_handler(Exception, handle_exception)
admin.Admin.register_error_handler(Exception, handle_exception)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -7,13 +7,12 @@ from wand.image import Image
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy import Column, Integer, String, BLOB, Boolean, DateTime, Numeric, func, over, ARRAY, TIMESTAMP from sqlalchemy import Column, Integer, String, BLOB, Boolean, DateTime, Numeric, func, over, ARRAY, TIMESTAMP, JSON
from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
from sqlalchemy.sql.expression import cast from sqlalchemy.sql.expression import cast
import sqlalchemy import sqlalchemy
import sqlite3 import sqlite3
import psycopg2
import os import os
import inspect import inspect
import logging import logging
@ -240,6 +239,27 @@ class DupComic(Base):
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e)) current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
class Game(Base):
__tablename__ = "games"
title = Column(String)
game_id = Column(Integer, primary_key=True)
description = Column(String)
poster_path = Column(String)
windows = Column(JSON)
mac = Column(JSON)
linux = Column(JSON)
def __init__(self, data):
i = 0
try:
for column in self.__table__.columns:
setattr(self, column.name, data[i])
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
class User(Base, UserMixin): class User(Base, UserMixin):
__tablename__ = "users" __tablename__ = "users"
@ -335,13 +355,13 @@ class TvMovieKeywords(Base):
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))""" current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))"""
def get_db(): # def get_db():
db = getattr(g, '_database', None) # db = getattr(g, '_database', None)
if db is None: # if db is None:
db = g._database = psycopg2.connect(host="bombur", database="rpiwebapp", user="rpiwebapp", password="hello").cursor() # db = g._database = psycopg2.connect(host="bombur", database="rpiwebapp", user="rpiwebapp", password="hello").cursor()
#
#db.row_factory = sqlite3.Row # #db.row_factory = sqlite3.Row
return db # return db
def get_imdb(): def get_imdb():
@ -454,6 +474,14 @@ def add_comics(meta, thumbnails):
current_app.logger.info("{} comic{} added".format(len(meta), "s" if len(meta)>1 or len(meta)<1 else "")) current_app.logger.info("{} comic{} added".format(len(meta), "s" if len(meta)>1 or len(meta)<1 else ""))
def add_games(games):
session = Session()
for game_data in games:
game = Game(game_data)
session.add(game)
session.commit()
def db_get_all_comics(): def db_get_all_comics():
session = Session() session = Session()
result = session.query(Comic).all() result = session.query(Comic).all()
@ -549,6 +577,17 @@ def tv_episode_path_in_db(path):
return False return False
def game_in_db(game_id):
try:
session = Session()
result = session.query(Game).filter(Game.game_id == game_id).one_or_none()
if result:
return True
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return False
def imdb_get_movie(title, year): def imdb_get_movie(title, year):
row = get_imdb().execute("SELECT tconst, runtimeMinutes FROM title_basics WHERE (originalTitle LIKE ? OR primaryTitle LIKE ?) AND (titleType LIKE '%movie' OR titleType='video') AND startYear=?", (title, title, year)).fetchone() row = get_imdb().execute("SELECT tconst, runtimeMinutes FROM title_basics WHERE (originalTitle LIKE ? OR primaryTitle LIKE ?) AND (titleType LIKE '%movie' OR titleType='video') AND startYear=?", (title, title, year)).fetchone()
return row return row
@ -718,6 +757,18 @@ def db_get_current_tv_show_episode_and_data(parent_imdb_id, episodes):
return most_recent_episode, most_recent_data return most_recent_episode, most_recent_data
def get_all_games():
session = Session()
result = session.query(Game).order_by(Game.title).all()
return result
def get_game(game_id):
session = Session()
result = session.query(Game).filter(Game.game_id == game_id).one_or_none()
return result
def db_search_table_columns_by_query(query, table, columns, group=[], order=[]): def db_search_table_columns_by_query(query, table, columns, group=[], order=[]):
session = Session() session = Session()
results = {} results = {}
@ -811,21 +862,21 @@ def resize_image(image, new_width=256, new_height=256):
return new_image return new_image
def fix_thumbnails(): # def fix_thumbnails():
new_height = 256 # new_height = 256
new_width = 256 # new_width = 256
print("Start fix thumbnail size") # print("Start fix thumbnail size")
rows = get_db().execute("SELECT * FROM rpiwebapp.comic_thumbnails") # rows = get_db().execute("SELECT * FROM rpiwebapp.comic_thumbnails")
print("got list of all thumbnails\n") # print("got list of all thumbnails\n")
#
for row in rows: # for row in rows:
image = Image(file=BytesIO(row["image"])) # image = Image(file=BytesIO(row["image"]))
if image.width > new_width or image.height > new_height: # if image.width > new_width or image.height > new_height:
print("id:", row["id"], "pageNumber:", row["pageNumber"]) # print("id:", row["id"], "pageNumber:", row["pageNumber"])
get_db().execute("UPDATE rpiwebapp.comic_thumbnails SET image=? WHERE comic_id=? AND pageNumber=?", # get_db().execute("UPDATE rpiwebapp.comic_thumbnails SET image=? WHERE comic_id=? AND pageNumber=?",
[resize_image(image, new_width, new_height).make_blob(), row["id"], row["pageNumber"]]) # [resize_image(image, new_width, new_height).make_blob(), row["id"], row["pageNumber"]])
get_db().commit() # get_db().commit()
print("Finished fix thumbnail size") # print("Finished fix thumbnail size")
def get_user(email): def get_user(email):

View File

@ -9,6 +9,7 @@ import os, re
import inspect import inspect
import json import json
import enzyme import enzyme
import requests
from scripts import database from scripts import database
@ -17,6 +18,7 @@ comic_loaded = rpi_signals.signal("comic-loaded")
movie_loaded = rpi_signals.signal("movie-loaded") movie_loaded = rpi_signals.signal("movie-loaded")
tv_show_loaded = rpi_signals.signal("tv_show_loaded") tv_show_loaded = rpi_signals.signal("tv_show_loaded")
tv_episodes_loaded = rpi_signals.signal("tv_episodes_loaded") tv_episodes_loaded = rpi_signals.signal("tv_episodes_loaded")
games_loaded = rpi_signals.signal("games_loaded")
publishers_to_ignore = ["***REMOVED***"] publishers_to_ignore = ["***REMOVED***"]
@ -26,16 +28,18 @@ RPI_COMICS_DIRECTORY = "/usb/storage/media/Comics/"
RPI_MOVIES_DIRECTORY = "/usb/storage/media/Videos/Movies/" RPI_MOVIES_DIRECTORY = "/usb/storage/media/Videos/Movies/"
RPI_TV_SHOWS_DIRECTORY = "/usb/storage/media/Videos/TV/" RPI_TV_SHOWS_DIRECTORY = "/usb/storage/media/Videos/TV/"
RPI_VIDEOS_DIRECTORY = "/usb/storage/media/Videos/Videos/" RPI_VIDEOS_DIRECTORY = "/usb/storage/media/Videos/Videos/"
RPI_GAMES_DIRECTORY = "/usb/storage/media/games/" RPI_GAMES_DIRECTORY = "/usb/storage/Games/"
RPI_MUSIC_DIRECTORY = "/usb/storage/media/Music/" RPI_MUSIC_DIRECTORY = "/usb/storage/media/Music/"
MC_COMICS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Comics" MC_COMICS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Comics/"
MC_MOVIES_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Movies" MC_MOVIES_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Movies/"
MC_TV_SHOWS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/TV" MC_TV_SHOWS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/TV/"
MC_GAMES_DIRECTORY = "/mnt/g/Humble Bundle/rpi/"
COMICS_DIRECTORY = RPI_COMICS_DIRECTORY if os.path.exists(RPI_COMICS_DIRECTORY) else MC_COMICS_DIRECTORY COMICS_DIRECTORY = RPI_COMICS_DIRECTORY if os.path.exists(RPI_COMICS_DIRECTORY) else MC_COMICS_DIRECTORY
MOVIES_DIRECTORY = RPI_MOVIES_DIRECTORY if os.path.exists(RPI_MOVIES_DIRECTORY) else MC_MOVIES_DIRECTORY MOVIES_DIRECTORY = RPI_MOVIES_DIRECTORY if os.path.exists(RPI_MOVIES_DIRECTORY) else MC_MOVIES_DIRECTORY
TV_SHOWS_DIRECTORY = RPI_TV_SHOWS_DIRECTORY if os.path.exists(RPI_TV_SHOWS_DIRECTORY) else MC_TV_SHOWS_DIRECTORY TV_SHOWS_DIRECTORY = RPI_TV_SHOWS_DIRECTORY if os.path.exists(RPI_TV_SHOWS_DIRECTORY) else MC_TV_SHOWS_DIRECTORY
GAMES_DIRECTORY = RPI_GAMES_DIRECTORY if os.path.exists(RPI_GAMES_DIRECTORY) else MC_GAMES_DIRECTORY
############# #############
@ -423,3 +427,52 @@ def get_tags(path):
if simple.name == "SUMMARY": if simple.name == "SUMMARY":
mkv_info["movie"]["summary"] = simple.string mkv_info["movie"]["summary"] = simple.string
return mkv_info return mkv_info
def get_games():
games = []
cover_url = "https://api-v3.igdb.com/covers"
games_url = "https://api-v3.igdb.com/games"
headers = {
"accept": "application/json",
"user-key": "641f7f0e3af5273dcc1105ce851ea804"
}
i = 0
for folder in sorted(os.listdir(GAMES_DIRECTORY), key=str.casefold):
root = os.path.join(GAMES_DIRECTORY, folder)
if os.path.isdir(os.path.join(root)):
path = os.path.join(root, "info.json")
with open(path, "r") as f:
info = json.load(f)
game_id = info["id"]
if not database.game_in_db(game_id):
current_app.logger.info(f"start loading game: {info['name']}:{info['id']}")
data = f"fields summary;limit 1;where id={game_id};"
r = requests.get(games_url, headers=headers, data=data).json()[0]
description = ""
if "summary" in r.keys():
description = r["summary"]
data = f"fields image_id;limit 1;where game={game_id};"
r = requests.get(cover_url, headers=headers, data=data).json()
poster_path = None
if r:
if "image_id" in r[0].keys():
poster_path = "https://images.igdb.com/igdb/image/upload/t_cover_big/" + r[0]["image_id"] + ".jpg"
windows = None
mac = None
linux = None
if "windows" in info.keys():
windows = info["windows"]
if "mac" in info.keys():
mac = info["mac"]
if "linux" in info.keys():
linux = info["linux"]
game = (info["name"], game_id, description, poster_path, windows, mac, linux)
games.append(game)
i += 1
if i >= 5:
games_loaded.send("anonymous", games=games.copy())
games.clear()
i = 0
games_loaded.send("anonymous", games=games)
current_app.logger.info("finished loading games")

5
templates/404.html Normal file
View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<h1>Page not found</h1>
{% endblock %}

View File

@ -35,6 +35,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for("tv_movies.index") }}">Tv & Movies</a> <a class="nav-link" href="{{ url_for("tv_movies.index") }}">Tv & Movies</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for("games.index") }}">Games</a>
</li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">

5
templates/error.html Normal file
View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
{{ e }}
{% endblock %}

View File

@ -20,7 +20,6 @@
<button type="submit">Prev</button> <button type="submit">Prev</button>
</form> </form>
{% endif %} {% endif %}
<h1 style="display: inline-block">Episode {{ episode.episode }}: {{ episode.title }}</h1>
{% if loop.index0 + 1 < episodes|length %} {% if loop.index0 + 1 < episodes|length %}
<form style="display: inline-block; float: right; margin-top: 5px" action="" method="get"> <form style="display: inline-block; float: right; margin-top: 5px" action="" method="get">
<input name="season" value="{{ episodes[loop.index0 + 1].season }}" hidden> <input name="season" value="{{ episodes[loop.index0 + 1].season }}" hidden>
@ -28,6 +27,8 @@
<button type="submit">Next</button> <button type="submit">Next</button>
</form> </form>
{% endif %} {% endif %}
<h1 style="display: inline-block">Episode {{ episode.episode }}: {{ episode.title }}</h1>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<p style="text-align: left">{{ episode.description }}</p> <p style="text-align: left">{{ episode.description }}</p>