2015-01-18 08:59:10 +03:00
|
|
|
// Copyright (c) 2014-2015, b3log.org
|
2014-11-20 06:30:18 +03:00
|
|
|
//
|
2014-11-12 18:13:14 +03:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
2014-11-20 06:30:18 +03:00
|
|
|
//
|
2014-11-12 18:13:14 +03:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2014-11-20 06:30:18 +03:00
|
|
|
//
|
2014-11-12 18:13:14 +03:00
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
// Package session includes session related manipulations.
|
2014-09-25 09:37:59 +04:00
|
|
|
//
|
2014-10-29 13:15:18 +03:00
|
|
|
// Wide server side needs maintain two kinds of sessions:
|
2014-09-25 09:29:04 +04:00
|
|
|
//
|
2014-10-29 13:15:18 +03:00
|
|
|
// 1. HTTP session: mainly used for login authentication
|
|
|
|
// 2. Wide session: browser tab open/refresh will create one, and associates with HTTP session
|
2014-09-17 10:35:48 +04:00
|
|
|
//
|
2014-10-29 13:15:18 +03:00
|
|
|
// When a session gone: release all resources associated with it, such as running processes, event queues.
|
2014-09-17 10:35:48 +04:00
|
|
|
package session
|
|
|
|
|
|
|
|
import (
|
2014-11-21 09:08:29 +03:00
|
|
|
"bytes"
|
2014-09-22 19:13:07 +04:00
|
|
|
"encoding/json"
|
2015-09-27 02:36:34 +03:00
|
|
|
"math/rand"
|
2014-09-19 20:56:32 +04:00
|
|
|
"net/http"
|
2014-09-19 15:21:13 +04:00
|
|
|
"os"
|
2015-03-13 16:04:31 +03:00
|
|
|
"path/filepath"
|
2014-12-23 11:03:09 +03:00
|
|
|
"sort"
|
2014-11-21 06:19:57 +03:00
|
|
|
"strconv"
|
2014-09-17 10:35:48 +04:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2014-09-22 19:13:07 +04:00
|
|
|
"github.com/b3log/wide/conf"
|
2014-09-19 15:21:13 +04:00
|
|
|
"github.com/b3log/wide/event"
|
2014-12-13 13:47:41 +03:00
|
|
|
"github.com/b3log/wide/log"
|
2014-09-19 20:56:32 +04:00
|
|
|
"github.com/b3log/wide/util"
|
2015-03-13 16:04:31 +03:00
|
|
|
"github.com/go-fsnotify/fsnotify"
|
2014-09-17 10:35:48 +04:00
|
|
|
"github.com/gorilla/sessions"
|
2014-09-19 20:56:32 +04:00
|
|
|
"github.com/gorilla/websocket"
|
2014-09-17 10:35:48 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2014-12-07 06:07:32 +03:00
|
|
|
sessionStateActive = iota
|
|
|
|
sessionStateClosed // (not used so far)
|
2014-09-17 10:35:48 +04:00
|
|
|
)
|
|
|
|
|
2014-12-13 13:47:41 +03:00
|
|
|
// Logger.
|
|
|
|
var logger = log.NewLogger(os.Stdout)
|
|
|
|
|
2014-09-20 06:39:29 +04:00
|
|
|
var (
|
2014-12-07 06:07:32 +03:00
|
|
|
// SessionWS holds all session channels. <sid, *util.WSChannel>
|
2014-09-25 05:51:00 +04:00
|
|
|
SessionWS = map[string]*util.WSChannel{}
|
2014-09-20 06:39:29 +04:00
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
// EditorWS holds all editor channels. <sid, *util.WSChannel>
|
2014-10-29 13:15:18 +03:00
|
|
|
EditorWS = map[string]*util.WSChannel{}
|
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
// OutputWS holds all output channels. <sid, *util.WSChannel>
|
2014-09-20 06:39:29 +04:00
|
|
|
OutputWS = map[string]*util.WSChannel{}
|
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
// NotificationWS holds all notification channels. <sid, *util.WSChannel>
|
2014-09-20 06:39:29 +04:00
|
|
|
NotificationWS = map[string]*util.WSChannel{}
|
2015-02-13 04:59:51 +03:00
|
|
|
|
|
|
|
// PlaygroundWS holds all playground channels. <sid, *util.WSChannel>
|
|
|
|
PlaygroundWS = map[string]*util.WSChannel{}
|
2014-09-20 06:39:29 +04:00
|
|
|
)
|
2014-09-19 20:56:32 +04:00
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// HTTP session store.
|
2014-09-17 10:35:48 +04:00
|
|
|
var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
|
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
// WideSession represents a session associated with a browser tab.
|
2014-09-17 10:35:48 +04:00
|
|
|
type WideSession struct {
|
2014-12-07 06:07:32 +03:00
|
|
|
ID string // id
|
2014-10-29 13:15:18 +03:00
|
|
|
Username string // username
|
|
|
|
HTTPSession *sessions.Session // HTTP session related
|
|
|
|
Processes []*os.Process // process set
|
|
|
|
EventQueue *event.UserEventQueue // event queue
|
|
|
|
State int // state
|
|
|
|
Content *conf.LatestSessionContent // the latest session content
|
2015-03-13 16:04:31 +03:00
|
|
|
FileWatcher *fsnotify.Watcher // files change watcher
|
2014-10-29 13:15:18 +03:00
|
|
|
Created time.Time // create time
|
|
|
|
Updated time.Time // the latest use time
|
2014-09-17 10:35:48 +04:00
|
|
|
}
|
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// Type of wide sessions.
|
2014-12-07 06:07:32 +03:00
|
|
|
type wSessions []*WideSession
|
2014-09-17 10:35:48 +04:00
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// Wide sessions.
|
2014-12-07 06:07:32 +03:00
|
|
|
var WideSessions wSessions
|
2014-09-17 10:35:48 +04:00
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// Exclusive lock.
|
2014-09-17 10:35:48 +04:00
|
|
|
var mutex sync.Mutex
|
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
// FixedTimeRelease releases invalid sessions.
|
|
|
|
//
|
2014-10-29 13:15:18 +03:00
|
|
|
// In some special cases (such as a browser uninterrupted refresh / refresh in the source code view) will occur
|
|
|
|
// some invalid sessions, the function checks and removes these invalid sessions periodically (1 hour).
|
2014-09-25 09:37:59 +04:00
|
|
|
//
|
2014-10-29 13:15:18 +03:00
|
|
|
// Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
|
2014-09-23 17:03:44 +04:00
|
|
|
func FixedTimeRelease() {
|
|
|
|
go func() {
|
2015-03-16 06:24:55 +03:00
|
|
|
defer util.Recover()
|
|
|
|
|
2014-10-10 10:24:47 +04:00
|
|
|
for _ = range time.Tick(time.Hour) {
|
2014-09-23 17:03:44 +04:00
|
|
|
hour, _ := time.ParseDuration("-30m")
|
|
|
|
threshold := time.Now().Add(hour)
|
|
|
|
|
|
|
|
for _, s := range WideSessions {
|
|
|
|
if s.Updated.Before(threshold) {
|
2014-12-13 13:47:41 +03:00
|
|
|
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.Username)
|
2014-09-23 17:03:44 +04:00
|
|
|
|
2014-12-07 06:07:32 +03:00
|
|
|
WideSessions.Remove(s.ID)
|
2014-09-23 17:03:44 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2014-11-21 06:19:57 +03:00
|
|
|
// Online user statistic report.
|
|
|
|
type userReport struct {
|
|
|
|
username string
|
|
|
|
sessionCnt int
|
2014-12-05 12:31:21 +03:00
|
|
|
processCnt int
|
2014-11-21 06:19:57 +03:00
|
|
|
updated time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
// report returns a online user statistics in pretty format.
|
|
|
|
func (u *userReport) report() string {
|
2014-12-05 12:31:21 +03:00
|
|
|
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
|
|
|
|
"] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
|
2014-11-21 06:19:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// FixedTimeReport reports the Wide sessions status periodically (10 minutes).
|
|
|
|
func FixedTimeReport() {
|
|
|
|
go func() {
|
2015-03-16 06:24:55 +03:00
|
|
|
defer util.Recover()
|
|
|
|
|
2014-12-23 11:15:45 +03:00
|
|
|
for _ = range time.Tick(10 * time.Minute) {
|
2014-12-23 11:03:09 +03:00
|
|
|
users := userReports{}
|
2014-12-07 07:08:10 +03:00
|
|
|
processSum := 0
|
2014-11-21 06:19:57 +03:00
|
|
|
|
|
|
|
for _, s := range WideSessions {
|
2014-12-05 12:31:21 +03:00
|
|
|
processCnt := len(s.Processes)
|
2014-12-07 07:08:10 +03:00
|
|
|
processSum += processCnt
|
2014-12-05 12:31:21 +03:00
|
|
|
|
2014-12-23 11:03:09 +03:00
|
|
|
if report, exists := contains(users, s.Username); exists {
|
2014-11-21 06:19:57 +03:00
|
|
|
if s.Updated.After(report.updated) {
|
2014-12-23 11:03:09 +03:00
|
|
|
report.updated = s.Updated
|
2014-11-21 06:19:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
report.sessionCnt++
|
2014-12-05 12:31:21 +03:00
|
|
|
report.processCnt += processCnt
|
2014-11-21 06:19:57 +03:00
|
|
|
} else {
|
2014-12-23 11:03:09 +03:00
|
|
|
users = append(users, &userReport{username: s.Username, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
|
2014-11-21 06:19:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-21 09:08:29 +03:00
|
|
|
var buf bytes.Buffer
|
2014-12-07 12:20:10 +03:00
|
|
|
buf.WriteString("\n [" + strconv.Itoa(len(users)) + "] users, [" + strconv.Itoa(processSum) + "] running processes and [" +
|
2014-12-07 07:08:10 +03:00
|
|
|
strconv.Itoa(len(WideSessions)) + "] sessions currently\n")
|
2014-11-21 06:19:57 +03:00
|
|
|
|
2014-12-23 11:03:09 +03:00
|
|
|
sort.Sort(users)
|
|
|
|
|
2014-11-21 06:19:57 +03:00
|
|
|
for _, t := range users {
|
2014-11-21 09:08:29 +03:00
|
|
|
buf.WriteString(" " + t.report() + "\n")
|
2014-11-21 06:19:57 +03:00
|
|
|
}
|
2014-11-21 09:08:29 +03:00
|
|
|
|
2014-12-13 13:47:41 +03:00
|
|
|
logger.Info(buf.String())
|
2014-11-21 06:19:57 +03:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2014-12-23 11:03:09 +03:00
|
|
|
func contains(reports []*userReport, username string) (*userReport, bool) {
|
|
|
|
for _, ur := range reports {
|
|
|
|
if username == ur.username {
|
|
|
|
return ur, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
type userReports []*userReport
|
|
|
|
|
|
|
|
func (f userReports) Len() int { return len(f) }
|
|
|
|
func (f userReports) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
|
|
func (f userReports) Less(i, j int) bool { return f[i].processCnt > f[j].processCnt }
|
|
|
|
|
2015-10-02 17:20:47 +03:00
|
|
|
const (
|
|
|
|
// Time allowed to write a message to the peer.
|
|
|
|
writeWait = 10 * time.Second
|
|
|
|
|
|
|
|
// Time allowed to read the next pong message from the peer.
|
|
|
|
pongWait = 60 * time.Second
|
|
|
|
|
|
|
|
// Send pings to peer with this period. Must be less than pongWait.
|
|
|
|
pingPeriod = (pongWait * 9) / 10
|
|
|
|
)
|
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// WSHandler handles request of creating session channel.
|
|
|
|
//
|
|
|
|
// When a channel closed, releases all resources associated with it.
|
2014-09-19 20:56:32 +04:00
|
|
|
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
sid := r.URL.Query()["sid"][0]
|
2015-09-27 02:36:34 +03:00
|
|
|
|
|
|
|
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
|
|
|
|
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
|
|
|
|
|
|
|
|
ret := map[string]interface{}{"output": "Session initialized", "cmd": "init-session"}
|
|
|
|
err := wsChan.WriteJSON(&ret)
|
|
|
|
if nil != err {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
SessionWS[sid] = &wsChan
|
|
|
|
|
2014-09-19 20:56:32 +04:00
|
|
|
wSession := WideSessions.Get(sid)
|
|
|
|
if nil == wSession {
|
2014-11-02 10:44:24 +03:00
|
|
|
httpSession, _ := HTTPSession.Get(r, "wide-session")
|
2014-09-19 20:56:32 +04:00
|
|
|
|
2014-11-02 10:44:24 +03:00
|
|
|
if httpSession.IsNew {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
|
|
|
httpSession.Save(r, w)
|
|
|
|
|
2015-09-27 02:36:34 +03:00
|
|
|
wSession = WideSessions.new(httpSession, sid)
|
2014-11-02 10:44:24 +03:00
|
|
|
|
2014-12-14 18:05:54 +03:00
|
|
|
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username)
|
2014-09-19 20:56:32 +04:00
|
|
|
}
|
|
|
|
|
2014-12-14 18:05:54 +03:00
|
|
|
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
|
2014-09-19 20:56:32 +04:00
|
|
|
|
|
|
|
input := map[string]interface{}{}
|
2015-10-02 18:07:43 +03:00
|
|
|
|
|
|
|
wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
2015-10-02 17:20:47 +03:00
|
|
|
wsChan.Conn.SetPongHandler(func(string) error { wsChan.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
|
|
|
ticker := time.NewTicker(pingPeriod)
|
2015-10-02 18:07:43 +03:00
|
|
|
|
2015-10-02 17:20:47 +03:00
|
|
|
defer func() {
|
|
|
|
WideSessions.Remove(sid)
|
|
|
|
ticker.Stop()
|
|
|
|
wsChan.Close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// send websocket ping message.
|
|
|
|
go func(t *time.Ticker, channel util.WSChannel) {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-t.C:
|
|
|
|
if err := channel.Conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}(ticker, wsChan)
|
2014-09-19 20:56:32 +04:00
|
|
|
|
|
|
|
for {
|
2014-11-20 09:11:54 +03:00
|
|
|
if err := wsChan.ReadJSON(&input); err != nil {
|
2014-12-14 18:05:54 +03:00
|
|
|
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.Username)
|
2014-09-19 20:56:32 +04:00
|
|
|
|
2014-09-23 17:03:44 +04:00
|
|
|
return
|
2014-09-19 20:56:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = map[string]interface{}{"output": "", "cmd": "session-output"}
|
|
|
|
|
2014-11-20 08:59:08 +03:00
|
|
|
if err := wsChan.WriteJSON(&ret); err != nil {
|
2014-12-13 13:47:41 +03:00
|
|
|
logger.Error("Session WS ERROR: " + err.Error())
|
2014-11-20 17:53:54 +03:00
|
|
|
|
2014-09-19 20:56:32 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
wsChan.Time = time.Now()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-09 09:16:46 +03:00
|
|
|
// SaveContentHandler handles request of session content string.
|
|
|
|
func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
|
2015-11-24 12:39:35 +03:00
|
|
|
result := util.NewResult()
|
|
|
|
defer util.RetResult(w, r, result)
|
2014-09-22 19:13:07 +04:00
|
|
|
|
2014-09-23 07:20:01 +04:00
|
|
|
args := struct {
|
2014-09-23 17:03:44 +04:00
|
|
|
Sid string
|
2014-09-23 07:20:01 +04:00
|
|
|
*conf.LatestSessionContent
|
|
|
|
}{}
|
2014-09-22 19:13:07 +04:00
|
|
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
2014-12-13 13:47:41 +03:00
|
|
|
logger.Error(err)
|
2015-11-24 12:39:35 +03:00
|
|
|
result.Succ = false
|
2014-09-22 19:13:07 +04:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-23 17:03:44 +04:00
|
|
|
wSession := WideSessions.Get(args.Sid)
|
2014-09-22 19:13:07 +04:00
|
|
|
if nil == wSession {
|
2015-11-24 12:39:35 +03:00
|
|
|
result.Succ = false
|
2014-09-22 19:13:07 +04:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-23 07:20:01 +04:00
|
|
|
wSession.Content = args.LatestSessionContent
|
2014-09-22 19:13:07 +04:00
|
|
|
|
2014-12-19 10:43:59 +03:00
|
|
|
for _, user := range conf.Users {
|
2014-09-22 19:13:07 +04:00
|
|
|
if user.Name == wSession.Username {
|
2014-12-23 19:14:03 +03:00
|
|
|
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically
|
2014-09-22 19:13:07 +04:00
|
|
|
user.LatestSessionContent = wSession.Content
|
|
|
|
|
2014-12-23 19:14:03 +03:00
|
|
|
user.Lived = time.Now().UnixNano()
|
|
|
|
|
2014-09-23 17:03:44 +04:00
|
|
|
wSession.Refresh()
|
2014-09-22 19:13:07 +04:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// SetProcesses binds process set with the wide session.
|
2014-09-19 15:21:13 +04:00
|
|
|
func (s *WideSession) SetProcesses(ps []*os.Process) {
|
|
|
|
s.Processes = ps
|
|
|
|
|
|
|
|
s.Refresh()
|
|
|
|
}
|
|
|
|
|
2014-10-29 13:15:18 +03:00
|
|
|
// Refresh refreshes the channel by updating its use time.
|
2014-09-19 15:21:13 +04:00
|
|
|
func (s *WideSession) Refresh() {
|
|
|
|
s.Updated = time.Now()
|
|
|
|
}
|
|
|
|
|
2015-09-27 02:36:34 +03:00
|
|
|
// GenId generates a wide session id.
|
|
|
|
func (sessions *wSessions) GenId() string {
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
|
|
|
return strconv.Itoa(rand.Int())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get gets a wide session with the specified session id.
|
|
|
|
func (sessions *wSessions) Get(sid string) *WideSession {
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
|
|
|
for _, s := range *sessions {
|
|
|
|
if s.ID == sid {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove removes a wide session specified with the given session id, releases resources associated with it.
|
|
|
|
//
|
|
|
|
// Session-related resources:
|
|
|
|
//
|
|
|
|
// 1. user event queue
|
|
|
|
// 2. process set
|
|
|
|
// 3. websocket channels
|
|
|
|
// 4. file watcher
|
|
|
|
func (sessions *wSessions) Remove(sid string) {
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
|
|
|
for i, s := range *sessions {
|
|
|
|
if s.ID == sid {
|
|
|
|
// remove from session set
|
|
|
|
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
|
|
|
|
|
|
|
|
// close user event queue
|
|
|
|
event.UserEventQueues.Close(sid)
|
|
|
|
|
|
|
|
// kill processes
|
|
|
|
for _, p := range s.Processes {
|
|
|
|
if err := p.Kill(); nil != err {
|
|
|
|
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
|
|
|
|
} else {
|
|
|
|
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// close websocket channels
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ws, ok := PlaygroundWS[sid]; ok {
|
|
|
|
ws.Close()
|
|
|
|
delete(PlaygroundWS, sid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// file watcher
|
|
|
|
if nil != s.FileWatcher {
|
|
|
|
s.FileWatcher.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt := 0 // count wide sessions associated with HTTP session
|
|
|
|
for _, ses := range *sessions {
|
|
|
|
if ses.Username == s.Username {
|
|
|
|
cnt++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetByUsername gets wide sessions.
|
|
|
|
func (sessions *wSessions) GetByUsername(username string) []*WideSession {
|
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
|
|
|
ret := []*WideSession{}
|
|
|
|
|
|
|
|
for _, s := range *sessions {
|
|
|
|
if s.Username == username {
|
|
|
|
ret = append(ret, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
// new creates a wide session.
|
|
|
|
func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideSession {
|
2014-09-17 10:35:48 +04:00
|
|
|
mutex.Lock()
|
|
|
|
defer mutex.Unlock()
|
|
|
|
|
2015-03-13 16:04:31 +03:00
|
|
|
username := httpSession.Values["username"].(string)
|
2015-03-23 10:30:52 +03:00
|
|
|
now := time.Now()
|
2015-03-13 16:04:31 +03:00
|
|
|
|
2014-09-17 10:35:48 +04:00
|
|
|
ret := &WideSession{
|
2014-12-07 06:07:32 +03:00
|
|
|
ID: sid,
|
2015-03-13 16:04:31 +03:00
|
|
|
Username: username,
|
2014-09-17 10:35:48 +04:00
|
|
|
HTTPSession: httpSession,
|
2015-03-23 10:30:52 +03:00
|
|
|
EventQueue: nil,
|
2014-12-07 06:07:32 +03:00
|
|
|
State: sessionStateActive,
|
2014-09-22 19:13:07 +04:00
|
|
|
Content: &conf.LatestSessionContent{},
|
2014-09-17 10:35:48 +04:00
|
|
|
Created: now,
|
|
|
|
Updated: now,
|
|
|
|
}
|
|
|
|
|
|
|
|
*sessions = append(*sessions, ret)
|
|
|
|
|
2015-03-13 16:04:31 +03:00
|
|
|
if "playground" == username {
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2015-03-23 10:30:52 +03:00
|
|
|
// create user event queue
|
|
|
|
ret.EventQueue = event.UserEventQueues.New(sid)
|
|
|
|
|
2015-03-13 16:04:31 +03:00
|
|
|
// add a filesystem watcher to notify front-end after the files changed
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err)
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
2015-03-16 06:24:55 +03:00
|
|
|
defer util.Recover()
|
|
|
|
|
2015-03-13 16:04:31 +03:00
|
|
|
for {
|
2015-03-19 06:42:52 +03:00
|
|
|
ch := SessionWS[sid]
|
|
|
|
if nil == ch {
|
|
|
|
return // release this gorutine
|
|
|
|
}
|
|
|
|
|
2015-03-13 16:04:31 +03:00
|
|
|
select {
|
|
|
|
case event := <-watcher.Events:
|
2015-09-27 09:29:26 +03:00
|
|
|
path := filepath.ToSlash(event.Name)
|
|
|
|
dir := filepath.ToSlash(filepath.Dir(path))
|
2015-03-13 16:04:31 +03:00
|
|
|
|
2015-03-19 06:42:52 +03:00
|
|
|
ch = SessionWS[sid]
|
2015-03-13 16:04:31 +03:00
|
|
|
if nil == ch {
|
2015-03-19 06:42:52 +03:00
|
|
|
return // release this gorutine
|
2015-03-13 16:04:31 +03:00
|
|
|
}
|
|
|
|
|
2015-09-27 02:36:34 +03:00
|
|
|
logger.Trace(event)
|
2015-09-26 13:07:55 +03:00
|
|
|
|
2015-03-13 16:04:31 +03:00
|
|
|
if event.Op&fsnotify.Create == fsnotify.Create {
|
2015-09-27 09:29:26 +03:00
|
|
|
fileType := "f"
|
|
|
|
|
|
|
|
if util.File.IsDir(path) {
|
|
|
|
fileType = "d"
|
2015-10-03 10:00:19 +03:00
|
|
|
|
|
|
|
if err = watcher.Add(path); nil != err {
|
|
|
|
logger.Warn(err, path)
|
|
|
|
}
|
2015-09-27 09:29:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
cmd := map[string]interface{}{"path": path, "dir": dir,
|
|
|
|
"cmd": "create-file", "type": fileType}
|
2015-03-13 16:04:31 +03:00
|
|
|
ch.WriteJSON(&cmd)
|
|
|
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
2015-09-27 09:29:26 +03:00
|
|
|
cmd := map[string]interface{}{"path": path, "dir": dir,
|
|
|
|
"cmd": "remove-file", "type": ""}
|
2015-03-13 16:04:31 +03:00
|
|
|
ch.WriteJSON(&cmd)
|
|
|
|
|
|
|
|
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
|
2015-09-27 09:29:26 +03:00
|
|
|
cmd := map[string]interface{}{"path": path, "dir": dir,
|
|
|
|
"cmd": "rename-file", "type": ""}
|
2015-03-13 16:04:31 +03:00
|
|
|
ch.WriteJSON(&cmd)
|
|
|
|
}
|
|
|
|
case err := <-watcher.Errors:
|
|
|
|
if nil != err {
|
|
|
|
logger.Error("File watcher ERROR: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-09-26 13:07:55 +03:00
|
|
|
go func() {
|
|
|
|
defer util.Recover()
|
|
|
|
|
|
|
|
workspaces := filepath.SplitList(conf.GetUserWorkspace(username))
|
|
|
|
for _, workspace := range workspaces {
|
|
|
|
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
|
|
|
|
if ".git" == f.Name() { // XXX: discard other unconcered dirs
|
|
|
|
return filepath.SkipDir
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.IsDir() {
|
|
|
|
if err = watcher.Add(dirPath); nil != err {
|
|
|
|
logger.Error(err, dirPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.Tracef("File watcher added a dir [%s]", dirPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.FileWatcher = watcher
|
|
|
|
}()
|
|
|
|
|
2014-09-17 10:35:48 +04:00
|
|
|
return ret
|
|
|
|
}
|