tubearchivist-frontend/tubearchivist/home/views.py

1255 lines
42 KiB
Python
Raw Normal View History

2021-09-05 17:10:14 +00:00
"""
Functionality:
- all views for home app
2021-09-18 10:28:16 +00:00
- process post data received from frontend via ajax
2021-09-05 17:10:14 +00:00
"""
import json
2021-09-18 13:02:54 +00:00
import urllib.parse
2021-09-05 17:10:14 +00:00
from time import sleep
from django import forms
from django.contrib.auth import login
from django.contrib.auth.forms import AuthenticationForm
2021-09-05 17:10:14 +00:00
from django.http import JsonResponse
2021-09-18 13:02:54 +00:00
from django.shortcuts import redirect, render
2021-09-05 17:10:14 +00:00
from django.utils.http import urlencode
2021-09-18 13:02:54 +00:00
from django.views import View
2021-10-29 16:43:19 +00:00
from home.forms import (
2021-10-30 07:14:16 +00:00
AddToQueueForm,
2021-10-29 16:43:19 +00:00
ApplicationSettingsForm,
2021-10-30 06:19:16 +00:00
ChannelSearchForm,
2021-10-29 16:43:19 +00:00
CustomAuthForm,
2021-11-15 06:08:32 +00:00
PlaylistSearchForm,
SubscribeToChannelForm,
SubscribeToPlaylistForm,
2021-10-29 16:43:19 +00:00
UserSettingsForm,
2021-10-30 06:19:16 +00:00
VideoSearchForm,
2021-10-29 16:43:19 +00:00
)
2021-09-05 17:10:14 +00:00
from home.src.config import AppConfig
from home.src.download import (
ChannelSubscription,
PendingList,
PlaylistSubscription,
)
from home.src.helper import RedisArchivist, RedisQueue, UrlListParser
from home.src.index import (
WatchState,
YoutubeChannel,
YoutubePlaylist,
YoutubeVideo,
)
from home.src.searching import Pagination, SearchForm, SearchHandler
2021-09-21 09:25:22 +00:00
from home.tasks import (
download_pending,
download_single,
extrac_dl,
2021-11-13 10:34:58 +00:00
index_channel_playlists,
2021-09-24 16:37:26 +00:00
kill_dl,
2021-11-01 09:42:07 +00:00
re_sync_thumbs,
rescan_filesystem,
2021-09-21 09:25:22 +00:00
run_backup,
run_manual_import,
run_restore_backup,
subscribe_to,
2021-09-21 09:25:22 +00:00
update_subscribed,
)
2021-09-05 17:10:14 +00:00
class HomeView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /
2021-09-09 09:45:46 +00:00
handle home page and video search post functionality
"""
2021-09-05 17:10:14 +00:00
CONFIG = AppConfig().config
2021-09-21 09:25:22 +00:00
ES_URL = CONFIG["application"]["es_url"]
2021-09-05 17:10:14 +00:00
def get(self, request):
2021-09-21 09:25:22 +00:00
"""return home search results"""
user_id = request.user.id
view_config = self.read_config(user_id)
2021-09-05 17:10:14 +00:00
# handle search
2021-09-21 09:25:22 +00:00
search_get = request.GET.get("search", False)
2021-09-05 17:10:14 +00:00
if search_get:
search_encoded = urllib.parse.quote(search_get)
else:
search_encoded = False
# define page size
2021-09-21 09:25:22 +00:00
page_get = int(request.GET.get("page", 0))
pagination_handler = Pagination(
page_get, user_id, search_get=search_encoded
)
2021-09-05 17:10:14 +00:00
2021-09-21 09:25:22 +00:00
url = self.ES_URL + "/ta_video/_search"
2021-09-05 17:10:14 +00:00
data = self.build_data(
2021-10-27 04:01:09 +00:00
pagination_handler,
view_config["sort_by"],
2021-10-27 04:01:09 +00:00
view_config["sort_order"],
search_get,
view_config["hide_watched"],
2021-09-05 17:10:14 +00:00
)
search = SearchHandler(url, data)
videos_hits = search.get_data()
max_hits = search.max_hits
pagination_handler.validate(max_hits)
2021-10-30 06:19:16 +00:00
search_form = VideoSearchForm()
2021-09-05 17:10:14 +00:00
context = {
2021-10-30 06:19:16 +00:00
"search_form": search_form,
2021-09-21 09:25:22 +00:00
"videos": videos_hits,
"pagination": pagination_handler.pagination,
"sort_by": view_config["sort_by"],
"sort_order": view_config["sort_order"],
2021-10-27 04:01:09 +00:00
"hide_watched": view_config["hide_watched"],
"colors": view_config["colors"],
"view_style": view_config["view_style"],
2021-09-05 17:10:14 +00:00
}
2021-09-21 09:25:22 +00:00
return render(request, "home/home.html", context)
2021-09-05 17:10:14 +00:00
@staticmethod
def build_data(
pagination_handler, sort_by, sort_order, search_get, hide_watched
):
2021-09-21 09:25:22 +00:00
"""build the data dict for the search query"""
page_size = pagination_handler.pagination["page_size"]
page_from = pagination_handler.pagination["page_from"]
# overwrite sort_by to match key
if sort_by == "views":
sort_by = "stats.view_count"
elif sort_by == "likes":
sort_by = "stats.like_count"
elif sort_by == "downloaded":
sort_by = "date_downloaded"
2021-09-05 17:10:14 +00:00
data = {
2021-09-21 09:25:22 +00:00
"size": page_size,
"from": page_from,
"query": {"match_all": {}},
"sort": [{sort_by: {"order": sort_order}}],
2021-09-05 17:10:14 +00:00
}
if hide_watched:
2021-09-21 09:25:22 +00:00
data["query"] = {"term": {"player.watched": {"value": False}}}
2021-09-05 17:10:14 +00:00
if search_get:
del data["sort"]
2021-09-05 17:10:14 +00:00
query = {
"multi_match": {
"query": search_get,
"fields": ["title", "channel.channel_name", "tags"],
"type": "cross_fields",
2021-09-21 09:25:22 +00:00
"operator": "and",
2021-09-05 17:10:14 +00:00
}
}
2021-09-21 09:25:22 +00:00
data["query"] = query
2021-09-05 17:10:14 +00:00
return data
@staticmethod
2021-10-29 04:45:13 +00:00
def read_config(user_id):
2021-09-21 09:25:22 +00:00
"""read needed values from redis"""
2021-10-29 16:43:19 +00:00
config_handler = AppConfig(user_id)
2021-10-29 04:45:13 +00:00
view_key = f"{user_id}:view:home"
view_style = RedisArchivist().get_message(view_key)["status"]
if not view_style:
2021-10-29 16:43:19 +00:00
view_style = config_handler.config["default_view"]["home"]
2021-10-29 07:42:12 +00:00
sort_by = RedisArchivist().get_message(f"{user_id}:sort_by")["status"]
if not sort_by:
2021-10-29 16:43:19 +00:00
sort_by = config_handler.config["archive"]["sort_by"]
2021-10-29 07:42:12 +00:00
sort_order_key = f"{user_id}:sort_order"
sort_order = RedisArchivist().get_message(sort_order_key)["status"]
if not sort_order:
2021-10-29 16:43:19 +00:00
sort_order = config_handler.config["archive"]["sort_order"]
hide_watched_key = f"{user_id}:hide_watched"
hide_watched = RedisArchivist().get_message(hide_watched_key)["status"]
2021-10-27 04:01:09 +00:00
view_config = {
2021-10-29 16:43:19 +00:00
"colors": config_handler.colors,
2021-10-27 04:01:09 +00:00
"view_style": view_style,
"sort_by": sort_by,
2021-10-27 04:01:09 +00:00
"sort_order": sort_order,
"hide_watched": hide_watched,
}
return view_config
2021-09-05 17:10:14 +00:00
@staticmethod
def post(request):
2021-09-21 09:25:22 +00:00
"""handle post from search form"""
2021-10-30 06:19:16 +00:00
search_form = VideoSearchForm(data=request.POST)
if search_form.is_valid():
search_query = request.POST.get("searchInput")
print(search_query)
search_url = "/?" + urlencode({"search": search_query})
return redirect(search_url, permanent=True)
return redirect("home")
2021-09-05 17:10:14 +00:00
2021-10-18 10:14:59 +00:00
class LoginView(View):
"""resolves to /login/
Greeting and login page
"""
2021-10-29 16:43:19 +00:00
@staticmethod
def get(request):
2021-10-18 10:14:59 +00:00
"""handle get requests"""
2021-10-24 08:34:00 +00:00
failed = bool(request.GET.get("failed"))
2021-10-29 16:43:19 +00:00
colors = AppConfig(request.user.id).colors
form = CustomAuthForm()
2021-10-24 08:34:00 +00:00
context = {"colors": colors, "form": form, "form_error": failed}
2021-10-18 10:14:59 +00:00
return render(request, "home/login.html", context)
@staticmethod
def post(request):
"""handle login post request"""
form = AuthenticationForm(data=request.POST)
if form.is_valid():
2021-10-22 11:23:06 +00:00
next_url = request.POST.get("next") or "home"
user = form.get_user()
login(request, user)
2021-10-22 05:01:30 +00:00
return redirect(next_url)
2021-10-24 08:34:00 +00:00
return redirect("/login?failed=true")
2021-10-18 10:14:59 +00:00
2021-09-05 17:10:14 +00:00
class AboutView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /about/
2021-09-05 17:10:14 +00:00
show helpful how to information
"""
@staticmethod
def get(request):
2021-09-21 09:25:22 +00:00
"""handle http get"""
2021-10-29 16:43:19 +00:00
colors = AppConfig(request.user.id).colors
2021-09-21 09:25:22 +00:00
context = {"title": "About", "colors": colors}
return render(request, "home/about.html", context)
2021-09-05 17:10:14 +00:00
class DownloadView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /download/
2021-09-05 17:10:14 +00:00
takes POST for downloading youtube links
"""
def get(self, request):
2021-09-21 09:25:22 +00:00
"""handle get requests"""
user_id = request.user.id
view_config = self.read_config(user_id)
2021-09-21 09:25:22 +00:00
page_get = int(request.GET.get("page", 0))
pagination_handler = Pagination(page_get, user_id)
2021-10-29 04:45:13 +00:00
url = view_config["es_url"] + "/ta_download/_search"
data = self.build_data(
pagination_handler, view_config["show_ignored_only"]
)
search = SearchHandler(url, data)
videos_hits = search.get_data()
max_hits = search.max_hits
if videos_hits:
all_video_hits = [i["source"] for i in videos_hits]
pagination_handler.validate(max_hits)
pagination = pagination_handler.pagination
else:
all_video_hits = False
pagination = False
2021-10-30 07:14:16 +00:00
add_form = AddToQueueForm()
2021-09-05 17:10:14 +00:00
context = {
2021-10-30 07:14:16 +00:00
"add_form": add_form,
"all_video_hits": all_video_hits,
2021-09-21 09:25:22 +00:00
"max_hits": max_hits,
"pagination": pagination,
"title": "Downloads",
2021-10-29 04:45:13 +00:00
"colors": view_config["colors"],
"show_ignored_only": view_config["show_ignored_only"],
"view_style": view_config["view_style"],
}
return render(request, "home/downloads.html", context)
@staticmethod
def read_config(user_id):
"""read config vars"""
2021-10-29 16:43:19 +00:00
config_handler = AppConfig(user_id)
2021-10-29 04:45:13 +00:00
view_key = f"{user_id}:view:downloads"
view_style = RedisArchivist().get_message(view_key)["status"]
if not view_style:
2021-10-29 16:43:19 +00:00
view_style = config_handler.config["default_view"]["downloads"]
2021-10-29 04:45:13 +00:00
ignored = RedisArchivist().get_message(f"{user_id}:show_ignored_only")
2021-10-29 04:45:13 +00:00
show_ignored_only = ignored["status"]
2021-10-29 16:43:19 +00:00
es_url = config_handler.config["application"]["es_url"]
2021-10-29 04:45:13 +00:00
view_config = {
"es_url": es_url,
2021-10-29 16:43:19 +00:00
"colors": config_handler.colors,
"view_style": view_style,
"show_ignored_only": show_ignored_only,
2021-09-05 17:10:14 +00:00
}
2021-10-29 04:45:13 +00:00
return view_config
2021-09-05 17:10:14 +00:00
@staticmethod
def build_data(pagination_handler, show_ignored_only):
2021-09-21 09:25:22 +00:00
"""build data dict for search"""
page_size = pagination_handler.pagination["page_size"]
page_from = pagination_handler.pagination["page_from"]
if show_ignored_only:
filter_view = "ignore"
else:
filter_view = "pending"
data = {
2021-09-21 09:25:22 +00:00
"size": page_size,
"from": page_from,
"query": {"term": {"status": {"value": filter_view}}},
"sort": [{"timestamp": {"order": "asc"}}],
}
return data
2021-09-05 17:10:14 +00:00
@staticmethod
def post(request):
2021-09-21 09:25:22 +00:00
"""handle post requests"""
2021-10-30 07:14:16 +00:00
to_queue = AddToQueueForm(data=request.POST)
if to_queue.is_valid():
url_str = request.POST.get("vid_url")
print(url_str)
try:
youtube_ids = UrlListParser(url_str).process_list()
except ValueError:
# failed to process
print(f"failed to parse: {url_str}")
mess_dict = {
"status": "downloading",
"level": "error",
2021-09-21 09:25:22 +00:00
"title": "Failed to extract links.",
"message": "Not a video, channel or playlist ID or URL",
}
RedisArchivist().set_message("progress:download", mess_dict)
2021-09-21 09:25:22 +00:00
return redirect("downloads")
2021-09-05 17:10:14 +00:00
print(youtube_ids)
extrac_dl.delay(youtube_ids)
sleep(2)
2021-09-21 09:25:22 +00:00
return redirect("downloads", permanent=True)
2021-09-05 17:10:14 +00:00
class ChannelIdView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /channel/<channel-id>/
2021-09-09 09:45:46 +00:00
display single channel page from channel_id
"""
2021-09-05 17:10:14 +00:00
def get(self, request, channel_id_detail):
2021-09-21 09:25:22 +00:00
"""get method"""
# es_url, colors, view_style = self.read_config()
view_config = self.read_config(user_id=request.user.id)
context = self.get_channel_videos(
request, channel_id_detail, view_config
)
context.update(view_config)
2021-09-21 09:25:22 +00:00
return render(request, "home/channel_id.html", context)
2021-09-05 17:10:14 +00:00
@staticmethod
def read_config(user_id):
2021-09-21 09:25:22 +00:00
"""read config file"""
2021-10-29 16:43:19 +00:00
config_handler = AppConfig(user_id)
config = config_handler.config
2021-09-05 17:10:14 +00:00
2021-10-31 09:15:33 +00:00
view_key = f"{user_id}:view:home"
view_style = RedisArchivist().get_message(view_key)["status"]
if not view_style:
view_style = config_handler.config["default_view"]["home"]
2021-10-29 07:42:12 +00:00
sort_by = RedisArchivist().get_message(f"{user_id}:sort_by")["status"]
if not sort_by:
sort_by = config["archive"]["sort_by"]
sort_order_key = f"{user_id}:sort_order"
sort_order = RedisArchivist().get_message(sort_order_key)["status"]
if not sort_order:
sort_order = config["archive"]["sort_order"]
hide_watched_key = f"{user_id}:hide_watched"
hide_watched = RedisArchivist().get_message(hide_watched_key)["status"]
view_config = {
2021-10-29 16:43:19 +00:00
"colors": config_handler.colors,
"es_url": config["application"]["es_url"],
2021-10-31 09:15:33 +00:00
"view_style": view_style,
"sort_by": sort_by,
"sort_order": sort_order,
"hide_watched": hide_watched,
}
return view_config
def get_channel_videos(self, request, channel_id_detail, view_config):
2021-09-21 09:25:22 +00:00
"""get channel from video index"""
page_get = int(request.GET.get("page", 0))
pagination_handler = Pagination(page_get, request.user.id)
2021-09-05 17:10:14 +00:00
# get data
url = view_config["es_url"] + "/ta_video/_search"
data = self.build_data(
pagination_handler, channel_id_detail, view_config
)
2021-09-05 17:10:14 +00:00
search = SearchHandler(url, data)
videos_hits = search.get_data()
max_hits = search.max_hits
if max_hits:
2021-09-21 09:25:22 +00:00
channel_info = videos_hits[0]["source"]["channel"]
channel_name = channel_info["channel_name"]
2021-09-05 17:10:14 +00:00
pagination_handler.validate(max_hits)
pagination = pagination_handler.pagination
else:
# get details from channel index when when no hits
channel_info, channel_name = self.get_channel_info(
channel_id_detail, view_config["es_url"]
2021-09-05 17:10:14 +00:00
)
videos_hits = False
pagination = False
context = {
2021-09-21 09:25:22 +00:00
"channel_info": channel_info,
"videos": videos_hits,
"max_hits": max_hits,
"pagination": pagination,
"title": "Channel: " + channel_name,
2021-09-05 17:10:14 +00:00
}
return context
@staticmethod
def build_data(pagination_handler, channel_id_detail, view_config):
2021-09-21 09:25:22 +00:00
"""build data dict for search"""
sort_by = view_config["sort_by"]
sort_order = view_config["sort_order"]
# overwrite sort_by to match key
if sort_by == "views":
sort_by = "stats.view_count"
elif sort_by == "likes":
sort_by = "stats.like_count"
elif sort_by == "downloaded":
sort_by = "date_downloaded"
2021-09-05 17:10:14 +00:00
data = {
"size": pagination_handler.pagination["page_size"],
"from": pagination_handler.pagination["page_from"],
2021-09-05 17:10:14 +00:00
"query": {
"bool": {
"must": [
{
"term": {
"channel.channel_id": {
"value": channel_id_detail
}
}
}
]
}
2021-09-05 17:10:14 +00:00
},
"sort": [{sort_by: {"order": sort_order}}],
2021-09-05 17:10:14 +00:00
}
if view_config["hide_watched"]:
to_append = {"term": {"player.watched": {"value": False}}}
data["query"]["bool"]["must"].append(to_append)
2021-09-05 17:10:14 +00:00
return data
@staticmethod
def get_channel_info(channel_id_detail, es_url):
2021-09-21 09:25:22 +00:00
"""get channel info from channel index if no videos"""
url = f"{es_url}/ta_channel/_doc/{channel_id_detail}"
2021-09-05 17:10:14 +00:00
data = False
search = SearchHandler(url, data)
channel_data = search.get_data()
2021-09-21 09:25:22 +00:00
channel_info = channel_data[0]["source"]
channel_name = channel_info["channel_name"]
2021-09-05 17:10:14 +00:00
return channel_info, channel_name
class ChannelView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /channel/
2021-09-09 09:45:46 +00:00
handle functionality for channel overview page, subscribe to channel,
search as you type for channel name
"""
2021-09-05 17:10:14 +00:00
def get(self, request):
2021-09-21 09:25:22 +00:00
"""handle http get requests"""
user_id = request.user.id
view_config = self.read_config(user_id=user_id)
2021-09-21 09:25:22 +00:00
page_get = int(request.GET.get("page", 0))
pagination_handler = Pagination(page_get, user_id)
2021-09-05 17:10:14 +00:00
# get
url = view_config["es_url"] + "/ta_channel/_search"
2021-09-05 17:10:14 +00:00
data = {
"size": pagination_handler.pagination["page_size"],
"from": pagination_handler.pagination["page_from"],
2021-09-21 09:25:22 +00:00
"query": {"match_all": {}},
"sort": [{"channel_name.keyword": {"order": "asc"}}],
2021-09-05 17:10:14 +00:00
}
if view_config["show_subed_only"]:
2021-09-21 09:25:22 +00:00
data["query"] = {"term": {"channel_subscribed": {"value": True}}}
2021-09-05 17:10:14 +00:00
search = SearchHandler(url, data)
channel_hits = search.get_data()
pagination_handler.validate(search.max_hits)
2021-10-30 06:19:16 +00:00
search_form = ChannelSearchForm()
subscribe_form = SubscribeToChannelForm()
2021-09-05 17:10:14 +00:00
context = {
2021-10-30 06:19:16 +00:00
"search_form": search_form,
"subscribe_form": subscribe_form,
2021-09-21 09:25:22 +00:00
"channels": channel_hits,
"max_hits": search.max_hits,
2021-09-21 09:25:22 +00:00
"pagination": pagination_handler.pagination,
"show_subed_only": view_config["show_subed_only"],
2021-09-21 09:25:22 +00:00
"title": "Channels",
"colors": view_config["colors"],
"view_style": view_config["view_style"],
"running": view_config["running"],
2021-09-05 17:10:14 +00:00
}
2021-09-21 09:25:22 +00:00
return render(request, "home/channel.html", context)
2021-09-05 17:10:14 +00:00
@staticmethod
2021-10-29 04:45:13 +00:00
def read_config(user_id):
2021-09-21 09:25:22 +00:00
"""read config file"""
2021-10-29 16:43:19 +00:00
config_handler = AppConfig(user_id)
2021-10-29 04:45:13 +00:00
view_key = f"{user_id}:view:channel"
view_style = RedisArchivist().get_message(view_key)["status"]
running = RedisArchivist().get_message("progress:subscribe")["status"]
2021-10-29 04:45:13 +00:00
if not view_style:
2021-10-29 16:43:19 +00:00
view_style = config_handler.config["default_view"]["channel"]
2021-10-29 04:45:13 +00:00
sub_only_key = f"{user_id}:show_subed_only"
show_subed_only = RedisArchivist().get_message(sub_only_key)["status"]
view_config = {
2021-10-29 16:43:19 +00:00
"es_url": config_handler.config["application"]["es_url"],
"view_style": view_style,
"show_subed_only": show_subed_only,
2021-10-29 16:43:19 +00:00
"colors": config_handler.colors,
"running": running,
}
return view_config
2021-09-05 17:10:14 +00:00
@staticmethod
def post(request):
2021-09-21 09:25:22 +00:00
"""handle http post requests"""
subscribe_form = SubscribeToChannelForm(data=request.POST)
if subscribe_form.is_valid():
RedisArchivist().set_message(
"progress:subscribe", {"status": "subscribing"}
)
url_str = request.POST.get("subscribe")
print(url_str)
subscribe_to.delay(url_str)
sleep(1)
2021-09-21 09:25:22 +00:00
return redirect("channel", permanent=True)
2021-09-05 17:10:14 +00:00
class PlaylistIdView(View):
"""resolves to /playlist/<playlist_id>
show all videos in a playlist
"""
def get(self, request, playlist_id_detail):
"""handle get request"""
view_config = self.read_config(user_id=request.user.id)
context = self.get_playlist_videos(
request, playlist_id_detail, view_config
)
context.update(view_config)
return render(request, "home/playlist_id.html", context)
@staticmethod
def read_config(user_id):
"""build config dict"""
config_handler = AppConfig(user_id)
config = config_handler.config
view_key = f"{user_id}:view:home"
view_style = RedisArchivist().get_message(view_key)["status"]
if not view_style:
view_style = config_handler.config["default_view"]["home"]
sort_by = RedisArchivist().get_message(f"{user_id}:sort_by")["status"]
if not sort_by:
sort_by = config["archive"]["sort_by"]
sort_order_key = f"{user_id}:sort_order"
sort_order = RedisArchivist().get_message(sort_order_key)["status"]
if not sort_order:
sort_order = config["archive"]["sort_order"]
hide_watched_key = f"{user_id}:hide_watched"
hide_watched = RedisArchivist().get_message(hide_watched_key)["status"]
view_config = {
"colors": config_handler.colors,
"es_url": config["application"]["es_url"],
"view_style": view_style,
"sort_by": sort_by,
"sort_order": sort_order,
"hide_watched": hide_watched,
}
return view_config
def get_playlist_videos(self, request, playlist_id_detail, view_config):
"""get matching videos for playlist"""
page_get = int(request.GET.get("page", 0))
pagination_handler = Pagination(page_get, request.user.id)
# get data
playlist_info = self.get_playlist_info(
playlist_id_detail, view_config["es_url"]
)
sort = {
i["youtube_id"]: i["idx"]
for i in playlist_info["playlist_entries"]
}
playlist_name = playlist_info["playlist_name"]
data = self.build_data(
pagination_handler, playlist_id_detail, view_config, sort
)
search = SearchHandler(
view_config["es_url"] + "/ta_video/_search", data
)
videos_hits = search.get_data()
channel_info = self.get_channel_info(
playlist_info["playlist_channel_id"], view_config["es_url"]
)
if search.max_hits:
pagination_handler.validate(search.max_hits)
pagination = pagination_handler.pagination
else:
videos_hits = False
pagination = False
context = {
"playlist_info": playlist_info,
"playlist_name": playlist_name,
"channel_info": channel_info,
"videos": videos_hits,
"max_hits": search.max_hits,
"pagination": pagination,
"title": "Playlist: " + playlist_name,
}
return context
@staticmethod
def build_data(pagination_handler, playlist_id_detail, view_config, sort):
"""build data query for es"""
sort_by = view_config["sort_by"]
# overwrite sort_by to match key
if sort_by == "views":
sort_by = "stats.view_count"
elif sort_by == "likes":
sort_by = "stats.like_count"
elif sort_by == "downloaded":
sort_by = "date_downloaded"
script = (
"if(params.scores.containsKey(doc['youtube_id'].value)) "
+ "{return params.scores[doc['youtube_id'].value];} "
+ "return 100000;"
)
data = {
"size": pagination_handler.pagination["page_size"],
"from": pagination_handler.pagination["page_from"],
"query": {
"bool": {
"must": [
2021-11-13 10:34:58 +00:00
{"match": {"playlist.keyword": playlist_id_detail}}
]
}
},
"sort": [
{
"_script": {
"type": "number",
"script": {
"lang": "painless",
"source": script,
"params": {"scores": sort},
},
"order": "asc",
}
}
],
}
if view_config["hide_watched"]:
to_append = {"term": {"player.watched": {"value": False}}}
data["query"]["bool"]["must"].append(to_append)
return data
@staticmethod
def get_channel_info(channel_id_detail, es_url):
"""get channel info from channel index if no videos"""
url = f"{es_url}/ta_channel/_doc/{channel_id_detail}"
search = SearchHandler(url, data=False)
channel_data = search.get_data()
channel_info = channel_data[0]["source"]
return channel_info
@staticmethod
def get_playlist_info(playlist_id_detail, es_url):
"""get playlist info header to no fail if playlist is empty"""
url = f"{es_url}/ta_playlist/_doc/{playlist_id_detail}"
search = SearchHandler(url, data=False)
playlist_data = search.get_data()
playlist_info = playlist_data[0]["source"]
return playlist_info
class PlaylistView(View):
"""resolves to /playlist/
show all playlists indexed
"""
def get(self, request):
"""handle http get requests"""
2021-11-10 10:29:11 +00:00
user_id = request.user.id
view_config = self.read_config(user_id=user_id)
2021-11-15 06:08:32 +00:00
# handle search
search_get = request.GET.get("search", False)
if search_get:
search_encoded = urllib.parse.quote(search_get)
else:
search_encoded = False
# define page size
2021-11-10 10:29:11 +00:00
page_get = int(request.GET.get("page", 0))
2021-11-15 06:08:32 +00:00
pagination_handler = Pagination(
page_get, user_id, search_get=search_encoded
)
2021-11-10 10:29:11 +00:00
url = view_config["es_url"] + "/ta_playlist/_search"
data = self.build_data(pagination_handler, search_get, view_config)
2021-11-10 10:29:11 +00:00
search = SearchHandler(url, data)
playlist_hits = search.get_data()
pagination_handler.validate(search.max_hits)
2021-11-15 06:08:32 +00:00
search_form = PlaylistSearchForm()
subscribe_form = SubscribeToChannelForm()
2021-11-10 10:29:11 +00:00
context = {
"subscribe_form": subscribe_form,
2021-11-15 06:08:32 +00:00
"search_form": search_form,
2021-11-10 10:29:11 +00:00
"title": "Playlists",
"colors": view_config["colors"],
"show_subed_only": view_config["show_subed_only"],
2021-11-10 10:29:11 +00:00
"pagination": pagination_handler.pagination,
"playlists": playlist_hits,
"view_style": view_config["view_style"],
"running": view_config["running"],
2021-11-10 10:29:11 +00:00
}
return render(request, "home/playlist.html", context)
2021-11-15 06:08:32 +00:00
@staticmethod
def build_data(pagination_handler, search_get, view_config):
2021-11-15 06:08:32 +00:00
"""build data object for query"""
data = {
"size": pagination_handler.pagination["page_size"],
"from": pagination_handler.pagination["page_from"],
"query": {
"term": {"playlist_entries.downloaded": {"value": True}}
},
"sort": [{"playlist_name.keyword": {"order": "asc"}}],
}
if view_config["show_subed_only"]:
data["query"] = {"term": {"playlist_subscribed": {"value": True}}}
2021-11-15 06:08:32 +00:00
if search_get:
data["query"] = {
"bool": {
"should": [
{
"multi_match": {
"query": search_get,
"fields": [
"playlist_channel_id",
"playlist_channel",
"playlist_name",
],
}
}
],
"minimum_should_match": 1,
}
}
return data
@staticmethod
def read_config(user_id):
"""read config file"""
config_handler = AppConfig(user_id)
2021-11-10 10:29:11 +00:00
view_key = f"{user_id}:view:playlist"
view_style = RedisArchivist().get_message(view_key)["status"]
if not view_style:
view_style = config_handler.config["default_view"]["channel"]
sub_only_key = f"{user_id}:show_subed_only"
show_subed_only = RedisArchivist().get_message(sub_only_key)["status"]
running = RedisArchivist().get_message("progress:subscribe")["status"]
view_config = {
"es_url": config_handler.config["application"]["es_url"],
"colors": config_handler.colors,
2021-11-10 10:29:11 +00:00
"view_style": view_style,
"show_subed_only": show_subed_only,
"running": running,
}
return view_config
2021-11-15 06:08:32 +00:00
@staticmethod
def post(request):
"""handle post from search form"""
search_form = PlaylistSearchForm(data=request.POST)
if search_form.is_valid():
search_query = request.POST.get("searchInput")
print(search_query)
search_url = "/playlist/?" + urlencode({"search": search_query})
return redirect(search_url, permanent=True)
subscribe_form = SubscribeToPlaylistForm(data=request.POST)
if subscribe_form.is_valid():
url_str = request.POST.get("subscribe")
print(url_str)
subscribe_to.delay(url_str)
sleep(1)
2021-11-15 06:08:32 +00:00
return redirect("playlist")
2021-09-05 17:10:14 +00:00
class VideoView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /video/<video-id>/
2021-09-09 09:45:46 +00:00
display details about a single video
"""
2021-09-05 17:10:14 +00:00
def get(self, request, video_id):
2021-09-21 09:25:22 +00:00
"""get single video"""
2021-10-29 16:43:19 +00:00
es_url, colors = self.read_config(user_id=request.user.id)
2021-09-21 09:25:22 +00:00
url = f"{es_url}/ta_video/_doc/{video_id}"
2021-09-05 17:10:14 +00:00
data = None
look_up = SearchHandler(url, data)
video_hit = look_up.get_data()
2021-09-21 09:25:22 +00:00
video_data = video_hit[0]["source"]
2021-10-10 09:09:02 +00:00
try:
rating = video_data["stats"]["average_rating"]
video_data["stats"]["average_rating"] = self.star_creator(rating)
except KeyError:
video_data["stats"]["average_rating"] = False
if "playlist" in video_data.keys():
playlists = video_data["playlist"]
playlist_nav = self.build_playlists(video_id, playlists)
else:
playlist_nav = False
2021-09-21 09:25:22 +00:00
video_title = video_data["title"]
context = {
"video": video_data,
"playlist_nav": playlist_nav,
"title": video_title,
"colors": colors,
}
2021-09-21 09:25:22 +00:00
return render(request, "home/video.html", context)
2021-09-05 17:10:14 +00:00
@staticmethod
def build_playlists(video_id, playlists):
"""build playlist nav if available"""
all_navs = []
for playlist_id in playlists:
handler = YoutubePlaylist(playlist_id)
handler.get_playlist_dict()
nav = handler.build_nav(video_id)
if nav:
all_navs.append(nav)
return all_navs
2021-09-05 17:10:14 +00:00
@staticmethod
2021-10-29 16:43:19 +00:00
def read_config(user_id):
2021-09-21 09:25:22 +00:00
"""read config file"""
2021-10-29 16:43:19 +00:00
config_handler = AppConfig(user_id)
es_url = config_handler.config["application"]["es_url"]
colors = config_handler.colors
2021-09-05 17:10:14 +00:00
return es_url, colors
2021-10-10 09:09:02 +00:00
@staticmethod
def star_creator(rating):
"""convert rating float to stars"""
stars = []
for _ in range(1, 6):
if rating >= 0.75:
stars.append("full")
elif 0.25 < rating < 0.75:
stars.append("half")
else:
stars.append("empty")
rating = rating - 1
return stars
2021-09-05 17:10:14 +00:00
class SettingsView(View):
2021-09-21 09:25:22 +00:00
"""resolves to /settings/
2021-09-09 09:45:46 +00:00
handle the settings page, display current settings,
take post request from the form to update settings
"""
2021-09-05 17:10:14 +00:00
@staticmethod
def get(request):
2021-09-21 09:25:22 +00:00
"""read and display current settings"""
2021-10-29 16:43:19 +00:00
config_handler = AppConfig(request.user.id)
colors = config_handler.colors
2021-09-05 17:10:14 +00:00
user_form = UserSettingsForm()
app_form = ApplicationSettingsForm()
context = {
"title": "Settings",
2021-10-29 16:43:19 +00:00
"config": config_handler.config,
"colors": colors,
"user_form": user_form,
"app_form": app_form,
}
2021-09-05 17:10:14 +00:00
2021-09-21 09:25:22 +00:00
return render(request, "home/settings.html", context)
2021-09-05 17:10:14 +00:00
@staticmethod
def post(request):
2021-09-21 09:25:22 +00:00
"""handle form post to update settings"""
form_response = forms.Form(request.POST)
if form_response.is_valid():
form_post = dict(request.POST)
print(form_post)
del form_post["csrfmiddlewaretoken"]
config_handler = AppConfig()
if "application-settings" in form_post:
del form_post["application-settings"]
config_handler.update_config(form_post)
elif "user-settings" in form_post:
del form_post["user-settings"]
config_handler.set_user_config(form_post, request.user.id)
2021-09-05 17:10:14 +00:00
2021-09-21 09:25:22 +00:00
return redirect("settings", permanent=True)
2021-09-05 17:10:14 +00:00
def progress(request):
# pylint: disable=unused-argument
2021-09-21 09:25:22 +00:00
"""endpoint for download progress ajax calls"""
2021-09-05 17:10:14 +00:00
config = AppConfig().config
2021-09-21 09:25:22 +00:00
cache_dir = config["application"]["cache_dir"]
json_data = RedisArchivist().get_dl_message(cache_dir)
2021-09-05 17:10:14 +00:00
return JsonResponse(json_data)
def process(request):
2021-09-21 09:25:22 +00:00
"""handle all the buttons calls via POST ajax"""
if request.method == "POST":
2021-10-29 04:45:13 +00:00
current_user = request.user.id
2021-09-05 17:10:14 +00:00
post_dict = json.loads(request.body.decode())
2021-10-29 04:45:13 +00:00
post_handler = PostData(post_dict, current_user)
if post_handler.to_exec:
2021-09-05 17:10:14 +00:00
task_result = post_handler.run_task()
return JsonResponse(task_result)
2021-09-21 09:25:22 +00:00
return JsonResponse({"success": False})
2021-09-05 17:10:14 +00:00
class PostData:
"""
map frontend http post values to backend funcs
handover long running tasks to celery
"""
2021-09-05 17:10:14 +00:00
2021-10-29 04:45:13 +00:00
def __init__(self, post_dict, current_user):
2021-09-05 17:10:14 +00:00
self.post_dict = post_dict
self.to_exec, self.exec_val = list(post_dict.items())[0]
2021-10-29 04:45:13 +00:00
self.current_user = current_user
2021-09-05 17:10:14 +00:00
def run_task(self):
"""execute and return task result"""
to_exec = self.exec_map()
task_result = to_exec()
return task_result
def exec_map(self):
"""map dict key and return function to execute"""
exec_map = {
"watched": self.watched,
"un_watched": self.un_watched,
"change_view": self.change_view,
"rescan_pending": self.rescan_pending,
"ignore": self.ignore,
"dl_pending": self.dl_pending,
2021-09-24 11:03:22 +00:00
"queue": self.queue_handler,
"unsubscribe": self.unsubscribe,
"subscribe": self.subscribe,
"sort_order": self.sort_order,
"hide_watched": self.hide_watched,
"show_subed_only": self.show_subed_only,
"dlnow": self.dlnow,
"show_ignored_only": self.show_ignored_only,
"forgetIgnore": self.forget_ignore,
"addSingle": self.add_single,
"deleteQueue": self.delete_queue,
"manual-import": self.manual_import,
2021-11-01 09:42:07 +00:00
"re-embed": self.re_embed,
"db-backup": self.db_backup,
"db-restore": self.db_restore,
"fs-rescan": self.fs_rescan,
"channel-search": self.channel_search,
2021-10-08 09:18:01 +00:00
"delete-video": self.delete_video,
2021-10-09 12:54:36 +00:00
"delete-channel": self.delete_channel,
2021-11-20 11:27:10 +00:00
"delete-playlist": self.delete_playlist,
2021-11-13 10:34:58 +00:00
"find-playlists": self.find_playlists,
}
return exec_map[self.to_exec]
def watched(self):
"""mark as watched"""
WatchState(self.exec_val).mark_as_watched()
2021-09-21 09:25:22 +00:00
return {"success": True}
def un_watched(self):
"""mark as unwatched"""
WatchState(self.exec_val).mark_as_unwatched()
return {"success": True}
2021-09-05 17:10:14 +00:00
def change_view(self):
"""process view changes in home, channel, and downloads"""
origin, new_view = self.exec_val.split(":")
2021-10-29 04:45:13 +00:00
key = f"{self.current_user}:view:{origin}"
print(f"change view: {key} to {new_view}")
RedisArchivist().set_message(key, {"status": new_view}, expire=False)
return {"success": True}
@staticmethod
def rescan_pending():
"""look for new items in subscribed channels"""
print("rescan subscribed channels")
update_subscribed.delay()
return {"success": True}
2021-09-05 17:10:14 +00:00
def ignore(self):
"""ignore from download queue"""
id_to_ignore = self.exec_val
print("ignore video " + id_to_ignore)
handler = PendingList()
handler.ignore_from_pending([id_to_ignore])
# also clear from redis queue
RedisQueue("dl_queue").clear_item(id_to_ignore)
return {"success": True}
@staticmethod
def dl_pending():
"""start the download queue"""
print("download pending")
2021-09-24 16:37:26 +00:00
running = download_pending.delay()
task_id = running.id
print("set task id: " + task_id)
RedisArchivist().set_message("dl_queue_id", task_id, expire=False)
return {"success": True}
2021-09-24 11:03:22 +00:00
def queue_handler(self):
"""queue controls from frontend"""
to_execute = self.exec_val
if to_execute == "stop":
print("stopping download queue")
RedisQueue("dl_queue").clear()
2021-09-25 08:35:36 +00:00
elif to_execute == "kill":
task_id = RedisArchivist().get_message("dl_queue_id")
2021-10-11 09:11:30 +00:00
if not isinstance(task_id, str):
2021-10-11 08:26:31 +00:00
task_id = False
else:
print("brutally killing " + task_id)
2021-09-25 08:35:36 +00:00
kill_dl(task_id)
2021-09-24 11:03:22 +00:00
return {"success": True}
def unsubscribe(self):
"""unsubscribe from channels or playlists"""
id_unsub = self.exec_val
print("unsubscribe from " + id_unsub)
to_unsub_list = UrlListParser(id_unsub).process_list()
for to_unsub in to_unsub_list:
unsub_type = to_unsub["type"]
unsub_id = to_unsub["url"]
if unsub_type == "playlist":
PlaylistSubscription().change_subscribe(
unsub_id, subscribe_status=False
)
elif unsub_type == "channel":
ChannelSubscription().change_subscribe(
unsub_id, channel_subscribed=False
)
else:
raise ValueError("failed to process " + id_unsub)
return {"success": True}
def subscribe(self):
"""subscribe to channel or playlist, called from js buttons"""
id_sub = self.exec_val
print("subscribe to " + id_sub)
subscribe_to.delay(id_sub)
return {"success": True}
def sort_order(self):
"""change the sort between published to downloaded"""
2021-10-29 07:42:12 +00:00
sort_order = {"status": self.exec_val}
if self.exec_val in ["asc", "desc"]:
RedisArchivist().set_message(
2021-10-29 07:42:12 +00:00
f"{self.current_user}:sort_order", sort_order, expire=False
)
else:
2021-10-29 07:42:12 +00:00
RedisArchivist().set_message(
f"{self.current_user}:sort_by", sort_order, expire=False
)
return {"success": True}
def hide_watched(self):
"""toggle if to show watched vids or not"""
key = f"{self.current_user}:hide_watched"
message = {"status": bool(int(self.exec_val))}
print(f"toggle {key}: {message}")
RedisArchivist().set_message(key, message, expire=False)
return {"success": True}
def show_subed_only(self):
"""show or hide subscribed channels only on channels page"""
key = f"{self.current_user}:show_subed_only"
message = {"status": bool(int(self.exec_val))}
print(f"toggle {key}: {message}")
RedisArchivist().set_message(key, message, expire=False)
return {"success": True}
def dlnow(self):
"""start downloading single vid now"""
youtube_id = self.exec_val
print("downloading: " + youtube_id)
2021-09-25 10:40:33 +00:00
running = download_single.delay(youtube_id=youtube_id)
task_id = running.id
print("set task id: " + task_id)
RedisArchivist().set_message("dl_queue_id", task_id, expire=False)
return {"success": True}
def show_ignored_only(self):
"""switch view on /downloads/ to show ignored only"""
show_value = self.exec_val
key = f"{self.current_user}:show_ignored_only"
value = {"status": show_value}
print(f"Filter download view ignored only: {show_value}")
RedisArchivist().set_message(key, value, expire=False)
return {"success": True}
def forget_ignore(self):
"""delete from ta_download index"""
youtube_id = self.exec_val
print("forgetting from download index: " + youtube_id)
PendingList().delete_from_pending(youtube_id)
return {"success": True}
def add_single(self):
"""add single youtube_id to download queue"""
youtube_id = self.exec_val
print("add vid to dl queue: " + youtube_id)
PendingList().delete_from_pending(youtube_id)
youtube_ids = UrlListParser(youtube_id).process_list()
extrac_dl.delay(youtube_ids)
return {"success": True}
def delete_queue(self):
"""delete download queue"""
status = self.exec_val
print("deleting from download queue: " + status)
if status == "pending":
PendingList().delete_pending("pending")
elif status == "ignore":
PendingList().delete_pending("ignore")
return {"success": True}
@staticmethod
def manual_import():
"""run manual import from settings page"""
print("starting manual import")
run_manual_import.delay()
return {"success": True}
2021-11-01 09:42:07 +00:00
@staticmethod
def re_embed():
"""rewrite thumbnails into media files"""
print("start video thumbnail embed process")
re_sync_thumbs.delay()
return {"success": True}
@staticmethod
def db_backup():
"""backup es to zip from settings page"""
print("backing up database")
run_backup.delay()
return {"success": True}
@staticmethod
def db_restore():
"""restore es zip from settings page"""
print("restoring index from backup zip")
run_restore_backup.delay()
return {"success": True}
@staticmethod
def fs_rescan():
"""start file system rescan task"""
print("start filesystem scan")
rescan_filesystem.delay()
return {"success": True}
def channel_search(self):
"""search for channel name as_you_type"""
search_query = self.exec_val
print("searching for: " + search_query)
search_results = SearchForm().search_channels(search_query)
return search_results
2021-10-08 09:18:01 +00:00
def delete_video(self):
"""delete media file, metadata and thumb"""
youtube_id = self.exec_val
YoutubeVideo(youtube_id).delete_media_file()
return {"success": True}
2021-10-09 12:54:36 +00:00
def delete_channel(self):
"""delete channel and all matching videos"""
channel_id = self.exec_val
YoutubeChannel(channel_id).delete_channel()
return {"success": True}
2021-11-13 10:34:58 +00:00
2021-11-20 11:27:10 +00:00
def delete_playlist(self):
"""delete playlist, only metadata or incl all videos"""
playlist_dict = self.exec_val
playlist_id = playlist_dict["playlist-id"]
playlist_action = playlist_dict["playlist-action"]
print(f"delete {playlist_action} from playlist {playlist_id}")
if playlist_action == "metadata":
YoutubePlaylist(playlist_id).delete_metadata()
elif playlist_action == "all":
YoutubePlaylist(playlist_id).delete_videos_playlist()
return {"success": True}
2021-11-13 10:34:58 +00:00
def find_playlists(self):
"""add all playlists of a channel"""
channel_id = self.exec_val
index_channel_playlists.delay(channel_id)
return {"success": True}