from math import ceil
from urllib.parse import unquote
from flask import jsonify, request
from flask_restx import inputs
from sqlalchemy.orm.exc import NoResultFound
from flexget.api import APIResource, api
from flexget.api.app import (
NotFoundError,
base_message_schema,
etag,
pagination_headers,
success_response,
)
from . import db
seen_api = api.namespace('seen', description='Managed Flexget seen entries and fields')
[docs]
class ObjectsContainer:
seen_field_object = {
'type': 'object',
'properties': {
'id': {'type': 'integer'},
'field': {'type': 'string'},
'value': {'type': 'string'},
'added': {'type': 'string', 'format': 'date-time'},
'seen_entry_id': {'type': 'integer'},
},
}
seen_object = {
'type': 'object',
'properties': {
'id': {'type': 'integer'},
'title': {'type': 'string'},
'reason': {'type': 'string'},
'task': {'type': 'string'},
'added': {'type': 'string', 'format': 'date-time'},
'local': {'type': 'boolean'},
'fields': {'type': 'array', 'items': seen_field_object},
},
}
seen_search_object = {'type': 'array', 'items': seen_object}
seen_object_schema = api.schema_model('seen_object_schema', ObjectsContainer.seen_object)
seen_search_schema = api.schema_model('seen_search_schema', ObjectsContainer.seen_search_object)
seen_base_parser = api.parser()
seen_base_parser.add_argument(
'value', help='Filter by any field value or leave empty to get all entries'
)
seen_base_parser.add_argument(
'local', type=inputs.boolean, default=None, help='Filter results by seen locality.'
)
sort_choices = ('title', 'task', 'added', 'local', 'reason', 'id')
seen_search_parser = api.pagination_parser(seen_base_parser, sort_choices)
[docs]
@seen_api.route('/')
class SeenSearchAPI(APIResource):
[docs]
@etag
@api.response(NotFoundError)
@api.response(200, 'Successfully retrieved seen objects', seen_search_schema)
@api.doc(expect=[seen_search_parser], description='Get seen entries')
def get(self, session):
"""Search for seen entries."""
args = seen_search_parser.parse_args()
# Filter params
value = args['value']
local = args['local']
# Pagination and sorting params
page = args['page']
per_page = args['per_page']
sort_by = args['sort_by']
sort_order = args['order']
# Handle max size limit
per_page = min(per_page, 100)
descending = sort_order == 'desc'
# Unquotes and prepares value for DB lookup
if value:
value = unquote(value)
value = f'%{value}%'
start = per_page * (page - 1)
stop = start + per_page
kwargs = {
'value': value,
'status': local,
'stop': stop,
'start': start,
'order_by': sort_by,
'descending': descending,
'session': session,
}
total_items = db.search(count=True, **kwargs)
if not total_items:
return jsonify([])
raw_seen_entries_list = db.search(**kwargs).all()
converted_seen_entry_list = [entry.to_dict() for entry in raw_seen_entries_list]
# Total number of pages
total_pages = ceil(total_items / float(per_page))
# Actual results in page
actual_size = min(len(converted_seen_entry_list), per_page)
# Invalid page request
if page > total_pages and total_pages != 0:
raise NotFoundError(f'page {page} does not exist')
# Get pagination headers
pagination = pagination_headers(total_pages, total_items, actual_size, request)
# Create response
rsp = jsonify(converted_seen_entry_list)
# Add link header to response
rsp.headers.extend(pagination)
return rsp
[docs]
@api.response(200, 'Successfully delete all entries', model=base_message_schema)
@api.doc(expect=[seen_base_parser], description='Delete seen entries')
def delete(self, session):
"""Delete seen entries."""
args = seen_base_parser.parse_args()
value = args['value']
local = args['local']
if value:
value = unquote(value)
value = '%' + value + '%'
seen_entries_list = db.search(value=value, status=local, session=session)
deleted = 0
for se in seen_entries_list:
db.forget_by_id(se.id, session=session)
deleted += 1
return success_response(f'successfully deleted {deleted} entries')
[docs]
@seen_api.route('/<int:seen_entry_id>/')
@api.doc(params={'seen_entry_id': 'ID of seen entry'})
@api.response(NotFoundError)
class SeenSearchIDAPI(APIResource):
[docs]
@etag
@api.response(200, model=seen_object_schema)
def get(self, seen_entry_id, session):
"""Get seen entry by ID."""
try:
seen_entry = db.get_entry_by_id(seen_entry_id, session=session)
except NoResultFound:
raise NotFoundError(f'Could not find entry ID {seen_entry_id}')
return jsonify(seen_entry.to_dict())
[docs]
@api.response(200, 'Successfully deleted entry', model=base_message_schema)
def delete(self, seen_entry_id, session):
"""Delete seen entry by ID."""
try:
entry = db.get_entry_by_id(seen_entry_id, session=session)
except NoResultFound:
raise NotFoundError(f'Could not delete entry ID {seen_entry_id}')
db.forget_by_id(entry.id, session=session)
return success_response(f'successfully deleted seen entry {seen_entry_id}')