From 51c6b5e572851227fb24968898d8de77368f8252 Mon Sep 17 00:00:00 2001 From: Matthew Welch Date: Wed, 6 May 2020 14:34:38 -0700 Subject: [PATCH] added the Games tab as well as Basic access authentication --- comics/comics.py | 2 + games/games.py | 91 +++++++++++++++++ games/templates/games/index.html | 8 ++ rpiWebApp.py | 84 ++++++++++++++-- scripts/database.py | 99 ++++++++++++++----- scripts/func.py | 61 +++++++++++- templates/404.html | 5 + templates/base.html | 3 + templates/error.html | 5 + .../templates/tv_movies/episodeViewer.html | 3 +- 10 files changed, 326 insertions(+), 35 deletions(-) create mode 100644 games/games.py create mode 100644 games/templates/games/index.html create mode 100644 templates/404.html create mode 100644 templates/error.html diff --git a/comics/comics.py b/comics/comics.py index dd80ad2..35ab492 100644 --- a/comics/comics.py +++ b/comics/comics.py @@ -142,6 +142,7 @@ def get_comic_page(comic_id, page_number): response.headers["cache-control"] = "public" 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["Content-Disposition"] = "attachment; filename=\"{} {}_{}{}\"".format(str(meta.series), meta.issuetext, str(page_number), filetype.guess(byte_image).extension) 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))) response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') response.headers["content-type"] = thumb.type + response.headers["Content-Disposition"] = "attachment; filename=\"{} {}_{}_thumbnail\"".format(str(meta.series), meta.issuetext, str(page_number)) return response diff --git a/games/games.py b/games/games.py new file mode 100644 index 0000000..183f17c --- /dev/null +++ b/games/games.py @@ -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/') +@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/") +@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) diff --git a/games/templates/games/index.html b/games/templates/games/index.html new file mode 100644 index 0000000..5706467 --- /dev/null +++ b/games/templates/games/index.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +{% for game in games %} + Download +

{{ game.title }} - {{ game.game_id }}

