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 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() : 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 Remaining_Tokens; } // Searches for videos using the specified tag(s). [HttpGet] public async Task 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 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 greaterthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('>')).ToList(); List 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); } } }