openidec

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit beba7d3038c0693b37db325069d7693c92dc9118
parent 87b50bd9aea8e6596cd216fc80c39ee60ac99a63
Author: Peter Kosyh <p.kosyh@gmail.com>
Date:   Fri, 18 Sep 2020 13:29:17 +0300

db.go commented

Diffstat:
Mii/db.go | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mii/log.go | 5+++++
2 files changed, 158 insertions(+), 0 deletions(-)

diff --git a/ii/db.go b/ii/db.go @@ -1,3 +1,7 @@ +// Database functions. +// Database is the file with line-bundles: msgid:base64 encoded msg. +// File db.idx is created and mantained automatically. +// There is also points.txt, db of users. package ii import ( @@ -17,6 +21,13 @@ import ( "time" ) +// This is index entry. Information about message that is loaded in memory. +// So, the index could not be very huge. +// Num: sequence number. +// Id: MsgId +// Echo: Echoarea +// To, From, Repto: message attributes +// Off: offset to bundle-line in database (in bytes) type MsgInfo struct { Num int Id string @@ -27,12 +38,21 @@ type MsgInfo struct { From string } +// Index object. Holds List and Hash for all MsgInfo entries +// FileSize is used to auto reread new entries if it has changed by +// someone. type Index struct { Hash map[string]MsgInfo List []string FileSize int64 } +// Database object. Returns by OpenDB. +// Idx: Index structure (like dictionary). +// Name: database name, 'db' by default. +// Sync: used to syncronize access to DB from goroutines (many readers, one writer). +// IdxSync: same, but for Index. +// LockDepth: used for recursive file lock, to avoid conflict between ii-tool and ii-node. type DB struct { Path string Idx Index @@ -42,6 +62,7 @@ type DB struct { LockDepth int32 } +// Utility function. Just append line (text) to file (fn) func append_file(fn string, text string) error { f, err := os.OpenFile(fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { @@ -54,6 +75,10 @@ func append_file(fn string, text string) error { return nil } +// Recursive file lock. Used to avoid conflicts between ii-tool and ii-node. +// Uses mkdir as atomic operation. +// Note: dirs created as db.LockPath() +// 16 sec is limit. func (db *DB) Lock() bool { if atomic.AddInt32(&db.LockDepth, 1) > 1 { return true @@ -70,6 +95,8 @@ func (db *DB) Lock() bool { return false } +// Recursive file lock: unlock +// See Lock comment. func (db *DB) Unlock() { if atomic.AddInt32(&db.LockDepth, -1) > 0 { return @@ -77,14 +104,17 @@ func (db *DB) Unlock() { os.Remove(db.LockPath()) } +// Returns path to index file. func (db *DB) IndexPath() string { return fmt.Sprintf("%s.idx", db.Path) } +// Return path to database itself func (db *DB) BundlePath() string { return fmt.Sprintf("%s", db.Path) } +// Returns path to lock. func (db *DB) LockPath() string { pat := strings.Replace(db.Path, "/", "_", -1) return fmt.Sprintf("%s/%s-bundle.lock", os.TempDir(), pat) @@ -92,6 +122,7 @@ func (db *DB) LockPath() string { // var MaxMsgLen int = 128 * 1024 * 1024 +// This function creates index. It locks. func (db *DB) CreateIndex() error { db.Sync.Lock() defer db.Sync.Unlock() @@ -100,6 +131,9 @@ func (db *DB) CreateIndex() error { return db._CreateIndex() } + +// Utility to pass all lines of file (path) to fn(line). +// Stops on EOF or fn returns false. func FileLines(path string, fn func(string) bool) error { f, err := os.Open(path) if err != nil { @@ -112,6 +146,8 @@ func FileLines(path string, fn func(string) bool) error { return f_lines(f, fn) } +// Internal function to implement FileLines. Works with +// file by *File object. func f_lines(f *os.File, fn func(string) bool) error { reader := bufio.NewReader(f) for { @@ -139,6 +175,8 @@ func f_lines(f *os.File, fn func(string) bool) error { return nil } +// Internal function of CreateIndex. +// Does not lock! func (db *DB) _CreateIndex() error { fidx, err := os.OpenFile(db.IndexPath(), os.O_CREATE|os.O_WRONLY, 0644) if err != nil { @@ -159,6 +197,8 @@ func (db *DB) _CreateIndex() error { return true }) } + +// Internal function. Create and open new index. func (db *DB) _ReopenIndex() (*os.File, error) { err := db._CreateIndex() if err != nil { @@ -170,6 +210,10 @@ func (db *DB) _ReopenIndex() (*os.File, error) { } return file, nil } + +// Loads index. If index doesent exists, create and load it. +// If index was changed, reread tail. +// This function does lock. func (db *DB) LoadIndex() error { db.IdxSync.Lock() defer db.IdxSync.Unlock() @@ -258,6 +302,11 @@ func (db *DB) LoadIndex() error { return nil } +// Internal function to Lookup message in loaded index. +// If idx parameter is true, load and created index. +// Returns MsgInfo pointer or nil if fails. +// Does lock! +// bl: look in blacklisted messages too? func (db *DB) _Lookup(Id string, bl bool, idx bool) *MsgInfo { if idx { if err := db.LoadIndex(); err != nil { @@ -273,6 +322,8 @@ func (db *DB) _Lookup(Id string, bl bool, idx bool) *MsgInfo { return &info } +// Lookup variant, but without locking. +// Useful if caller do locking logic himself. func (db *DB) LookupFast(Id string, bl bool) *MsgInfo { if Id == "" { return nil @@ -280,6 +331,11 @@ func (db *DB) LookupFast(Id string, bl bool) *MsgInfo { return db._Lookup(Id, bl, false) } +// Lookup message in index. +// Do not search blacklisted messages. +// Creates/load index if needed. +// Returns MsgInfo pointer. +// Does lock! func (db *DB) Lookup(Id string) *MsgInfo { db.Sync.RLock() defer db.Sync.RUnlock() @@ -289,6 +345,7 @@ func (db *DB) Lookup(Id string) *MsgInfo { return db._Lookup(Id, false, true) } +// Same as Lookup, but checks in blacklisted messages too func (db *DB) Exists(Id string) *MsgInfo { db.Sync.RLock() defer db.Sync.RUnlock() @@ -298,6 +355,10 @@ func (db *DB) Exists(Id string) *MsgInfo { return db._Lookup(Id, true, true) } +// Lookup messages in index. +// Gets: slice of message ids to get. +// Returns slice of MsgInfo pointers. +// Does lock! func (db *DB) LookupIDS(Ids []string) []*MsgInfo { var info []*MsgInfo db.Sync.RLock() @@ -313,6 +374,10 @@ func (db *DB) LookupIDS(Ids []string) []*MsgInfo { return info } +// Internal function. Gets bundle by message id. +// If idx is true: load/create index. +// Returns: msgid:base64 bundle. +// Does not lock! func (db *DB) _GetBundle(Id string, idx bool) string { info := db._Lookup(Id, false, idx) if info == nil { @@ -342,6 +407,9 @@ func (db *DB) _GetBundle(Id string, idx bool) string { return bundle } +// Get bundle line by message id from db. +// Does lock! +// Loads/create index if needed. func (db *DB) GetBundle(Id string) string { db.Sync.RLock() defer db.Sync.RUnlock() @@ -351,6 +419,8 @@ func (db *DB) GetBundle(Id string) string { return db._GetBundle(Id, true) } +// Get decoded message from db by message id. +// Does lock. Loads/create index if needed. func (db *DB) Get(Id string) *Msg { bundle := db.GetBundle(Id) if bundle == "" { @@ -363,6 +433,9 @@ func (db *DB) Get(Id string) *Msg { return m } +// Fast varian (w/o locking) of Get. +// Get decoded message from db by message id. +// Does NOT lock! Loads/create index if needed. func (db *DB) GetFast(Id string) *Msg { bundle := db._GetBundle(Id, false) if bundle == "" { @@ -375,6 +448,13 @@ func (db *DB) GetFast(Id string) *Msg { return m } +// Query used to make queries to Index +// If some field of: Echo, Repto, From, To is not "" +// fields will be matched with MsgInfo entry (logical AND). +// If Match function is not nil, this function will be used for matching. +// Blacklisted: search in blacklisted messages if true. +// User: authorized access to private areas. +// Start & Lim: slice of query. For example: -1, 1 -- get last message in db. 0, 1 -- first. type Query struct { Echo string Repto string @@ -387,6 +467,7 @@ type Query struct { Match func(mi MsgInfo, q Query) bool } +// utility function to add string in front of slice func prependStr(x []string, y string) []string { x = append(x, "") copy(x[1:], x) @@ -394,6 +475,7 @@ func prependStr(x []string, y string) []string { return x } +// Default match function for queries. func (db *DB) Match(info MsgInfo, r Query) bool { if r.Blacklisted { return info.Off < 0 @@ -424,6 +506,11 @@ func (db *DB) Match(info MsgInfo, r Query) bool { return true } +// Used to get information about echoarea +// Count: number of messages +// Topics: number of topics +// Last: last MsgInfo +// Msg: last message pointer type Echo struct { Name string Count int @@ -432,6 +519,12 @@ type Echo struct { Msg *Msg } +// Make query and select Echoes +// Returns: slice of pointers to Echo. +// names: if not empty, lookup only in theese echoareas +// Does lock. +// Load/create index if needed. +// Echoes sorted by date of last messages. func (db *DB) Echoes(names []string, q Query) []*Echo { db.Sync.Lock() defer db.Sync.Unlock() @@ -507,6 +600,9 @@ func (db *DB) Echoes(names []string, q Query) []*Echo { return list } +// Make query and retuen ids as slice of strings. +// Does lock. Can create/load index if needed. +// r: request, see Query func (db *DB) SelectIDS(r Query) []string { var Resp []string db.Sync.Lock() @@ -556,6 +652,11 @@ func (db *DB) SelectIDS(r Query) []string { return Resp } +// Internal function. Get slice of MsgInfo pointers +// and create information about topics. +// Information returns in form of: [topicid][]ids +// topic id is the msg id of most old parent in echo +// ids - is the messages in this topic func (db *DB) GetTopics(mi []*MsgInfo) map[string][]string { db.Sync.RLock() defer db.Sync.RUnlock() @@ -599,14 +700,23 @@ func (db *DB) GetTopics(mi []*MsgInfo) map[string][]string { return topics } +// Store decoded message in database +// If message exists, returns error func (db *DB) Store(m *Msg) error { return db._Store(m, false) } +// Store decoded message in database +// even it is exists. So, it's like Edit operation. +// While index loaded, it got last version of message data. func (db *DB) Edit(m *Msg) error { return db._Store(m, true) } +// Blacklist decoded message. +// Blacklisting is adding special tag: access/blacklist and Edit operation +// to store it in DB. While loading index, blacklisted messages +// are marked by negative Off field (-1). func (db *DB) Blacklist(m *Msg) error { m.Tags.Add("access/blacklist") return db.Edit(m) @@ -626,6 +736,7 @@ func (db *DB) Blacklist(m *Msg) error { // return nil } +// Internal function used by Store. See Store comment. func (db *DB) _Store(m *Msg, edit bool) error { db.Sync.Lock() defer db.Sync.Unlock() @@ -661,6 +772,9 @@ func (db *DB) _Store(m *Msg, edit bool) error { return nil } +// Opens DB and returns pointer to DB object. +// path is the path to db. By default it is ./db +// Index will be named as path + ".idx" func OpenDB(path string) *DB { var db DB db.Path = path @@ -673,6 +787,9 @@ func OpenDB(path string) *DB { return &db } +// User entry in points.txt db +// User with Id == 1 is superuser. +// Tags: custom information (like avatars :) in Tags format type User struct { Id int32 Name string @@ -681,6 +798,12 @@ type User struct { Tags Tags } +// User database. +// FileSize - size of points.txt to detect DB changes. +// Names: holds User structure by user name +// ById: holds user name by user id +// Secrets: holds user name by user secret (pauth) +// List: holds user names as list type UDB struct { Path string Names map[string]User @@ -691,6 +814,7 @@ type UDB struct { FileSize int64 } +// Check username if it is valid func IsUsername(u string) bool { return !strings.ContainsAny(u, ":\n\r\t/") && !strings.HasPrefix(u, " ") && @@ -698,16 +822,20 @@ func IsUsername(u string) bool { len(u) <= 16 && len(u) > 2 } +// Check password if it is valid to be used func IsPassword(u string) bool { return len(u) >= 1 } +// Make secret from string. +// String is something like id + user + password func MakeSecret(msg string) string { h := sha256.Sum256([]byte(msg)) s := base64.URLEncoding.EncodeToString(h[:]) return s[0:10] } +// Return secret for username or "" if no such user func (db *UDB) Secret(User string) string { db.Sync.RLock() defer db.Sync.RUnlock() @@ -718,6 +846,7 @@ func (db *UDB) Secret(User string) string { return ui.Secret } +// Returns true if user+password is valid func (db *UDB) Auth(User string, Passwd string) bool { db.Sync.RLock() defer db.Sync.RUnlock() @@ -728,6 +857,7 @@ func (db *UDB) Auth(User string, Passwd string) bool { return ui.Secret == MakeSecret(User+Passwd) } +// Returns true if Secret (pauth) is valid func (db *UDB) Access(Secret string) bool { db.Sync.RLock() defer db.Sync.RUnlock() @@ -735,6 +865,7 @@ func (db *UDB) Access(Secret string) bool { return ok } +// Return username for given Secret func (db *UDB) Name(Secret string) string { db.Sync.RLock() defer db.Sync.RUnlock() @@ -746,6 +877,7 @@ func (db *UDB) Name(Secret string) string { return "" } +// Return User pointer for given Secret func (db *UDB) UserInfo(Secret string) *User { db.Sync.RLock() defer db.Sync.RUnlock() @@ -757,6 +889,8 @@ func (db *UDB) UserInfo(Secret string) *User { Error.Printf("No user for secret: %s", Secret) return nil } + +// Return User pointer for user id func (db *UDB) UserInfoId(id int32) *User { db.Sync.RLock() defer db.Sync.RUnlock() @@ -768,6 +902,8 @@ func (db *UDB) UserInfoId(id int32) *User { Error.Printf("No user for Id: %d", id) return nil } + +// Return User pointer for given user name func (db *UDB) UserInfoName(name string) *User { db.Sync.RLock() defer db.Sync.RUnlock() @@ -778,6 +914,7 @@ func (db *UDB) UserInfoName(name string) *User { return nil } +// Return user id for given secret func (db *UDB) Id(Secret string) int32 { db.Sync.RLock() defer db.Sync.RUnlock() @@ -795,6 +932,8 @@ func (db *UDB) Id(Secret string) int32 { var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") +// Add (register) user in database +// Mail is optional but someday it will be used in registration process func (db *UDB) Add(Name string, Mail string, Passwd string) error { db.Sync.Lock() defer db.Sync.Unlock() @@ -831,11 +970,16 @@ func (db *UDB) Add(Name string, Mail string, Passwd string) error { return nil } +// Open user database and return pointer to UDB object func OpenUsers(path string) *UDB { var db UDB db.Path = path return &db } + +// Change (replace) information about user. +// Gets pointer to User object and write it in DB, replacing old information. +// Works atomically using rename. func (db *UDB) Edit(u *User) error { db.Sync.Lock() defer db.Sync.Unlock() @@ -858,6 +1002,8 @@ func (db *UDB) Edit(u *User) error { return nil } +// Load user information in memory if it is needed (FileSize changed). +// So, it is safe to call it on every request. func (db *UDB) LoadUsers() error { db.Sync.Lock() defer db.Sync.Unlock() @@ -915,12 +1061,16 @@ func (db *UDB) LoadUsers() error { return nil } +// Echo database entry +// Holds echo descriptions in Info hash. +// List - names of echoareas. type EDB struct { Info map[string]string List []string Path string } +// Check if echo is exists in echo database func (db *EDB) Allowed(name string) bool { if len(db.List) == 0 { return true @@ -930,6 +1080,9 @@ func (db *EDB) Allowed(name string) bool { } return false } + +// Loads echolist database and returns pointer to EDB +// Supposed to be called only once func LoadEcholist(path string) *EDB { var db EDB db.Path = path diff --git a/ii/log.go b/ii/log.go @@ -1,3 +1,4 @@ +// Simple log system. package ii import ( @@ -12,11 +13,15 @@ var ( Error *log.Logger ) +// Default mode. All messages are shown. func InitLog() { Trace = log.New(os.Stdout, "=== ", log.Ldate|log.Ltime) Info = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime) Error = log.New(os.Stderr, "ERR: ", log.Ldate|log.Ltime) } + +// Custom mode. Use io.Writers to select what verbose level is needed. +// For example: ii.OpenLog(ioutil.Discard, os.Stdout, os.Stderr) func OpenLog(trace io.Writer, info io.Writer, error io.Writer) { Trace = log.New(trace, "=== ", log.Ldate|log.Ltime) Info = log.New(info, "INFO: ", log.Ldate|log.Ltime)