Added users and thumbnails
Added users so that the site can only be used if logged in. Thumbnails for each comic are also generated with ImageMagick.
152
app.py
@ -1,13 +1,24 @@
|
||||
from flask import Flask
|
||||
from flask import render_template, request, g, redirect, url_for
|
||||
from flask import render_template, request, g, redirect, url_for, make_response, flash, session
|
||||
from flask_login import LoginManager, current_user, login_user, logout_user, login_required
|
||||
from flask_log import Logging
|
||||
|
||||
from urllib import parse
|
||||
import threading
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from wand.image import Image
|
||||
|
||||
import threading, os, datetime, pytz
|
||||
|
||||
import scripts.func as func
|
||||
from scripts import database
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SECRET_KEY"] = "***REMOVED***"
|
||||
app.config["FLASK_LOG_LEVEL"] = "DEBUG"
|
||||
flask_log = Logging(app)
|
||||
|
||||
login_manager = LoginManager(app)
|
||||
login_manager.login_view = "login"
|
||||
|
||||
|
||||
def get_comics():
|
||||
@ -21,6 +32,7 @@ def verify_paths():
|
||||
|
||||
|
||||
with app.app_context():
|
||||
app.logger.debug("server start")
|
||||
database.initialize_db()
|
||||
thread = threading.Thread(target=get_comics, args=())
|
||||
thread.daemon = True
|
||||
@ -47,27 +59,74 @@ def update_comic_db(sender, **kw):
|
||||
func.comic_loaded.connect(update_comic_db)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return render_template("index.html", title="Hello World")
|
||||
@login_manager.user_loader
|
||||
def load_user(username):
|
||||
return database.get_user(username)
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
try:
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username")
|
||||
password = request.form.get("password")
|
||||
user = database.get_user(username)
|
||||
if user is None or not user.check_password(password):
|
||||
flash("invalid username or password")
|
||||
return redirect(url_for("login"))
|
||||
login_user(user)
|
||||
next_page = request.args.get("next")
|
||||
if not next_page:
|
||||
next_page = url_for("home")
|
||||
return redirect(next_page)
|
||||
return render_template("login.html", title="login")
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
try:
|
||||
logout_user()
|
||||
return redirect(url_for("login"))
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def root():
|
||||
return redirect(url_for("home"))
|
||||
|
||||
|
||||
@app.route("/home")
|
||||
@login_required
|
||||
def home():
|
||||
try:
|
||||
return render_template("home.html", title="Hello World", current_user=current_user)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
@app.route("/movies")
|
||||
@login_required
|
||||
def movies():
|
||||
return "No Movies"
|
||||
|
||||
|
||||
@app.route("/music")
|
||||
@login_required
|
||||
def music():
|
||||
return "No music"
|
||||
|
||||
|
||||
@app.route("/games")
|
||||
@login_required
|
||||
def games():
|
||||
return "No Games"
|
||||
|
||||
|
||||
@app.route("/comics")
|
||||
@login_required
|
||||
def comics():
|
||||
polling = request.args.get("polling")
|
||||
|
||||
@ -76,12 +135,12 @@ def comics():
|
||||
return render_template("publisherList.html", comics=database.get_publishers())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return e
|
||||
return str(e)
|
||||
try:
|
||||
return render_template("publisherView.html", title="Comics", comics=database.get_publishers())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return e
|
||||
return str(e)
|
||||
|
||||
|
||||
@app.route("/comics/")
|
||||
@ -90,27 +149,29 @@ def comic_reroute():
|
||||
|
||||
|
||||
@app.route("/comics/<publisher>")
|
||||
@login_required
|
||||
def comics_publisher(publisher):
|
||||
publisher = parse.unquote(publisher)
|
||||
series = request.args.get("series")
|
||||
series_year = request.args.get("seriesYear")
|
||||
comic_path = request.args.get("path")
|
||||
issue = request.args.get("issue")
|
||||
page_number = request.args.get("pageNumber")
|
||||
if series:
|
||||
if comic_path:
|
||||
comic_path = parse.unquote(comic_path)
|
||||
return comic_viewer(comic_path, publisher, series, series_year)
|
||||
return render_template("seriesView.html", title="Comics", publisher=publisher, seriesYear=series_year, comics=database.db_get_comics_in_series(series, publisher, series_year))
|
||||
if issue:
|
||||
if page_number:
|
||||
return comic_viewer(publisher, series, series_year, issue)
|
||||
return comic_gallery(publisher, series, series_year, issue)
|
||||
return render_template("seriesView.html", title="Comics", comics=database.db_get_comics_in_series(series, publisher, series_year))
|
||||
return render_template("publisherSeriesView.html", title="Comics", comics=database.db_get_series_by_publisher(publisher))
|
||||
|
||||
|
||||
def comic_viewer(comic_path, publisher, series, series_year):
|
||||
def comic_viewer(publisher, series, series_year, issue):
|
||||
try:
|
||||
comic_path_parsed = parse.quote(comic_path)
|
||||
publisher_parsed = parse.quote(publisher)
|
||||
series_parsed = parse.quote(series)
|
||||
page_number = int(request.args.get("pageNumber"))
|
||||
comic = func.open_comic(comic_path)
|
||||
page_count = comic.getNumberOfPages()
|
||||
meta = database.db_get_comic(publisher, series, series_year, issue)
|
||||
page_count = int(meta["pageCount"])
|
||||
|
||||
prev_page = page_number - 1
|
||||
next_page = page_number + 1
|
||||
@ -118,30 +179,49 @@ def comic_viewer(comic_path, publisher, series, series_year):
|
||||
next_page = 0
|
||||
if prev_page < 0:
|
||||
prev_page = page_count - 1
|
||||
|
||||
page = comic.getPage(page_number)
|
||||
page = str(base64.b64encode(page))[2:-1]
|
||||
prev_url = "/comics/{}?series={}&seriesYear={}&path={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, comic_path_parsed, prev_page)
|
||||
next_url = "/comics/{}?series={}&seriesYear={}&path={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, comic_path_parsed, next_page)
|
||||
return render_template("comicView.html", title="Comics", page=page, page_count=page_count, prev_url=prev_url, next_url=next_url)
|
||||
prev_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, prev_page)
|
||||
next_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, next_page)
|
||||
return render_template("comicView.html", title="Comics", prev_url=prev_url, next_url=next_url, comic=meta, page_number=page_number)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return e
|
||||
return str(e)
|
||||
|
||||
|
||||
@app.route("/comics/getPage")
|
||||
def get_comic_page():
|
||||
path = parse.unquote(request.args.get("path"))
|
||||
page_number = int(request.args.get("pageNumber"))
|
||||
comic = func.open_comic(path)
|
||||
if page_number > comic.getNumberOfPages()-1:
|
||||
page_number = 1
|
||||
if page_number <= 0:
|
||||
page_number = comic.getNumberOfPages()-1
|
||||
page = comic.getPage(page_number)
|
||||
def comic_gallery(publisher, series, series_year, issue):
|
||||
try:
|
||||
meta = database.db_get_comic(publisher, series, series_year, issue)
|
||||
return render_template("comicGallery.html", title="Comics", comic=meta)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return str(e)
|
||||
|
||||
page = base64.b64encode(page)
|
||||
return page
|
||||
|
||||
@app.route("/comics/get_comic/<int:comic_id>/<int:page_number>")
|
||||
@login_required
|
||||
def get_comic_page(comic_id, page_number):
|
||||
meta = database.db_get_comic_by_id(comic_id)
|
||||
comic = func.open_comic(meta["path"])
|
||||
byteImage = BytesIO(comic.getPage(page_number))
|
||||
image = Image(file=byteImage)
|
||||
response = make_response(image.make_blob())
|
||||
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-type"] = "image/"+image.format
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/comics/get_comic/<int:comic_id>/<int:page_number>/thumbnail")
|
||||
@login_required
|
||||
def get_comic_thumbnail(comic_id, page_number):
|
||||
meta = database.db_get_comic_by_id(comic_id)
|
||||
thumb = database.db_get_thumbnail_by_id_page(comic_id, page_number)
|
||||
response = make_response(thumb["image"])
|
||||
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-type"] = thumb["type"]
|
||||
return response
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
0
database.db
Normal file
@ -1,5 +1,8 @@
|
||||
server_name rpi.narnian.us;
|
||||
#location / { try_files $uri @rpiWebApp; }
|
||||
location /static/ {
|
||||
alias /usb/www/matthew/rpiWebApp/static/;
|
||||
}
|
||||
location / {
|
||||
include fastcgi_params;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
|
@ -1,17 +1,25 @@
|
||||
from flask import Flask
|
||||
from flask import g
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import sqlite3
|
||||
import os, time
|
||||
|
||||
from comicapi.issuestring import IssueString
|
||||
|
||||
DATABASE = "/var/lib/rpiWebApp/database.db"
|
||||
DATABASE2 = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi web interface\\database.db"
|
||||
|
||||
|
||||
def get_db():
|
||||
db = getattr(g, '_database', None)
|
||||
if db is None:
|
||||
db = g._database = sqlite3.connect(DATABASE)
|
||||
try:
|
||||
db = g._database = sqlite3.connect(DATABASE)
|
||||
except Exception:
|
||||
db = g._database = sqlite3.connect(DATABASE2)
|
||||
|
||||
db.row_factory = sqlite3.Row
|
||||
return db
|
||||
|
||||
|
||||
@ -45,7 +53,7 @@ def initialize_db():
|
||||
"format" TEXT,
|
||||
"manga" TEXT,
|
||||
"blackAndWhite" TEXT,
|
||||
"pageCount" TEXT,
|
||||
"pageCount" INTEGER,
|
||||
"maturityRating" TEXT,
|
||||
"storyArc" TEXT,
|
||||
"seriesGroup" TEXT,
|
||||
@ -64,9 +72,16 @@ def initialize_db():
|
||||
get_db().execute("""CREATE TABLE IF NOT EXISTS "comic_thumbnails" (
|
||||
"id" INTEGER,
|
||||
"pageNumber" INTEGER,
|
||||
"image" BLOB
|
||||
"image" BLOB,
|
||||
"type" TEXT
|
||||
)""")
|
||||
get_db().execute("""CREATE TABLE IF NOT EXISTS "users" (
|
||||
"username" TEXT UNIQUE PRIMARY KEY,
|
||||
"passwordHash" VARCHAR(128),
|
||||
"isAdmin" INTEGER NOT NULL DEFAULT 0
|
||||
)""")
|
||||
get_db().execute("CREATE INDEX IF NOT EXISTS path_index ON comics(path);")
|
||||
get_db().execute("CREATE INDEX IF NOT EXISTS id_index ON comic_thumbnails(id);")
|
||||
get_db().commit()
|
||||
|
||||
|
||||
@ -99,7 +114,7 @@ def add_comics(meta, thumbnails):
|
||||
get_db().commit()
|
||||
comic_id = get_db().execute("SELECT id from comics ORDER BY id DESC LIMIT 1").fetchone()[0]
|
||||
for index in range(len(images)):
|
||||
get_db().execute("INSERT INTO comic_thumbnails(id, pageNumber, image) VALUES (?,?,?)", (comic_id, index, images[index]))
|
||||
get_db().execute("INSERT INTO comic_thumbnails(id, pageNumber, image, type) VALUES (?,?,?,?)", (comic_id, index, images[index][0], images[index][1]))
|
||||
get_db().commit()
|
||||
|
||||
|
||||
@ -119,23 +134,14 @@ def add_comic_thumbnails(thumbnails):
|
||||
|
||||
|
||||
def db_get_all_comics():
|
||||
original_factory = get_db().row_factory
|
||||
|
||||
def dict_factory(cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
return d
|
||||
get_db().row_factory = dict_factory
|
||||
result = get_db().execute("SELECT series, issue, title FROM comics ORDER BY series, issue").fetchall()
|
||||
result = get_db().execute("SELECT * FROM comics ORDER BY series, issue").fetchall()
|
||||
get_db().commit()
|
||||
get_db().row_factory = original_factory
|
||||
return result
|
||||
|
||||
|
||||
def db_get_series_by_publisher(publisher):
|
||||
series = []
|
||||
rows = get_db().execute("SELECT DISTINCT series, seriesYear, publisher FROM comics WHERE publisher LIKE ? ORDER BY series, seriesYear", [publisher]).fetchall()
|
||||
rows = get_db().execute("SELECT publisher, series, seriesYear, id, min(issue) FROM comics WHERE publisher=? GROUP BY publisher, series, seriesYear ORDER BY series, seriesYear", [publisher]).fetchall()
|
||||
for row in rows:
|
||||
series.append(row)
|
||||
return series
|
||||
@ -143,7 +149,7 @@ def db_get_series_by_publisher(publisher):
|
||||
|
||||
def db_get_comics_in_series(series, publisher, series_year):
|
||||
comics = []
|
||||
rows = get_db().execute("SELECT series, issue, title, path FROM comics WHERE series LIKE ? AND publisher LIKE ? AND seriesYear LIKE ? ORDER BY series, issue", [series, publisher, series_year]).fetchall()
|
||||
rows = get_db().execute("SELECT * FROM comics WHERE series=? AND publisher=? AND seriesYear=? ORDER BY series, issue", [series, publisher, series_year]).fetchall()
|
||||
get_db().commit()
|
||||
for row in rows:
|
||||
comics.append(row)
|
||||
@ -159,9 +165,25 @@ def get_publishers():
|
||||
return publishers
|
||||
|
||||
|
||||
def db_get_comic_by_id(comic_id):
|
||||
row = get_db().execute("SELECT * FROM comics WHERE id=?", [comic_id]).fetchone()
|
||||
return row
|
||||
|
||||
|
||||
def db_get_thumbnail_by_id_page(comic_id, pageNumber):
|
||||
row = get_db().execute("SELECT * FROM comic_thumbnails WHERE id=? AND pageNumber=?", [comic_id, pageNumber]).fetchone()
|
||||
return row
|
||||
|
||||
|
||||
def db_get_comic(publisher, series, series_year, issue):
|
||||
row = get_db().execute("SELECT * FROM comics WHERE publisher=? AND series=? AND seriesYear=? AND issue=?",
|
||||
[publisher, series, series_year, issue]).fetchone()
|
||||
return row
|
||||
|
||||
|
||||
def comic_path_in_db(path):
|
||||
try:
|
||||
result = get_db().execute("SELECT path FROM comics WHERE path LIKE ?", [path])
|
||||
result = get_db().execute("SELECT path FROM comics WHERE path=?", [path])
|
||||
get_db().commit()
|
||||
|
||||
if result.fetchone():
|
||||
@ -181,3 +203,36 @@ def verify_paths():
|
||||
get_db().execute("DELETE FROM comics WHERE path LIKE ?", [path[0]])
|
||||
get_db().commit()
|
||||
time.sleep(60*60*24*5)
|
||||
|
||||
|
||||
def get_user(username):
|
||||
user_data = get_db().execute("SELECT * FROM users WHERE username=?", [username]).fetchone()
|
||||
if user_data:
|
||||
return User(user_data)
|
||||
return None
|
||||
|
||||
|
||||
def verify_user(username, password):
|
||||
password_hash = get_db().execute("SELECT passwordHash FROM users WHERE username=?", [username]).fetchone()
|
||||
if password_hash:
|
||||
valid_password = check_password_hash(password_hash["passwordHash"], password)
|
||||
if valid_password:
|
||||
user_data = get_db().execute("SELECT * FROM users WHERE username=?", [username]).fetchone()
|
||||
user = User(user_data)
|
||||
return user
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, user):
|
||||
self.username = user["username"]
|
||||
self.password_hash = user["passwordHash"]
|
||||
self.is_admin = user["isAdmin"]
|
||||
|
||||
def get_id(self):
|
||||
return self.username
|
||||
|
||||
def check_password(self, password):
|
||||
result = check_password_hash(self.password_hash, password)
|
||||
return result
|
||||
|
@ -1,7 +1,7 @@
|
||||
from comicapi import comicarchive
|
||||
from blinker import Namespace
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from wand.image import Image
|
||||
|
||||
import os, sys
|
||||
|
||||
@ -10,14 +10,20 @@ from scripts import database
|
||||
rpi_signals = Namespace()
|
||||
comic_loaded = rpi_signals.signal("comic-loaded")
|
||||
|
||||
publishers_to_ignore = ["***REMOVED***"]
|
||||
|
||||
# Directories
|
||||
|
||||
COMICS_DIRECTORY = "/usb/storage/media/Comics/"
|
||||
MOVIES_DIRECTORY = "/usb/storage/media/Videos/Movies/"
|
||||
TV_SHOWS_DIRECTORY = "/usb/storage/media/Videos/TV/"
|
||||
VIDEOS_DIRECTORY = "/usb/storage/media/Videos/Videos/"
|
||||
GAMES_DIRECTORY = "/usb/storage/media/games/"
|
||||
MUSIC_DIRECTORY = "/usb/storage/media/Music/"
|
||||
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_MUSIC_DIRECTORY = "/usb/storage/media/Music/"
|
||||
|
||||
MC_COMICS_DIRECTORY = "C:\\Users\\Matthew\\Documents\\Comics"
|
||||
|
||||
COMICS_DIRECTORY = RPI_COMICS_DIRECTORY if os.path.exists(RPI_COMICS_DIRECTORY) else MC_COMICS_DIRECTORY
|
||||
|
||||
#############
|
||||
|
||||
@ -41,10 +47,13 @@ def get_comics():
|
||||
print("encoding failed on:", path)
|
||||
print(e)
|
||||
continue
|
||||
print(path)
|
||||
archive = open_comic(path)
|
||||
md = archive.readCIX()
|
||||
if md.publisher in publishers_to_ignore:
|
||||
continue
|
||||
print(path)
|
||||
meta.append((path, md))
|
||||
thumbnails.append(get_comic_thumbnails(archive))
|
||||
meta.append((path, archive.readCIX()))
|
||||
comics_added += 1
|
||||
comics_in_db += 1
|
||||
i += 1
|
||||
@ -63,17 +72,27 @@ def get_comics():
|
||||
|
||||
def get_comic_thumbnails(comic):
|
||||
thumbnails = []
|
||||
size = 128, 128
|
||||
size = "256x256"
|
||||
new_height = 256
|
||||
new_width = 256
|
||||
for page in range(comic.getNumberOfPages()):
|
||||
image_bytes = BytesIO(comic.getPage(page))
|
||||
image = Image.open(image_bytes)
|
||||
image.thumbnail(size)
|
||||
thumbnails.append(image.tobytes())
|
||||
image = Image(file=image_bytes)
|
||||
orig_height = image.height
|
||||
orig_width = image.width
|
||||
if (orig_width/orig_height)*new_height <= new_width:
|
||||
height = int((orig_height/orig_width) * new_width)
|
||||
width = new_width
|
||||
else:
|
||||
width = int((orig_width/orig_height) * new_height)
|
||||
height = new_height
|
||||
image.thumbnail(width, height)
|
||||
thumbnails.append((image.make_blob(), "image/"+image.format))
|
||||
return thumbnails
|
||||
|
||||
|
||||
def open_comic(path):
|
||||
archive = comicarchive.ComicArchive(path, default_image_path="/usb/www/matthew/rpiWebApp/static/icon.png")
|
||||
archive = comicarchive.ComicArchive(path, default_image_path="static/images/icon.png")
|
||||
return archive
|
||||
|
||||
|
||||
|
7
static/bootstrap.min.css
vendored
Normal file
BIN
static/icon.png
Before Width: | Height: | Size: 2.9 KiB |
BIN
static/images/Ace Magazines.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/America's Best Comics.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
static/images/American Mythology Productions.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
static/images/Archie Comics.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
static/images/Aspen MLT.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
static/images/Atlas.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
static/images/Benitez Productions.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/images/Bongo.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
static/images/Boom! Studios.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
static/images/Charlton.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/DC Comics.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/Dark Horse Comics.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
static/images/Disney.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/images/Dynamite Entertainment.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/EC.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
static/images/Eaglemoss Publications.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/images/Europe Comics.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
static/images/Fawcett Publications.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
static/images/Harvey.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
static/images/IDW Publishing.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/Lion Forge Comics.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
static/images/Marvel.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
static/images/New England Comics.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/Prize.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/Quality Comics.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/images/Titan Comics.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/images/Top Shelf.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/images/Vertigo.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/Western Publishing.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
static/images/Wildstorm.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
static/images/icon.png
Normal file
After Width: | Height: | Size: 13 KiB |
13
static/style.css
Normal file
@ -0,0 +1,13 @@
|
||||
.comic-grid {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto auto auto auto;
|
||||
grid-gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.comic-page {
|
||||
max-height: 100vh;
|
||||
max-width: 100vw;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
@ -1,3 +1,12 @@
|
||||
{% for comic in comics %}
|
||||
<li><a href="/comics/{{ comic[2]|urlencode }}?series={{ comic[0]|urlencode }}&seriesYear={{ comic[1] }}">{{ comic[0] }} {{ comic[1] }}</a></li>
|
||||
<div class="col-3" style="padding: 10px">
|
||||
<a href="/comics/{{ comic["publisher"]|urlencode }}?series={{ comic["series"]|urlencode }}&seriesYear={{ comic["seriesYear"] }}">
|
||||
<div class="card">
|
||||
<img class="card-img" src="/comics/get_comic/{{ comic['id'] }}/0/thumbnail">
|
||||
<div class="card-body">
|
||||
{{ comic["series"] }} {{ comic["seriesYear"] }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -3,11 +3,38 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
<link rel="icon" type="image/png" href="../static/icon.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/static/images/icon.png" sizes="32x32">
|
||||
<link rel="stylesheet" href="/static/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar sticky-top navbar-expand navbar-light bg-secondary">
|
||||
<a class="navbar-brand" href="{{ url_for("home") }}">
|
||||
<img src="/static/images/icon.png" height="40px" alt="">
|
||||
</a>
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for("home") }}">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for("comics") }}">Comics</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for("login") }}">
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for("logout") }}">
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% block content %}
|
||||
<p>Hello World!</p>
|
||||
{% endblock %}
|
||||
|
16
templates/comicGallery.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div style="text-align: center">
|
||||
<div class="comic-grid">
|
||||
{% for page_number in range(comic["pageCount"]|int) %}
|
||||
<div style="margin: auto" class="comic-thumbnail card bg-dark text-white">
|
||||
<a href="/comics/{{ comic["publisher"]|urlencode }}?series={{ comic["series"]|urlencode }}&seriesYear={{ comic["seriesYear"] }}&issue={{ comic["issue"]|urlencode }}&pageNumber={{ page_number }}">
|
||||
<img src="/comics/get_comic/{{ comic["id"] }}/{{ page_number }}/thumbnail" alt="" style="display: inline">
|
||||
<p class="card-text">{{ 1+page_number }}/{{ comic["pageCount"] }}</p>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -2,8 +2,8 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<a id="prev-button" href="{{ prev_url }}" style="position: fixed; height: 100%; width: 50%; text-align: center"></a>
|
||||
<a id="next-button" href="{{ next_url }}" style="position: fixed; right: 0; height: 100%; width: 50%; text-align: center"></a>
|
||||
<img id="comic-page" src="data:image/jpeg;base64,{{ page }}" alt="" style="height: 100vh; display: block; margin: auto">
|
||||
<a href="{{ prev_url }}" style="position: fixed; height: 100%; width: 50%;"></a>
|
||||
<a href="{{ next_url }}" style="position: fixed; right: 0; height: 100%; width: 50%;"></a>
|
||||
<img class="comic-page" src="/comics/get_comic/{{ comic["id"] }}/{{ page_number }}" alt="">
|
||||
|
||||
{% endblock %}
|
||||
|
5
templates/home.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
@ -1 +0,0 @@
|
||||
{% extends "base.html" %}
|
24
templates/login.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container w-25 p-3 align-middle" style="margin: auto">
|
||||
<form action="" method="post" style="display: inline">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input class="form-control" name="username" id="username" type="text" placeholder="Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input class="form-control" name="password" id="password" type="password" placeholder="Password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</form>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-danger" role="alert" style="margin-top: 10px">
|
||||
{{ messages[0] }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,3 +1,12 @@
|
||||
{% for publisher in comics %}
|
||||
<li><a href="/comics/{{ publisher }}">{{ publisher }}</a></li>
|
||||
<div class="col-4" style="padding: 10px">
|
||||
<a href="/comics/{{ publisher }}">
|
||||
<div class="card">
|
||||
<img class="card-img" src="/static/images/{{ publisher }}.png">
|
||||
<div class="card-body">
|
||||
{{ publisher }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -1,7 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<ul id="series">
|
||||
{% include "PublisherSeriesList.html" %}
|
||||
</ul>
|
||||
<div class="container col-7">
|
||||
<div class="row justify-content-start">
|
||||
{% include "PublisherSeriesList.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<ul id="publishers">
|
||||
{% include "publisherList.html" %}
|
||||
</ul>
|
||||
<div class="container col-7">
|
||||
<div class="row justify-content-start">
|
||||
{% include "publisherList.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +1,12 @@
|
||||
{% for comic in comics %}
|
||||
<li>
|
||||
<a href="/comics/{{ publisher|urlencode }}?series={{ comic[0]|urlencode }}&seriesYear={{ seriesYear }}&path={{ comic[3]|urlencode }}&pageNumber=0">
|
||||
{{ comic[0] }} {% if comic[1] > 0 %}{{ "#{0:g}".format(comic[1]) }}{% endif %} {% if comic[2] != None %}{{ comic[2] }} {% endif %}
|
||||
<div class="col-3" style="padding: 10px">
|
||||
<a href="/comics/{{ comic["publisher"]|urlencode }}?series={{ comic["series"]|urlencode }}&seriesYear={{ comic["seriesYear"] }}&issue={{ comic["issue"]|urlencode }}">
|
||||
<div class="card">
|
||||
<img class="card-img" src="/comics/get_comic/{{ comic['id'] }}/0/thumbnail">
|
||||
<div class="card-body">
|
||||
{{ comic["series"] }} {% if comic["issue"] > 0 %}{{ "#{0:g}".format(comic["issue"]) }}{% endif %} {% if comic["title"] != None %}{{ comic["title"] }} {% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -1,7 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<ul id="comics">
|
||||
{% include "seriesList.html" %}
|
||||
</ul>
|
||||
<div class="container col-7">
|
||||
<div class="row justify-content-start">
|
||||
{% include "seriesList.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
14
test.py
@ -7,6 +7,8 @@ from comicapi.issuestring import IssueString
|
||||
from urllib import parse
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import datetime, pytz
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
os.environ["UNRAR_LIB_PATH"] = 'C:\\Program Files (x86)\\UnrarDLL\\UnRAR.dll'
|
||||
|
||||
@ -18,14 +20,4 @@ def get_db():
|
||||
return db
|
||||
|
||||
|
||||
row_id = 1
|
||||
comic_id = 0
|
||||
for row in get_db().execute("SELECT pageNumber, ROWID FROM comic_thumbnails ORDER BY ROWID").fetchall():
|
||||
print("ROWID:", row[1])
|
||||
if row[0] == 0:
|
||||
comic_id += 1
|
||||
print("comic id:", comic_id)
|
||||
|
||||
get_db().execute("UPDATE comic_thumbnails SET id=? WHERE ROWID=?", (comic_id, row[1]))
|
||||
row_id += 1
|
||||
get_db().commit()
|
||||
print(generate_password_hash("guanfacine"))
|
||||
|