mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-18 06:00:32 +00:00
Improved whitespace removal
Testresults
This commit is contained in:
parent
5f81084e66
commit
87b82424d5
22
cps/admin.py
22
cps/admin.py
@ -54,6 +54,7 @@ from .services.worker import WorkerThread
|
|||||||
from .usermanagement import user_login_required
|
from .usermanagement import user_login_required
|
||||||
from .babel import get_available_translations, get_available_locale, get_user_locale_language
|
from .babel import get_available_translations, get_available_locale, get_user_locale_language
|
||||||
from . import debug_info
|
from . import debug_info
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -463,9 +464,9 @@ def edit_list_user(param):
|
|||||||
if 'value[]' in vals:
|
if 'value[]' in vals:
|
||||||
setattr(user, param, prepare_tags(user, vals['action'][0], param, vals['value[]']))
|
setattr(user, param, prepare_tags(user, vals['action'][0], param, vals['value[]']))
|
||||||
else:
|
else:
|
||||||
setattr(user, param, vals['value'].strip())
|
setattr(user, param, strip_whitespaces(vals['value']))
|
||||||
else:
|
else:
|
||||||
vals['value'] = vals['value'].strip()
|
vals['value'] = strip_whitespaces(vals['value'])
|
||||||
if param == 'name':
|
if param == 'name':
|
||||||
if user.name == "Guest":
|
if user.name == "Guest":
|
||||||
raise Exception(_("Guest Name can't be changed"))
|
raise Exception(_("Guest Name can't be changed"))
|
||||||
@ -690,7 +691,7 @@ def delete_domain():
|
|||||||
def list_domain(allow):
|
def list_domain(allow):
|
||||||
answer = ub.session.query(ub.Registration).filter(ub.Registration.allow == allow).all()
|
answer = ub.session.query(ub.Registration).filter(ub.Registration.allow == allow).all()
|
||||||
json_dumps = json.dumps([{"domain": r.domain.replace('%', '*').replace('_', '?'), "id": r.id} for r in answer])
|
json_dumps = json.dumps([{"domain": r.domain.replace('%', '*').replace('_', '?'), "id": r.id} for r in answer])
|
||||||
js = json.dumps(json_dumps.replace('"', "'")).lstrip('"').strip('"')
|
js = json.dumps(json_dumps.replace('"', "'")).strip('"')
|
||||||
response = make_response(js.replace("'", '"'))
|
response = make_response(js.replace("'", '"'))
|
||||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||||
return response
|
return response
|
||||||
@ -1100,7 +1101,7 @@ def _config_checkbox_int(to_save, x):
|
|||||||
|
|
||||||
|
|
||||||
def _config_string(to_save, x):
|
def _config_string(to_save, x):
|
||||||
return config.set_from_dictionary(to_save, x, lambda y: y.strip().strip(u'\u200B\u200C\u200D\ufeff') if y else y)
|
return config.set_from_dictionary(to_save, x, lambda y: strip_whitespaces(y) if y else y)
|
||||||
|
|
||||||
|
|
||||||
def _configuration_gdrive_helper(to_save):
|
def _configuration_gdrive_helper(to_save):
|
||||||
@ -1311,9 +1312,9 @@ def update_mailsettings():
|
|||||||
if to_save.get("mail_password_e", ""):
|
if to_save.get("mail_password_e", ""):
|
||||||
_config_string(to_save, "mail_password_e")
|
_config_string(to_save, "mail_password_e")
|
||||||
_config_int(to_save, "mail_size", lambda y: int(y) * 1024 * 1024)
|
_config_int(to_save, "mail_size", lambda y: int(y) * 1024 * 1024)
|
||||||
config.mail_server = to_save.get('mail_server', "").strip()
|
config.mail_server = strip_whitespaces(to_save.get('mail_server', ""))
|
||||||
config.mail_from = to_save.get('mail_from', "").strip()
|
config.mail_from = strip_whitespaces(to_save.get('mail_from', ""))
|
||||||
config.mail_login = to_save.get('mail_login', "").strip()
|
config.mail_login = strip_whitespaces(to_save.get('mail_login', ""))
|
||||||
try:
|
try:
|
||||||
config.save()
|
config.save()
|
||||||
except (OperationalError, InvalidRequestError) as e:
|
except (OperationalError, InvalidRequestError) as e:
|
||||||
@ -1678,10 +1679,10 @@ def cancel_task():
|
|||||||
def _db_simulate_change():
|
def _db_simulate_change():
|
||||||
param = request.form.to_dict()
|
param = request.form.to_dict()
|
||||||
to_save = dict()
|
to_save = dict()
|
||||||
to_save['config_calibre_dir'] = re.sub(r'[\\/]metadata\.db$',
|
to_save['config_calibre_dir'] = strip_whitespaces(re.sub(r'[\\/]metadata\.db$',
|
||||||
'',
|
'',
|
||||||
param['config_calibre_dir'],
|
param['config_calibre_dir'],
|
||||||
flags=re.IGNORECASE).strip()
|
flags=re.IGNORECASE))
|
||||||
db_valid, db_change = calibre_db.check_valid_db(to_save["config_calibre_dir"],
|
db_valid, db_change = calibre_db.check_valid_db(to_save["config_calibre_dir"],
|
||||||
ub.app_DB_path,
|
ub.app_DB_path,
|
||||||
config.config_calibre_uuid)
|
config.config_calibre_uuid)
|
||||||
@ -1775,9 +1776,8 @@ def _configuration_update_helper():
|
|||||||
|
|
||||||
if "config_upload_formats" in to_save:
|
if "config_upload_formats" in to_save:
|
||||||
to_save["config_upload_formats"] = ','.join(
|
to_save["config_upload_formats"] = ','.join(
|
||||||
helper.uniq([x.lstrip().rstrip().lower() for x in to_save["config_upload_formats"].split(',')]))
|
helper.uniq([x.strip().lower() for x in to_save["config_upload_formats"].split(',')]))
|
||||||
_config_string(to_save, "config_upload_formats")
|
_config_string(to_save, "config_upload_formats")
|
||||||
# constants.EXTENSIONS_UPLOAD = config.config_upload_formats.split(',')
|
|
||||||
|
|
||||||
_config_string(to_save, "config_calibre")
|
_config_string(to_save, "config_calibre")
|
||||||
_config_string(to_save, "config_binariesdir")
|
_config_string(to_save, "config_binariesdir")
|
||||||
|
@ -35,7 +35,7 @@ except ImportError:
|
|||||||
|
|
||||||
from . import constants, logger
|
from . import constants, logger
|
||||||
from .subproc_wrapper import process_wait
|
from .subproc_wrapper import process_wait
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
_Base = declarative_base()
|
_Base = declarative_base()
|
||||||
@ -288,19 +288,19 @@ class ConfigSQL(object):
|
|||||||
|
|
||||||
def list_denied_tags(self):
|
def list_denied_tags(self):
|
||||||
mct = self.config_denied_tags or ""
|
mct = self.config_denied_tags or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def list_allowed_tags(self):
|
def list_allowed_tags(self):
|
||||||
mct = self.config_allowed_tags or ""
|
mct = self.config_allowed_tags or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def list_denied_column_values(self):
|
def list_denied_column_values(self):
|
||||||
mct = self.config_denied_column_value or ""
|
mct = self.config_denied_column_value or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def list_allowed_column_values(self):
|
def list_allowed_column_values(self):
|
||||||
mct = self.config_allowed_column_value or ""
|
mct = self.config_allowed_column_value or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def get_log_level(self):
|
def get_log_level(self):
|
||||||
return logger.get_level_name(self.config_log_level)
|
return logger.get_level_name(self.config_log_level)
|
||||||
@ -372,7 +372,7 @@ class ConfigSQL(object):
|
|||||||
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
|
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
|
||||||
have_metadata_db = os.path.isfile(db_file)
|
have_metadata_db = os.path.isfile(db_file)
|
||||||
self.db_configured = have_metadata_db
|
self.db_configured = have_metadata_db
|
||||||
# constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')]
|
|
||||||
from . import cli_param
|
from . import cli_param
|
||||||
if os.environ.get('FLASK_DEBUG'):
|
if os.environ.get('FLASK_DEBUG'):
|
||||||
logfile = logger.setup(logger.LOG_TO_STDOUT, logger.logging.DEBUG)
|
logfile = logger.setup(logger.LOG_TO_STDOUT, logger.logging.DEBUG)
|
||||||
|
11
cps/db.py
11
cps/db.py
@ -48,7 +48,7 @@ from flask import flash
|
|||||||
|
|
||||||
from . import logger, ub, isoLanguages
|
from . import logger, ub, isoLanguages
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -875,10 +875,11 @@ class CalibreDB:
|
|||||||
authors_ordered = list()
|
authors_ordered = list()
|
||||||
# error = False
|
# error = False
|
||||||
for auth in sort_authors:
|
for auth in sort_authors:
|
||||||
results = self.session.query(Authors).filter(Authors.sort == auth.lstrip().strip()).all()
|
auth = strip_whitespaces(auth)
|
||||||
|
results = self.session.query(Authors).filter(Authors.sort == auth).all()
|
||||||
# ToDo: How to handle not found author name
|
# ToDo: How to handle not found author name
|
||||||
if not len(results):
|
if not len(results):
|
||||||
log.error("Author {} not found to display name in right order".format(auth.strip()))
|
log.error("Author {} not found to display name in right order".format(auth))
|
||||||
# error = True
|
# error = True
|
||||||
break
|
break
|
||||||
for r in results:
|
for r in results:
|
||||||
@ -918,7 +919,7 @@ class CalibreDB:
|
|||||||
.filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first()
|
.filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first()
|
||||||
|
|
||||||
def search_query(self, term, config, *join):
|
def search_query(self, term, config, *join):
|
||||||
term.strip().lower()
|
strip_whitespaces(term).lower()
|
||||||
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
q = list()
|
q = list()
|
||||||
author_terms = re.split("[, ]+", term)
|
author_terms = re.split("[, ]+", term)
|
||||||
@ -1026,7 +1027,7 @@ class CalibreDB:
|
|||||||
if match:
|
if match:
|
||||||
prep = match.group(1)
|
prep = match.group(1)
|
||||||
title = title[len(prep):] + ', ' + prep
|
title = title[len(prep):] + ', ' + prep
|
||||||
return title.strip()
|
return strip_whitespaces(title)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# sqlalchemy <1.4.24 and sqlalchemy 2.0
|
# sqlalchemy <1.4.24 and sqlalchemy 2.0
|
||||||
|
@ -47,7 +47,7 @@ from .kobo_sync_status import change_archived_books
|
|||||||
from .redirect import get_redirect_location
|
from .redirect import get_redirect_location
|
||||||
from .file_helper import validate_mime_type
|
from .file_helper import validate_mime_type
|
||||||
from .usermanagement import user_login_required, login_required_if_no_ano
|
from .usermanagement import user_login_required, login_required_if_no_ano
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
editbook = Blueprint('edit-book', __name__)
|
editbook = Blueprint('edit-book', __name__)
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
@ -979,7 +979,7 @@ def render_edit_book(book_id):
|
|||||||
|
|
||||||
def edit_book_ratings(to_save, book):
|
def edit_book_ratings(to_save, book):
|
||||||
changed = False
|
changed = False
|
||||||
if to_save.get("rating", "").strip():
|
if strip_whitespaces(to_save.get("rating", "")):
|
||||||
old_rating = False
|
old_rating = False
|
||||||
if len(book.ratings) > 0:
|
if len(book.ratings) > 0:
|
||||||
old_rating = book.ratings[0].rating
|
old_rating = book.ratings[0].rating
|
||||||
@ -1003,14 +1003,14 @@ def edit_book_ratings(to_save, book):
|
|||||||
|
|
||||||
def edit_book_tags(tags, book):
|
def edit_book_tags(tags, book):
|
||||||
input_tags = tags.split(',')
|
input_tags = tags.split(',')
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
|
||||||
# Remove duplicates
|
# Remove duplicates
|
||||||
input_tags = helper.uniq(input_tags)
|
input_tags = helper.uniq(input_tags)
|
||||||
return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
|
return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
|
||||||
|
|
||||||
|
|
||||||
def edit_book_series(series, book):
|
def edit_book_series(series, book):
|
||||||
input_series = [series.strip()]
|
input_series = [strip_whitespaces(series)]
|
||||||
input_series = [x for x in input_series if x != '']
|
input_series = [x for x in input_series if x != '']
|
||||||
return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
|
return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
|
||||||
|
|
||||||
@ -1072,7 +1072,7 @@ def edit_book_languages(languages, book, upload_mode=False, invalid=None):
|
|||||||
def edit_book_publisher(publishers, book):
|
def edit_book_publisher(publishers, book):
|
||||||
changed = False
|
changed = False
|
||||||
if publishers:
|
if publishers:
|
||||||
publisher = publishers.rstrip().strip()
|
publisher = strip_whitespaces(publishers)
|
||||||
if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
|
if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
|
||||||
changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session,
|
changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session,
|
||||||
'publisher')
|
'publisher')
|
||||||
@ -1119,7 +1119,7 @@ def edit_cc_data_string(book, c, to_save, cc_db_value, cc_string):
|
|||||||
changed = False
|
changed = False
|
||||||
if c.datatype == 'rating':
|
if c.datatype == 'rating':
|
||||||
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))
|
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))
|
||||||
if to_save[cc_string].strip() != cc_db_value:
|
if strip_whitespaces(to_save[cc_string]) != cc_db_value:
|
||||||
if cc_db_value is not None:
|
if cc_db_value is not None:
|
||||||
# remove old cc_val
|
# remove old cc_val
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
@ -1129,15 +1129,15 @@ def edit_cc_data_string(book, c, to_save, cc_db_value, cc_string):
|
|||||||
changed = True
|
changed = True
|
||||||
cc_class = db.cc_classes[c.id]
|
cc_class = db.cc_classes[c.id]
|
||||||
new_cc = calibre_db.session.query(cc_class).filter(
|
new_cc = calibre_db.session.query(cc_class).filter(
|
||||||
cc_class.value == to_save[cc_string].strip()).first()
|
cc_class.value == strip_whitespaces(to_save[cc_string])).first()
|
||||||
# if no cc val is found add it
|
# if no cc val is found add it
|
||||||
if new_cc is None:
|
if new_cc is None:
|
||||||
new_cc = cc_class(value=to_save[cc_string].strip())
|
new_cc = cc_class(value=strip_whitespaces(to_save[cc_string]))
|
||||||
calibre_db.session.add(new_cc)
|
calibre_db.session.add(new_cc)
|
||||||
changed = True
|
changed = True
|
||||||
calibre_db.session.flush()
|
calibre_db.session.flush()
|
||||||
new_cc = calibre_db.session.query(cc_class).filter(
|
new_cc = calibre_db.session.query(cc_class).filter(
|
||||||
cc_class.value == to_save[cc_string].strip()).first()
|
cc_class.value == strip_whitespaces(to_save[cc_string])).first()
|
||||||
# add cc value to book
|
# add cc value to book
|
||||||
getattr(book, cc_string).append(new_cc)
|
getattr(book, cc_string).append(new_cc)
|
||||||
return changed, to_save
|
return changed, to_save
|
||||||
@ -1165,7 +1165,7 @@ def edit_cc_data(book_id, book, to_save, cc):
|
|||||||
cc_db_value = getattr(book, cc_string)[0].value
|
cc_db_value = getattr(book, cc_string)[0].value
|
||||||
else:
|
else:
|
||||||
cc_db_value = None
|
cc_db_value = None
|
||||||
if to_save[cc_string].strip():
|
if strip_whitespaces(to_save[cc_string]):
|
||||||
if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]:
|
if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]:
|
||||||
change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string)
|
change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string)
|
||||||
else:
|
else:
|
||||||
@ -1181,7 +1181,7 @@ def edit_cc_data(book_id, book, to_save, cc):
|
|||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
input_tags = to_save[cc_string].split(',')
|
input_tags = to_save[cc_string].split(',')
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
|
||||||
changed |= modify_database_object(input_tags,
|
changed |= modify_database_object(input_tags,
|
||||||
getattr(book, cc_string),
|
getattr(book, cc_string),
|
||||||
db.cc_classes[c.id],
|
db.cc_classes[c.id],
|
||||||
@ -1284,7 +1284,7 @@ def upload_cover(cover_request, book):
|
|||||||
|
|
||||||
def handle_title_on_edit(book, book_title):
|
def handle_title_on_edit(book, book_title):
|
||||||
# handle book title
|
# handle book title
|
||||||
book_title = book_title.rstrip().strip()
|
book_title = strip_whitespaces(book_title)
|
||||||
if book.title != book_title:
|
if book.title != book_title:
|
||||||
if book_title == '':
|
if book_title == '':
|
||||||
book_title = _(u'Unknown')
|
book_title = _(u'Unknown')
|
||||||
|
@ -25,6 +25,7 @@ from . import config, logger
|
|||||||
from .helper import split_authors
|
from .helper import split_authors
|
||||||
from .epub_helper import get_content_opf, default_ns
|
from .epub_helper import get_content_opf, default_ns
|
||||||
from .constants import BookMeta
|
from .constants import BookMeta
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||||||
elif s == 'date':
|
elif s == 'date':
|
||||||
epub_metadata[s] = tmp[0][:10]
|
epub_metadata[s] = tmp[0][:10]
|
||||||
else:
|
else:
|
||||||
epub_metadata[s] = tmp[0].strip()
|
epub_metadata[s] = strip_whitespaces(tmp[0])
|
||||||
else:
|
else:
|
||||||
epub_metadata[s] = 'Unknown'
|
epub_metadata[s] = 'Unknown'
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ except ImportError:
|
|||||||
UnacceptableAddressException = MissingSchema = BaseException
|
UnacceptableAddressException = MissingSchema = BaseException
|
||||||
|
|
||||||
from . import calibre_db, cli_param
|
from . import calibre_db, cli_param
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
from .tasks.convert import TaskConvert
|
from .tasks.convert import TaskConvert
|
||||||
from . import logger, config, db, ub, fs
|
from . import logger, config, db, ub, fs
|
||||||
from . import gdriveutils as gd
|
from . import gdriveutils as gd
|
||||||
@ -118,7 +119,7 @@ def convert_book_format(book_id, calibre_path, old_book_format, new_book_format,
|
|||||||
# Texts are not lazy translated as they are supposed to get send out as is
|
# Texts are not lazy translated as they are supposed to get send out as is
|
||||||
def send_test_mail(ereader_mail, user_name):
|
def send_test_mail(ereader_mail, user_name):
|
||||||
for email in ereader_mail.split(','):
|
for email in ereader_mail.split(','):
|
||||||
email = email.strip()
|
email = strip_whitespaces(email)
|
||||||
WorkerThread.add(user_name, TaskEmail(_('Calibre-Web Test Email'), None, None,
|
WorkerThread.add(user_name, TaskEmail(_('Calibre-Web Test Email'), None, None,
|
||||||
config.get_mail_settings(), email, N_("Test Email"),
|
config.get_mail_settings(), email, N_("Test Email"),
|
||||||
_('This Email has been sent via Calibre-Web.')))
|
_('This Email has been sent via Calibre-Web.')))
|
||||||
@ -228,7 +229,7 @@ def send_mail(book_id, book_format, convert, ereader_mail, calibrepath, user_id)
|
|||||||
link = '<a href="{}">{}</a>'.format(url_for('web.show_book', book_id=book_id), escape(book.title))
|
link = '<a href="{}">{}</a>'.format(url_for('web.show_book', book_id=book_id), escape(book.title))
|
||||||
email_text = N_("%(book)s send to eReader", book=link)
|
email_text = N_("%(book)s send to eReader", book=link)
|
||||||
for email in ereader_mail.split(','):
|
for email in ereader_mail.split(','):
|
||||||
email = email.strip()
|
email = strip_whitespaces(email)
|
||||||
WorkerThread.add(user_id, TaskEmail(_("Send to eReader"), book.path, converted_file_name,
|
WorkerThread.add(user_id, TaskEmail(_("Send to eReader"), book.path, converted_file_name,
|
||||||
config.get_mail_settings(), email,
|
config.get_mail_settings(), email,
|
||||||
email_text, _('This Email has been sent via Calibre-Web.'), book.id))
|
email_text, _('This Email has been sent via Calibre-Web.'), book.id))
|
||||||
@ -252,7 +253,7 @@ def get_valid_filename(value, replace_whitespace=True, chars=128):
|
|||||||
# pipe has to be replaced with comma
|
# pipe has to be replaced with comma
|
||||||
value = re.sub(r'[|]+', ',', value, flags=re.U)
|
value = re.sub(r'[|]+', ',', value, flags=re.U)
|
||||||
|
|
||||||
value = value.encode('utf-8')[:chars].decode('utf-8', errors='ignore').strip()
|
value = strip_whitespaces(value.encode('utf-8')[:chars].decode('utf-8', errors='ignore'))
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
raise ValueError("Filename cannot be empty")
|
raise ValueError("Filename cannot be empty")
|
||||||
@ -267,11 +268,11 @@ def split_authors(values):
|
|||||||
commas = author.count(',')
|
commas = author.count(',')
|
||||||
if commas == 1:
|
if commas == 1:
|
||||||
author_split = author.split(',')
|
author_split = author.split(',')
|
||||||
authors_list.append(author_split[1].strip() + ' ' + author_split[0].strip())
|
authors_list.append(strip_whitespaces(author_split[1]) + ' ' + strip_whitespaces(author_split[0]))
|
||||||
elif commas > 1:
|
elif commas > 1:
|
||||||
authors_list.extend([x.strip() for x in author.split(',')])
|
authors_list.extend([strip_whitespaces(x) for x in author.split(',')])
|
||||||
else:
|
else:
|
||||||
authors_list.append(author.strip())
|
authors_list.append(strip_whitespaces(author))
|
||||||
return authors_list
|
return authors_list
|
||||||
|
|
||||||
|
|
||||||
@ -661,7 +662,7 @@ def check_email(email):
|
|||||||
|
|
||||||
|
|
||||||
def check_username(username):
|
def check_username(username):
|
||||||
username = username.strip()
|
username = strip_whitespaces(username)
|
||||||
if ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).scalar():
|
if ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).scalar():
|
||||||
log.error("This username is already taken")
|
log.error("This username is already taken")
|
||||||
raise Exception(_("This username is already taken"))
|
raise Exception(_("This username is already taken"))
|
||||||
@ -670,14 +671,14 @@ def check_username(username):
|
|||||||
|
|
||||||
def valid_email(emails):
|
def valid_email(emails):
|
||||||
for email in emails.split(','):
|
for email in emails.split(','):
|
||||||
email = email.strip()
|
email = strip_whitespaces(email)
|
||||||
# if email is not deleted
|
# if email is not deleted
|
||||||
if email:
|
if email:
|
||||||
# Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation
|
# Regex according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#validation
|
||||||
if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$",
|
if not re.search(r"^[\w.!#$%&'*+\\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$",
|
||||||
email):
|
email):
|
||||||
log.error("Invalid Email address format")
|
log.error("Invalid Email address format")
|
||||||
raise Exception(_("Invalid Email address format"))
|
raise Exception(_("Invalid Email address format"))
|
||||||
return email
|
return email
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ from flask_babel import format_date
|
|||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from sqlalchemy.sql.expression import func, not_, and_, or_, text, true
|
from sqlalchemy.sql.expression import func, not_, and_, or_, text, true
|
||||||
from sqlalchemy.sql.functions import coalesce
|
from sqlalchemy.sql.functions import coalesce
|
||||||
from sqlalchemy import exists
|
|
||||||
|
|
||||||
from . import logger, db, calibre_db, config, ub
|
from . import logger, db, calibre_db, config, ub
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
from .usermanagement import login_required_if_no_ano
|
from .usermanagement import login_required_if_no_ano
|
||||||
from .render_template import render_title_template
|
from .render_template import render_title_template
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
@ -267,11 +267,11 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
|||||||
description = term.get("comment")
|
description = term.get("comment")
|
||||||
read_status = term.get("read_status")
|
read_status = term.get("read_status")
|
||||||
if author_name:
|
if author_name:
|
||||||
author_name = author_name.strip().lower().replace(',', '|')
|
author_name = strip_whitespaces(author_name).lower().replace(',', '|')
|
||||||
if book_title:
|
if book_title:
|
||||||
book_title = book_title.strip().lower()
|
book_title = strip_whitespaces(book_title).lower()
|
||||||
if publisher:
|
if publisher:
|
||||||
publisher = publisher.strip().lower()
|
publisher = strip_whitespaces(publisher).lower()
|
||||||
|
|
||||||
search_term = []
|
search_term = []
|
||||||
cc_present = False
|
cc_present = False
|
||||||
|
23
cps/string_helper.py
Normal file
23
cps/string_helper.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2024 OzzieIsaacs
|
||||||
|
#
|
||||||
|
# This program 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.
|
||||||
|
#
|
||||||
|
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def strip_whitespaces(text):
|
||||||
|
return re.sub("(^[\s\u200B-\u200D\ufeff]+)|([\s\u200B-\u200D\ufeff]+$)","", text)
|
||||||
|
|
@ -39,6 +39,7 @@ from cps.file_helper import get_temp_dir
|
|||||||
from cps.tasks.mail import TaskEmail
|
from cps.tasks.mail import TaskEmail
|
||||||
from cps import gdriveutils, helper
|
from cps import gdriveutils, helper
|
||||||
from cps.constants import SUPPORTED_CALIBRE_BINARIES
|
from cps.constants import SUPPORTED_CALIBRE_BINARIES
|
||||||
|
from cps.string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ class TaskConvert(CalibreTask):
|
|||||||
try:
|
try:
|
||||||
EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title))
|
EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title))
|
||||||
for email in self.ereader_mail.split(','):
|
for email in self.ereader_mail.split(','):
|
||||||
email = email.strip()
|
email = strip_whitespaces(email)
|
||||||
worker_thread.add(self.user, TaskEmail(self.settings['subject'],
|
worker_thread.add(self.user, TaskEmail(self.settings['subject'],
|
||||||
self.results["path"],
|
self.results["path"],
|
||||||
filename,
|
filename,
|
||||||
|
@ -34,6 +34,7 @@ from cps.services import gmail
|
|||||||
from cps.embed_helper import do_calibre_export
|
from cps.embed_helper import do_calibre_export
|
||||||
from cps import logger, config
|
from cps import logger, config
|
||||||
from cps import gdriveutils
|
from cps import gdriveutils
|
||||||
|
from cps.string_helper import strip_whitespaces
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
@ -127,9 +128,9 @@ class TaskEmail(CalibreTask):
|
|||||||
try:
|
try:
|
||||||
# Parse out the address from the From line, and then the domain from that
|
# Parse out the address from the From line, and then the domain from that
|
||||||
from_email = parseaddr(self.settings["mail_from"])[1]
|
from_email = parseaddr(self.settings["mail_from"])[1]
|
||||||
msgid_domain = from_email.partition('@')[2].strip()
|
msgid_domain = strip_whitespaces(from_email.partition('@')[2])
|
||||||
# This can sometimes sneak through parseaddr if the input is malformed
|
# This can sometimes sneak through parseaddr if the input is malformed
|
||||||
msgid_domain = msgid_domain.rstrip('>').strip()
|
msgid_domain = strip_whitespaces(msgid_domain.rstrip('>'))
|
||||||
except Exception:
|
except Exception:
|
||||||
msgid_domain = ''
|
msgid_domain = ''
|
||||||
return msgid_domain or 'calibre-web.com'
|
return msgid_domain or 'calibre-web.com'
|
||||||
|
10
cps/ub.py
10
cps/ub.py
@ -54,7 +54,7 @@ from sqlalchemy.orm import backref, relationship, sessionmaker, Session, scoped_
|
|||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from . import constants, logger
|
from . import constants, logger
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -196,19 +196,19 @@ class UserBase:
|
|||||||
|
|
||||||
def list_denied_tags(self):
|
def list_denied_tags(self):
|
||||||
mct = self.denied_tags or ""
|
mct = self.denied_tags or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def list_allowed_tags(self):
|
def list_allowed_tags(self):
|
||||||
mct = self.allowed_tags or ""
|
mct = self.allowed_tags or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def list_denied_column_values(self):
|
def list_denied_column_values(self):
|
||||||
mct = self.denied_column_value or ""
|
mct = self.denied_column_value or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def list_allowed_column_values(self):
|
def list_allowed_column_values(self):
|
||||||
mct = self.allowed_column_value or ""
|
mct = self.allowed_column_value or ""
|
||||||
return [t.strip() for t in mct.split(",")]
|
return [strip_whitespaces(t) for t in mct.split(",")]
|
||||||
|
|
||||||
def get_view_property(self, page, prop):
|
def get_view_property(self, page, prop):
|
||||||
if not self.view_settings.get(page):
|
if not self.view_settings.get(page):
|
||||||
|
@ -24,6 +24,7 @@ from . import logger, comic, isoLanguages
|
|||||||
from .constants import BookMeta
|
from .constants import BookMeta
|
||||||
from .helper import split_authors
|
from .helper import split_authors
|
||||||
from .file_helper import get_temp_dir
|
from .file_helper import get_temp_dir
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
@ -97,9 +98,9 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.warning('cannot parse metadata, using default: %s', ex)
|
log.warning('cannot parse metadata, using default: %s', ex)
|
||||||
|
|
||||||
if not meta.title.strip():
|
if not strip_whitespaces(meta.title):
|
||||||
meta = meta._replace(title=original_file_name)
|
meta = meta._replace(title=original_file_name)
|
||||||
if not meta.author.strip() or meta.author.lower() == 'unknown':
|
if not strip_whitespaces(meta.author) or meta.author.lower() == 'unknown':
|
||||||
meta = meta._replace(author=_('Unknown'))
|
meta = meta._replace(author=_('Unknown'))
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
|
11
cps/web.py
11
cps/web.py
@ -60,6 +60,7 @@ from . import limiter
|
|||||||
from .services.worker import WorkerThread
|
from .services.worker import WorkerThread
|
||||||
from .tasks_status import render_task_status
|
from .tasks_status import render_task_status
|
||||||
from .usermanagement import user_login_required
|
from .usermanagement import user_login_required
|
||||||
|
from .string_helper import strip_whitespaces
|
||||||
|
|
||||||
|
|
||||||
feature_support = {
|
feature_support = {
|
||||||
@ -1286,7 +1287,7 @@ def register_post():
|
|||||||
if not config.get_mail_server_configured():
|
if not config.get_mail_server_configured():
|
||||||
flash(_("Oops! Email server is not configured, please contact your administrator."), category="error")
|
flash(_("Oops! Email server is not configured, please contact your administrator."), category="error")
|
||||||
return render_title_template('register.html', title=_("Register"), page="register")
|
return render_title_template('register.html', title=_("Register"), page="register")
|
||||||
nickname = to_save.get("email", "").strip() if config.config_register_email else to_save.get('name')
|
nickname = strip_whitespaces(to_save.get("email", "")) if config.config_register_email else to_save.get('name')
|
||||||
if not nickname or not to_save.get("email"):
|
if not nickname or not to_save.get("email"):
|
||||||
flash(_("Oops! Please complete all fields."), category="error")
|
flash(_("Oops! Please complete all fields."), category="error")
|
||||||
return render_title_template('register.html', title=_("Register"), page="register")
|
return render_title_template('register.html', title=_("Register"), page="register")
|
||||||
@ -1311,7 +1312,7 @@ def register_post():
|
|||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
if feature_support['oauth']:
|
if feature_support['oauth']:
|
||||||
register_user_with_oauth(content)
|
register_user_with_oauth(content)
|
||||||
send_registration_mail(to_save.get("email", "").strip(), nickname, password)
|
send_registration_mail(strip_whitespaces(to_save.get("email", "")), nickname, password)
|
||||||
except Exception:
|
except Exception:
|
||||||
ub.session.rollback()
|
ub.session.rollback()
|
||||||
flash(_("Oops! An unknown error occurred. Please try again later."), category="error")
|
flash(_("Oops! An unknown error occurred. Please try again later."), category="error")
|
||||||
@ -1370,11 +1371,11 @@ def login():
|
|||||||
|
|
||||||
|
|
||||||
@web.route('/login', methods=['POST'])
|
@web.route('/login', methods=['POST'])
|
||||||
@limiter.limit("40/day", key_func=lambda: request.form.get('username', "").strip().lower())
|
@limiter.limit("40/day", key_func=lambda: strip_whitespaces(request.form.get('username', "")).lower())
|
||||||
@limiter.limit("3/minute", key_func=lambda: request.form.get('username', "").strip().lower())
|
@limiter.limit("3/minute", key_func=lambda: strip_whitespaces(request.form.get('username', "")).lower())
|
||||||
def login_post():
|
def login_post():
|
||||||
form = request.form.to_dict()
|
form = request.form.to_dict()
|
||||||
username = form.get('username', "").strip().lower().replace("\n","").replace("\r","")
|
username = strip_whitespaces(form.get('username', "")).lower().replace("\n","").replace("\r","")
|
||||||
try:
|
try:
|
||||||
limiter.check()
|
limiter.check()
|
||||||
except RateLimitExceeded:
|
except RateLimitExceeded:
|
||||||
|
@ -37,20 +37,20 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
|
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
|
||||||
|
|
||||||
<p class='text-justify attribute'><strong>Start Time: </strong>2024-08-11 19:57:31</p>
|
<p class='text-justify attribute'><strong>Start Time: </strong>2024-08-12 18:42:30</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
||||||
|
|
||||||
<p class='text-justify attribute'><strong>Stop Time: </strong>2024-08-12 03:10:54</p>
|
<p class='text-justify attribute'><strong>Stop Time: </strong>2024-08-13 01:57:45</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
<div class="col-xs-6 col-md-6 col-sm-offset-3">
|
||||||
<p class='text-justify attribute'><strong>Duration: </strong>5h 59 min</p>
|
<p class='text-justify attribute'><strong>Duration: </strong>6h 1 min</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4519,11 +4519,11 @@ ModuleNotFoundError: No module named 'build_release'</pre>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr id="su" class="failClass">
|
<tr id="su" class="skipClass">
|
||||||
<td>TestThumbnails</td>
|
<td>TestThumbnails</td>
|
||||||
<td class="text-center">8</td>
|
<td class="text-center">8</td>
|
||||||
<td class="text-center">6</td>
|
<td class="text-center">7</td>
|
||||||
<td class="text-center">1</td>
|
<td class="text-center">0</td>
|
||||||
<td class="text-center">0</td>
|
<td class="text-center">0</td>
|
||||||
<td class="text-center">1</td>
|
<td class="text-center">1</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
@ -4560,31 +4560,11 @@ ModuleNotFoundError: No module named 'build_release'</pre>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr id="ft51.4" class="none bg-danger">
|
<tr id='pt51.4' class='hiddenRow bg-success'>
|
||||||
<td>
|
<td>
|
||||||
<div class='testcase'>TestThumbnails - test_cover_change_on_upload_new_cover</div>
|
<div class='testcase'>TestThumbnails - test_cover_change_on_upload_new_cover</div>
|
||||||
</td>
|
</td>
|
||||||
<td colspan='6'>
|
<td colspan='6' align='center'>PASS</td>
|
||||||
<div class="text-center">
|
|
||||||
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft51.4')">FAIL</a>
|
|
||||||
</div>
|
|
||||||
<!--css div popup start-->
|
|
||||||
<div id="div_ft51.4" class="popup_window test_output" style="display:block;">
|
|
||||||
<div class='close_button pull-right'>
|
|
||||||
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
|
|
||||||
onclick="document.getElementById('div_ft51.4').style.display='none'"><span
|
|
||||||
aria-hidden="true">×</span></button>
|
|
||||||
</div>
|
|
||||||
<div class="text-left pull-left">
|
|
||||||
<pre class="text-left">Traceback (most recent call last):
|
|
||||||
File "/home/ozzie/Development/calibre-web-test/test/test_thumbnails.py", line 140, in test_cover_change_on_upload_new_cover
|
|
||||||
self.assertGreaterEqual(diff(BytesIO(updated_cover), BytesIO(original_cover), delete_diff_file=True), 0.03)
|
|
||||||
AssertionError: 0.023989181595169266 not greater than or equal to 0.03</pre>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<!--css div popup end-->
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
@ -4739,11 +4719,11 @@ AssertionError: 0.023989181595169266 not greater than or equal to 0.03</pre>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr id="su" class="failClass">
|
<tr id="su" class="passClass">
|
||||||
<td>TestUploadAudio</td>
|
<td>TestUploadAudio</td>
|
||||||
<td class="text-center">12</td>
|
<td class="text-center">12</td>
|
||||||
<td class="text-center">11</td>
|
<td class="text-center">12</td>
|
||||||
<td class="text-center">1</td>
|
<td class="text-center">0</td>
|
||||||
<td class="text-center">0</td>
|
<td class="text-center">0</td>
|
||||||
<td class="text-center">0</td>
|
<td class="text-center">0</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
@ -4780,33 +4760,11 @@ AssertionError: 0.023989181595169266 not greater than or equal to 0.03</pre>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr id="ft53.4" class="none bg-danger">
|
<tr id='pt53.4' class='hiddenRow bg-success'>
|
||||||
<td>
|
<td>
|
||||||
<div class='testcase'>TestUploadAudio - test_upload_flac</div>
|
<div class='testcase'>TestUploadAudio - test_upload_flac</div>
|
||||||
</td>
|
</td>
|
||||||
<td colspan='6'>
|
<td colspan='6' align='center'>PASS</td>
|
||||||
<div class="text-center">
|
|
||||||
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft53.4')">FAIL</a>
|
|
||||||
</div>
|
|
||||||
<!--css div popup start-->
|
|
||||||
<div id="div_ft53.4" class="popup_window test_output" style="display:block;">
|
|
||||||
<div class='close_button pull-right'>
|
|
||||||
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
|
|
||||||
onclick="document.getElementById('div_ft53.4').style.display='none'"><span
|
|
||||||
aria-hidden="true">×</span></button>
|
|
||||||
</div>
|
|
||||||
<div class="text-left pull-left">
|
|
||||||
<pre class="text-left">Traceback (most recent call last):
|
|
||||||
File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 367, in test_upload_flac
|
|
||||||
self.assertEqual('Album', details['series'])
|
|
||||||
AssertionError: 'Album' != 'Flac Album'
|
|
||||||
- Album
|
|
||||||
+ Flac Album</pre>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<!--css div popup end-->
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
@ -5849,8 +5807,8 @@ AssertionError: 'Album' != 'Flac Album'
|
|||||||
<tr id='total_row' class="text-center bg-grey">
|
<tr id='total_row' class="text-center bg-grey">
|
||||||
<td>Total</td>
|
<td>Total</td>
|
||||||
<td>519</td>
|
<td>519</td>
|
||||||
<td>506</td>
|
<td>508</td>
|
||||||
<td>2</td>
|
<td>0</td>
|
||||||
<td>2</td>
|
<td>2</td>
|
||||||
<td>9</td>
|
<td>9</td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
@ -6390,7 +6348,7 @@ AssertionError: 'Album' != 'Flac Album'
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
drawCircle(506, 2, 2, 9);
|
drawCircle(508, 0, 2, 9);
|
||||||
showCase(5);
|
showCase(5);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user