BackEnd/Controllers/Videos.cs

160 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace YTManager.Controllers {
[Produces("application/json")]
[Route("api/Videos")]
public class VideosController : Controller {
// Custom return type for API accesses. Done this way to ensure we
// always return the expected data regardless of the underlying model.
struct Video_ForAPI {
// Title of the video.
public string Title;
// Description of the video.
public string Description;
// Youtube ID of the video.
public string ID;
// Thumbnail URL.
public string Thumbnail;
// Channel on youtube that owns this video
public string Channel;
// Duration of the video in seconds.
public int Seconds;
// What tags are relevant with this video.
public List<string> Tags;
// Populate this struct using a model video.
public Video_ForAPI(Models.Video video) {
Title = video.Title;
Description = video.Description;
ID = video.YoutubeID;
Thumbnail = video.ThumbnailURL;
Channel = video.Channel.Title;
Seconds = (int)video.Duration.TotalSeconds;
Tags = video.Tags == null ? new List<string>() : video.Tags.ToList();
}
}
// Maximum number of entries to return per query.
private readonly int max_per_query = 18;
// DB context used for all these calls.
private readonly MediaDB db;
// Constructor to fetch the db context.
public VideosController(MediaDB context) => db = context;
struct Search_Query {
// What the duration type is.
public enum _Duration_Type { LessThan, GreaterThan, Unset };
public _Duration_Type Duration_Type;
// What the expected duration of the videos are.
public TimeSpan Duration;
// Remaining tokens.
public List<String> Remaining_Tokens;
}
// Searches for videos using the specified tag(s).
[HttpGet]
public async Task<IActionResult> Search() {
var converted = await db.Videos
.OrderByDescending(i => i.AddedToYT)
.Take(max_per_query)
.Include(v => v.Channel)
.Select(v => new Video_ForAPI(v))
.ToListAsync();
// Convert all the videos to what we will send back.
return Ok(converted);
}
// Searches for videos using the specified tag(s).
[HttpGet("{searchstr}")]
public async Task<IActionResult> Search([FromRoute] string searchstr) {
// Log this to the terminal.
Console.WriteLine($"{DateTime.Now} == Search request for -> {searchstr}.");
// What to use for searching videos with.
var parsed_query = new Search_Query();
// Assume the search string is comma seperated.
parsed_query.Remaining_Tokens = searchstr.Split(',').Select(t => t.Trim().ToLower()).ToList();
// Find a potential single video duration.
List<string> greaterthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('>')).ToList();
List<string> lessthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('<')).ToList();
if (greaterthan.Count() + lessthan.Count() > 1)
return BadRequest();
if (greaterthan.Count() == 1) parsed_query.Duration_Type = Search_Query._Duration_Type.GreaterThan;
else if (lessthan.Count() == 1) parsed_query.Duration_Type = Search_Query._Duration_Type.LessThan;
else parsed_query.Duration_Type = Search_Query._Duration_Type.Unset;
greaterthan.ForEach(g => parsed_query.Remaining_Tokens.Remove(g));
lessthan.ForEach(l => parsed_query.Remaining_Tokens.Remove(l));
// Attempt to parse it using ISO8601 duration.
if (parsed_query.Duration_Type != Search_Query._Duration_Type.Unset) {
string timingstr = "PT" + greaterthan
.Concat(lessthan)
.Select(s => s.TrimStart(new char[] { '<', '>' }))
.First();
try {
parsed_query.Duration = XmlConvert.ToTimeSpan(timingstr.ToUpper());
} catch (Exception) {
return BadRequest("Failed to parse duration.");
}
}
// Get from the database all videos which satisfy the query via
// AND'ing all the queries.
var dbquery = db.Videos.Select(v => v);
// Get all videos that satisfy the time duration.
if (parsed_query.Duration_Type == Search_Query._Duration_Type.GreaterThan)
dbquery = dbquery.Where(v => v.Duration >= parsed_query.Duration);
else if (parsed_query.Duration_Type == Search_Query._Duration_Type.LessThan)
dbquery = dbquery.Where(v => v.Duration <= parsed_query.Duration);
// Get all videos that match their title, description, or channel name
// with the remaining tokens.
parsed_query.Remaining_Tokens.ForEach(token => {
dbquery = dbquery.Where(v =>
v.Channel.Title.ToLower().Contains(token) ||
v.Title.ToLower().Contains(token) ||
v.Description.ToLower().Contains(token) ||
v.YoutubeID.ToLower().Contains(token));
});
// Get all the relevant videos.
var vids = await dbquery
.OrderByDescending(i => i.AddedToYT)
.Take(max_per_query)
.Include(v => v.Channel)
.ToListAsync();
// Convert them to what we will send out.
var converted = vids
.Select(v => new Video_ForAPI(v))
.ToList();
// Log this to the terminal.
Console.WriteLine($"{DateTime.Now} == Search request for -> {searchstr} found {converted.Count()} videos.");
// Convert all the videos to what we will send back.
return Ok(converted);
}
}
}