openidec

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

db.go (27342B)


      1 // Database functions.
      2 // Database is the file with line-bundles: msgid:base64 encoded msg.
      3 // File db.idx is created and mantained automatically.
      4 // There is also points.txt, db of users.
      5 package ii
      6 
      7 import (
      8 	"bufio"
      9 	"crypto/sha256"
     10 	"errors"
     11 	"fmt"
     12 	"io"
     13 	"os"
     14 	"path/filepath"
     15 	"regexp"
     16 	"sort"
     17 	"strings"
     18 	"sync"
     19 	"sync/atomic"
     20 	"time"
     21 	"golang.org/x/crypto/bcrypt"
     22 )
     23 
     24 // This is index entry. Information about message that is loaded in memory.
     25 // So, the index could not be very huge.
     26 // Num: sequence number.
     27 // Id: MsgId
     28 // Echo: Echoarea
     29 // To, From, Repto: message attributes
     30 // Off: offset to bundle-line in database (in bytes)
     31 type MsgInfo struct {
     32 	Num   int
     33 	Id    string
     34 	Echo  string
     35 	To    string
     36 	Off   int64
     37 	Repto string
     38 	From  string
     39 	Topic string
     40 }
     41 
     42 // Index object. Holds List and Hash for all MsgInfo entries
     43 // FileSize is used to auto reread new entries if it has changed by
     44 // someone.
     45 type Index struct {
     46 	Hash     map[string]*MsgInfo
     47 	List     []string
     48 	FileSize int64
     49 }
     50 
     51 // Database object. Returns by OpenDB.
     52 // Idx: Index structure (like dictionary).
     53 // Name: database name, 'db' by default.
     54 // Sync: used to syncronize access to DB from goroutines (many readers, one writer).
     55 // IdxSync: same, but for Index.
     56 // LockDepth: used for recursive file lock, to avoid conflict between idecctl and idecd.
     57 type DB struct {
     58 	Path      string
     59 	Idx       Index
     60 	Sync      sync.RWMutex
     61 	IdxSync   sync.RWMutex
     62 	Name      string
     63 	LockDepth int32
     64 }
     65 
     66 // Utility function. Just append line (text) to file (fn)
     67 func append_file(fn string, text string) error {
     68 	f, err := os.OpenFile(fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
     69 	if err != nil {
     70 		return err
     71 	}
     72 	defer f.Close()
     73 	if _, err := f.WriteString(text + "\n"); err != nil {
     74 		return err
     75 	}
     76 	return nil
     77 }
     78 
     79 // Recursive file lock. Used to avoid conflicts between idecctl and idecd.
     80 // Uses mkdir as atomic operation.
     81 // Note: dirs created as db.LockPath()
     82 // 16 sec is limit.
     83 func (db *DB) Lock() bool {
     84 	if atomic.AddInt32(&db.LockDepth, 1) > 1 {
     85 		return true
     86 	}
     87 	try := 16
     88 	for try > 0 {
     89 		if err := os.Mkdir(db.LockPath(), 0777); err == nil {
     90 			return true
     91 		}
     92 		time.Sleep(time.Second)
     93 		try -= 1
     94 	}
     95 	Error.Printf("Can not acquire lock for 16 seconds: %s", db.LockPath())
     96 	return false
     97 }
     98 
     99 // Recursive file lock: unlock
    100 // See Lock comment.
    101 func (db *DB) Unlock() {
    102 	if atomic.AddInt32(&db.LockDepth, -1) > 0 {
    103 		return
    104 	}
    105 	os.Remove(db.LockPath())
    106 }
    107 
    108 // Returns path to index file.
    109 func (db *DB) IndexPath() string {
    110 	return fmt.Sprintf("%s.idx", db.Path)
    111 }
    112 
    113 // Return path to database itself
    114 func (db *DB) BundlePath() string {
    115 	return db.Path
    116 }
    117 
    118 // Returns path to lock.
    119 func (db *DB) LockPath() string {
    120 	pat := strings.Replace(db.Path, "/", "_", -1)
    121 	return fmt.Sprintf("%s/%s-bundle.lock", os.TempDir(), pat)
    122 }
    123 
    124 // var MaxMsgLen int = 128 * 1024 * 1024
    125 
    126 // This function creates index. It locks.
    127 func (db *DB) CreateIndex() error {
    128 	db.Sync.Lock()
    129 	defer db.Sync.Unlock()
    130 	db.Lock()
    131 	defer db.Unlock()
    132 
    133 	return db._CreateIndex()
    134 }
    135 
    136 // Utility to pass all lines of file (path) to fn(line).
    137 // Stops on EOF or fn returns false.
    138 func FileLines(path string, fn func(string) bool) error {
    139 	f, err := os.Open(path)
    140 	if err != nil {
    141 		if os.IsNotExist(err) {
    142 			return nil
    143 		}
    144 		return err
    145 	}
    146 	defer f.Close()
    147 	return f_lines(f, fn)
    148 }
    149 
    150 // Internal function to implement FileLines. Works with
    151 // file by *File object.
    152 func f_lines(f *os.File, fn func(string) bool) error {
    153 	reader := bufio.NewReader(f)
    154 	for {
    155 		line, err := reader.ReadString('\n')
    156 		if err != nil && err != io.EOF {
    157 			return err
    158 		}
    159 		line = strings.TrimSuffix(line, "\n")
    160 		if err == io.EOF {
    161 			break
    162 		}
    163 		if !fn(line) {
    164 			break
    165 		}
    166 	}
    167 	// scanner := bufio.NewScanner(f)
    168 	// scanner.Buffer(make([]byte, MaxMsgLen), MaxMsgLen)
    169 
    170 	// for scanner.Scan() {
    171 	// 	line := scanner.Text()
    172 	// 	if !fn(line) {
    173 	// 		break
    174 	// 	}
    175 	// }
    176 	return nil
    177 }
    178 
    179 // Internal function of CreateIndex.
    180 // Does not lock!
    181 func (db *DB) _CreateIndex() error {
    182 	fidx, err := os.OpenFile(db.IndexPath(), os.O_CREATE|os.O_WRONLY, 0644)
    183 	if err != nil {
    184 		return err
    185 	}
    186 	defer fidx.Close()
    187 	var off int64
    188 	return FileLines(db.BundlePath(), func(line string) bool {
    189 		msg, _ := DecodeBundle(line)
    190 		if msg == nil {
    191 			off += int64(len(line) + 1)
    192 			return true
    193 		}
    194 		repto, _ := msg.Tag("repto")
    195 		ioff := off
    196 		if v, _ := msg.Tag("access"); v == "blacklist" {
    197 			ioff = -1
    198 		}
    199 
    200 		_, err := fidx.WriteString(fmt.Sprintf("%s:%s:%d:%s:%s:%s\n",
    201 			msg.MsgId, msg.Echo, ioff, msg.To, msg.From, repto))
    202 		if err != nil {
    203 			// FIXME: handle this error
    204 			panic(err)
    205 		}
    206 		off += int64(len(line) + 1)
    207 		return true
    208 	})
    209 }
    210 
    211 // Internal function. Create and open new index.
    212 func (db *DB) _ReopenIndex() (*os.File, error) {
    213 	err := db._CreateIndex()
    214 	if err != nil {
    215 		return nil, err
    216 	}
    217 	file, err := os.Open(db.IndexPath())
    218 	if err != nil {
    219 		return nil, err
    220 	}
    221 	return file, nil
    222 }
    223 
    224 // Loads index. If index doesent exists, create and load it.
    225 // If index was changed, reread tail.
    226 // This function does lock.
    227 func (db *DB) LoadIndex() error {
    228 	db.IdxSync.Lock()
    229 	defer db.IdxSync.Unlock()
    230 	var Idx Index
    231 	file, err := os.Open(db.IndexPath())
    232 	if err != nil {
    233 		db.Idx = Idx
    234 		if os.IsNotExist(err) {
    235 			file, err = db._ReopenIndex()
    236 			if err != nil {
    237 				Error.Printf("Can not seek to end of index")
    238 				return err
    239 			}
    240 		} else {
    241 			Error.Printf("Can not open index: %s", err)
    242 			return err
    243 		}
    244 	}
    245 	defer file.Close()
    246 
    247 	info, err := file.Stat()
    248 	if err != nil {
    249 		Error.Printf("Can not stat index: %s", err)
    250 		return err
    251 	}
    252 	fsize := info.Size()
    253 
    254 	if db.Idx.Hash != nil { // already loaded
    255 		if fsize > db.Idx.FileSize {
    256 			Trace.Printf("Refreshing index file...%d>%d", fsize, db.Idx.FileSize)
    257 			if _, err := file.Seek(db.Idx.FileSize, 0); err != nil {
    258 				Error.Printf("Can not seek index: %s", err)
    259 				return err
    260 			}
    261 			Idx = db.Idx
    262 			// rebuild topics
    263 			for _, v := range Idx.Hash {
    264 				v.Topic = ""
    265 			}
    266 		} else if info.Size() < db.Idx.FileSize {
    267 			Info.Printf("Index file truncated, rebuild inndex...")
    268 			file, err = db._ReopenIndex()
    269 			if err != nil {
    270 				Error.Printf("Can not reopen index: %s", err)
    271 				return err
    272 			}
    273 			defer file.Close()
    274 		} else {
    275 			return nil
    276 		}
    277 	} else {
    278 		Idx.Hash = make(map[string]*MsgInfo)
    279 	}
    280 	var err2 error
    281 	linenr := 0
    282 	nr := len(Idx.List)
    283 	err = f_lines(file, func(line string) bool {
    284 		linenr++
    285 		info := strings.Split(line, ":")
    286 		if len(info) < 6 {
    287 			err2 = errors.New("Wrong format on line:" + fmt.Sprintf("%d", linenr))
    288 			return false
    289 		}
    290 		mi := MsgInfo{Num: nr, Id: info[0], Echo: info[1], To: info[3], From: info[4]}
    291 		if _, err := fmt.Sscanf(info[2], "%d", &mi.Off); err != nil {
    292 			err2 = errors.New("Wrong offset on line: " + fmt.Sprintf("%d", linenr))
    293 			return false
    294 		}
    295 		mi.Repto = info[5]
    296 		if mm, ok := Idx.Hash[mi.Id]; !ok { // new msg
    297 			Idx.List = append(Idx.List, mi.Id)
    298 			nr++
    299 		} else {
    300 			mi.Num = mm.Num
    301 		}
    302 		Idx.Hash[mi.Id] = &mi
    303 		// Trace.Printf("Adding %s to index", mi.Id)
    304 		return true
    305 	})
    306 	if err != nil {
    307 		Error.Printf("Can not parse index: %s", err)
    308 		return err
    309 	}
    310 	if err2 != nil {
    311 		Error.Printf("Can not parse index: %s", err2)
    312 		return err2
    313 	}
    314 	Idx.FileSize = fsize
    315 	db.Idx = Idx
    316 	return nil
    317 }
    318 
    319 // Internal function to Lookup message in loaded index.
    320 // If idx parameter is true, load and created index.
    321 // Returns MsgInfo pointer or nil if fails.
    322 // Does lock!
    323 // bl: look in blacklisted messages too?
    324 func (db *DB) _Lookup(Id string, bl bool, idx bool) *MsgInfo {
    325 	if idx {
    326 		if err := db.LoadIndex(); err != nil {
    327 			return nil
    328 		}
    329 	}
    330 	db.IdxSync.RLock()
    331 	defer db.IdxSync.RUnlock()
    332 	info, ok := db.Idx.Hash[Id]
    333 	if !ok || (!bl && info.Off < 0) {
    334 		return nil
    335 	}
    336 	return info
    337 }
    338 
    339 // Lookup variant, but without locking.
    340 // Useful if caller do locking logic himself.
    341 func (db *DB) LookupFast(Id string, bl bool) *MsgInfo {
    342 	if Id == "" {
    343 		return nil
    344 	}
    345 	return db._Lookup(Id, bl, false)
    346 }
    347 
    348 // Lookup message in index.
    349 // Do not search blacklisted messages.
    350 // Creates/load index if needed.
    351 // Returns MsgInfo pointer.
    352 // Does lock!
    353 func (db *DB) Lookup(Id string) *MsgInfo {
    354 	db.Sync.RLock()
    355 	defer db.Sync.RUnlock()
    356 	db.Lock()
    357 	defer db.Unlock()
    358 
    359 	return db._Lookup(Id, false, true)
    360 }
    361 
    362 // Same as Lookup, but checks in blacklisted messages too
    363 func (db *DB) Exists(Id string) *MsgInfo {
    364 	db.Sync.RLock()
    365 	defer db.Sync.RUnlock()
    366 	db.Lock()
    367 	defer db.Unlock()
    368 
    369 	return db._Lookup(Id, true, true)
    370 }
    371 
    372 // Lookup messages in index.
    373 // Gets: slice of message ids to get.
    374 // Returns slice of MsgInfo pointers.
    375 // Does lock!
    376 func (db *DB) LookupIDS(Ids []string) []*MsgInfo {
    377 	var info []*MsgInfo
    378 	db.Sync.RLock()
    379 	defer db.Sync.RUnlock()
    380 	db.Lock()
    381 	defer db.Unlock()
    382 	for _, id := range Ids {
    383 		i := db._Lookup(id, false, true)
    384 		if i != nil {
    385 			info = append(info, i)
    386 		}
    387 	}
    388 	return info
    389 }
    390 
    391 // Internal function. Gets bundle by message id.
    392 // If idx is true: load/create index.
    393 // Returns: msgid:base64 bundle.
    394 // Does not lock!
    395 func (db *DB) _GetBundle(Id string, idx bool) (string, *MsgInfo) {
    396 	info := db._Lookup(Id, false, idx)
    397 	if info == nil {
    398 		Info.Printf("Can not find bundle: %s\n", Id)
    399 		return "", nil
    400 	}
    401 	f, err := os.Open(db.BundlePath())
    402 	if err != nil {
    403 		Error.Printf("Can not open DB: %s\n", err)
    404 		return "", nil
    405 	}
    406 	defer f.Close()
    407 	_, err = f.Seek(info.Off, 0)
    408 	if err != nil {
    409 		Error.Printf("Can not seek DB: %s\n", err)
    410 		return "", nil
    411 	}
    412 	var bundle string
    413 	err = f_lines(f, func(line string) bool {
    414 		bundle = line
    415 		return false
    416 	})
    417 	if err != nil {
    418 		Error.Printf("Can not get %s from DB: %s\n", Id, err)
    419 		return "", nil
    420 	}
    421 	return bundle, info
    422 }
    423 
    424 // Get bundle line by message id from db.
    425 // Does lock!
    426 // Loads/create index if needed.
    427 func (db *DB) GetBundle(Id string) string {
    428 	db.Sync.RLock()
    429 	defer db.Sync.RUnlock()
    430 	db.Lock()
    431 	defer db.Unlock()
    432 
    433 	b, _ := db._GetBundle(Id, true)
    434 	return b
    435 }
    436 
    437 func (db *DB) GetBundleInfo(Id string) (string, *MsgInfo) {
    438 	db.Sync.RLock()
    439 	defer db.Sync.RUnlock()
    440 	db.Lock()
    441 	defer db.Unlock()
    442 
    443 	return db._GetBundle(Id, true)
    444 }
    445 
    446 // Get decoded message from db by message id.
    447 // Does lock. Loads/create index if needed.
    448 func (db *DB) Get(Id string) *Msg {
    449 	bundle := db.GetBundle(Id)
    450 	if bundle == "" {
    451 		return nil
    452 	}
    453 	m, err := DecodeBundle(bundle)
    454 	if err != nil {
    455 		Error.Printf("Can not decode bundle on get: %s\n", Id)
    456 	}
    457 	return m
    458 }
    459 
    460 // Fast varian (w/o locking) of Get.
    461 // Get decoded message from db by message id.
    462 // Does NOT lock! Loads/create index if needed.
    463 func (db *DB) GetFast(Id string) *Msg {
    464 	bundle, _ := db._GetBundle(Id, false)
    465 	if bundle == "" {
    466 		return nil
    467 	}
    468 	m, err := DecodeBundle(bundle)
    469 	if err != nil {
    470 		Error.Printf("Can not decode bundle on get: %s\n", Id)
    471 	}
    472 	return m
    473 }
    474 
    475 // Query used to make queries to Index
    476 // If some field of: Echo, Repto, From, To is not ""
    477 // fields will be matched with MsgInfo entry (logical AND).
    478 // If Match function is not nil, this function will be used for matching.
    479 // Blacklisted: search in blacklisted messages if true.
    480 // User: authorized access to private areas.
    481 // Start & Lim: slice of query. For example: -1, 1 -- get last message in db. 0, 1 -- first.
    482 type Query struct {
    483 	Echo        string
    484 	Repto       string
    485 	From        string
    486 	To          string
    487 	Start       int
    488 	Lim         int
    489 	Blacklisted bool
    490 	User        User
    491 	Match       func(mi *MsgInfo, q Query) bool
    492 }
    493 
    494 // utility function to add string in front of slice
    495 func prependStr(x []string, y string) []string {
    496 	x = append(x, "")
    497 	copy(x[1:], x)
    498 	x[0] = y
    499 	return x
    500 }
    501 
    502 // Check if message is private
    503 func (db *DB) Access(info *MsgInfo, user *User) bool {
    504 	if IsPrivate(info.Echo) {
    505 		if user.Name == "" {
    506 			return false
    507 		}
    508 		if info.To != "All" && info.From != user.Name && info.To != user.Name {
    509 			return false
    510 		}
    511 	}
    512 	return true
    513 }
    514 
    515 // Default match function for queries.
    516 func (db *DB) Match(info *MsgInfo, r Query) bool {
    517 	if r.Blacklisted {
    518 		if info.Off >= 0 {
    519 			return false
    520 		}
    521 	} else if info.Off < 0 {
    522 		return false
    523 	}
    524 	if r.Echo != "" && r.Echo != info.Echo {
    525 		return false
    526 	}
    527 	if r.Repto == "!" {
    528 		if info.Repto != "" {
    529 			return false
    530 		}
    531 	} else if r.Repto != "" && r.Repto != info.Repto {
    532 		return false
    533 	}
    534 	if r.To != "" && r.To != info.To {
    535 		return false
    536 	}
    537 	if r.From != "" && r.From != info.From {
    538 		return false
    539 	}
    540 	if !db.Access(info, &r.User) {
    541 		return false
    542 	}
    543 	if r.Match != nil {
    544 		return r.Match(info, r)
    545 	}
    546 	return true
    547 }
    548 
    549 // Used to get information about echoarea
    550 // Count: number of messages
    551 // Topics: number of topics
    552 // Last: last MsgInfo
    553 // Msg: last message pointer
    554 type Echo struct {
    555 	Name   string
    556 	Count  int
    557 	Topics int
    558 	Last   *MsgInfo
    559 	Msg    *Msg
    560 }
    561 
    562 // Make query and select Echoes
    563 // Returns: slice of pointers to Echo.
    564 // names: if not empty, lookup only in theese echoareas
    565 // Does lock.
    566 // Load/create index if needed.
    567 // Echoes sorted by date of last messages.
    568 func (db *DB) Echoes(names []string, q Query) []*Echo {
    569 	db.Sync.Lock()
    570 	defer db.Sync.Unlock()
    571 	db.Lock()
    572 	defer db.Unlock()
    573 	var list []*Echo
    574 
    575 	filter := make(map[string]bool)
    576 	for _, n := range names {
    577 		filter[n] = true
    578 	}
    579 
    580 	if err := db.LoadIndex(); err != nil {
    581 		return list
    582 	}
    583 
    584 	db.IdxSync.RLock()
    585 	defer db.IdxSync.RUnlock()
    586 
    587 	hash := make(map[string]Echo)
    588 	size := len(db.Idx.List)
    589 	for i := 0; i < size; i++ {
    590 		id := db.Idx.List[i]
    591 		info := db.Idx.Hash[id]
    592 		if info.Off < 0 {
    593 			continue
    594 		}
    595 		if !db.Match(info, q) {
    596 			continue
    597 		}
    598 		e := info.Echo
    599 		if names != nil { // filter?
    600 			if _, ok := filter[e]; !ok {
    601 				continue
    602 			}
    603 		}
    604 		if v, ok := hash[e]; ok {
    605 			if info.Repto == "" {
    606 				v.Topics++
    607 			}
    608 			v.Count++
    609 			v.Last = info
    610 			hash[e] = v
    611 		} else {
    612 			v := Echo{Name: e, Count: 1, Last: info}
    613 			if info.Repto == "" {
    614 				v.Topics = 1
    615 			}
    616 			hash[e] = v
    617 		}
    618 	}
    619 	if names != nil {
    620 		for _, v := range names {
    621 			n := hash[v]
    622 			list = append(list, &n)
    623 		}
    624 	} else {
    625 		for _, v := range hash {
    626 			n := v
    627 			list = append(list, &n)
    628 		}
    629 	}
    630 	for _, v := range list {
    631 		v.Msg = db.GetFast(v.Last.Id)
    632 		if v.Msg == nil {
    633 			Error.Printf("Can not get echo last message: %s", v.Last.Id)
    634 			v.Msg = &Msg{}
    635 		}
    636 	}
    637 	sort.SliceStable(list, func(i, j int) bool {
    638 		return list[i].Msg.Date > list[j].Msg.Date
    639 	})
    640 	return list
    641 }
    642 
    643 // Make query and retuen ids as slice of strings.
    644 // Does lock. Can create/load index if needed.
    645 // r: request, see Query
    646 func (db *DB) SelectIDS(r Query) []string {
    647 	var Resp []string
    648 	db.Sync.Lock()
    649 	defer db.Sync.Unlock()
    650 	db.Lock()
    651 	defer db.Unlock()
    652 
    653 	if err := db.LoadIndex(); err != nil {
    654 		return Resp
    655 	}
    656 	size := len(db.Idx.List)
    657 	if size == 0 {
    658 		return Resp
    659 	}
    660 
    661 	db.IdxSync.RLock()
    662 	defer db.IdxSync.RUnlock()
    663 
    664 	if r.Start < 0 {
    665 		start := 0
    666 		for i := size - 1; i >= 0; i-- {
    667 			id := db.Idx.List[i]
    668 			if db.Match(db.Idx.Hash[id], r) {
    669 				Resp = prependStr(Resp, id)
    670 				start -= 1
    671 				if start == r.Start {
    672 					break
    673 				}
    674 			}
    675 		}
    676 		if r.Lim > 0 && len(Resp) > r.Lim {
    677 			Resp = Resp[0:r.Lim]
    678 		}
    679 		return Resp
    680 	}
    681 	found := 0
    682 	for i := r.Start; i < size; i++ {
    683 		id := db.Idx.List[i]
    684 		if db.Match(db.Idx.Hash[id], r) {
    685 			Resp = append(Resp, id)
    686 			found += 1
    687 			if r.Lim > 0 && found == r.Lim {
    688 				break
    689 			}
    690 		}
    691 	}
    692 	return Resp
    693 }
    694 
    695 // Internal function. Get slice of MsgInfo pointers
    696 // and create information about topics.
    697 // Information returns in form of: [topicid][]ids
    698 // topic id is the msg id of most old parent in echo
    699 // ids - is the messages in this topic
    700 func (db *DB) GetTopics(mi []*MsgInfo) map[string][]string {
    701 	db.Sync.RLock()
    702 	defer db.Sync.RUnlock()
    703 
    704 	intopic := make(map[string]string)
    705 	topics := make(map[string][]string)
    706 
    707 	if err := db.LoadIndex(); err != nil {
    708 		// FIXME: handle this error
    709 		panic(err)
    710 	}
    711 	for _, m := range mi {
    712 		if _, ok := intopic[m.Id]; ok {
    713 			continue
    714 		}
    715 		var l []*MsgInfo
    716 		if m.Topic != "" { // fast path
    717 			if len(topics[m.Topic]) == 0 {
    718 				topics[m.Topic] = append(topics[m.Topic], m.Topic)
    719 			}
    720 			if m.Id != m.Topic {
    721 				topics[m.Topic] = append(topics[m.Topic], m.Id)
    722 				intopic[m.Id] = m.Topic
    723 			}
    724 			continue
    725 		}
    726 		for p := m; p != nil; p = db.LookupFast(p.Repto, false) {
    727 			if p.Repto == p.Id || p.Topic == "visited" { // loop?
    728 				p.Topic = ""
    729 				break
    730 			}
    731 			if m.Echo != p.Echo {
    732 				continue
    733 			}
    734 			if p.Topic == "" {
    735 				p.Topic = "visited"
    736 			}
    737 			l = append(l, p)
    738 		}
    739 		if len(l) == 0 {
    740 			continue
    741 		}
    742 		t := l[len(l)-1]
    743 		if len(topics[t.Id]) == 0 {
    744 			topics[t.Id] = append(topics[t.Id], t.Id)
    745 		}
    746 		sort.SliceStable(l, func(i int, j int) bool {
    747 			return l[i].Num < l[j].Num
    748 		})
    749 		for _, i := range l {
    750 			if i.Id == t.Id {
    751 				i.Topic = t.Id
    752 				continue
    753 			}
    754 			if _, ok := intopic[i.Id]; ok {
    755 				continue
    756 			}
    757 			topics[t.Id] = append(topics[t.Id], i.Id)
    758 			intopic[i.Id] = t.Id
    759 			i.Topic = t.Id
    760 		}
    761 	}
    762 
    763 	return topics
    764 }
    765 
    766 // Store decoded message in database
    767 // If message exists, returns error
    768 func (db *DB) Store(m *Msg) error {
    769 	if r, _ := m.Tag("repto"); r == "" { //  new one!
    770 		if m.Echo == "std.hugeping" && m.Addr != "ping,1" {
    771 			return errors.New("Access denied")
    772 		}
    773 	}
    774 	return db._Store(m, false)
    775 }
    776 
    777 // Store decoded message in database
    778 // even it is exists. So, it's like Edit operation.
    779 // While index loaded, it got last version of message data.
    780 func (db *DB) Edit(m *Msg) error {
    781 	return db._Store(m, true)
    782 }
    783 
    784 // Blacklist decoded message.
    785 // Blacklisting is adding special tag: access/blacklist and Edit operation
    786 // to store it in DB. While loading index, blacklisted messages
    787 // are marked by negative Off field (-1).
    788 func (db *DB) Blacklist(m *Msg) error {
    789 	if err := m.Tags.Add("access/blacklist"); err != nil {
    790 		return err
    791 	}
    792 	return db.Edit(m)
    793 
    794 	//db.Sync.Lock()
    795 	//defer db.Sync.Unlock()
    796 	//db.Lock()
    797 	//defer db.Unlock()
    798 	// repto, _ := m.Tag("repto")
    799 	// if repto != "" {
    800 	// 	repto = ":" + repto
    801 	// }
    802 	// rec := fmt.Sprintf("%s:%s:%d%s", m.MsgId, m.Echo, -1, repto)
    803 	// if err := append_file(db.IndexPath(), rec); err != nil {
    804 	// 	return err
    805 	// }
    806 	// return nil
    807 }
    808 
    809 // Internal function used by Store. See Store comment.
    810 func (db *DB) _Store(m *Msg, edit bool) error {
    811 	db.Sync.Lock()
    812 	defer db.Sync.Unlock()
    813 	db.Lock()
    814 	defer db.Unlock()
    815 	repto, _ := m.Tag("repto")
    816 	if err := db.LoadIndex(); err != nil {
    817 		return err
    818 	}
    819 
    820 	db.IdxSync.RLock()
    821 	defer db.IdxSync.RUnlock()
    822 
    823 	if _, ok := db.Idx.Hash[m.MsgId]; ok && !edit { // exist and not edit
    824 		return errors.New("Already exists")
    825 	}
    826 	//	if repto != "" {
    827 	//		if _, ok := db.Idx.Hash[repto]; !ok { // repto is absent, we should avoid loops!
    828 	//			return errors.New("Wrong repto: " + repto)
    829 	//		}
    830 	//	}
    831 	fi, err := os.Stat(db.BundlePath())
    832 	var off int64
    833 	if err == nil {
    834 		off = fi.Size()
    835 	}
    836 	if v, _ := m.Tag("access"); v == "blacklist" {
    837 		off = -1
    838 	}
    839 	if err := append_file(db.BundlePath(), m.Encode()); err != nil {
    840 		return err
    841 	}
    842 
    843 	rec := fmt.Sprintf("%s:%s:%d:%s:%s:%s", m.MsgId, m.Echo, off, m.To, m.From, repto)
    844 	if err := append_file(db.IndexPath(), rec); err != nil {
    845 		return err
    846 	}
    847 	return nil
    848 }
    849 
    850 // Opens DB and returns pointer to DB object.
    851 // path is the path to db. By default it is ./db
    852 // Index will be named as path + ".idx"
    853 func OpenDB(path string) *DB {
    854 	var db DB
    855 	db.Path = path
    856 	info, err := os.Stat(filepath.Dir(path))
    857 	if err != nil || !info.IsDir() {
    858 		return nil
    859 	}
    860 	db.Name = "node"
    861 	//	db.Idx = make(map[string]Index)
    862 	return &db
    863 }
    864 
    865 // User entry in points.txt db
    866 // User with Id == 1 is superuser.
    867 // Tags: custom information (like avatars :) in Tags format
    868 type User struct {
    869 	Id     int32
    870 	Name   string
    871 	Mail   string
    872 	Secret string
    873 	Token  string
    874 	Tags   Tags
    875 }
    876 
    877 // User database.
    878 // ModTime: last modification time of points.txt to detect DB changes.
    879 // FileSize - size of points.txt to detect DB changes.
    880 // Names: holds User structure by user name
    881 // ById: holds user name by user id
    882 // Tokens: holds user name by user token
    883 // List: holds user names as list
    884 type UDB struct {
    885 	Path     string
    886 	Names    map[string]User
    887 	ById     map[int32]string
    888 	Tokens   map[string]string
    889 	List     []string
    890 	Sync     sync.RWMutex
    891 	ModTime  int64
    892 	FileSize int64
    893 }
    894 
    895 // Check username if it is valid
    896 func IsUsername(u string) bool {
    897 	return !strings.ContainsAny(u, ":\n\r\t/") &&
    898 		!strings.HasPrefix(u, " ") &&
    899 		!strings.HasSuffix(u, " ") &&
    900 		len(u) <= 16 && len(u) > 2
    901 }
    902 
    903 // Check password if it is valid to be used
    904 func IsPassword(u string) bool {
    905 	return len(u) >= 1
    906 }
    907 
    908 // Make secret from string.
    909 // String is something like id + user + password
    910 func MakeSecret(msg string) string {
    911 	h := sha256.Sum256([]byte(msg))
    912 	hash, err := bcrypt.GenerateFromPassword(h[:], bcrypt.DefaultCost)
    913 	if err != nil {
    914 		Error.Printf("bcrypt problem")
    915 		return "bcryptProblem"
    916 	}
    917 	return string(hash)
    918 }
    919 
    920 // Return token for username or "" if no such user
    921 func (db *UDB) Token(User string) string {
    922 	db.Sync.RLock()
    923 	defer db.Sync.RUnlock()
    924 	ui, ok := db.Names[User]
    925 	if !ok {
    926 		return ""
    927 	}
    928 	return ui.Token
    929 }
    930 
    931 // Returns true if user+password is valid
    932 func (db *UDB) Auth(User string, Passwd string) bool {
    933 	db.Sync.RLock()
    934 	defer db.Sync.RUnlock()
    935 	ui, ok := db.Names[User]
    936 	if !ok {
    937 		return false
    938 	}
    939 	locked, _ := ui.Tags.Get("locked")
    940 	if locked == "" {
    941 		Error.Printf("Can't get locked tag (%s)", User)
    942 		return false
    943 	}
    944 	if locked != "no" {
    945 		Info.Printf("Login locked user attempt (%s)", User)
    946 		return false
    947 	}
    948 	secret := sha256.Sum256([]byte(User+Passwd))
    949 	return bcrypt.CompareHashAndPassword([]byte(ui.Secret), secret[:]) == nil
    950 }
    951 
    952 // Returns true if token is valid
    953 func (db *UDB) Access(Token string) bool {
    954 	db.Sync.RLock()
    955 	defer db.Sync.RUnlock()
    956 	_, ok := db.Tokens[Token]
    957 	return ok
    958 }
    959 
    960 // Return username for given Token
    961 func (db *UDB) Name(Token string) string {
    962 	db.Sync.RLock()
    963 	defer db.Sync.RUnlock()
    964 	name, ok := db.Tokens[Token]
    965 	if ok {
    966 		return name
    967 	}
    968 	Error.Printf("No user for Token: %s", Token)
    969 	return ""
    970 }
    971 
    972 // Return User pointer for given Token
    973 func (db *UDB) UserInfo(Token string) *User {
    974 	db.Sync.RLock()
    975 	defer db.Sync.RUnlock()
    976 	name, ok := db.Tokens[Token]
    977 	if ok {
    978 		v := db.Names[name]
    979 		return &v
    980 	}
    981 	Error.Printf("No user for token: %s", Token)
    982 	return nil
    983 }
    984 
    985 // Return User pointer for user id
    986 func (db *UDB) UserInfoId(id int32) *User {
    987 	db.Sync.RLock()
    988 	defer db.Sync.RUnlock()
    989 	name, ok := db.ById[id]
    990 	if ok {
    991 		v := db.Names[name]
    992 		return &v
    993 	}
    994 	Error.Printf("No user for Id: %d", id)
    995 	return nil
    996 }
    997 
    998 // Return User pointer for given user name
    999 func (db *UDB) UserInfoName(name string) *User {
   1000 	db.Sync.RLock()
   1001 	defer db.Sync.RUnlock()
   1002 	v, ok := db.Names[name]
   1003 	if ok {
   1004 		return &v
   1005 	}
   1006 	return nil
   1007 }
   1008 
   1009 // Return user id for given token
   1010 func (db *UDB) Id(token string) int32 {
   1011 	db.Sync.RLock()
   1012 	defer db.Sync.RUnlock()
   1013 	name, ok := db.Tokens[token]
   1014 	if ok {
   1015 		v, ok := db.Names[name]
   1016 		if !ok {
   1017 			return -1
   1018 		}
   1019 		return v.Id
   1020 	}
   1021 	Error.Printf("No user for token: %s", token)
   1022 	return -1
   1023 }
   1024 
   1025 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])?)*$")
   1026 
   1027 // Add (register) user in database
   1028 // Mail is optional but someday it will be used in registration process
   1029 func (db *UDB) Add(Name string, Mail string, Passwd string) error {
   1030 	db.Sync.Lock()
   1031 	defer db.Sync.Unlock()
   1032 
   1033 	if _, ok := db.Names[Name]; ok {
   1034 		return errors.New("User already exists")
   1035 	}
   1036 	if !IsUsername(Name) {
   1037 		return errors.New("Wrong username")
   1038 	}
   1039 	if !IsPassword(Passwd) {
   1040 		return errors.New("Bad password")
   1041 	}
   1042 	if !emailRegex.MatchString(Mail) {
   1043 		return errors.New("Wrong email")
   1044 	}
   1045 	var id int32 = 0
   1046 	for _, v := range db.Names {
   1047 		if v.Id > id {
   1048 			id = v.Id
   1049 		}
   1050 	}
   1051 	id++
   1052 	var u User
   1053 	u.Name = Name
   1054 	u.Mail = Mail
   1055 	u.Secret = MakeSecret(Name + Passwd)
   1056 	u.Tags = NewTags("locked/yes")
   1057 	db.List = append(db.List, u.Name)
   1058 	if err := append_file(db.Path, fmt.Sprintf("%d:%s:%s:%s:%s",
   1059 		id, Name, Mail, u.Secret, u.Tags.String())); err != nil {
   1060 		return err
   1061 	}
   1062 	return nil
   1063 }
   1064 
   1065 // Open user database and return pointer to UDB object
   1066 func OpenUsers(path string) *UDB {
   1067 	var db UDB
   1068 	db.Path = path
   1069 	return &db
   1070 }
   1071 
   1072 // Change (replace) information about user.
   1073 // Gets pointer to User object and write it in DB, replacing old information.
   1074 // Works atomically using rename.
   1075 func (db *UDB) Edit(u *User) error {
   1076 	db.Sync.Lock()
   1077 	defer db.Sync.Unlock()
   1078 	if _, ok := db.Names[u.Name]; !ok {
   1079 		return errors.New("No such user")
   1080 	}
   1081 	db.Names[u.Name] = *u // new version
   1082 	os.Remove(db.Path + ".tmp")
   1083 	for _, Name := range db.List {
   1084 		ui := db.Names[Name]
   1085 		if err := append_file(db.Path+".tmp", fmt.Sprintf("%d:%s:%s:%s:%s",
   1086 			ui.Id, Name, ui.Mail, ui.Secret, ui.Tags.String())); err != nil {
   1087 			return err
   1088 		}
   1089 	}
   1090 	if err := os.Rename(db.Path+".tmp", db.Path); err != nil {
   1091 		return err
   1092 	}
   1093 	db.ModTime = 0 // force to reload
   1094 	return nil
   1095 }
   1096 
   1097 // Load user information in memory if it is needed (ModTime or FileSize changed).
   1098 // So, it is safe to call it on every request.
   1099 func (db *UDB) LoadUsers() error {
   1100 	db.Sync.Lock()
   1101 	defer db.Sync.Unlock()
   1102 	var mtime int64
   1103 	var fsize int64
   1104 	file, err := os.Open(db.Path)
   1105 	if err == nil {
   1106 		info, err := file.Stat()
   1107 		file.Close()
   1108 		if err != nil {
   1109 			Error.Printf("Can not stat %s file: %s", db.Path, err)
   1110 			return err
   1111 		}
   1112 		mtime = info.ModTime().Unix()
   1113 		fsize = info.Size()
   1114 	} else if os.IsNotExist(err) {
   1115 		mtime = 0
   1116 	} else {
   1117 		Error.Printf("Can not open %s file: %s", db.Path, err)
   1118 		return err
   1119 	}
   1120 	if db.ModTime == mtime && db.FileSize == fsize {
   1121 		return nil
   1122 	}
   1123 	// save old tokens before reload
   1124 	old_tokens := make(map[string]string)
   1125 	for otoken, oname := range db.Tokens {
   1126         old_tokens[oname] = otoken
   1127     }
   1128 	db.Names = make(map[string]User)
   1129 	db.Tokens = make(map[string]string)
   1130 	db.ById = make(map[int32]string)
   1131 	db.List = nil
   1132 	err = FileLines(db.Path, func(line string) bool {
   1133 		a := strings.Split(line, ":")
   1134 		if len(a) < 4 {
   1135 			Error.Printf("Wrong entry in user DB: %s", line)
   1136 			return true
   1137 		}
   1138 		var u User
   1139 		var err error
   1140 		_, err = fmt.Sscanf(a[0], "%d", &u.Id)
   1141 		if err != nil {
   1142 			Error.Printf("Wrong ID in user DB: %s", a[0])
   1143 			return true
   1144 		}
   1145 		u.Name = a[1]
   1146 		u.Mail = a[2]
   1147 		u.Secret = a[3]
   1148 		u.Tags = NewTags(a[4])
   1149 		//restore token if user onlocked
   1150 		token, ok := old_tokens[u.Name]
   1151 		if ok {
   1152 			locked, _ := u.Tags.Get("locked")
   1153 			if locked != "" && locked == "no" {
   1154 				u.Token = token
   1155 				db.Tokens[token] = u.Name
   1156 			}
   1157 		}
   1158 		db.ById[u.Id] = u.Name
   1159 		db.Names[u.Name] = u
   1160 		db.List = append(db.List, u.Name)
   1161 		return true
   1162 	})
   1163 	if err != nil {
   1164 		Error.Printf("Can not read user DB: %s", err)
   1165 		return errors.New(err.Error())
   1166 	}
   1167 	db.ModTime = mtime
   1168 	db.FileSize = fsize
   1169 	return nil
   1170 }
   1171 
   1172 // Echo database entry
   1173 // Holds echo descriptions in Info hash.
   1174 // List - names of echoareas.
   1175 type EDB struct {
   1176 	Info map[string]string
   1177 	List []string
   1178 	Path string
   1179 }
   1180 
   1181 // Check if echo is exists in echo database
   1182 func (db *EDB) Allowed(name string) bool {
   1183 	if len(db.List) == 0 {
   1184 		return true
   1185 	}
   1186 	if _, ok := db.Info[name]; ok {
   1187 		return true
   1188 	}
   1189 	return false
   1190 }
   1191 
   1192 // Loads echolist database and returns pointer to EDB
   1193 // Supposed to be called only once
   1194 func LoadEcholist(path string) *EDB {
   1195 	var db EDB
   1196 	db.Path = path
   1197 	db.Info = make(map[string]string)
   1198 
   1199 	err := FileLines(path, func(line string) bool {
   1200 		a := strings.SplitN(line, ":", 3)
   1201 		if len(a) < 2 {
   1202 			Error.Printf("Wrong entry in echo DB: %s", line)
   1203 			return true
   1204 		}
   1205 		db.Info[a[0]] = a[2]
   1206 		db.List = append(db.List, a[0])
   1207 		return true
   1208 	})
   1209 	if err != nil {
   1210 		Error.Printf("Can not read echo DB: %s", err)
   1211 		return nil
   1212 	}
   1213 	return &db
   1214 }