diff options
| -rw-r--r-- | build/server.js | 74 | ||||
| -rw-r--r-- | res/client.js | 16 | ||||
| -rw-r--r-- | res/langs/en.json | 1 | ||||
| -rw-r--r-- | res/langs/ru.json | 1 | ||||
| -rw-r--r-- | src/client/Main.hx | 7 | ||||
| -rw-r--r-- | src/server/Main.hx | 49 | ||||
| -rw-r--r-- | src/server/ServerState.hx | 13 |
7 files changed, 147 insertions, 14 deletions
diff --git a/build/server.js b/build/server.js index ce5bb7b..406d1b4 100644 --- a/build/server.js +++ b/build/server.js @@ -647,22 +647,29 @@ var server_Main = function(port,wsPort) { this.clients = []; this.rootDir = "" + __dirname + "/.."; var _gthis = this; + this.statePath = "" + this.rootDir + "/user/state.json"; this.config = this.getUserConfig(); + this.loadState(); this.wss = new js_npm_ws_Server({ port : wsPort}); this.wss.on("connection",$bind(this,this.onConnect)); var exit = function() { + _gthis.saveState(); process.exit(); }; process.on("exit",exit); process.on("SIGINT",exit); process.on("SIGUSR1",exit); process.on("SIGUSR2",exit); - process.on("uncaughtException",function(log) { - haxe_Log.trace(log,{ fileName : "src/server/Main.hx", lineNumber : 49, className : "server.Main", methodName : "new"}); + process.on("uncaughtException",function(err) { + haxe_Log.trace(err,{ fileName : "src/server/Main.hx", lineNumber : 52, className : "server.Main", methodName : "new"}); + _gthis.logError("uncaughtException",{ message : err.message, stack : err.stack}); + exit(); return; }); process.on("unhandledRejection",function(reason,promise) { - haxe_Log.trace("Unhandled Rejection at:",{ fileName : "src/server/Main.hx", lineNumber : 52, className : "server.Main", methodName : "new", customParams : [reason]}); + haxe_Log.trace("Unhandled Rejection at:",{ fileName : "src/server/Main.hx", lineNumber : 60, className : "server.Main", methodName : "new", customParams : [reason]}); + _gthis.logError("unhandledRejection",reason); + exit(); return; }); this.localIp = server_Utils.getLocalIp(); @@ -670,8 +677,8 @@ var server_Main = function(port,wsPort) { this.port = port; server_Utils.getGlobalIp(function(ip) { _gthis.globalIp = ip; - haxe_Log.trace("Local: http://" + _gthis.localIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 60, className : "server.Main", methodName : "new"}); - haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 61, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Local: http://" + _gthis.localIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 70, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + port,{ fileName : "src/server/Main.hx", lineNumber : 71, className : "server.Main", methodName : "new"}); return; }); var dir = "" + this.rootDir + "/res"; @@ -700,18 +707,47 @@ server_Main.prototype = { var field = _g1[_g]; ++_g; if(Reflect.field(config,field) == null) { - haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 79, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 89, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } return config; } + ,saveState: function() { + var json = JSON.stringify({ videoList : this.videoList, messages : this.messages, timer : { time : this.videoTimer.getTime(), paused : this.videoTimer.isPaused()}},null,"\t"); + js_node_Fs.writeFileSync(this.statePath,json); + } + ,loadState: function() { + if(!sys_FileSystem.exists(this.statePath)) { + return; + } + var data = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"})); + this.videoList.length = 0; + this.messages.length = 0; + var _g = 0; + var _g1 = data.videoList; + while(_g < _g1.length) this.videoList.push(_g1[_g++]); + var _g2 = 0; + var _g3 = data.messages; + while(_g2 < _g3.length) this.messages.push(_g3[_g2++]); + this.videoTimer.start(); + this.videoTimer.setTime(data.timer.time); + this.videoTimer.pause(); + } + ,logError: function(type,data) { + var crashesFolder = "" + this.rootDir + "/user/crashes"; + var name = new Date().toISOString() + "-" + type; + if(!sys_FileSystem.exists(crashesFolder)) { + sys_FileSystem.createDirectory(crashesFolder); + } + js_node_Fs.writeFileSync("" + crashesFolder + "/" + name + ".json",JSON.stringify(data,null,"\t")); + } ,onConnect: function(ws,req) { var _gthis = this; var ip = req.connection.remoteAddress; var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length; var name = "Guest " + (id + 1); - haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 89, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("" + name + " connected (" + ip + ")",{ fileName : "src/server/Main.hx", lineNumber : 131, className : "server.Main", methodName : "onConnect"}); var client = new Client(ws,req,id,name,0); if(req.connection.localAddress == ip) { client.group |= 4; @@ -737,7 +773,7 @@ server_Main.prototype = { return; }); ws.on("close",function(err) { - haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 117, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace("Client " + client.name + " disconnected",{ fileName : "src/server/Main.hx", lineNumber : 159, className : "server.Main", methodName : "onConnect"}); server_Utils.sortedPush(_gthis.freeIds,client.id); HxOverrides.remove(_gthis.clients,client); _gthis.sendClientList(); @@ -774,6 +810,7 @@ server_Main.prototype = { } break; case "ClearChat": + this.messages.length = 0; if((client.group & 4) != 0) { this.broadcast(data); } @@ -1087,6 +1124,27 @@ sys_FileSystem.exists = function(path) { return false; } }; +sys_FileSystem.createDirectory = function(path) { + try { + js_node_Fs.mkdirSync(path); + } catch( e ) { + var e1 = ((e) instanceof js__$Boot_HaxeError) ? e.val : e; + if(e1.code == "ENOENT") { + sys_FileSystem.createDirectory(js_node_Path.dirname(path)); + js_node_Fs.mkdirSync(path); + } else { + var stat; + try { + stat = js_node_Fs.statSync(path); + } catch( _ ) { + throw e1; + } + if(!stat.isDirectory()) { + throw e1; + } + } + } +}; function $getIterator(o) { if( o instanceof Array ) return HxOverrides.iter(o); else return o.iterator(); } function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $global.$haxeUID++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = m.bind(o); o.hx__closures__[m.__id__] = f; } return f; } $global.$haxeUID |= 0; diff --git a/res/client.js b/res/client.js index d9cc67b..4c3a8a5 100644 --- a/res/client.js +++ b/res/client.js @@ -591,6 +591,11 @@ client_Main.prototype = { name = Lang.get("rawVideo"); } this.getRemoteVideoDuration(url,function(duration) { + if(duration == 0) { + var tmp = Lang.get("addVideoError"); + _gthis.serverMessage(4,tmp); + return; + } _gthis.send({ type : "AddVideo", addVideo : { item : { url : url, title : name, author : _gthis.personal.name, duration : duration}, atEnd : atEnd}}); callback(); return; @@ -606,7 +611,7 @@ client_Main.prototype = { while(_g1 < items.length) _g.push(items[_g1++].url); return _g; } - ,replaceLocalIp: function(url) { + ,tryLocalIp: function(url) { if(this.host == this.globalIp) { return url; } @@ -617,6 +622,9 @@ client_Main.prototype = { var video = window.document.createElement("video"); video.src = src; video.onerror = function(e) { + if(player.contains(video)) { + player.removeChild(video); + } callback(0); return; }; @@ -633,7 +641,7 @@ client_Main.prototype = { var data = JSON.parse(e.data); var t = data.type; var t1 = t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null); - haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 188, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); + haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 193, className : "client.Main", methodName : "onMessage", customParams : [data[t1]]}); switch(data.type) { case "AddVideo": if(this.player.isListEmpty()) { @@ -758,6 +766,8 @@ client_Main.prototype = { if(guestName.value.length > 0) { this.send({ type : "Login", login : { clientName : guestName.value}}); } + window.document.querySelector("#messagebuffer").innerHTML = ""; + this.serverMessage(1); var _g = 0; var _g1 = connected.history; while(_g < _g1.length) { @@ -997,7 +1007,7 @@ client_Player.prototype = { this.isLoaded = false; this.video = window.document.createElement("video"); this.video.id = "videoplayer"; - item.url = this.main.replaceLocalIp(item.url); + item.url = this.main.tryLocalIp(item.url); this.video.src = item.url; this.video.controls = true; this.video.oncanplaythrough = function(e) { diff --git a/res/langs/en.json b/res/langs/en.json index 5fa5d53..ea1ca74 100644 --- a/res/langs/en.json +++ b/res/langs/en.json @@ -6,6 +6,7 @@ "online": "online", "nothingPlaying": "Nothing Playing", "usernameError": "Username must be from 1 to $MAX characters and don't repeat another's.", + "addVideoError": "Failed to add video.", "rawVideo": "Raw video", "videos": "videos", "addedBy": "Added by", diff --git a/res/langs/ru.json b/res/langs/ru.json index f9944d1..eb2c1b3 100644 --- a/res/langs/ru.json +++ b/res/langs/ru.json @@ -6,6 +6,7 @@ "online": "онлайн", "nothingPlaying": "Ничего не играет", "usernameError": "Ник должен быть от 1 до $MAX символов и не повторять чужие.", + "addVideoError": "Не удалось добавить видео.", "rawVideo": "Исходное видео", "videos": "видео", "addedBy": "Добавлено", diff --git a/src/client/Main.hx b/src/client/Main.hx index 432a095..aa0a648 100644 --- a/src/client/Main.hx +++ b/src/client/Main.hx @@ -136,6 +136,10 @@ class Main { else name = Lang.get("rawVideo"); getRemoteVideoDuration(url, (duration:Float) -> { + if (duration == 0) { + serverMessage(4, Lang.get("addVideoError")); + return; + } send({ type: AddVideo, addVideo: { item: { @@ -172,6 +176,7 @@ class Main { video.src = src; // TODO catch errors on AddVideo and getRemoteVideoDuration video.onerror = e -> { + if (player.contains(video)) player.removeChild(video); callback(0); } video.onloadedmetadata = () -> { @@ -294,6 +299,8 @@ class Main { clientName: guestName.value } }); + ge("#messagebuffer").innerHTML = ""; + serverMessage(1); for (message in connected.history) { addMessage(message.name, message.text, message.time); } diff --git a/src/server/Main.hx b/src/server/Main.hx index 442abc8..9a245fe 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -20,6 +20,7 @@ using Lambda; class Main { final rootDir = '$__dirname/..'; + final statePath:String; final wss:WSServer; final localIp:String; var globalIp:String; @@ -34,22 +35,31 @@ class Main { static function main():Void new Main(); public function new(port = 4200, wsPort = 4201) { + statePath = '$rootDir/user/state.json'; config = getUserConfig(); + loadState(); wss = new WSServer({port: wsPort}); wss.on("connection", onConnect); function exit() { - // TODO save state + saveState(); process.exit(); } process.on("exit", exit); process.on("SIGINT", exit); // ctrl+c process.on("SIGUSR1", exit); // kill pid process.on("SIGUSR2", exit); - process.on("uncaughtException", (log) -> { - trace(log); + process.on("uncaughtException", err -> { + trace(err); + logError("uncaughtException", { + message: err.message, + stack: err.stack + }); + exit(); }); process.on("unhandledRejection", (reason, promise) -> { trace("Unhandled Rejection at:", reason); + logError("unhandledRejection", reason); + exit(); }); localIp = Utils.getLocalIp(); globalIp = localIp; @@ -82,6 +92,38 @@ class Main { return config; } + function saveState():Void { + final data:ServerState = { + videoList: videoList, + messages: messages, + timer: { + time: videoTimer.getTime(), + paused: videoTimer.isPaused() + } + } + final json = Json.stringify(data, "\t"); + File.saveContent(statePath, json); + } + + function loadState():Void { + if (!FileSystem.exists(statePath)) return; + final data:ServerState = Json.parse(File.getContent(statePath)); + videoList.resize(0); + messages.resize(0); + for (item in data.videoList) videoList.push(item); + for (message in data.messages) messages.push(message); + videoTimer.start(); + videoTimer.setTime(data.timer.time); + videoTimer.pause(); + } + + function logError(type:String, data:Dynamic):Void { + final crashesFolder = '$rootDir/user/crashes'; + final name = new Date().toISOString() + "-" + type; + if (!FileSystem.exists(crashesFolder)) FileSystem.createDirectory(crashesFolder); + File.saveContent('$crashesFolder/$name.json', Json.stringify(data, "\t")); + } + function onConnect(ws:WebSocket, req:IncomingMessage):Void { final ip = req.connection.remoteAddress; final id = freeIds.length > 0 ? freeIds.shift() : clients.length; @@ -267,6 +309,7 @@ class Main { } case ClearChat: + messages.resize(0); if (client.isAdmin) broadcast(data); case ClearPlaylist: diff --git a/src/server/ServerState.hx b/src/server/ServerState.hx new file mode 100644 index 0000000..06240e5 --- /dev/null +++ b/src/server/ServerState.hx @@ -0,0 +1,13 @@ +package server; + +import Types.Message; +import Types.VideoItem; + +typedef ServerState = { + videoList:Array<VideoItem>, + messages:Array<Message>, + timer:{ + time:Float, + paused:Bool + } +} |
