using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Director; using franz; using Newtonsoft.Json; using ShowHandlers; namespace director { public class Program { //private static Telefranz tf; public static Config conf; private static TimeSpan calendarNaptime = TimeSpan.FromHours(1); public static Scratch scratch; private static HttpClient httpClient; private static readonly ConcurrentQueue workQueue = new ConcurrentQueue(); private static readonly AutoResetEvent _signal = new AutoResetEvent(false); private const int concurrentWorkers = 2; static void Main(string[] args) { if (!File.Exists("appsettings.json")) { Console.Error.WriteLine("appsettings.json was not found!"); conf = new Config(); File.WriteAllText("appsettings.json", JsonConvert.SerializeObject(conf, Formatting.Indented)); return; } conf = JsonConvert.DeserializeObject(File.ReadAllText("appsettings.json")); HumanCommunication.Configure(conf.call_for_humans_discord_webhook); httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String( System.Text.Encoding.ASCII.GetBytes($"{conf.webdav_username}:{conf.webdav_password}"))); Telefranz.Configure("scheduler", conf.kafka_bootstrap); scratch = Scratch.LoadScratch(); for (var i = 0; i < concurrentWorkers; i++) { Task.Run(threadwork); } while (true) { Task.WaitAll( Task.Run(checkCalendars), Task.Delay(calendarNaptime) ); } } private static void checkCalendars() { try { lock (scratch) { scratch.agenda.Clear(); } Task.WaitAll( checkCalendar(conf.calendar_youtube, "youtube", (ref Schedulable.Schedulable n) => { n.ScedulableType = Schedulable.ScedulableType.YTRelease; }), checkCalendar(conf.calendar_twitch, "twitch", (ref Schedulable.Schedulable n) => { n.ScedulableType = Schedulable.ScedulableType.TwitchStream; }) ); lock (scratch) { scratch.Save(); } } catch (Exception e) { Console.Error.WriteLine(e); } foreach (var s in scratch.agenda) { //todo: find the perfect lead time. if ((s.Showtime - TimeSpan.FromDays(1)) - DateTime.Now <= calendarNaptime) { var copy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(s)); workQueue.Enqueue(copy); _signal.Set(); } } Console.WriteLine("calendars checked"); #if (DEBUG) for (int i = 0; i < 0; i++) { var psuedo = new Schedulable.Schedulable(); psuedo.Showtime = DateTime.Now + TimeSpan.FromSeconds(30); var partiallyCopiable = scratch.agenda?.FirstOrDefault(s => s.ScedulableType == Schedulable.ScedulableType.YTRelease); psuedo.Occurrence = partiallyCopiable.Occurrence; psuedo.Occurrence.OccurrenceStart = psuedo.Showtime; psuedo.Occurrence.OccurrenceEnd = psuedo.Showtime; psuedo.ScedulableType = Schedulable.ScedulableType.YTRelease; workQueue.Enqueue(psuedo); _signal.Set(); } #endif } private delegate void schedulableCreate(ref Schedulable.Schedulable creating); private static async Task checkCalendar(string calendarUri, string calLabel, schedulableCreate createSchedulable) { //?export is a hack to allow me to access the calendar //it likes to throw an error saying "this is the webDAV interface, use webDAV" at my webDAV client, stopping me from using webDAV. var calString = await httpClient.GetStringAsync(conf.webdav_uri + calendarUri + "?export"); var knownChecklist = new List(); lock (scratch) { scratch.Calendars[calLabel] = calString; iCalHoopJumping.LoadCalendar(calLabel, calString); //todo: I'm pretty sure some library is returning me only things in the future. Verify, and also make sure it's returning things in the present. foreach (var occurrence in iCalHoopJumping.getOccurrences(calLabel)) { var newSchedulable = new Schedulable.Schedulable() { Occurrence = occurrence, Showtime = occurrence.OccurrenceStart }; createSchedulable(ref newSchedulable); scratch.agenda.Add(newSchedulable); } } } private static void threadwork() { Schedulable.Schedulable todo = null; while (true) { _signal.WaitOne(calendarNaptime); if (!workQueue.TryDequeue(out todo)) { continue; } ShowHandler handler = null; switch (todo.ScedulableType) { case Schedulable.ScedulableType.TwitchStream: Console.WriteLine("it's a twitch stream"); handler = new TwitchStreamHandler(); break; case Schedulable.ScedulableType.YTRelease: Console.WriteLine("it's a yt release"); handler = new YoutubeHandler(); break; default: HumanCommunication.Instance.Say($"unknown schedulable type!\n{JsonConvert.SerializeObject(todo)}", HumanCommunication.LogLevel.Showstopper); continue; } var napLength = (todo.Showtime - handler.LeadTimeDesired) - DateTime.Now; Console.WriteLine($"threadwork consumes! showtime at {todo.Showtime}; napping until {todo.Showtime - handler.LeadTimeDesired} ({napLength})"); if (napLength.TotalMinutes > 0) { Task.WaitAll(Task.Delay(napLength)); } Console.WriteLine("places, everyone!"); try { handler.Handle(todo.Occurrence); } catch (Exception e) { HumanCommunication.Instance.Say($"error in show handler! Panicking!\n{JsonConvert.SerializeObject(e)}", HumanCommunication.LogLevel.Showstopper); } break; } } } }