Replace all double blank lines with single, improve hash headers.

There was always some semblance that two blank lines has some kind of
meaning or structure that's different from single blank lines, but
in reality it was mostly arbitrary and I can't stand to look at it
any more.
This commit is contained in:
voussoir 2020-09-19 03:13:23 -07:00
parent a7cc6d2383
commit adb1d0ef39
31 changed files with 38 additions and 71 deletions

View file

@ -129,7 +129,6 @@ CREATE INDEX IF NOT EXISTS index_tags_name on tags(name);
CREATE INDEX IF NOT EXISTS index_tags_author_id on tags(author_id); CREATE INDEX IF NOT EXISTS index_tags_author_id on tags(author_id);
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS album_associated_directories( CREATE TABLE IF NOT EXISTS album_associated_directories(
albumid TEXT NOT NULL, albumid TEXT NOT NULL,

View file

@ -4,7 +4,6 @@ import warnings
from . import exceptions from . import exceptions
def _get_relevant_photodb(instance): def _get_relevant_photodb(instance):
from . import objects from . import objects
if isinstance(instance, objects.ObjectBase): if isinstance(instance, objects.ObjectBase):

View file

@ -44,8 +44,8 @@ class EtiquetteException(Exception, metaclass=ErrorTypeAdder):
def __str__(self): def __str__(self):
return self.error_type + '\n' + self.error_message return self.error_type + '\n' + self.error_message
# NO SUCH ##########################################################################################
# NO SUCH
class NoSuch(EtiquetteException): class NoSuch(EtiquetteException):
pass pass
@ -70,8 +70,8 @@ class NoSuchTag(NoSuch):
class NoSuchUser(NoSuch): class NoSuchUser(NoSuch):
error_message = 'User "{}" does not exist.' error_message = 'User "{}" does not exist.'
# EXISTS ###########################################################################################
# EXISTS
class Exists(EtiquetteException): class Exists(EtiquetteException):
pass pass
@ -106,8 +106,8 @@ class UserExists(Exists):
self.user = user self.user = user
EtiquetteException.__init__(self, user) EtiquetteException.__init__(self, user)
# TAG ERRORS #######################################################################################
# TAG ERRORS
class CantGroupSelf(EtiquetteException): class CantGroupSelf(EtiquetteException):
error_message = 'Cannot group {} into itself.' error_message = 'Cannot group {} into itself.'
@ -126,8 +126,8 @@ class TagTooLong(EtiquetteException):
class TagTooShort(EtiquetteException): class TagTooShort(EtiquetteException):
error_message = 'Tag "{}" has too few valid characters.' error_message = 'Tag "{}" has too few valid characters.'
# USER ERRORS ######################################################################################
# USER ERRORS
class AlreadySignedIn(EtiquetteException): class AlreadySignedIn(EtiquetteException):
error_message = 'You\'re already signed in.' error_message = 'You\'re already signed in.'
@ -155,16 +155,16 @@ class DisplayNameTooLong(EtiquetteException):
class WrongLogin(EtiquetteException): class WrongLogin(EtiquetteException):
error_message = 'Wrong username-password combination.' error_message = 'Wrong username-password combination.'
# SQL ERRORS #######################################################################################
# SQL ERRORS
class BadSQL(EtiquetteException): class BadSQL(EtiquetteException):
pass pass
class BadTable(BadSQL): class BadTable(BadSQL):
error_message = 'Table "{}" does not exist.' error_message = 'Table "{}" does not exist.'
# GENERAL ERRORS ###################################################################################
# GENERAL ERRORS
class BadDataDirectory(EtiquetteException): class BadDataDirectory(EtiquetteException):
''' '''
Raised by PhotoDB __init__ if the requested data_directory is invalid. Raised by PhotoDB __init__ if the requested data_directory is invalid.

View file

@ -19,7 +19,6 @@ from . import decorators
from . import exceptions from . import exceptions
from . import helpers from . import helpers
BAIL = sentinel.Sentinel('BAIL') BAIL = sentinel.Sentinel('BAIL')
def normalize_db_row(db_row, table): def normalize_db_row(db_row, table):
@ -27,7 +26,6 @@ def normalize_db_row(db_row, table):
db_row = dict(zip(constants.SQL_COLUMNS[table], db_row)) db_row = dict(zip(constants.SQL_COLUMNS[table], db_row))
return db_row return db_row
class ObjectBase: class ObjectBase:
def __init__(self, photodb): def __init__(self, photodb):
super().__init__() super().__init__()
@ -71,7 +69,6 @@ class ObjectBase:
return None return None
return self.photodb.get_user(id=self.author_id) return self.photodb.get_user(id=self.author_id)
class GroupableMixin: class GroupableMixin:
group_getter = None group_getter = None
group_getter_many = None group_getter_many = None
@ -227,7 +224,6 @@ class GroupableMixin:
more_parents = more_parents.difference(seen) more_parents = more_parents.difference(seen)
todo.extend(more_parents) todo.extend(more_parents)
class Album(ObjectBase, GroupableMixin): class Album(ObjectBase, GroupableMixin):
table = 'albums' table = 'albums'
group_table = 'album_group_rel' group_table = 'album_group_rel'
@ -546,7 +542,6 @@ class Album(ObjectBase, GroupableMixin):
for child in children: for child in children:
yield from child.walk_photos() yield from child.walk_photos()
class Bookmark(ObjectBase): class Bookmark(ObjectBase):
table = 'bookmarks' table = 'bookmarks'
@ -1168,7 +1163,6 @@ class Photo(ObjectBase):
self.photodb.sql_update(table='photos', pairs=data, where_key='id') self.photodb.sql_update(table='photos', pairs=data, where_key='id')
self.searchhidden = searchhidden self.searchhidden = searchhidden
class Tag(ObjectBase, GroupableMixin): class Tag(ObjectBase, GroupableMixin):
''' '''
A Tag, which can be applied to Photos for organization. A Tag, which can be applied to Photos for organization.
@ -1465,7 +1459,6 @@ class Tag(ObjectBase, GroupableMixin):
self.name = new_name self.name = new_name
self._uncache() self._uncache()
class User(ObjectBase): class User(ObjectBase):
''' '''
A dear friend of ours. A dear friend of ours.
@ -1529,7 +1522,6 @@ class User(ObjectBase):
self.photodb.sql_update(table='users', pairs=data, where_key='id') self.photodb.sql_update(table='users', pairs=data, where_key='id')
self._display_name = display_name self._display_name = display_name
class WarningBag: class WarningBag:
def __init__(self): def __init__(self):
self.warnings = set() self.warnings = set()

View file

@ -24,10 +24,7 @@ from . import objects
from . import searchhelpers from . import searchhelpers
from . import tag_export from . import tag_export
#################################################################################################### ####################################################################################################
####################################################################################################
class PDBAlbumMixin: class PDBAlbumMixin:
def __init__(self): def __init__(self):
@ -143,6 +140,7 @@ class PDBAlbumMixin:
to_check.update(album.get_parents()) to_check.update(album.get_parents())
album.delete() album.delete()
####################################################################################################
class PDBBookmarkMixin: class PDBBookmarkMixin:
def __init__(self): def __init__(self):
@ -185,6 +183,7 @@ class PDBBookmarkMixin:
return bookmark return bookmark
####################################################################################################
class PDBCacheManagerMixin: class PDBCacheManagerMixin:
_THING_CLASSES = { _THING_CLASSES = {
@ -380,6 +379,7 @@ class PDBCacheManagerMixin:
thing_cache[thing.id] = thing thing_cache[thing.id] = thing
yield thing yield thing
####################################################################################################
class PDBPhotoMixin: class PDBPhotoMixin:
def __init__(self): def __init__(self):
@ -896,6 +896,7 @@ class PDBPhotoMixin:
end_time = time.time() end_time = time.time()
print('Search took:', end_time - start_time) print('Search took:', end_time - start_time)
####################################################################################################
class PDBSQLMixin: class PDBSQLMixin:
def __init__(self): def __init__(self):
@ -1050,6 +1051,7 @@ class PDBSQLMixin:
query = f'UPDATE {table} {qmarks}' query = f'UPDATE {table} {qmarks}'
self.sql_execute(query, bindings) self.sql_execute(query, bindings)
####################################################################################################
class PDBTagMixin: class PDBTagMixin:
def __init__(self): def __init__(self):
@ -1196,6 +1198,7 @@ class PDBTagMixin:
) )
return tagname return tagname
####################################################################################################
class PDBUserMixin: class PDBUserMixin:
def __init__(self): def __init__(self):
@ -1359,6 +1362,7 @@ class PDBUserMixin:
return self.get_cached_instance('user', data) return self.get_cached_instance('user', data)
####################################################################################################
class PDBUtilMixin: class PDBUtilMixin:
def __init__(self): def __init__(self):
@ -1625,6 +1629,7 @@ class PDBUtilMixin:
return output_notes return output_notes
####################################################################################################
class PhotoDB( class PhotoDB(
PDBAlbumMixin, PDBAlbumMixin,
@ -1820,7 +1825,6 @@ class PhotoDB(
with open(self.config_filepath.absolute_path, 'w', encoding='utf-8') as handle: with open(self.config_filepath.absolute_path, 'w', encoding='utf-8') as handle:
handle.write(json.dumps(self.config, indent=4, sort_keys=True)) handle.write(json.dumps(self.config, indent=4, sort_keys=True))
if __name__ == '__main__': if __name__ == '__main__':
p = PhotoDB() p = PhotoDB()
print(p) print(p)

View file

@ -11,7 +11,6 @@ from . import objects
from voussoirkit import expressionmatch from voussoirkit import expressionmatch
from voussoirkit import sqlhelpers from voussoirkit import sqlhelpers
def expand_mmf(tag_musts, tag_mays, tag_forbids): def expand_mmf(tag_musts, tag_mays, tag_forbids):
''' '''
In order to generate SQL queries for `tagid IN (options)`, we need to In order to generate SQL queries for `tagid IN (options)`, we need to

View file

@ -6,7 +6,6 @@ from voussoirkit import cacheclass
import etiquette import etiquette
def cached_endpoint(max_age): def cached_endpoint(max_age):
''' '''
The cached_endpoint decorator can be used on slow endpoints that don't need The cached_endpoint decorator can be used on slow endpoints that don't need
@ -63,7 +62,6 @@ def cached_endpoint(max_age):
return wrapped return wrapped
return wrapper return wrapper
class FileCacheManager: class FileCacheManager:
''' '''
The FileCacheManager serves ETag and Cache-Control headers for disk files. The FileCacheManager serves ETag and Cache-Control headers for disk files.
@ -116,7 +114,6 @@ class FileCacheManager:
return server_value.get_headers() return server_value.get_headers()
class CacheFile: class CacheFile:
def __init__(self, filepath, max_age): def __init__(self, filepath, max_age):
self.filepath = filepath self.filepath = filepath

View file

@ -15,6 +15,8 @@ from . import jinja_filters
from . import jsonify from . import jsonify
from . import sessions from . import sessions
# Runtime init #####################################################################################
root_dir = pathclass.Path(__file__).parent.parent root_dir = pathclass.Path(__file__).parent.parent
TEMPLATE_DIR = root_dir.with_child('templates') TEMPLATE_DIR = root_dir.with_child('templates')
@ -46,6 +48,8 @@ file_cache_manager = caching.FileCacheManager(
max_age=BROWSER_CACHE_DURATION, max_age=BROWSER_CACHE_DURATION,
) )
# Response wrappers ################################################################################
# Flask provides decorators for before_request and after_request, but not for # Flask provides decorators for before_request and after_request, but not for
# wrapping the whole request. The decorators I am using need to wrap the whole # wrapping the whole request. The decorators I am using need to wrap the whole
# request, either to catch exceptions (which don't get passed through # request, either to catch exceptions (which don't get passed through
@ -98,6 +102,8 @@ def after_request(response):
return response return response
# P functions ######################################################################################
def P_wrapper(function): def P_wrapper(function):
def P_wrapped(thingid, response_type): def P_wrapped(thingid, response_type):
if response_type not in {'html', 'json'}: if response_type not in {'html', 'json'}:
@ -160,6 +166,11 @@ def P_user(username):
def P_user_id(user_id): def P_user_id(user_id):
return P.get_user(id=user_id) return P.get_user(id=user_id)
# Other functions ##################################################################################
def back_url():
return request.args.get('goto') or request.referrer or '/'
def render_template(request, template_name, **kwargs): def render_template(request, template_name, **kwargs):
session = session_manager.get(request) session = session_manager.get(request)
@ -186,9 +197,6 @@ def render_template(request, template_name, **kwargs):
return response return response
def back_url():
return request.args.get('goto') or request.referrer or '/'
def send_file(filepath, override_mimetype=None): def send_file(filepath, override_mimetype=None):
''' '''
Range-enabled file sending. Range-enabled file sending.

View file

@ -6,7 +6,6 @@ import etiquette
from . import jsonify from . import jsonify
def catch_etiquette_exception(function): def catch_etiquette_exception(function):
''' '''
If an EtiquetteException is raised, automatically catch it and convert it If an EtiquetteException is raised, automatically catch it and convert it

View file

@ -10,7 +10,6 @@ from .. import jsonify
site = common.site site = common.site
session_manager = common.session_manager session_manager = common.session_manager
# Individual albums ################################################################################ # Individual albums ################################################################################
@site.route('/album/<album_id>') @site.route('/album/<album_id>')

View file

@ -6,7 +6,6 @@ from .. import common
site = common.site site = common.site
session_manager = common.session_manager session_manager = common.session_manager
#################################################################################################### ####################################################################################################
@site.route('/') @site.route('/')

View file

@ -9,7 +9,6 @@ from .. import jsonify
site = common.site site = common.site
session_manager = common.session_manager session_manager = common.session_manager
# Individual bookmarks ############################################################################# # Individual bookmarks #############################################################################
@site.route('/bookmark/<bookmark_id>.json') @site.route('/bookmark/<bookmark_id>.json')

View file

@ -15,7 +15,6 @@ site = common.site
session_manager = common.session_manager session_manager = common.session_manager
photo_download_zip_tokens = cacheclass.Cache(maxlen=100) photo_download_zip_tokens = cacheclass.Cache(maxlen=100)
# Individual photos ################################################################################ # Individual photos ################################################################################
@site.route('/photo/<photo_id>') @site.route('/photo/<photo_id>')

View file

@ -10,7 +10,6 @@ from .. import jsonify
site = common.site site = common.site
session_manager = common.session_manager session_manager = common.session_manager
# Individual tags ################################################################################## # Individual tags ##################################################################################
@site.route('/tags/<specific_tag>') @site.route('/tags/<specific_tag>')

View file

@ -10,7 +10,6 @@ from .. import sessions
site = common.site site = common.site
session_manager = common.session_manager session_manager = common.session_manager
# Individual users ################################################################################# # Individual users #################################################################################
@site.route('/user/<username>') @site.route('/user/<username>')

View file

@ -21,6 +21,8 @@ def register_all(site):
for function in global_functions: for function in global_functions:
site.jinja_env.globals[function.__name__] = function site.jinja_env.globals[function.__name__] = function
####################################################################################################
@filter_function @filter_function
def bytestring(x): def bytestring(x):
try: try:
@ -41,14 +43,6 @@ def file_link(photo, short=False):
basename = jinja2.filters.do_urlencode(photo.basename) basename = jinja2.filters.do_urlencode(photo.basename)
return f'/file/{photo.id}/{basename}' return f'/file/{photo.id}/{basename}'
@global_function
def make_attributes(*booleans, **keyvalues):
keyvalues = {key: value for (key, value) in keyvalues.items() if value is not None}
attributes = [f'{key}="{jinja2.filters.escape(value)}"' for (key, value) in keyvalues.items()]
attributes.extend(booleans)
attributes = ' '.join(attributes)
return attributes
@filter_function @filter_function
def sort_tags(tags): def sort_tags(tags):
tags = sorted(tags, key=lambda x: x.name) tags = sorted(tags, key=lambda x: x.name)
@ -72,3 +66,13 @@ def users_to_usernames(users):
if not users: if not users:
return [] return []
return [user.username for user in users] return [user.username for user in users]
####################################################################################################
@global_function
def make_attributes(*booleans, **keyvalues):
keyvalues = {key: value for (key, value) in keyvalues.items() if value is not None}
attributes = [f'{key}="{jinja2.filters.escape(value)}"' for (key, value) in keyvalues.items()]
attributes.extend(booleans)
attributes = ' '.join(attributes)
return attributes

View file

@ -1,7 +1,6 @@
import flask import flask
import json import json
def make_json_response(j, *args, **kwargs): def make_json_response(j, *args, **kwargs):
dumped = json.dumps(j) dumped = json.dumps(j)
response = flask.Response(dumped, *args, **kwargs) response = flask.Response(dumped, *args, **kwargs)

View file

@ -7,7 +7,6 @@ from voussoirkit import cacheclass
import etiquette import etiquette
SESSION_MAX_AGE = 86400 SESSION_MAX_AGE = 86400
REQUEST_TYPES = (flask.Request, werkzeug.wrappers.Request, werkzeug.local.LocalProxy) REQUEST_TYPES = (flask.Request, werkzeug.wrappers.Request, werkzeug.local.LocalProxy)
RESPONSE_TYPES = (flask.Response, werkzeug.wrappers.Response) RESPONSE_TYPES = (flask.Response, werkzeug.wrappers.Response)
@ -31,7 +30,6 @@ def _normalize_token(token):
raise TypeError('Unsupported token normalization', type(token)) raise TypeError('Unsupported token normalization', type(token))
return token return token
class SessionManager: class SessionManager:
def __init__(self, maxlen=None): def __init__(self, maxlen=None):
self.sessions = cacheclass.Cache(maxlen=maxlen) self.sessions = cacheclass.Cache(maxlen=maxlen)
@ -106,7 +104,6 @@ class SessionManager:
except KeyError: except KeyError:
pass pass
class Session: class Session:
def __init__(self, request, user): def __init__(self, request, user):
self.token = _normalize_token(request) self.token = _normalize_token(request)

View file

@ -90,7 +90,6 @@ h2, h3
{{shared_css()}} {{shared_css()}}
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body" class="sticky_side_right sticky_bottom_right"> <div id="content_body" class="sticky_side_right sticky_bottom_right">
@ -121,7 +120,6 @@ h2, h3
</div> </div>
</body> </body>
<script id="album_listing_script" type="text/javascript"> <script id="album_listing_script" type="text/javascript">
const ALBUM_ID = undefined; const ALBUM_ID = undefined;
</script> </script>
@ -151,7 +149,6 @@ const ALBUM_ID = undefined;
{{shared_css()}} {{shared_css()}}
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body" class="sticky_side_right sticky_bottom_right"> <div id="content_body" class="sticky_side_right sticky_bottom_right">
@ -289,7 +286,6 @@ const ALBUM_ID = undefined;
</div> </div>
</body> </body>
<script id="album_individual_script" type="text/javascript"> <script id="album_individual_script" type="text/javascript">
const ALBUM_ID = "{{album.id}}"; const ALBUM_ID = "{{album.id}}";

View file

@ -44,7 +44,6 @@
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body"> <div id="content_body">
@ -95,7 +94,6 @@
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
function create_bookmark_form() function create_bookmark_form()
{ {

View file

@ -85,7 +85,6 @@
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body" class="sticky_side_right sticky_bottom_right"> <div id="content_body" class="sticky_side_right sticky_bottom_right">
@ -129,7 +128,6 @@
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
const divs = {}; const divs = {};
const needed = new Set(); const needed = new Set();

View file

@ -69,7 +69,6 @@ form h2
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body"> <div id="content_body">
@ -92,7 +91,6 @@ form h2
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
const message_area = document.getElementById("message_area"); const message_area = document.getElementById("message_area");

View file

@ -129,7 +129,6 @@
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body"> <div id="content_body">
@ -191,7 +190,6 @@
</li> </li>
</ul> </ul>
<!-- CONTAINING ALBUMS --> <!-- CONTAINING ALBUMS -->
{% set albums = photo.get_containing_albums() %} {% set albums = photo.get_containing_albums() %}
{% if albums %} {% if albums %}
@ -262,7 +260,6 @@
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
const PHOTO_ID = "{{photo.id}}"; const PHOTO_ID = "{{photo.id}}";

View file

@ -45,7 +45,6 @@ body, .nice_link
</style> </style>
</head> </head>
<body> <body>
<span id="motd">{{motd}}</span> <span id="motd">{{motd}}</span>
<a class="nice_link" href="/search">Search</a> <a class="nice_link" href="/search">Search</a>
@ -60,7 +59,6 @@ body, .nice_link
<a class="plain_link" href="http://www.github.com/voussoir/etiquette">GitHub</a> <a class="plain_link" href="http://www.github.com/voussoir/etiquette">GitHub</a>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
</script> </script>
</html> </html>

View file

@ -175,7 +175,6 @@
{% endmacro %} {% endmacro %}
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body"> <div id="content_body">
@ -363,7 +362,6 @@
{{clipboard_tray.clipboard_tray()}} {{clipboard_tray.clipboard_tray()}}
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
/* /*
These values should match those of the server itself. The purpose of this dict These values should match those of the server itself. The purpose of this dict

View file

@ -88,7 +88,6 @@ h2, h3
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body" class="sticky_side_right sticky_bottom_right"> <div id="content_body" class="sticky_side_right sticky_bottom_right">
@ -252,7 +251,6 @@ h2, h3
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
let SPECIFIC_TAG = "{{specific_tag.name}}"; let SPECIFIC_TAG = "{{specific_tag.name}}";

View file

@ -15,7 +15,6 @@
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body"> <div id="content_body">
@ -23,7 +22,6 @@
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
</script> </script>
</html> </html>

View file

@ -20,7 +20,6 @@
</style> </style>
</head> </head>
<body> <body>
{{header.make_header(session=session)}} {{header.make_header(session=session)}}
<div id="content_body"> <div id="content_body">
@ -31,7 +30,6 @@
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
</script> </script>
</html> </html>

View file

@ -36,6 +36,7 @@ def photag(photo_id):
get = P.get_tag get = P.get_tag
################################################################################ ################################################################################
def erepl_argparse(args): def erepl_argparse(args):
if args.exec_statement: if args.exec_statement:
exec(args.exec_statement) exec(args.exec_statement)

View file

@ -359,7 +359,6 @@ def upgrade_all(data_directory):
current_version = version_number current_version = version_number
print('Upgrades finished.') print('Upgrades finished.')
def upgrade_all_argparse(args): def upgrade_all_argparse(args):
return upgrade_all(data_directory=args.data_directory) return upgrade_all(data_directory=args.data_directory)

View file

@ -73,7 +73,6 @@ CREATE INDEX IF NOT EXISTS index_tags_name on tags(name);
CREATE INDEX IF NOT EXISTS index_tags_author_id on tags(author_id); CREATE INDEX IF NOT EXISTS index_tags_author_id on tags(author_id);
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS album_associated_directories( CREATE TABLE IF NOT EXISTS album_associated_directories(
albumid TEXT NOT NULL, albumid TEXT NOT NULL,