From 565167ecc25208b45b45dce3bf98d3db5059fc89 Mon Sep 17 00:00:00 2001 From: schwarzlichtbezirk Date: Tue, 28 Feb 2023 23:54:51 +0300 Subject: [PATCH] uaid, part 1. --- aec.go | 4 ++- folder.go | 4 +-- handlers.go | 12 +++---- jwtauth.go | 8 ++--- routes.go | 63 ++++++++++++++++++-------------- users.go | 101 +++++++++++++++++++++++++++++----------------------- workflow.go | 3 +- 7 files changed, 108 insertions(+), 87 deletions(-) diff --git a/aec.go b/aec.go index 3bb51ac..83aeaf8 100644 --- a/aec.go +++ b/aec.go @@ -249,7 +249,9 @@ const ( AECgpsscannodata // stat/usrlst - AECusrlstusts + AECusrlstasts + AECusrlstfost + AECusrlstpost ) // HTTP error messages diff --git a/folder.go b/folder.go index de1feb3..93d1d53 100644 --- a/folder.go +++ b/folder.go @@ -305,9 +305,9 @@ func folderAPI(w http.ResponseWriter, r *http.Request, aid, uid ID_t) { var latency = time.Since(t) Log.Infof("id%d: navigate to %s, items %d, timeout %s", acc.ID, syspath, len(ret.List), latency) - if cid, err := GetCID(r); err == nil { + if uaid, err := GetUAID(r); err == nil { go xormUserlog.InsertOne(&OpenStore{ - CID: cid, + UAID: uaid, AID: aid, UID: uid, Path: syspath, diff --git a/handlers.go b/handlers.go index 801699e..eefed29 100644 --- a/handlers.go +++ b/handlers.go @@ -112,9 +112,9 @@ func fileHandler(w http.ResponseWriter, r *http.Request, aid, uid ID_t) { if HasRangeBegin(r) { // beginning of content Log.Infof("id%d: media-hd %s", acc.ID, path.Base(syspath)) - if cid, err := GetCID(r); err == nil { + if uaid, err := GetUAID(r); err == nil { go xormUserlog.InsertOne(&OpenStore{ - CID: cid, + UAID: uaid, AID: aid, UID: uid, Path: syspath, @@ -147,9 +147,9 @@ func fileHandler(w http.ResponseWriter, r *http.Request, aid, uid ID_t) { if HasRangeBegin(r) { // beginning of content Log.Infof("id%d: media %s", acc.ID, path.Base(syspath)) - if cid, err := GetCID(r); err == nil { + if uaid, err := GetUAID(r); err == nil { go xormUserlog.InsertOne(&OpenStore{ - CID: cid, + UAID: uaid, AID: aid, UID: uid, Path: syspath, @@ -165,9 +165,9 @@ func fileHandler(w http.ResponseWriter, r *http.Request, aid, uid ID_t) { if HasRangeBegin(r) { // beginning of content Log.Infof("id%d: serve %s", acc.ID, path.Base(syspath)) - if cid, err := GetCID(r); err == nil { + if uaid, err := GetUAID(r); err == nil { go xormUserlog.InsertOne(&OpenStore{ - CID: cid, + UAID: uaid, AID: aid, UID: uid, Path: syspath, diff --git a/jwtauth.go b/jwtauth.go index 6732037..15158c7 100644 --- a/jwtauth.go +++ b/jwtauth.go @@ -97,13 +97,13 @@ func ParseID(s string) (id ID_t, err error) { return } -// GetCID extract client ID from cookie. -func GetCID(r *http.Request) (cid ID_t, err error) { +// GetUAID extract user agent ID from cookie. +func GetUAID(r *http.Request) (uaid ID_t, err error) { var c *http.Cookie - if c, err = r.Cookie("CID"); err != nil { + if c, err = r.Cookie("UAID"); err != nil { return } - if cid, err = ParseID(c.Value); err != nil { + if uaid, err = ParseID(c.Value); err != nil { return } return diff --git a/routes.go b/routes.go index 4f7110d..c722ee0 100644 --- a/routes.go +++ b/routes.go @@ -473,6 +473,8 @@ func LoadTemplates() (err error) { // Transaction locker, locks until handler will be done. var handwg sync.WaitGroup +const alias_cond = "(cid1=? AND cid2=?) OR (cid1=? AND cid2=?)" + // AjaxMiddleware is base handler middleware for AJAX API calls. func AjaxMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -501,40 +503,47 @@ func AjaxMiddleware(next http.Handler) http.Handler { handwg.Add(1) defer handwg.Done() - // get CID - if cid, err := GetCID(r); err == nil { + var ( + cid ID_t + uaold, uanew ID_t + isold, isnew bool + ) + + var ast = &AgentStore{ + Addr: StripPort(r.RemoteAddr), + UA: r.UserAgent(), + } + uanew = ID_t(ast.Hash()) + + // UAID at cookie + if uaold, _ = GetUAID(r); uaold == 0 { + http.SetCookie(w, &http.Cookie{ + Name: "UAID", + Value: strconv.FormatUint(uint64(uanew), 10), + Path: "/", + }) + } + + uamux.Lock() + if cid, isnew = UaMap[uanew]; !isnew { + if cid, isold = UaMap[uaold]; !isold { + maxcid++ + cid = maxcid + } + UaMap[uanew] = cid go func() { - var ast = AgentStore{ - CID: cid, - Addr: StripPort(r.RemoteAddr), - UA: r.UserAgent(), - } + ast.CID = cid + ast.UAID = uanew if lang, ok := r.Header["Accept-Language"]; ok { ast.Lang = lang[0] } - - var hv = ast.Hash() - uamux.Lock() - var _, ok = UaMap[hv] - if !ok { - UaMap[hv] = void{} - } - UserOnline[cid] = time.Now() - uamux.Unlock() - if !ok { - xormUserlog.InsertOne(&ast) + if _, err := xormUserlog.InsertOne(ast); err != nil { + panic(err.Error()) } }() - } else { - var cst ClientStore - if _, err := xormUserlog.InsertOne(&cst); err == nil { - http.SetCookie(w, &http.Cookie{ - Name: "CID", - Value: strconv.FormatUint(uint64(cst.CID), 16), - Path: "/", - }) - } } + UserOnline[uanew] = time.Now() + uamux.Unlock() // call the next handler, which can be another middleware in the chain, or the final handler next.ServeHTTP(w, r) diff --git a/users.go b/users.go index 5cf8e17..750e367 100644 --- a/users.go +++ b/users.go @@ -1,7 +1,6 @@ package hms import ( - "encoding/binary" "encoding/xml" "net/http" "path" @@ -16,13 +15,9 @@ import ( var xormUserlog *xorm.Engine -type ClientStore struct { - CID ID_t `xorm:"pk autoincr"` - Time Time `xorm:"created"` -} - type AgentStore struct { - CID ID_t + UAID ID_t `xorm:"unique"` // user agent ID + CID ID_t // client ID Addr string // remote address UA string // user agent Lang string // accept language @@ -30,8 +25,8 @@ type AgentStore struct { } type OpenStore struct { - CID ID_t // client unique ID - AID ID_t `xorm:"default 0"` // access ID + UAID ID_t // client ID + AID ID_t `xorm:"default 0"` // access profile ID UID ID_t `xorm:"default 0"` // user profile ID Path string // system path Latency int // event latency, in milliseconds, or -1 if it file @@ -41,20 +36,22 @@ type OpenStore struct { var ( // UserOnline is map of last AJAX query time for each user. UserOnline = map[ID_t]time.Time{} - // UaMap is the set hashes of of user-agent records. - UaMap = map[uint64]void{} + // UaMap is the map of user agent hashes and associated client IDs. + UaMap = map[ID_t]ID_t{} + // current maximum client ID + maxcid ID_t // mutex to get access to user-agent maps. uamux sync.Mutex ) +const ua_salt = "hms" + func (ast *AgentStore) Hash() uint64 { var h = xxhash.New() - var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], uint64(ast.CID)) - h.Write(buf[:]) + h.Write(s2b(ua_salt)) h.Write(s2b(ast.Addr)) h.Write(s2b(ast.UA)) - return h.Sum64() + return h.Sum64() & 0x7fff_ffff_ffff_ffff } // InitUserlog inits database user log engine. @@ -65,7 +62,7 @@ func InitUserlog() (err error) { xormUserlog.SetMapper(names.GonicMapper{}) xormUserlog.ShowSQL(false) - if err = xormUserlog.Sync(&ClientStore{}, &AgentStore{}, &OpenStore{}); err != nil { + if err = xormUserlog.Sync(&AgentStore{}, &OpenStore{}); err != nil { return } return @@ -76,6 +73,12 @@ func LoadUaMap() (err error) { var session = xormUserlog.NewSession() defer session.Close() + var u64 uint64 + if _, err = session.Table(&AgentStore{}).Select("MAX(cid)").Get(&u64); err != nil { + return + } + maxcid = ID_t(u64) + const limit = 256 var offset int for { @@ -85,7 +88,7 @@ func LoadUaMap() (err error) { } offset += limit for _, ast := range chunk { - UaMap[ast.Hash()] = void{} + UaMap[ast.UAID] = ast.CID } if limit > len(chunk) { break @@ -100,11 +103,11 @@ func usrlstAPI(w http.ResponseWriter, r *http.Request) { Addr string `json:"addr" yaml:"addr" xml:"addr"` UA uas.UserAgent `json:"ua" yaml:"ua" xml:"ua"` Lang string `json:"lang" yaml:"lang" xml:"lang"` + Online bool `json:"online" yaml:"online" xml:"online,attr"` + AID ID_t `json:"accid" yaml:"accid" xml:"accid,attr"` + UID ID_t `json:"usrid" yaml:"usrid" xml:"usrid,attr"` Path string `json:"path" yaml:"path" xml:"path"` File string `json:"file" yaml:"file" xml:"file"` - Online bool `json:"online" yaml:"online" xml:"online"` - AID ID_t `json:"accid" yaml:"accid" xml:"accid"` - UID ID_t `json:"usrid" yaml:"usrid" xml:"usrid"` } var err error @@ -129,39 +132,47 @@ func usrlstAPI(w http.ResponseWriter, r *http.Request) { var session = xormUserlog.NewSession() defer session.Close() - type jstore struct { - ClientStore `xorm:"extends"` - AgentStore `xorm:"extends"` - Path OpenStore `xorm:"extends"` - File OpenStore `xorm:"extends"` - } - - ret.Total, _ = session.Count(&ClientStore{}) - var justs []jstore - if err = session.Distinct().Table("client_store"). - Join("INNER", "agent_store", "client_store.cid = agent_store.cid AND agent_store.time = (SELECT MIN(time) FROM agent_store WHERE cid = client_store.cid)"). - Join("INNER", "open_store t1", "client_store.cid = t1.cid AND t1.time = (SELECT MAX(time) FROM open_store WHERE cid = client_store.cid AND latency>=0)"). - Join("INNER", "open_store t2", "client_store.cid = t2.cid AND t2.time = (SELECT MAX(time) FROM open_store WHERE cid = client_store.cid AND latency=-1)"). - Limit(arg.Num, arg.Pos).Find(&justs); err != nil { - WriteError500(w, r, err, AECusrlstusts) + var asts []AgentStore + if err = xormUserlog.Limit(arg.Num, arg.Pos).Find(&asts); err != nil { + WriteError500(w, r, err, AECusrlstasts) return } + + ret.List = make([]item, len(asts)) var now = time.Now() - for _, rec := range justs { + for i, ast := range asts { uamux.Lock() - var online = now.Sub(UserOnline[rec.AgentStore.CID]) < cfg.OnlineTimeout + var online = now.Sub(UserOnline[ast.UAID]) < cfg.OnlineTimeout uamux.Unlock() var ui = item{ - Addr: rec.Addr, - Lang: rec.Lang, - Path: rec.Path.Path, - File: rec.File.Path, + Addr: ast.Addr, + Lang: ast.Lang, Online: online, - AID: rec.Path.AID, - UID: rec.Path.UID, } - uas.ParseUserAgent(rec.UA, &ui.UA) - ret.List = append(ret.List, ui) + uas.ParseUserAgent(ast.UA, &ui.UA) + + var is bool + var fost, post OpenStore + if is, err = xormUserlog.Where("uaid=? AND latency<0", ast.UAID).Desc("time").Get(&fost); err != nil { + WriteError500(w, r, err, AECusrlstfost) + return + } + if is { + ui.File = fost.Path + ui.AID = fost.AID + ui.UID = fost.UID + } + if is, err = xormUserlog.Where("uaid=? AND latency>=0", ast.UAID).Desc("time").Get(&post); err != nil { + WriteError500(w, r, err, AECusrlstpost) + return + } + if is { + ui.File = post.Path + ui.AID = post.AID + ui.UID = post.UID + } + + ret.List[i] = ui } WriteOK(w, r, &ret) diff --git a/workflow.go b/workflow.go index 4c56849..6a7c945 100644 --- a/workflow.go +++ b/workflow.go @@ -128,10 +128,9 @@ func Init() { Log.Fatal("can not init XORM user log: " + err.Error()) } { - var usercount, _ = xormUserlog.Count(&ClientStore{}) - Log.Infof("user count %d items", usercount) var uacount, _ = xormUserlog.Count(&AgentStore{}) Log.Infof("user agent count %d items", uacount) + Log.Infof("clients count %d", maxcid) var opencount, _ = xormUserlog.Count(&OpenStore{}) Log.Infof("resources open count %d items", opencount) }