+{% endfor %} +{% endblock %} diff --git a/rpiWebApp.py b/rpiWebApp.py index fee1d00..2d82692 100644 --- a/rpiWebApp.py +++ b/rpiWebApp.py @@ -1,5 +1,5 @@ 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 oauthlib.oauth2 import WebApplicationClient @@ -12,12 +12,14 @@ import datetime import requests import json import os +import base64 import scripts.func as func from scripts import database from admin import admin from comics import comics from tv_movies import tv_movies +from games import games class NullHandler(logging.Handler): @@ -46,11 +48,14 @@ app = Flask(__name__) app.register_blueprint(comics.Comics) app.register_blueprint(admin.Admin) app.register_blueprint(tv_movies.TV_Movies) +app.register_blueprint(games.Games) app.config["SECRET_KEY"] = "***REMOVED***" app.logger.setLevel("DEBUG") +# app.use_x_sendfile = True login_manager = LoginManager(app) login_manager.login_view = "login" +app.config["REMEMBER_COOKIE_DOMAIN"] = "narnian.us" MOBILE_DEVICES = ["android", "blackberry", "ipad", "iphone"] @@ -91,9 +96,13 @@ def get_tv_shows(): func.get_tv_episode(file_path) +def get_games(): + with app.app_context(): + func.get_games() + + with app.app_context(): current_app.logger.info("server start") - #database.initialize_db() thread = threading.Thread(target=get_comics, args=()) thread.daemon = True thread.start() @@ -103,6 +112,9 @@ with app.app_context(): thread3 = threading.Thread(target=get_tv_shows, args=()) thread3.daemon = True thread3.start() + thread4 = threading.Thread(target=get_games, args=()) + thread4.daemon = True + thread4.start() @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)) +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.movie_loaded.connect(update_movie_db) func.tv_show_loaded.connect(update_tv_show_db) func.tv_episodes_loaded.connect(update_tv_episodes_db) +func.games_loaded.connect(update_games_db) @login_manager.user_loader @@ -155,6 +175,27 @@ def load_user(email): 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"]) def login(): try: @@ -267,16 +308,47 @@ def home(): 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") @login_required def music(): return "No music" -@app.route("/games") -@login_required -def games(): - return "No Games" +@app.errorhandler(404) +def resource_not_found(e): + try: + 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__': diff --git a/scripts/database.py b/scripts/database.py index b11c45c..280b613 100644 --- a/scripts/database.py +++ b/scripts/database.py @@ -7,13 +7,12 @@ from wand.image import Image from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine 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.pool import NullPool from sqlalchemy.sql.expression import cast import sqlalchemy import sqlite3 -import psycopg2 import os import inspect import logging @@ -240,6 +239,27 @@ class DupComic(Base): 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): __tablename__ = "users" @@ -335,13 +355,13 @@ class TvMovieKeywords(Base): current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))""" -def get_db(): - db = getattr(g, '_database', None) - if db is None: - db = g._database = psycopg2.connect(host="bombur", database="rpiwebapp", user="rpiwebapp", password="hello").cursor() - - #db.row_factory = sqlite3.Row - return db +# def get_db(): +# db = getattr(g, '_database', None) +# if db is None: +# db = g._database = psycopg2.connect(host="bombur", database="rpiwebapp", user="rpiwebapp", password="hello").cursor() +# +# #db.row_factory = sqlite3.Row +# return db 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 "")) +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(): session = Session() result = session.query(Comic).all() @@ -549,6 +577,17 @@ def tv_episode_path_in_db(path): 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): 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 @@ -718,6 +757,18 @@ def db_get_current_tv_show_episode_and_data(parent_imdb_id, episodes): 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=[]): session = Session() results = {} @@ -811,21 +862,21 @@ def resize_image(image, new_width=256, new_height=256): return new_image -def fix_thumbnails(): - new_height = 256 - new_width = 256 - print("Start fix thumbnail size") - rows = get_db().execute("SELECT * FROM rpiwebapp.comic_thumbnails") - print("got list of all thumbnails\n") - - for row in rows: - image = Image(file=BytesIO(row["image"])) - if image.width > new_width or image.height > new_height: - print("id:", row["id"], "pageNumber:", row["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"]]) - get_db().commit() - print("Finished fix thumbnail size") +# def fix_thumbnails(): +# new_height = 256 +# new_width = 256 +# print("Start fix thumbnail size") +# rows = get_db().execute("SELECT * FROM rpiwebapp.comic_thumbnails") +# print("got list of all thumbnails\n") +# +# for row in rows: +# image = Image(file=BytesIO(row["image"])) +# if image.width > new_width or image.height > new_height: +# print("id:", row["id"], "pageNumber:", row["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"]]) +# get_db().commit() +# print("Finished fix thumbnail size") def get_user(email): diff --git a/scripts/func.py b/scripts/func.py index 0a5d64e..7fa6687 100644 --- a/scripts/func.py +++ b/scripts/func.py @@ -9,6 +9,7 @@ import os, re import inspect import json import enzyme +import requests from scripts import database @@ -17,6 +18,7 @@ comic_loaded = rpi_signals.signal("comic-loaded") movie_loaded = rpi_signals.signal("movie-loaded") tv_show_loaded = rpi_signals.signal("tv_show_loaded") tv_episodes_loaded = rpi_signals.signal("tv_episodes_loaded") +games_loaded = rpi_signals.signal("games_loaded") publishers_to_ignore = ["***REMOVED***"] @@ -26,16 +28,18 @@ RPI_COMICS_DIRECTORY = "/usb/storage/media/Comics/" RPI_MOVIES_DIRECTORY = "/usb/storage/media/Videos/Movies/" RPI_TV_SHOWS_DIRECTORY = "/usb/storage/media/Videos/TV/" 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/" -MC_COMICS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Comics" -MC_MOVIES_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Movies" -MC_TV_SHOWS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/TV" +MC_COMICS_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Comics/" +MC_MOVIES_DIRECTORY = "/mnt/c/Users/Matthew/Documents/Movies/" +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 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 +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": mkv_info["movie"]["summary"] = simple.string 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") diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..340f2b0 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block content %} +

Page not found

+{% endblock %} diff --git a/templates/base.html b/templates/base.html index e4c5edd..135f6b2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -35,6 +35,9 @@ +