yavsc/src/Yavsc/Services/LiveProcessor.cs

250 lines
11 KiB
C#
Raw Normal View History

2019-06-27 10:30:28 +01:00
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.SignalR;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Logging;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.ViewModels.Streaming;
using Yavsc.Models.Messaging;
2019-06-28 00:49:20 +01:00
using Yavsc.Models.FileSystem;
using Newtonsoft.Json;
2019-06-27 10:30:28 +01:00
namespace Yavsc.Services
{
2020-09-12 01:11:30 +01:00
public class LiveProcessor : ILiveProcessor
{
readonly IHubContext _hubContext;
private readonly ILogger _logger;
readonly ApplicationDbContext _dbContext;
public PathString LiveCastingPath { get; set; } = Constants.LivePath;
2019-06-27 10:30:28 +01:00
2020-09-12 01:11:30 +01:00
public ConcurrentDictionary<string, LiveCastHandler> Casters { get; } = new ConcurrentDictionary<string, LiveCastHandler>();
2019-06-27 10:30:28 +01:00
public LiveProcessor(ApplicationDbContext dbContext, ILoggerFactory loggerFactory)
{
_dbContext = dbContext;
_hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
_logger = loggerFactory.CreateLogger<LiveProcessor>();
}
2020-09-12 01:11:30 +01:00
public async Task<bool> AcceptStream(HttpContext context)
2019-06-27 10:30:28 +01:00
{
// TODO defer request handling
var liveId = long.Parse(context.Request.Path.Value.Substring(LiveCastingPath.Value.Length + 1));
var userId = context.User.GetUserId();
var user = await _dbContext.Users.FirstAsync(u => u.Id == userId);
var uname = user.UserName;
var flow = _dbContext.LiveFlow.Include(f => f.Owner).SingleOrDefault(f => (f.OwnerId == userId && f.Id == liveId));
if (flow == null)
{
_logger.LogWarning("Aborting. Flow info was not found.");
context.Response.StatusCode = 400;
return false;
}
2020-09-12 01:11:30 +01:00
_logger.LogInformation("flow : " + flow.Title + " for " + uname);
LiveCastHandler liveHandler = null;
2019-06-27 10:30:28 +01:00
if (Casters.ContainsKey(uname))
{
2019-06-28 00:49:20 +01:00
_logger.LogWarning($"Casters.ContainsKey({uname})");
liveHandler = Casters[uname];
2020-09-12 01:11:30 +01:00
if (liveHandler.Socket.State == WebSocketState.Open || liveHandler.Socket.State == WebSocketState.Connecting)
2019-06-27 10:30:28 +01:00
{
2020-09-12 01:11:30 +01:00
_logger.LogWarning($"Closing cx");
2019-06-27 10:30:28 +01:00
// FIXME loosed connexion should be detected & disposed else where
2020-09-12 01:11:30 +01:00
await liveHandler.Socket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "one by user", CancellationToken.None);
2019-06-27 10:30:28 +01:00
}
2020-09-12 01:11:30 +01:00
if (!liveHandler.TokenSource.IsCancellationRequested)
{
liveHandler.TokenSource.Cancel();
2019-06-27 10:30:28 +01:00
}
liveHandler.Socket.Dispose();
liveHandler.Socket = await context.WebSockets.AcceptWebSocketAsync();
liveHandler.TokenSource = new CancellationTokenSource();
2019-06-27 10:30:28 +01:00
}
else
{
2019-06-28 00:49:20 +01:00
_logger.LogInformation($"new caster");
2019-06-27 10:30:28 +01:00
// Accept the socket
liveHandler = new LiveCastHandler { Socket = await context.WebSockets.AcceptWebSocketAsync() };
2019-06-27 10:30:28 +01:00
}
_logger.LogInformation("Accepted web socket");
// Dispatch the flow
2020-09-12 01:11:30 +01:00
2019-06-27 10:30:28 +01:00
try
{
if (liveHandler.Socket != null && liveHandler.Socket.State == WebSocketState.Open)
2019-06-27 10:30:28 +01:00
{
Casters[uname] = liveHandler;
2019-06-27 10:30:28 +01:00
// TODO: Handle the socket here.
// Find receivers: others in the chat room
// send them the flow
2020-09-12 01:11:30 +01:00
var buffer = new byte[Constants.WebSocketsMaxBufLen];
2019-06-27 10:30:28 +01:00
var sBuffer = new ArraySegment<byte>(buffer);
_logger.LogInformation("Receiving bytes...");
WebSocketReceiveResult received = await liveHandler.Socket.ReceiveAsync(sBuffer, liveHandler.TokenSource.Token);
2020-09-12 01:11:30 +01:00
_logger.LogInformation($"Received bytes : {received.Count}");
2019-06-27 10:30:28 +01:00
_logger.LogInformation($"Is the end : {received.EndOfMessage}");
2019-06-28 00:49:20 +01:00
const string livePath = "live";
2019-06-27 10:30:28 +01:00
2019-06-28 00:49:20 +01:00
string destDir = context.User.InitPostToFileSystem(livePath);
2019-06-27 10:30:28 +01:00
_logger.LogInformation($"Saving flow to {destDir}");
2020-09-12 01:11:30 +01:00
string fileName = flow.GetFileName();
FileInfo destFileInfo = new FileInfo(Path.Combine(destDir, fileName));
// this should end :-)
2020-09-12 01:11:30 +01:00
while (destFileInfo.Exists)
{
flow.SequenceNumber++;
2020-09-12 01:11:30 +01:00
fileName = flow.GetFileName();
destFileInfo = new FileInfo(Path.Combine(destDir, fileName));
}
2019-06-28 00:49:20 +01:00
var fsInputQueue = new Queue<ArraySegment<byte>>();
2020-09-12 01:11:30 +01:00
bool endOfInput = false;
fsInputQueue.Enqueue(sBuffer);
2020-09-12 01:11:30 +01:00
var taskWritingToFs = liveHandler.ReceiveUserFile(user, _logger, destDir, fsInputQueue, fileName, flow.MediaType, () => endOfInput);
2019-06-27 10:30:28 +01:00
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
hubContext.Clients.All.addPublicStream(new PublicStreamInfo
{
id = flow.Id,
sender = flow.Owner.UserName,
title = flow.Title,
url = flow.GetFileUrl(),
mediaType = flow.MediaType
}, $"{flow.Owner.UserName} is starting a stream!");
Stack<string> ToClose = new Stack<string>();
try
{
2020-09-12 01:11:30 +01:00
do
{
_logger.LogInformation($"Echoing {received.Count} bytes received in a {received.MessageType} message; Fin={received.EndOfMessage}");
// Echo anything we receive
// and send to all listner found
2020-09-12 01:11:30 +01:00
_logger.LogInformation($"{liveHandler.Listeners.Count} listeners");
foreach (var cliItem in liveHandler.Listeners)
2019-06-27 10:30:28 +01:00
{
var listenningSocket = cliItem.Value;
2020-09-12 01:11:30 +01:00
if (listenningSocket.State == WebSocketState.Open)
{
_logger.LogInformation(cliItem.Key);
2019-06-27 10:30:28 +01:00
await listenningSocket.SendAsync(
sBuffer, received.MessageType, received.EndOfMessage, liveHandler.TokenSource.Token);
2019-06-27 10:30:28 +01:00
}
else if (listenningSocket.State == WebSocketState.CloseReceived || listenningSocket.State == WebSocketState.CloseSent)
2019-06-27 10:30:28 +01:00
{
ToClose.Push(cliItem.Key);
}
}
2020-09-12 01:11:30 +01:00
if (!received.CloseStatus.HasValue)
{
_logger.LogInformation("try and receive new bytes");
buffer = new byte[Constants.WebSocketsMaxBufLen];
sBuffer = new ArraySegment<byte>(buffer);
received = await liveHandler.Socket.ReceiveAsync(sBuffer, liveHandler.TokenSource.Token);
2020-09-12 01:11:30 +01:00
_logger.LogInformation($"Received bytes : {received.Count}");
2019-06-27 10:30:28 +01:00
_logger.LogInformation($"Is the end : {received.EndOfMessage}");
2020-09-12 01:11:30 +01:00
fsInputQueue.Enqueue(sBuffer);
if (received.CloseStatus.HasValue)
{
endOfInput=true;
_logger.LogInformation($"received a close status: {received.CloseStatus.Value}: {received.CloseStatusDescription}");
}
}
2020-09-12 01:11:30 +01:00
else endOfInput=true;
while (ToClose.Count > 0)
2019-06-27 10:30:28 +01:00
{
string no = ToClose.Pop();
_logger.LogInformation("Closing follower connection");
WebSocket listenningSocket;
2020-09-12 01:11:30 +01:00
if (liveHandler.Listeners.TryRemove(no, out listenningSocket))
{
2019-06-28 00:49:20 +01:00
await listenningSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,
"State != WebSocketState.Open", CancellationToken.None);
listenningSocket.Dispose();
}
2019-06-27 10:30:28 +01:00
}
}
2020-09-12 01:11:30 +01:00
while (liveHandler.Socket.State == WebSocketState.Open);
2019-06-27 10:30:28 +01:00
_logger.LogInformation("Closing connection");
2019-06-28 00:49:20 +01:00
taskWritingToFs.Wait();
2020-09-12 01:11:30 +01:00
await liveHandler.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, received.CloseStatusDescription, liveHandler.TokenSource.Token);
liveHandler.TokenSource.Cancel();
liveHandler.Dispose();
2020-09-12 01:11:30 +01:00
_logger.LogInformation("Resulting file : " + JsonConvert.SerializeObject(taskWritingToFs.Result));
2019-06-27 10:30:28 +01:00
}
catch (Exception ex)
{
_logger.LogError($"Exception occured : {ex.Message}");
_logger.LogError(ex.StackTrace);
liveHandler.TokenSource.Cancel();
throw;
2019-06-27 10:30:28 +01:00
}
2019-06-28 00:49:20 +01:00
taskWritingToFs.Dispose();
2019-06-27 10:30:28 +01:00
}
else
2019-06-28 00:49:20 +01:00
{
// Socket was not accepted open ...
2020-09-12 01:11:30 +01:00
// not (meta.Socket != null && meta.Socket.State == WebSocketState.Open)
if (liveHandler.Socket != null)
2019-06-27 10:30:28 +01:00
{
2020-09-12 01:11:30 +01:00
_logger.LogError($"meta.Socket.State not Open: {liveHandler.Socket.State} ");
liveHandler.Socket.Dispose();
2019-06-27 10:30:28 +01:00
}
else
_logger.LogError("socket object is null");
}
2020-09-12 01:11:30 +01:00
2019-06-28 00:49:20 +01:00
RemoveLiveInfo(uname);
2019-06-27 10:30:28 +01:00
}
catch (IOException ex)
{
if (ex.Message == "Unexpected end of stream")
{
_logger.LogError($"Unexpected end of stream");
}
else
{
_logger.LogError($"Really unexpected end of stream");
await liveHandler.Socket?.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, ex.Message, CancellationToken.None);
}
liveHandler.Socket?.Dispose();
2019-06-28 00:49:20 +01:00
RemoveLiveInfo(uname);
2019-06-27 10:30:28 +01:00
}
return true;
}
2019-06-28 00:49:20 +01:00
void RemoveLiveInfo(string userName)
{
LiveCastHandler caster;
2020-09-12 01:11:30 +01:00
if (Casters.TryRemove(userName, out caster))
2019-06-28 00:49:20 +01:00
_logger.LogInformation("removed live info");
2020-09-12 01:11:30 +01:00
else
2019-06-28 00:49:20 +01:00
_logger.LogError("could not remove live info");
}
2019-06-27 10:30:28 +01:00
}
2020-09-12 01:11:30 +01:00
}