...
 
Commits (3)
......@@ -5,4 +5,5 @@ cover/
*.egg-info/
dist/
build/
test-res/
\ No newline at end of file
test-res/
toktokkie/web/cache
\ No newline at end of file
......@@ -50,7 +50,9 @@ if __name__ == "__main__":
"manga-dl",
"musicbrainzngs",
"jsonschema",
"Pillow"
"Pillow",
"flask",
"flask_sqlalchemy"
],
extras_require={
"gui": ["PyQt5"]
......
......@@ -20,15 +20,18 @@ LICENSE"""
import os
import sys
import logging
from typing import Dict, Any
from typing import Dict, Any, List, Optional
from puffotter.prompt import yn_prompt
from toktokkie.renaming.Renamer import Renamer
from toktokkie.iconizing.Iconizer import Iconizer, Procedure
from toktokkie.metadata.functions import get_metadata, create_metadata
from toktokkie.metadata.Metadata import Metadata
from toktokkie.metadata.MediaType import MediaType
from toktokkie.exceptions import MissingMetadata, InvalidUpdateInstructions, \
MissingUpdateInstructions
MissingUpdateInstructions, InvalidMetadata
from toktokkie.update import updaters
from toktokkie.check.map import checker_map
from puffotter.os import listdir
class Directory:
......@@ -60,7 +63,7 @@ class Directory:
if not os.path.isfile(self.metadata_file):
raise MissingMetadata(self.metadata_file + " missing")
self.metadata = get_metadata(self.path)
self.metadata = get_metadata(self.path) # type: Metadata
if not os.path.isdir(self.metadata.icon_directory):
os.makedirs(self.metadata.icon_directory)
......@@ -167,3 +170,42 @@ class Directory:
"Update instructions for {} are invalid: {}"
.format(self.path, e)
)
@classmethod
def load_directories(
cls,
parent_dir: str,
restrictions: Optional[List[MediaType]] = None
) -> List["Directory"]:
"""
Loads all media directories in a directory
:param parent_dir: The directory to search for media directories
:param restrictions: Restricts the found media directories to media directories
with a specific media type
:return: The list of Media Directories
"""
if restrictions is None:
restrictions = [x for x in MediaType]
logger = logging.getLogger(cls.__name__)
directories = [] # type: List[Directory]
for name, path in listdir(parent_dir):
try:
logger.debug("Loading directory {}".format(path))
directory = cls(path)
if directory.metadata.media_type() not in restrictions:
logger.info(
"Skipping directory {} with incorrect type {}"
.format(path, directory.metadata.media_type())
)
continue
directories.append(directory)
except MissingMetadata:
logger.warning("{} has no metadata file.".format(path))
except InvalidMetadata as e:
logger.warning("{}'s metadata is invalid. ({})".format(
path, e
))
return directories
......@@ -23,6 +23,7 @@ import argparse
from PIL import Image
from bs4 import BeautifulSoup
from typing import Dict, List
from toktokkie.Directory import Directory
from toktokkie.scripts.Command import Command
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.MediaType import MediaType
......@@ -57,7 +58,7 @@ class AlbumArtFetchCommand(Command):
Executes the commands
:return: None
"""
for directory in self.load_directories(
for directory in Directory.load_directories(
self.args.directories, restrictions=[MediaType.MUSIC_ARTIST]
):
metadata = directory.metadata # type: MusicArtist
......
......@@ -19,6 +19,7 @@ LICENSE"""
import argparse
from subprocess import Popen
from toktokkie.Directory import Directory
from toktokkie.scripts.Command import Command
from toktokkie.metadata.MediaType import MediaType
......@@ -58,7 +59,7 @@ class AnilistOpenCommand(Command):
MediaType.TV_SERIES,
MediaType.MOVIE
]
directories = self.load_directories(
directories = Directory.load_directories(
self.args.directories, anilist_types
)
for directory in directories:
......
......@@ -21,6 +21,7 @@ import os
import argparse
import shutil
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
from puffotter.os import makedirs
......@@ -60,7 +61,7 @@ class ArchiveCommand(Command):
if output_path is not None:
makedirs(output_path)
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
if output_path is None:
archive_path = directory.path + ".archive"
else:
......
......@@ -21,6 +21,7 @@ import tvdb_api
import argparse
from toktokkie.scripts.Command import Command
from anime_list_apis.api.AnilistApi import AnilistApi
from toktokkie.Directory import Directory
class CheckCommand(Command):
......@@ -69,5 +70,5 @@ class CheckCommand(Command):
config["anilist_anime_list"] = anime_list
config["anilist_manga_list"] = manga_list
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
directory.check(self.args.warnings, self.args.fix, config)
......@@ -61,40 +61,6 @@ class Command:
"""
raise NotImplementedError()
def load_directories(
self,
paths: List[str],
restrictions: Optional[List[MediaType]] = None
) -> List[Directory]:
"""
Loads directory objects from a list of file paths
:param paths: The paths to convert into objects
:param restrictions: Limits the media type of directories
:return: The converted directories
"""
directories = [] # type: List[Directory]
for path in paths:
try:
self.logger.debug("Loading directory {}".format(path))
directory = Directory(path)
if restrictions is not None:
if directory.metadata.media_type() not in restrictions:
self.logger.info(
"Skipping directory {} with incorrect type {}"
.format(path, directory.metadata.media_type())
)
continue
directories.append(directory)
except MissingMetadata:
self.logger.warning("{} has no metadata file.".format(path))
except InvalidMetadata as e:
self.logger.warning("{}'s metadata is invalid. ({})".format(
path, e
))
return directories
@staticmethod
def add_directories_arg(parser: argparse.ArgumentParser):
"""
......
......@@ -20,6 +20,7 @@ LICENSE"""
import argparse
from toktokkie.iconizing import procedures, default_procedure
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
class IconizeCommand(Command):
......@@ -57,7 +58,7 @@ class IconizeCommand(Command):
lambda x: x.name == self.args.procedure, procedures
))[0]
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
try:
directory.iconize(procedure)
except ValueError:
......
......@@ -77,10 +77,10 @@ class MusicMergeCommand(Command):
except MissingMetadata:
sources += [x[1] for x in listdir(path)]
target_artists = self.load_directories(
target_artists = Directory.load_directories(
targets, restrictions=[MediaType.MUSIC_ARTIST]
)
source_artists = self.load_directories(
source_artists = Directory.load_directories(
sources, restrictions=[MediaType.MUSIC_ARTIST]
)
......
......@@ -21,6 +21,7 @@ import os
import argparse
import mutagen.id3
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
from toktokkie.metadata.MediaType import MediaType
from toktokkie.metadata.types.MusicArtist import MusicArtist
......@@ -53,7 +54,7 @@ class MusicTagCommand(Command):
Executes the commands
:return: None
"""
for directory in self.load_directories(
for directory in Directory.load_directories(
self.args.directories, restrictions=[MediaType.MUSIC_ARTIST]
):
music_metadata = directory.metadata # type: MusicArtist
......
......@@ -21,6 +21,7 @@ import os
import argparse
from puffotter.os import listdir, get_ext
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
class PlaylistCreateCommand(Command):
......@@ -58,7 +59,7 @@ class PlaylistCreateCommand(Command):
playlist_files = []
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
for album, album_path in listdir(directory.path, no_files=True):
for song, song_path in listdir(album_path, no_dirs=True):
if get_ext(song) in music_exts:
......
......@@ -19,6 +19,7 @@ LICENSE"""
import argparse
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
class PrintCommand(Command):
......@@ -47,5 +48,5 @@ class PrintCommand(Command):
Executes the commands
:return: None
"""
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
print(directory.metadata)
......@@ -19,6 +19,7 @@ LICENSE"""
import argparse
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
class RenameCommand(Command):
......@@ -49,7 +50,7 @@ class RenameCommand(Command):
Executes the commands
:return: None
"""
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
try:
directory.rename(self.args.noconfirm)
except ValueError:
......
......@@ -22,6 +22,7 @@ import argparse
from subprocess import Popen
from toktokkie.scripts.Command import Command
from toktokkie.metadata.MediaType import MediaType
from toktokkie.Directory import Directory
class SetMangaCoverCommand(Command):
......@@ -50,7 +51,7 @@ class SetMangaCoverCommand(Command):
Executes the commands
:return: None
"""
for directory in self.load_directories(
for directory in Directory.load_directories(
self.args.directories, restrictions=[MediaType.MANGA]
):
......
......@@ -19,6 +19,7 @@ LICENSE"""
import argparse
from toktokkie.scripts.Command import Command
from toktokkie.Directory import Directory
class UpdateCommand(Command):
......@@ -69,5 +70,5 @@ class UpdateCommand(Command):
Executes the commands
:return: None
"""
for directory in self.load_directories(self.args.directories):
for directory in Directory.load_directories(self.args.directories):
directory.update(dict(self.args.__dict__))
......@@ -18,7 +18,7 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
import argparse
from toktokkie.web.routes import app
from toktokkie.web.run import run_web
from toktokkie.scripts.Command import Command
......@@ -48,4 +48,4 @@ class WebStartCommand(Command):
Executes the commands
:return: None
"""
app.run(port=1234)
run_web()
......@@ -19,6 +19,37 @@ LICENSE"""
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from puffotter.os import makedirs
root_path = os.path.join(os.path.dirname(os.path.abspath(__file__)))
config_path = os.path.join(os.path.expanduser("~"), ".config/toktokkie")
sqlite_uri = "sqlite:///" + os.path.join(config_path, "web.models")
makedirs(config_path)
app = Flask("toktokkie", root_path=root_path)
db = SQLAlchemy()
app.config["SQLALCHEMY_DATABASE_URI"] = sqlite_uri
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
# noinspection PyUnresolvedReferences
def init_db():
"""
Initializes the database
:return: None
"""
with app.app_context():
from toktokkie.web.models.MediaLocation import MediaLocation
db.create_all()
# noinspection PyUnresolvedReferences
def init_routes():
"""
Initializes the app routes
:return:
"""
import toktokkie.web.routes
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
from toktokkie.web import db
class MediaLocation(db.Model):
"""
Database table that stores media locations
"""
__tablename__ = "media_locations"
"""
The table name
"""
id = db.Column(
db.Integer,
primary_key=True,
autoincrement=True,
nullable=False
)
"""
The ID of the media location
"""
path = db.Column(
db.String(255),
unique=True,
nullable=False
)
"""
The path to the media location
"""
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
......@@ -17,10 +17,36 @@ You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
import os
from flask import render_template, Response, request
from puffotter.os import get_ext
from toktokkie.web import app
from flask import render_template
from toktokkie.Directory import Directory
from toktokkie.web.models.MediaLocation import MediaLocation
@app.route('/')
@app.route("/")
def root():
return render_template("index.html")
paths = [x.path for x in MediaLocation.query.all()]
media_dirs = []
for path in paths:
media_dirs += Directory.load_directories(path)
return render_template("list.html", media_dirs=media_dirs)
@app.route("/image/<image_format>")
def image(image_format: str) -> Response:
"""
Sends an image file from the local file system
:return: A PNG image read from a local file
"""
path = request.args.get("path")
ext = get_ext(path)
if path is None or not os.path.isfile(path) or ext != image_format:
return Response(status=404)
else:
with open(path, "rb") as f:
return Response(f.read(), mimetype="image/" + image_format)
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
from toktokkie.web import app, init_routes, init_db
def run_web():
"""
Starts the web app using flask's built-in server
:return: None
"""
init_db()
init_routes()
app.run(port=1234)
toktokkie
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>List</title>
</head>
<body>
<ul>
{% for directory in media_dirs %}
<li>{{ directory.metadata.name }}
<img src="{{ url_for('image') + '?path=' + directory.metadata.get_icon_file('main') }}">
</li>
{% endfor %}
</ul>
</body>
</html>
\ No newline at end of file