This commit is contained in:
parent
18c2cc2ad9
commit
bbe17c431c
|
@ -15,16 +15,9 @@
|
||||||
"Password": "admin",
|
"Password": "admin",
|
||||||
"Workspace": "{pwd}/data/user_workspaces/admin",
|
"Workspace": "{pwd}/data/user_workspaces/admin",
|
||||||
"LatestSessionContent": {
|
"LatestSessionContent": {
|
||||||
"FileTree": [
|
"FileTree": [],
|
||||||
"1/",
|
"Files": [],
|
||||||
"2/"
|
"CurrentFile": ""
|
||||||
],
|
|
||||||
"Files": [
|
|
||||||
"1.go",
|
|
||||||
"2.go",
|
|
||||||
"3.go"
|
|
||||||
],
|
|
||||||
"CurrentFile": "current file"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
3
main.go
3
main.go
|
@ -40,6 +40,9 @@ func init() {
|
||||||
|
|
||||||
// 定时保存配置
|
// 定时保存配置
|
||||||
conf.FixedTimeSave()
|
conf.FixedTimeSave()
|
||||||
|
|
||||||
|
// 定时检查无效会话
|
||||||
|
session.FixedTimeRelease()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录.
|
// 登录.
|
||||||
|
|
|
@ -63,6 +63,27 @@ var WideSessions Sessions
|
||||||
// 排它锁,防止并发修改.
|
// 排它锁,防止并发修改.
|
||||||
var mutex sync.Mutex
|
var mutex sync.Mutex
|
||||||
|
|
||||||
|
// 在一些特殊情况(例如浏览器不间断刷新时会话通道建立并发)下 Wide 会话集内会出现无效会话,该函数定时(1 小时)检查并移除这些无效会话.
|
||||||
|
// 无效会话:在检查时间内 30 分钟都没有使用过的会话,WideSession.Updated 字段.
|
||||||
|
func FixedTimeRelease() {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
hour, _ := time.ParseDuration("-30m")
|
||||||
|
threshold := time.Now().Add(hour)
|
||||||
|
|
||||||
|
for _, s := range WideSessions {
|
||||||
|
if s.Updated.Before(threshold) {
|
||||||
|
glog.V(3).Infof("Removes a invalid session [%s]", s.Id)
|
||||||
|
|
||||||
|
WideSessions.Remove(s.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Hour)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// 建立会话通道.
|
// 建立会话通道.
|
||||||
// 通道断开时销毁会话状态,回收相关资源.
|
// 通道断开时销毁会话状态,回收相关资源.
|
||||||
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -90,40 +111,9 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := wsChan.Conn.ReadJSON(&input); err != nil {
|
if err := wsChan.Conn.ReadJSON(&input); err != nil {
|
||||||
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid)
|
glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid)
|
||||||
|
|
||||||
for i, s := range WideSessions {
|
WideSessions.Remove(sid)
|
||||||
if s.Id == sid {
|
|
||||||
mutex.Lock()
|
|
||||||
|
|
||||||
// 从会话集中移除
|
return
|
||||||
WideSessions = append(WideSessions[:i], WideSessions[i+1:]...)
|
|
||||||
|
|
||||||
// 关闭用户事件队列
|
|
||||||
event.UserEventQueues.Close(sid)
|
|
||||||
|
|
||||||
// 杀进程
|
|
||||||
for _, p := range s.Processes {
|
|
||||||
if err := p.Kill(); nil != err {
|
|
||||||
glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid)
|
|
||||||
} else {
|
|
||||||
glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回收所有通道
|
|
||||||
OutputWS[sid].Close()
|
|
||||||
delete(OutputWS, sid)
|
|
||||||
|
|
||||||
NotificationWS[sid].Close()
|
|
||||||
delete(NotificationWS, sid)
|
|
||||||
|
|
||||||
sessionWS[sid].Close()
|
|
||||||
delete(sessionWS, sid)
|
|
||||||
|
|
||||||
mutex.Unlock()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = map[string]interface{}{"output": "", "cmd": "session-output"}
|
ret = map[string]interface{}{"output": "", "cmd": "session-output"}
|
||||||
|
@ -143,7 +133,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
|
||||||
defer util.RetJSON(w, r, data)
|
defer util.RetJSON(w, r, data)
|
||||||
|
|
||||||
args := struct {
|
args := struct {
|
||||||
sid string
|
Sid string
|
||||||
*conf.LatestSessionContent
|
*conf.LatestSessionContent
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
|
@ -154,7 +144,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wSession := WideSessions.Get(args.sid)
|
wSession := WideSessions.Get(args.Sid)
|
||||||
if nil == wSession {
|
if nil == wSession {
|
||||||
data["succ"] = false
|
data["succ"] = false
|
||||||
|
|
||||||
|
@ -165,9 +155,10 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
for _, user := range conf.Wide.Users {
|
for _, user := range conf.Wide.Users {
|
||||||
if user.Name == wSession.Username {
|
if user.Name == wSession.Username {
|
||||||
|
// 更新配置(内存变量),conf.FixedTimeSave() 会负责定时持久化
|
||||||
user.LatestSessionContent = wSession.Content
|
user.LatestSessionContent = wSession.Content
|
||||||
|
|
||||||
// 定时任务会负责持久化
|
wSession.Refresh()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -236,9 +227,46 @@ func (sessions *Sessions) Remove(sid string) {
|
||||||
|
|
||||||
for i, s := range *sessions {
|
for i, s := range *sessions {
|
||||||
if s.Id == sid {
|
if s.Id == sid {
|
||||||
|
// 从会话集中移除
|
||||||
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
|
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
|
||||||
|
|
||||||
glog.V(3).Infof("Removed a session [%s], has [%d] wide sessions currently", sid, len(*sessions))
|
// 关闭用户事件队列
|
||||||
|
event.UserEventQueues.Close(sid)
|
||||||
|
|
||||||
|
// 杀进程
|
||||||
|
for _, p := range s.Processes {
|
||||||
|
if err := p.Kill(); nil != err {
|
||||||
|
glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid)
|
||||||
|
} else {
|
||||||
|
glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回收所有通道
|
||||||
|
if ws, ok := OutputWS[sid]; ok {
|
||||||
|
ws.Close()
|
||||||
|
delete(OutputWS, sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ws, ok := NotificationWS[sid]; ok {
|
||||||
|
ws.Close()
|
||||||
|
delete(NotificationWS, sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ws, ok := sessionWS[sid]; ok {
|
||||||
|
ws.Close()
|
||||||
|
delete(sessionWS, sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(3).Infof("Removed a session [%s]", s.Id)
|
||||||
|
|
||||||
|
cnt := 0 // 统计当前 HTTP 会话关联的 Wide 会话数量
|
||||||
|
for _, s := range *sessions {
|
||||||
|
if s.HTTPSession.ID == s.HTTPSession.ID {
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var editors = {
|
var editors = {
|
||||||
data: [],
|
data: [],
|
||||||
|
tabs: {},
|
||||||
init: function () {
|
init: function () {
|
||||||
editors._initAutocomplete();
|
editors._initAutocomplete();
|
||||||
editors.tabs = new Tabs({
|
editors.tabs = new Tabs({
|
||||||
|
|
|
@ -1,37 +1,58 @@
|
||||||
// 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源.
|
var session = {
|
||||||
var sessionWS = new WebSocket(config.channel.session + '/session/ws?sid=' + config.wideSessionId);
|
init: function () {
|
||||||
sessionWS.onopen = function () {
|
this._initWS();
|
||||||
console.log('[session onopen] connected');
|
|
||||||
|
// 定时(30 秒)保存会话内容.
|
||||||
|
setInterval(function () {
|
||||||
|
var request = newWideRequest(),
|
||||||
|
filse = [],
|
||||||
|
fileTree = [],
|
||||||
|
currentFile = "";
|
||||||
|
|
||||||
|
editors.tabs.obj._$tabs.find("div").each(function () {
|
||||||
|
var $it = $(this);
|
||||||
|
if ($it.hasClass("current")) {
|
||||||
|
currentFile = $it.find("span:eq(0)").attr("title");
|
||||||
|
}
|
||||||
|
|
||||||
|
filse.push($it.find("span:eq(0)").attr("title"));
|
||||||
|
});
|
||||||
|
|
||||||
|
fileTree = tree.getOpenPaths();
|
||||||
|
|
||||||
|
request.currentFile = currentFile; // 当前编辑器
|
||||||
|
request.fileTree = fileTree; // 文件树展开状态
|
||||||
|
request.files = filse; // 编辑器打开状态
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/session/save',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
|
_initWS: function () {
|
||||||
|
// 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源.
|
||||||
|
var sessionWS = new WebSocket(config.channel.session + '/session/ws?sid=' + config.wideSessionId);
|
||||||
|
|
||||||
|
sessionWS.onopen = function () {
|
||||||
|
console.log('[session onopen] connected');
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionWS.onmessage = function (e) {
|
||||||
|
console.log('[session onmessage]' + e.data);
|
||||||
|
var data = JSON.parse(e.data);
|
||||||
|
|
||||||
|
};
|
||||||
|
sessionWS.onclose = function (e) {
|
||||||
|
console.log('[session onclose] disconnected (' + e.code + ')');
|
||||||
|
delete sessionWS;
|
||||||
|
};
|
||||||
|
sessionWS.onerror = function (e) {
|
||||||
|
console.log('[session onerror] ' + JSON.parse(e));
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sessionWS.onmessage = function (e) {
|
|
||||||
console.log('[session onmessage]' + e.data);
|
|
||||||
var data = JSON.parse(e.data);
|
|
||||||
|
|
||||||
};
|
|
||||||
sessionWS.onclose = function (e) {
|
|
||||||
console.log('[session onclose] disconnected (' + e.code + ')');
|
|
||||||
delete sessionWS;
|
|
||||||
};
|
|
||||||
sessionWS.onerror = function (e) {
|
|
||||||
console.log('[session onerror] ' + JSON.parse(e));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 定时(30 秒)保存会话内容.
|
|
||||||
setTimeout(function () {
|
|
||||||
var request = newWideRequest();
|
|
||||||
|
|
||||||
// TODO: 会话状态保存
|
|
||||||
request.currentFile = "current file"; // 当前编辑器
|
|
||||||
request.fileTree = ["1/", "2/"]; // 文件树展开状态
|
|
||||||
request.files = ["1.go", "2.go", "3.go"]; // 编辑器打开状态
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/session/save',
|
|
||||||
data: JSON.stringify(request),
|
|
||||||
dataType: "json",
|
|
||||||
success: function (data) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 30000);
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
var tree = {
|
var tree = {
|
||||||
// 递归获取当前节点展开中的最后一个节点
|
// 递归获取当前节点展开中的最后一个节点
|
||||||
getCurrentNodeLastNode: function(node) {
|
getCurrentNodeLastNode: function (node) {
|
||||||
var returnNode = node.children[node.children.length - 1];
|
var returnNode = node.children[node.children.length - 1];
|
||||||
if (returnNode.open) {
|
if (returnNode.open) {
|
||||||
return tree.getCurrentNodeLastNode(returnNode);
|
return tree.getCurrentNodeLastNode(returnNode);
|
||||||
|
@ -9,7 +9,7 @@ var tree = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 按照树展现获取下一个节点
|
// 按照树展现获取下一个节点
|
||||||
getNextShowNode: function(node) {
|
getNextShowNode: function (node) {
|
||||||
if (node.level !== 0) {
|
if (node.level !== 0) {
|
||||||
if (node.getParentNode().getNextNode()) {
|
if (node.getParentNode().getNextNode()) {
|
||||||
return node.getParentNode().getNextNode();
|
return node.getParentNode().getNextNode();
|
||||||
|
@ -20,7 +20,7 @@ var tree = {
|
||||||
return node.getNextNode();
|
return node.getNextNode();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isBottomNode: function(node) {
|
isBottomNode: function (node) {
|
||||||
if (node.open) {
|
if (node.open) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ var tree = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getTIdByPath: function(path) {
|
getTIdByPath: function (path) {
|
||||||
var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes());
|
var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes());
|
||||||
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
||||||
if (nodes[i].path === path) {
|
if (nodes[i].path === path) {
|
||||||
|
@ -49,8 +49,19 @@ var tree = {
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
getOpenPaths: function () {
|
||||||
|
var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes()),
|
||||||
|
paths = [];
|
||||||
|
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
||||||
|
if (nodes[i].open) {
|
||||||
|
paths.push(nodes[i].path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
},
|
||||||
fileTree: undefined,
|
fileTree: undefined,
|
||||||
_isParents: function(tId, parentTId) {
|
_isParents: function (tId, parentTId) {
|
||||||
var node = tree.fileTree.getNodeByTId(tId);
|
var node = tree.fileTree.getNodeByTId(tId);
|
||||||
if (!node || !node.parentTId) {
|
if (!node || !node.parentTId) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -62,20 +73,20 @@ var tree = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
newFile: function() {
|
newFile: function () {
|
||||||
$("#dirRMenu").hide();
|
$("#dirRMenu").hide();
|
||||||
$("#dialogNewFilePrompt").dialog("open");
|
$("#dialogNewFilePrompt").dialog("open");
|
||||||
},
|
},
|
||||||
newDir: function() {
|
newDir: function () {
|
||||||
$("#dirRMenu").hide();
|
$("#dirRMenu").hide();
|
||||||
$("#dialogNewDirPrompt").dialog("open");
|
$("#dialogNewDirPrompt").dialog("open");
|
||||||
},
|
},
|
||||||
removeIt: function() {
|
removeIt: function () {
|
||||||
$("#dirRMenu").hide();
|
$("#dirRMenu").hide();
|
||||||
$("#fileRMenu").hide();
|
$("#fileRMenu").hide();
|
||||||
$("#dialogRemoveConfirm").dialog("open");
|
$("#dialogRemoveConfirm").dialog("open");
|
||||||
},
|
},
|
||||||
init: function() {
|
init: function () {
|
||||||
var request = newWideRequest();
|
var request = newWideRequest();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -83,7 +94,7 @@ var tree = {
|
||||||
url: '/files',
|
url: '/files',
|
||||||
data: JSON.stringify(request),
|
data: JSON.stringify(request),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data) {
|
success: function (data) {
|
||||||
if (data.succ) {
|
if (data.succ) {
|
||||||
var dirRMenu = $("#dirRMenu");
|
var dirRMenu = $("#dirRMenu");
|
||||||
var fileRMenu = $("#fileRMenu");
|
var fileRMenu = $("#fileRMenu");
|
||||||
|
@ -92,7 +103,7 @@ var tree = {
|
||||||
selectedMulti: false
|
selectedMulti: false
|
||||||
},
|
},
|
||||||
callback: {
|
callback: {
|
||||||
onRightClick: function(event, treeId, treeNode) {
|
onRightClick: function (event, treeId, treeNode) {
|
||||||
if (treeNode) {
|
if (treeNode) {
|
||||||
wide.curNode = treeNode;
|
wide.curNode = treeNode;
|
||||||
if ("ico-ztree-dir " !== treeNode.iconSkin) { // 如果右击了文件
|
if ("ico-ztree-dir " !== treeNode.iconSkin) { // 如果右击了文件
|
||||||
|
@ -112,7 +123,7 @@ var tree = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick: function(event, treeId, treeNode, clickFlag) {
|
onClick: function (event, treeId, treeNode, clickFlag) {
|
||||||
tree._onClick(treeNode);
|
tree._onClick(treeNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +133,7 @@ var tree = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_onClick: function(treeNode) {
|
_onClick: function (treeNode) {
|
||||||
if (wide.curNode) {
|
if (wide.curNode) {
|
||||||
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
for (var i = 0, ii = editors.data.length; i < ii; i++) {
|
||||||
// 该节点文件已经打开
|
// 该节点文件已经打开
|
||||||
|
@ -147,7 +158,7 @@ var tree = {
|
||||||
url: '/file',
|
url: '/file',
|
||||||
data: JSON.stringify(request),
|
data: JSON.stringify(request),
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data) {
|
success: function (data) {
|
||||||
if (!data.succ) {
|
if (!data.succ) {
|
||||||
alert(data.msg);
|
alert(data.msg);
|
||||||
|
|
||||||
|
|
|
@ -452,4 +452,5 @@ $(document).ready(function() {
|
||||||
menu.init();
|
menu.init();
|
||||||
hotkeys.init();
|
hotkeys.init();
|
||||||
notification.init();
|
notification.init();
|
||||||
|
session.init();
|
||||||
});
|
});
|
Loading…
Reference in New Issue