diff --git a/src/Yavsc/Hubs/ChatHub.cs b/src/Yavsc/Hubs/ChatHub.cs index 9ef1e0a2..3182316f 100644 --- a/src/Yavsc/Hubs/ChatHub.cs +++ b/src/Yavsc/Hubs/ChatHub.cs @@ -189,7 +189,7 @@ namespace Yavsc public string Topic; } - public void Join(string roomName) + public ChatRoomInfo Join(string roomName) { _logger.LogInformation("a client for " + roomName); var userName = ChatUserNames[Context.ConnectionId]; @@ -214,13 +214,13 @@ namespace Yavsc Clients.Group("room_" + roomName).notify(NotificationTypes.UserJoin, Context.ConnectionId, Clients.Caller.UserName); _logger.LogInformation("exiting ok."); - return; + return chanInfo; } else { _logger.LogInformation("room seemd to be avaible ... but we could get no info on it."); Clients.Caller.notify(NotificationTypes.Error, "join get chan failed ..."); - return; + return null; } } // chan was almost empty @@ -247,9 +247,10 @@ namespace Yavsc if (Channels.TryAdd(roomName, chanInfo)) { Groups.Add(Context.ConnectionId, roomGroupName); - Clients.Caller.onJoined(chanInfo); + return(chanInfo); } else _logger.LogError("Chan create failed unexpectly..."); + return null; } [Authorize] diff --git a/src/Yavsc/Migrations/20190510021107_chanDates.Designer.cs b/src/Yavsc/Migrations/20190510021107_chanDates.Designer.cs index d5e3fbe2..b38e8e4b 100644 --- a/src/Yavsc/Migrations/20190510021107_chanDates.Designer.cs +++ b/src/Yavsc/Migrations/20190510021107_chanDates.Designer.cs @@ -1,7 +1,6 @@ using System; using Microsoft.Data.Entity; using Microsoft.Data.Entity.Infrastructure; -using Microsoft.Data.Entity.Metadata; using Microsoft.Data.Entity.Migrations; using Yavsc.Models; diff --git a/src/Yavsc/Migrations/20190510021107_chanDates.cs b/src/Yavsc/Migrations/20190510021107_chanDates.cs index cdb0fe83..4c498a6a 100644 --- a/src/Yavsc/Migrations/20190510021107_chanDates.cs +++ b/src/Yavsc/Migrations/20190510021107_chanDates.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Microsoft.Data.Entity.Migrations; namespace Yavsc.Migrations diff --git a/src/Yavsc/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Yavsc/Migrations/ApplicationDbContextModelSnapshot.cs index afb03189..a92d834b 100644 --- a/src/Yavsc/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Yavsc/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,8 +1,6 @@ using System; using Microsoft.Data.Entity; using Microsoft.Data.Entity.Infrastructure; -using Microsoft.Data.Entity.Metadata; -using Microsoft.Data.Entity.Migrations; using Yavsc.Models; namespace Yavsc.Migrations diff --git a/src/Yavsc/Models/ApplicationDbContext.cs b/src/Yavsc/Models/ApplicationDbContext.cs index 1f0f33e2..2785f1fe 100644 --- a/src/Yavsc/Models/ApplicationDbContext.cs +++ b/src/Yavsc/Models/ApplicationDbContext.cs @@ -258,7 +258,9 @@ namespace Yavsc.Models public DbSet Announce { get; set; } - // TODO useless, to drop + // TODO remove and opt for for memory only storing, + // as long as it must be set empty each time the service is restarted, + // and that chatting should be kept as must as possible independent from db context public DbSet ChatConnection { get; set; } public DbSet ChatRoom { get; set; } diff --git a/src/Yavsc/Startup/Startup.cs b/src/Yavsc/Startup/Startup.cs index b2e80e6a..db811c97 100755 --- a/src/Yavsc/Startup/Startup.cs +++ b/src/Yavsc/Startup/Startup.cs @@ -51,11 +51,11 @@ namespace Yavsc public static string AvatarsDirName { private set; get; } public static string GitDirName { private set; get; } public static string Authority { get; private set; } - public static string Temp { get; set; } + public static string Temp { get; set; } public static SiteSettings SiteSetup { get; private set; } public static GoogleServiceAccount GServiceAccount { get; private set; } - public static string HostingFullName { get; set; } + public static string HostingFullName { get; set; } public static PayPalSettings PayPalSettings { get; private set; } private static ILogger logger; @@ -66,9 +66,9 @@ namespace Yavsc public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) { - var devtag = env.IsDevelopment()?"D":""; - var prodtag = env.IsProduction()?"P":""; - var stagetag = env.IsStaging()?"S":""; + var devtag = env.IsDevelopment() ? "D" : ""; + var prodtag = env.IsProduction() ? "P" : ""; + var stagetag = env.IsStaging() ? "S" : ""; HostingFullName = $"{appEnv.RuntimeFramework.FullName} [{env.EnvironmentName}:{prodtag}{devtag}{stagetag}]"; // Set up configuration sources. @@ -87,22 +87,22 @@ namespace Yavsc builder.AddEnvironmentVariables(); Configuration = builder.Build(); - + var auth = Configuration["Site:Authority"]; var cxstr = Configuration["ConnectionStrings:Default"]; ConnectionString = cxstr; AppDomain.CurrentDomain.SetData(Constants.YavscConnectionStringEnvName, ConnectionString); - var googleClientFile = Configuration["Authentication:Google:GoogleWebClientJson"]; - var googleServiceAccountJsonFile = Configuration["Authentication:Google:GoogleServiceAccountJson"]; - if (googleClientFile!=null) - GoogleWebClientConfiguration = new ConfigurationBuilder().AddJsonFile(googleClientFile).Build(); - if (googleServiceAccountJsonFile!=null) - { - var safile = new FileInfo(googleServiceAccountJsonFile); - GServiceAccount = JsonConvert.DeserializeObject(safile.OpenText().ReadToEnd()); - } + var googleClientFile = Configuration["Authentication:Google:GoogleWebClientJson"]; + var googleServiceAccountJsonFile = Configuration["Authentication:Google:GoogleServiceAccountJson"]; + if (googleClientFile != null) + GoogleWebClientConfiguration = new ConfigurationBuilder().AddJsonFile(googleClientFile).Build(); + if (googleServiceAccountJsonFile != null) + { + var safile = new FileInfo(googleServiceAccountJsonFile); + GServiceAccount = JsonConvert.DeserializeObject(safile.OpenText().ReadToEnd()); + } } public static string ConnectionString { get; set; } @@ -115,7 +115,7 @@ namespace Yavsc { // Database connection - + services.AddOptions(); var siteSettings = Configuration.GetSection("Site"); services.Configure(siteSettings); @@ -135,7 +135,7 @@ namespace Yavsc services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); services.Add(ServiceDescriptor.Singleton(typeof(IOptions), typeof(OptionsManager))); - + services.Configure(options => { var supportedCultures = new[] @@ -144,7 +144,7 @@ namespace Yavsc new CultureInfo("fr"), new CultureInfo("pt") }; - + var supportedUICultures = new[] { new CultureInfo("fr"), @@ -184,14 +184,14 @@ namespace Yavsc // DataProtection ConfigureProtectionServices(services); - + // Add framework services. services.AddEntityFramework() - .AddNpgsql() + .AddNpgsql() .AddDbContext( db => db.UseNpgsql(ConnectionString) ); - + ConfigureOAuthServices(services); services.AddCors( @@ -248,8 +248,8 @@ namespace Yavsc .Build(); config.Filters.Add(new AuthorizeFilter(policy)); config.Filters.Add(new ProducesAttribute("application/json")); - // config.ModelBinders.Insert(0,new MyDateTimeModelBinder()); - // config.ModelBinders.Insert(0,new MyDecimalModelBinder()); + // config.ModelBinders.Insert(0,new MyDateTimeModelBinder()); + // config.ModelBinders.Insert(0,new MyDecimalModelBinder()); config.OutputFormatters.Add(new PdfFormatter()); }).AddFormatterMappings( @@ -276,9 +276,9 @@ namespace Yavsc services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient( (sp) => new FileDataStore("googledatastore",false) ); + services.AddTransient((sp) => new FileDataStore("googledatastore", false)); services.AddTransient(); - + // TODO for SMS: services.AddTransient(); services.AddLocalization(options => @@ -312,12 +312,12 @@ namespace Yavsc SiteSetup = siteSettings.Value; Authority = siteSettings.Value.Authority; var blogsDir = siteSettings.Value.Blog; - if (blogsDir==null) throw new Exception ("blogsDir is not set."); + if (blogsDir == null) throw new Exception("blogsDir is not set."); var billsDir = siteSettings.Value.Bills; - if (billsDir==null) throw new Exception ("billsDir is not set."); + if (billsDir == null) throw new Exception("billsDir is not set."); - AbstractFileSystemHelpers.UserFilesDirName = new DirectoryInfo(blogsDir).FullName; - AbstractFileSystemHelpers.UserBillsDirName = new DirectoryInfo(billsDir).FullName; + AbstractFileSystemHelpers.UserFilesDirName = new DirectoryInfo(blogsDir).FullName; + AbstractFileSystemHelpers.UserBillsDirName = new DirectoryInfo(billsDir).FullName; Temp = siteSettings.Value.TempDir; PayPalSettings = payPalSettings.Value; @@ -325,7 +325,7 @@ namespace Yavsc // Create required directories foreach (string dir in new string[] { AbstractFileSystemHelpers.UserFilesDirName, AbstractFileSystemHelpers.UserBillsDirName, SiteSetup.TempDir }) { - if (dir==null) throw new Exception (nameof(dir)); + if (dir == null) throw new Exception(nameof(dir)); DirectoryInfo di = new DirectoryInfo(dir); if (!di.Exists) di.Create(); @@ -339,22 +339,23 @@ namespace Yavsc if (env.IsDevelopment()) { var logenvvar = Environment.GetEnvironmentVariable("ASPNET_LOG_LEVEL"); - if (logenvvar!=null) - switch (logenvvar) { - case "info": - loggerFactory.MinimumLevel = LogLevel.Information; - break; - case "warn": - loggerFactory.MinimumLevel = LogLevel.Warning; - break; - case "err": - loggerFactory.MinimumLevel = LogLevel.Error; - break; - case "debug": - default: - loggerFactory.MinimumLevel = LogLevel.Debug; - break; - } + if (logenvvar != null) + switch (logenvvar) + { + case "info": + loggerFactory.MinimumLevel = LogLevel.Information; + break; + case "warn": + loggerFactory.MinimumLevel = LogLevel.Warning; + break; + case "err": + loggerFactory.MinimumLevel = LogLevel.Error; + break; + case "debug": + default: + loggerFactory.MinimumLevel = LogLevel.Debug; + break; + } app.UseDeveloperExceptionPage(); @@ -399,28 +400,36 @@ namespace Yavsc // before fixing the security protocol, let beleive our lib it's done with it. var cxmgr = ConnectionManager.Instance; // then, fix it. - ServicePointManager.SecurityProtocol = (SecurityProtocolType) 0xC00; // Tls12, required by PayPal + ServicePointManager.SecurityProtocol = (SecurityProtocolType)0xC00; // Tls12, required by PayPal app.UseIISPlatformHandler(options => { options.AuthenticationDescriptions.Clear(); options.AutomaticAuthentication = false; - }); + }); app.UseSession(); ConfigureOAuthApp(app, SiteSetup, logger); ConfigureFileServerApp(app, SiteSetup, env, authorizationService); - app.UseRequestLocalization(localizationOptions.Value, (RequestCulture) new RequestCulture((string)"en-US")); + app.UseRequestLocalization(localizationOptions.Value, (RequestCulture)new RequestCulture((string)"en-US")); ConfigureWorkflow(app, SiteSetup, logger); + // Empty this odd chat user list from db + foreach (var p in dbContext.ChatConnection) + { + dbContext.Entry(p).State = EntityState.Deleted; + } + dbContext.SaveChanges(); + ConfigureWebSocketsApp(app, SiteSetup, env); + app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); - logger.LogInformation("LocalApplicationData: "+Environment.GetFolderPath(SpecialFolder.LocalApplicationData, SpecialFolderOption.DoNotVerify)); + logger.LogInformation("LocalApplicationData: " + Environment.GetFolderPath(SpecialFolder.LocalApplicationData, SpecialFolderOption.DoNotVerify)); app.Use(async (context, next) => { var liveCasting = context.Request.Path.StartsWithSegments(liveCastingPath); @@ -432,132 +441,152 @@ namespace Yavsc { if (!context.User.Identity.IsAuthenticated) context.Response.StatusCode = 403; - else { - // get the flow id from request path - var castid = long.Parse(context.Request.Path.Value.Substring(liveCastingPath.Value.Length+1)); - - - var uname = context.User.GetUserName(); - // ensure uniqueness of casting stream from this user - - - var uid = context.User.GetUserId(); - // get some setup from user - var flow = _dbContext.LiveFlow.Include(f=>f.Owner).SingleOrDefault(f=> (f.OwnerId==uid && f.Id == castid)); - if (flow == null) - { - context.Response.StatusCode = 400; - } - else { - LiveCastMeta meta=null; - if (LiveApiController.Casters.ContainsKey(uname)) - { - meta = LiveApiController.Casters[uname]; - if (meta.Socket.State != WebSocketState.Closed) { - // FIXME loosed connexion should be detected & disposed else where - meta.Socket.Dispose(); - meta.Socket = await context.WebSockets.AcceptWebSocketAsync(); - } else { - meta.Socket.Dispose(); - // Accept the socket - meta.Socket = await context.WebSockets.AcceptWebSocketAsync(); - } - } - else - { - // Accept the socket - meta = new LiveCastMeta { Socket = await context.WebSockets.AcceptWebSocketAsync() }; - } - logger.LogInformation("Accepted web socket"); - // Dispatch the flow - try { - - - - if (meta.Socket != null && meta.Socket.State == WebSocketState.Open) - { - LiveApiController.Casters[uname] = meta; - // TODO: Handle the socket here. - // Find receivers: others in the chat room - // send them the flow - - var sBuffer = new ArraySegment(new byte[1024]); - logger.LogInformation("Receiving bytes..."); - - WebSocketReceiveResult received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); - logger.LogInformation("Received bytes!!!!"); - - var hubContext = GlobalHost.ConnectionManager.GetHubContext(); - - hubContext.Clients.All.addPublicStream(new { id = flow.Id, sender = flow.Owner.UserName, title = flow.Title, url = flow.GetFileUrl(), - mediaType = flow.MediaType }, $"{flow.Owner.UserName} is starting a stream!"); - - // FIXME do we really need to close those one in invalid state ? - Stack ToClose = new Stack(); - - try { - while (received.MessageType != WebSocketMessageType.Close) + else { - 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 - foreach (var cliItem in meta.Listeners) + // get the flow id from request path + var castid = long.Parse(context.Request.Path.Value.Substring(liveCastingPath.Value.Length + 1)); + + + var uname = context.User.GetUserName(); + // ensure uniqueness of casting stream from this user + + + var uid = context.User.GetUserId(); + // get some setup from user + var flow = _dbContext.LiveFlow.Include(f => f.Owner).SingleOrDefault(f => (f.OwnerId == uid && f.Id == castid)); + if (flow == null) { - var listenningSocket = cliItem.Value; - if (listenningSocket.State == WebSocketState.Open) - await listenningSocket.SendAsync( - sBuffer, received.MessageType, received.EndOfMessage, CancellationToken.None); - else - if (listenningSocket.State == WebSocketState.CloseReceived || listenningSocket.State == WebSocketState.CloseSent) + context.Response.StatusCode = 400; + } + else + { + LiveCastMeta meta = null; + if (LiveApiController.Casters.ContainsKey(uname)) { - ToClose.Push(cliItem.Key); + meta = LiveApiController.Casters[uname]; + if (meta.Socket.State != WebSocketState.Closed) + { + // FIXME loosed connexion should be detected & disposed else where + meta.Socket.Dispose(); + meta.Socket = await context.WebSockets.AcceptWebSocketAsync(); + } + else + { + meta.Socket.Dispose(); + // Accept the socket + meta.Socket = await context.WebSockets.AcceptWebSocketAsync(); + } + } + else + { + // Accept the socket + meta = new LiveCastMeta { Socket = await context.WebSockets.AcceptWebSocketAsync() }; + } + logger.LogInformation("Accepted web socket"); + // Dispatch the flow + try + { + + + + if (meta.Socket != null && meta.Socket.State == WebSocketState.Open) + { + LiveApiController.Casters[uname] = meta; + // TODO: Handle the socket here. + // Find receivers: others in the chat room + // send them the flow + + var sBuffer = new ArraySegment(new byte[1024]); + logger.LogInformation("Receiving bytes..."); + + WebSocketReceiveResult received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); + logger.LogInformation("Received bytes!!!!"); + + var hubContext = GlobalHost.ConnectionManager.GetHubContext(); + + hubContext.Clients.All.addPublicStream(new + { + id = flow.Id, + sender = flow.Owner.UserName, + title = flow.Title, + url = flow.GetFileUrl(), + mediaType = flow.MediaType + }, $"{flow.Owner.UserName} is starting a stream!"); + + // FIXME do we really need to close those one in invalid state ? + Stack ToClose = new Stack(); + + try + { + while (received.MessageType != WebSocketMessageType.Close) + { + 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 + foreach (var cliItem in meta.Listeners) + { + var listenningSocket = cliItem.Value; + if (listenningSocket.State == WebSocketState.Open) + await listenningSocket.SendAsync( + sBuffer, received.MessageType, received.EndOfMessage, CancellationToken.None); + else + if (listenningSocket.State == WebSocketState.CloseReceived || listenningSocket.State == WebSocketState.CloseSent) + { + ToClose.Push(cliItem.Key); + } + } + received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); + + string no; + do + { + no = ToClose.Pop(); + WebSocket listenningSocket; + if (meta.Listeners.TryRemove(no, out listenningSocket)) + await listenningSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "State != WebSocketState.Open", CancellationToken.None); + + } while (no != null); + } + await meta.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "eof", CancellationToken.None); + LiveApiController.Casters[uname] = null; + } + catch (Exception ex) + { + logger.LogError($"Exception occured : {ex.Message}"); + logger.LogError(ex.StackTrace); + meta.Socket.Dispose(); + LiveApiController.Casters[uname] = null; + } + } + else + { // not meta.Socket != null && meta.Socket.State == WebSocketState.Open + if (meta.Socket != null) + { + logger.LogError($"meta.Socket.State not Open: {meta.Socket.State.ToString()} "); + meta.Socket.Dispose(); + } + else + logger.LogError("socket object is null"); + } + } + 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 meta.Socket?.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, ex.Message, CancellationToken.None); + meta.Socket?.Dispose(); + LiveApiController.Casters[uname] = null; } } - received = await meta.Socket.ReceiveAsync(sBuffer, CancellationToken.None); - - string no; - do - { - no = ToClose.Pop(); - WebSocket listenningSocket; - if (meta.Listeners.TryRemove(no, out listenningSocket)) - await listenningSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "State != WebSocketState.Open", CancellationToken.None); - - } while (no != null); } - await meta.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "eof", CancellationToken.None); - LiveApiController.Casters[uname] = null; - } catch (Exception ex) - { - logger.LogError($"Exception occured : {ex.Message}"); - logger.LogError(ex.StackTrace); - meta.Socket.Dispose(); - LiveApiController.Casters[uname] = null; } } - else { // not meta.Socket != null && meta.Socket.State == WebSocketState.Open - if (meta.Socket != null) { - logger.LogError($"meta.Socket.State not Open: {meta.Socket.State.ToString()} "); - meta.Socket.Dispose(); - } - else - logger.LogError("socket object is null"); - } - } - 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 meta.Socket?.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, ex.Message, CancellationToken.None); - meta.Socket?.Dispose(); - LiveApiController.Casters[uname] = null; - } - }}}} else { await next(); diff --git a/src/Yavsc/wwwroot/js/chat.js b/src/Yavsc/wwwroot/js/chat.js index e33fc7bf..1d8b4adf 100644 --- a/src/Yavsc/wwwroot/js/chat.js +++ b/src/Yavsc/wwwroot/js/chat.js @@ -68,12 +68,6 @@ } } - chat.client.joint = function (rinfo) - { - console.log(rinfo); - setActiveRoom(rinfo.Name); - } - $.fn.filterByData = function (prop, val) { return this.filter( function () { return $(this).data(prop) == val; } @@ -145,7 +139,10 @@ $('#chatview').removeClass('disabled'); chans.forEach(function(room) { - chat.server.join(room); + chat.server.join(room).done(function(chatInfo) + {console.log(chatInfo); + setActiveRoom(chatInfo.Name); + }); }) } @@ -181,7 +178,10 @@ if (event.which == 13) { if (this.value.length==0) return; buildRoom(this.value); - chat.server.join(this.value); + chat.server.join(this.value).done(function(chatInfo) + {console.log(chatInfo); + setActiveRoom(chatInfo.Name); + }); this.value="" } else { // TODO showRoomInfo(this.value);