diff --git a/common/counters/forums.go b/common/counters/forums.go
index f30d50aa..cfa0acee 100644
--- a/common/counters/forums.go
+++ b/common/counters/forums.go
@@ -48,18 +48,18 @@ func (co *DefaultForumViewCounter) Tick() error {
l.Lock()
delete(m, fid)
l.Unlock()
- err := co.insertChunk(count, fid)
- if err != nil {
- return errors.Wrap(errors.WithStack(err),"forum counter")
+ e := co.insertChunk(count, fid)
+ if e != nil {
+ return errors.Wrap(errors.WithStack(e),"forum counter")
}
l.RLock()
}
l.RUnlock()
return nil
}
- err := cLoop(&co.oddLock,co.oddMap)
- if err != nil {
- return err
+ e := cLoop(&co.oddLock,co.oddMap)
+ if e != nil {
+ return e
}
return cLoop(&co.evenLock,co.evenMap)
}
@@ -69,8 +69,8 @@ func (co *DefaultForumViewCounter) insertChunk(count, forum int) error {
return nil
}
c.DebugLogf("Inserting a vchunk with a count of %d for forum %d", count, forum)
- _, err := co.insert.Exec(count, forum)
- return err
+ _, e := co.insert.Exec(count, forum)
+ return e
}
func (co *DefaultForumViewCounter) Bump(fid int) {
diff --git a/common/counters/langs.go b/common/counters/langs.go
index a22cf6f3..514e40a9 100644
--- a/common/counters/langs.go
+++ b/common/counters/langs.go
@@ -127,9 +127,9 @@ func NewDefaultLangViewCounter(acc *qgen.Accumulator) (*DefaultLangViewCounter,
func (co *DefaultLangViewCounter) Tick() error {
for id := 0; id < len(co.buckets); id++ {
count := atomic.SwapInt64(&co.buckets[id], 0)
- err := co.insertChunk(count, id) // TODO: Bulk insert for speed?
- if err != nil {
- return errors.Wrap(errors.WithStack(err), "langview counter")
+ e := co.insertChunk(count, id) // TODO: Bulk insert for speed?
+ if e != nil {
+ return errors.Wrap(errors.WithStack(e), "langview counter")
}
}
return nil
@@ -144,8 +144,8 @@ func (co *DefaultLangViewCounter) insertChunk(count int64, id int) error {
langCode = "none"
}
c.DebugLogf("Inserting a vchunk with a count of %d for lang %s (%d)", count, langCode, id)
- _, err := co.insert.Exec(count, langCode)
- return err
+ _, e := co.insert.Exec(count, langCode)
+ return e
}
func (co *DefaultLangViewCounter) Bump(langCode string) (validCode bool) {
diff --git a/common/counters/memory.go b/common/counters/memory.go
index 11bb5307..d88689c8 100644
--- a/common/counters/memory.go
+++ b/common/counters/memory.go
@@ -54,7 +54,7 @@ func NewMemoryCounter(acc *qgen.Accumulator) (*DefaultMemoryCounter, error) {
return co, acc.FirstError()
}
-func (co *DefaultMemoryCounter) Tick() (err error) {
+func (co *DefaultMemoryCounter) Tick() (e error) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
var rTotMem, rTotCount, rStackMem, rStackCount, rHeapMem, rHeapCount uint64
@@ -83,9 +83,9 @@ func (co *DefaultMemoryCounter) Tick() (err error) {
avgHeap = (rHeapMem + m.HeapAlloc) / (rHeapCount + 1)
c.DebugLogf("Inserting a memchunk with a value of %d - %d - %d", avgMem, avgStack, avgHeap)
- _, err = co.insert.Exec(avgMem, avgStack, avgHeap)
- if err != nil {
- return errors.Wrap(errors.WithStack(err), "mem counter")
+ _, e = co.insert.Exec(avgMem, avgStack, avgHeap)
+ if e != nil {
+ return errors.Wrap(errors.WithStack(e), "mem counter")
}
return nil
}
diff --git a/common/counters/topics.go b/common/counters/topics.go
index c00db38e..740745eb 100644
--- a/common/counters/topics.go
+++ b/common/counters/topics.go
@@ -30,7 +30,7 @@ func NewTopicCounter() (*DefaultTopicCounter, error) {
return co, acc.FirstError()
}
-func (co *DefaultTopicCounter) Tick() (err error) {
+func (co *DefaultTopicCounter) Tick() (e error) {
oldBucket := co.currentBucket
var nextBucket int64 // 0
if co.currentBucket == 0 {
@@ -42,9 +42,9 @@ func (co *DefaultTopicCounter) Tick() (err error) {
previousViewChunk := co.buckets[oldBucket]
atomic.AddInt64(&co.buckets[oldBucket], -previousViewChunk)
- err = co.insertChunk(previousViewChunk)
- if err != nil {
- return errors.Wrap(errors.WithStack(err), "topics counter")
+ e = co.insertChunk(previousViewChunk)
+ if e != nil {
+ return errors.Wrap(errors.WithStack(e), "topics counter")
}
return nil
}
@@ -58,6 +58,6 @@ func (co *DefaultTopicCounter) insertChunk(count int64) error {
return nil
}
c.DebugLogf("Inserting a topicchunk with a count of %d", count)
- _, err := co.insert.Exec(count)
- return err
+ _, e := co.insert.Exec(count)
+ return e
}
diff --git a/common/meta/meta_store.go b/common/meta/meta_store.go
index e1a54ccc..77ef38d2 100644
--- a/common/meta/meta_store.go
+++ b/common/meta/meta_store.go
@@ -10,6 +10,8 @@ import (
type MetaStore interface {
Get(name string) (val string, err error)
Set(name, val string) error
+ SetInt(name string, val int) error
+ SetInt64(name string, val int64) error
}
type DefaultMetaStore struct {
@@ -19,28 +21,41 @@ type DefaultMetaStore struct {
}
func NewDefaultMetaStore(acc *qgen.Accumulator) (*DefaultMetaStore, error) {
+ t := "meta"
m := &DefaultMetaStore{
- get: acc.Select("meta").Columns("value").Where("name = ?").Prepare(),
- set: acc.Update("meta").Set("value = ?").Where("name = ?").Prepare(),
- add: acc.Insert("meta").Columns("name,value").Fields("?,''").Prepare(),
+ get: acc.Select(t).Columns("value").Where("name=?").Prepare(),
+ set: acc.Update(t).Set("value=?").Where("name=?").Prepare(),
+ add: acc.Insert(t).Columns("name,value").Fields("?,''").Prepare(),
}
return m, acc.FirstError()
}
-func (s *DefaultMetaStore) Get(name string) (val string, err error) {
- err = s.get.QueryRow(name).Scan(&val)
- return val, err
+func (s *DefaultMetaStore) Get(name string) (val string, e error) {
+ e = s.get.QueryRow(name).Scan(&val)
+ return val, e
}
// TODO: Use timestamped rows as a more robust method of ensuring data integrity
-func (s *DefaultMetaStore) Set(name, val string) error {
- _, err := s.Get(name)
- if err == sql.ErrNoRows {
- _, err := s.add.Exec(name)
- if err != nil {
- return err
+func (s *DefaultMetaStore) setVal(name string, val interface{}) error {
+ _, e := s.Get(name)
+ if e == sql.ErrNoRows {
+ _, e := s.add.Exec(name)
+ if e != nil {
+ return e
}
}
- _, err = s.set.Exec(val, name)
- return err
+ _, e = s.set.Exec(val, name)
+ return e
+}
+
+func (s *DefaultMetaStore) Set(name, val string) error {
+ return s.setVal(name, val)
+}
+
+func (s *DefaultMetaStore) SetInt(name string, val int) error {
+ return s.setVal(name, val)
+}
+
+func (s *DefaultMetaStore) SetInt64(name string, val int64) error {
+ return s.setVal(name, val)
}
diff --git a/common/misc_logs.go b/common/misc_logs.go
index 97ecb06b..90f1de39 100644
--- a/common/misc_logs.go
+++ b/common/misc_logs.go
@@ -31,7 +31,7 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
rl := "registration_logs"
regLogStmts = RegLogStmts{
- update: acc.Update(rl).Set("username=?,email=?,failureReason=?,success=?").Where("rlid=?").Prepare(),
+ update: acc.Update(rl).Set("username=?,email=?,failureReason=?,success=?,doneAt=?").Where("rlid=?").Prepare(),
create: acc.Insert(rl).Columns("username,email,failureReason,success,ipaddress,doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
}
return acc.FirstError()
@@ -40,29 +40,32 @@ func init() {
// TODO: Reload this item in the store, probably doesn't matter right now, but it might when we start caching this stuff in memory
// ! Retroactive updates of date are not permitted for integrity reasons
+// TODO: Do we even use this anymore or can we just make the logs immutable (except for deletes) for simplicity sake?
func (l *RegLogItem) Commit() error {
- _, err := regLogStmts.update.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.ID)
- return err
+ _, e := regLogStmts.update.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.DoneAt, l.ID)
+ return e
}
-func (l *RegLogItem) Create() (id int, err error) {
- res, err := regLogStmts.create.Exec(l.Username, l.Email, l.FailureReason, l.Success, l.IP)
- if err != nil {
- return 0, err
- }
- id64, err := res.LastInsertId()
- l.ID = int(id64)
- return l.ID, err
+func (l *RegLogItem) Create() (id int, e error) {
+ id, e = Createf(regLogStmts.create, l.Username, l.Email, l.FailureReason, l.Success, l.IP)
+ l.ID = id
+ return l.ID, e
}
type RegLogStore interface {
Count() (count int)
GetOffset(offset, perPage int) (logs []RegLogItem, err error)
+ Purge() error
+
+ DeleteOlderThanDays(days int) error
}
type SQLRegLogStore struct {
count *sql.Stmt
getOffset *sql.Stmt
+ purge *sql.Stmt
+
+ deleteOlderThanDays *sql.Stmt
}
func NewRegLogStore(acc *qgen.Accumulator) (*SQLRegLogStore, error) {
@@ -70,30 +73,29 @@ func NewRegLogStore(acc *qgen.Accumulator) (*SQLRegLogStore, error) {
return &SQLRegLogStore{
count: acc.Count(rl).Prepare(),
getOffset: acc.Select(rl).Columns("rlid,username,email,failureReason,success,ipaddress,doneAt").Orderby("doneAt DESC").Limit("?,?").Prepare(),
+ purge: acc.Purge(rl),
+
+ deleteOlderThanDays: acc.Delete(rl).DateOlderThanQ("doneAt", "day").Prepare(),
}, acc.FirstError()
}
func (s *SQLRegLogStore) Count() (count int) {
- err := s.count.QueryRow().Scan(&count)
- if err != nil {
- LogError(err)
- }
- return count
+ return Count(s.count)
}
-func (s *SQLRegLogStore) GetOffset(offset, perPage int) (logs []RegLogItem, err error) {
- rows, err := s.getOffset.Query(offset, perPage)
- if err != nil {
- return logs, err
+func (s *SQLRegLogStore) GetOffset(offset, perPage int) (logs []RegLogItem, e error) {
+ rows, e := s.getOffset.Query(offset, perPage)
+ if e != nil {
+ return logs, e
}
defer rows.Close()
for rows.Next() {
var l RegLogItem
var doneAt time.Time
- err := rows.Scan(&l.ID, &l.Username, &l.Email, &l.FailureReason, &l.Success, &l.IP, &doneAt)
- if err != nil {
- return logs, err
+ e := rows.Scan(&l.ID, &l.Username, &l.Email, &l.FailureReason, &l.Success, &l.IP, &doneAt)
+ if e != nil {
+ return logs, e
}
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, l)
@@ -101,6 +103,17 @@ func (s *SQLRegLogStore) GetOffset(offset, perPage int) (logs []RegLogItem, err
return logs, rows.Err()
}
+func (s *SQLRegLogStore) DeleteOlderThanDays(days int) error {
+ _, e := s.deleteOlderThanDays.Exec(days)
+ return e
+}
+
+// Delete all registration logs
+func (s *SQLRegLogStore) Purge() error {
+ _, e := s.purge.Exec()
+ return e
+}
+
type LoginLogItem struct {
ID int
UID int
@@ -120,7 +133,7 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
ll := "login_logs"
loginLogStmts = LoginLogStmts{
- update: acc.Update(ll).Set("uid=?,success=?").Where("lid=?").Prepare(),
+ update: acc.Update(ll).Set("uid=?,success=?,doneAt=?").Where("lid=?").Prepare(),
create: acc.Insert(ll).Columns("uid,success,ipaddress,doneAt").Fields("?,?,?,UTC_TIMESTAMP()").Prepare(),
}
return acc.FirstError()
@@ -130,30 +143,36 @@ func init() {
// TODO: Reload this item in the store, probably doesn't matter right now, but it might when we start caching this stuff in memory
// ! Retroactive updates of date are not permitted for integrity reasons
func (l *LoginLogItem) Commit() error {
- _, err := loginLogStmts.update.Exec(l.UID, l.Success, l.ID)
- return err
+ _, e := loginLogStmts.update.Exec(l.UID, l.Success, l.DoneAt, l.ID)
+ return e
}
-func (l *LoginLogItem) Create() (id int, err error) {
- res, err := loginLogStmts.create.Exec(l.UID, l.Success, l.IP)
- if err != nil {
- return 0, err
+func (l *LoginLogItem) Create() (id int, e error) {
+ res, e := loginLogStmts.create.Exec(l.UID, l.Success, l.IP)
+ if e != nil {
+ return 0, e
}
- id64, err := res.LastInsertId()
+ id64, e := res.LastInsertId()
l.ID = int(id64)
- return l.ID, err
+ return l.ID, e
}
type LoginLogStore interface {
Count() (count int)
CountUser(uid int) (count int)
GetOffset(uid, offset, perPage int) (logs []LoginLogItem, err error)
+ Purge() error
+
+ DeleteOlderThanDays(days int) error
}
type SQLLoginLogStore struct {
count *sql.Stmt
countForUser *sql.Stmt
getOffsetByUser *sql.Stmt
+ purge *sql.Stmt
+
+ deleteOlderThanDays *sql.Stmt
}
func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
@@ -162,41 +181,47 @@ func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
count: acc.Count(ll).Prepare(),
countForUser: acc.Count(ll).Where("uid=?").Prepare(),
getOffsetByUser: acc.Select(ll).Columns("lid,success,ipaddress,doneAt").Where("uid=?").Orderby("doneAt DESC").Limit("?,?").Prepare(),
+ purge: acc.Purge(ll),
+
+ deleteOlderThanDays: acc.Delete(ll).DateOlderThanQ("doneAt", "day").Prepare(),
}, acc.FirstError()
}
func (s *SQLLoginLogStore) Count() (count int) {
- err := s.count.QueryRow().Scan(&count)
- if err != nil {
- LogError(err)
- }
- return count
+ return Count(s.count)
}
func (s *SQLLoginLogStore) CountUser(uid int) (count int) {
- err := s.countForUser.QueryRow(uid).Scan(&count)
- if err != nil {
- LogError(err)
- }
- return count
+ return Countf(s.countForUser, uid)
}
-func (s *SQLLoginLogStore) GetOffset(uid, offset, perPage int) (logs []LoginLogItem, err error) {
- rows, err := s.getOffsetByUser.Query(uid, offset, perPage)
- if err != nil {
- return logs, err
+func (s *SQLLoginLogStore) GetOffset(uid, offset, perPage int) (logs []LoginLogItem, e error) {
+ rows, e := s.getOffsetByUser.Query(uid, offset, perPage)
+ if e != nil {
+ return logs, e
}
defer rows.Close()
for rows.Next() {
l := LoginLogItem{UID: uid}
var doneAt time.Time
- err := rows.Scan(&l.ID, &l.Success, &l.IP, &doneAt)
- if err != nil {
- return logs, err
+ e := rows.Scan(&l.ID, &l.Success, &l.IP, &doneAt)
+ if e != nil {
+ return logs, e
}
l.DoneAt = doneAt.Format("2006-01-02 15:04:05")
logs = append(logs, l)
}
return logs, rows.Err()
}
+
+func (s *SQLLoginLogStore) DeleteOlderThanDays(days int) error {
+ _, e := s.deleteOlderThanDays.Exec(days)
+ return e
+}
+
+// Delete all login logs
+func (s *SQLLoginLogStore) Purge() error {
+ _, e := s.purge.Exec()
+ return e
+}
diff --git a/common/null_topic_cache.go b/common/null_topic_cache.go
index fc76c9b5..706b0673 100644
--- a/common/null_topic_cache.go
+++ b/common/null_topic_cache.go
@@ -31,6 +31,9 @@ func (c *NullTopicCache) AddUnsafe(_ *Topic) error {
func (c *NullTopicCache) Remove(id int) error {
return nil
}
+func (c *NullTopicCache) RemoveMany(ids []int) error {
+ return nil
+}
func (c *NullTopicCache) RemoveUnsafe(id int) error {
return nil
}
diff --git a/common/permissions.go b/common/permissions.go
index 1e14d61d..9045cebf 100644
--- a/common/permissions.go
+++ b/common/permissions.go
@@ -191,23 +191,23 @@ func RebuildGroupPermissions(g *Group) error {
log.Print("Reloading a group")
// TODO: Avoid re-initting this all the time
- getGroupPerms, err := qgen.Builder.SimpleSelect("users_groups", "permissions", "gid=?", "", "")
- if err != nil {
- return err
+ getGroupPerms, e := qgen.Builder.SimpleSelect("users_groups", "permissions", "gid=?", "", "")
+ if e != nil {
+ return e
}
defer getGroupPerms.Close()
- err = getGroupPerms.QueryRow(g.ID).Scan(&permstr)
- if err != nil {
- return err
+ e = getGroupPerms.QueryRow(g.ID).Scan(&permstr)
+ if e != nil {
+ return e
}
tmpPerms := Perms{
//ExtData: make(map[string]bool),
}
- err = json.Unmarshal(permstr, &tmpPerms)
- if err != nil {
- return err
+ e = json.Unmarshal(permstr, &tmpPerms)
+ if e != nil {
+ return e
}
g.Perms = tmpPerms
return nil
diff --git a/common/poll_store.go b/common/poll_store.go
index a90772e1..5cdb8504 100644
--- a/common/poll_store.go
+++ b/common/poll_store.go
@@ -26,6 +26,7 @@ type Pollable interface {
type PollStore interface {
Get(id int) (*Poll, error)
Exists(id int) bool
+ ClearIPs() error
Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error)
Reload(id int) error
//Count() int
@@ -43,6 +44,8 @@ type DefaultPollStore struct {
createPollOption *sql.Stmt
delete *sql.Stmt
//count *sql.Stmt
+
+ clearIPs *sql.Stmt
}
func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
@@ -54,11 +57,13 @@ func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
p := "polls"
return &DefaultPollStore{
cache: cache,
- get: acc.Select(p).Columns("parentID, parentTable, type, options, votes").Where("pollID=?").Prepare(),
- exists: acc.Select(p).Columns("pollID").Where("pollID=?").Prepare(),
- createPoll: acc.Insert(p).Columns("parentID, parentTable, type, options").Fields("?,?,?,?").Prepare(),
+ get: acc.Select(p).Columns("parentID,parentTable,type,options,votes").Where("pollID=?").Stmt(),
+ exists: acc.Select(p).Columns("pollID").Where("pollID=?").Stmt(),
+ createPoll: acc.Insert(p).Columns("parentID,parentTable,type,options").Fields("?,?,?,?").Prepare(),
createPollOption: acc.Insert("polls_options").Columns("pollID,option,votes").Fields("?,?,0").Prepare(),
//count: acc.SimpleCount(p, "", ""),
+
+ clearIPs: acc.Update("polls_votes").Set("ip=''").Where("ip!=''").Stmt(),
}, acc.FirstError()
}
@@ -144,8 +149,7 @@ func (s *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err error)
if idCount > len(list) {
var sidList string
for _, id := range ids {
- _, ok := list[id]
- if !ok {
+ if _, ok := list[id]; !ok {
sidList += strconv.Itoa(id) + ","
}
}
@@ -172,18 +176,16 @@ func (s *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err error)
func (s *DefaultPollStore) Reload(id int) error {
p := &Poll{ID: id}
var optionTxt []byte
- err := s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount)
- if err != nil {
- s.cache.Remove(id)
- return err
+ e := s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount)
+ if e != nil {
+ _ = s.cache.Remove(id)
+ return e
}
-
- err = json.Unmarshal(optionTxt, &p.Options)
- if err != nil {
- s.cache.Remove(id)
- return err
+ e = json.Unmarshal(optionTxt, &p.Options)
+ if e != nil {
+ _ = s.cache.Remove(id)
+ return e
}
-
p.QuickOptions = s.unpackOptionsMap(p.Options)
_ = s.cache.Set(p)
return nil
@@ -197,6 +199,11 @@ func (s *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOpt
return opts
}
+func (s *DefaultPollStore) ClearIPs() error {
+ _, e := s.clearIPs.Exec()
+ return e
+}
+
// TODO: Use a transaction for this
func (s *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions map[int]string) (id int, e error) {
// TODO: Move the option names into the polls_options table and get rid of this json sludge?
diff --git a/common/topic_cache.go b/common/topic_cache.go
index 29643844..5379d75d 100644
--- a/common/topic_cache.go
+++ b/common/topic_cache.go
@@ -15,6 +15,7 @@ type TopicCache interface {
AddUnsafe(item *Topic) error
Remove(id int) error
RemoveUnsafe(id int) error
+ RemoveMany(ids []int) error
Flush()
Length() int
SetCapacity(cap int)
@@ -70,16 +71,16 @@ func (s *MemoryTopicCache) BulkGet(ids []int) (list []*Topic) {
}
// Set overwrites the value of a topic in the cache, whether it's present or not. May return a capacity overflow error.
-func (s *MemoryTopicCache) Set(item *Topic) error {
+func (s *MemoryTopicCache) Set(it *Topic) error {
s.Lock()
- _, ok := s.items[item.ID]
+ _, ok := s.items[it.ID]
if ok {
- s.items[item.ID] = item
+ s.items[it.ID] = it
} else if int(s.length) >= s.capacity {
s.Unlock()
return ErrStoreCapacityOverflow
} else {
- s.items[item.ID] = item
+ s.items[it.ID] = it
atomic.AddInt64(&s.length, 1)
}
s.Unlock()
@@ -112,9 +113,9 @@ func (s *MemoryTopicCache) AddUnsafe(item *Topic) error {
// Remove removes a topic from the cache by ID, if they exist. Returns ErrNoRows if no items exist.
func (s *MemoryTopicCache) Remove(id int) error {
+ var ok bool
s.Lock()
- _, ok := s.items[id]
- if !ok {
+ if _, ok = s.items[id]; !ok {
s.Unlock()
return ErrNoRows
}
@@ -124,10 +125,24 @@ func (s *MemoryTopicCache) Remove(id int) error {
return nil
}
+func (s *MemoryTopicCache) RemoveMany(ids []int) error {
+ var n int64
+ var ok bool
+ s.Lock()
+ for _, id := range ids {
+ if _, ok = s.items[id]; ok {
+ delete(s.items, id)
+ n++
+ }
+ }
+ atomic.AddInt64(&s.length, -n)
+ s.Unlock()
+ return nil
+}
+
// RemoveUnsafe is the unsafe version of Remove. THIS METHOD IS NOT THREAD-SAFE.
func (s *MemoryTopicCache) RemoveUnsafe(id int) error {
- _, ok := s.items[id]
- if !ok {
+ if _, ok := s.items[id]; !ok {
return ErrNoRows
}
delete(s.items, id)
diff --git a/common/topic_store.go b/common/topic_store.go
index 700864f2..b9cf5444 100644
--- a/common/topic_store.go
+++ b/common/topic_store.go
@@ -40,6 +40,9 @@ type TopicStore interface {
CountMegaUser(uid int) int
CountBigUser(uid int) int
+ ClearIPs() error
+ LockMany(tids []int) error
+
SetCache(cache TopicCache)
GetCache() TopicCache
}
@@ -53,6 +56,9 @@ type DefaultTopicStore struct {
countUser *sql.Stmt
countWordUser *sql.Stmt
create *sql.Stmt
+
+ clearIPs *sql.Stmt
+ lockTen *sql.Stmt
}
// NewDefaultTopicStore gives you a new instance of DefaultTopicStore
@@ -64,22 +70,25 @@ func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
t := "topics"
return &DefaultTopicStore{
cache: cache,
- get: acc.Select(t).Columns("title,content,createdBy,createdAt,lastReplyBy,lastReplyAt,lastReplyID,is_closed,sticky,parentID,ip,views,postCount,likeCount,attachCount,poll,data").Where("tid=?").Prepare(),
- exists: acc.Exists(t, "tid").Prepare(),
- count: acc.Count(t).Prepare(),
- countUser: acc.Count(t).Where("createdBy=?").Prepare(),
- countWordUser: acc.Count(t).Where("createdBy=? AND words>=?").Prepare(),
- create: acc.Insert(t).Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ip, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
+ get: acc.Select(t).Columns("title,content,createdBy,createdAt,lastReplyBy,lastReplyAt,lastReplyID,is_closed,sticky,parentID,ip,views,postCount,likeCount,attachCount,poll,data").Where("tid=?").Stmt(),
+ exists: acc.Exists(t, "tid").Stmt(),
+ count: acc.Count(t).Stmt(),
+ countUser: acc.Count(t).Where("createdBy=?").Stmt(),
+ countWordUser: acc.Count(t).Where("createdBy=? AND words>=?").Stmt(),
+ create: acc.Insert(t).Columns("parentID,title,content,parsed_content,createdAt,lastReplyAt,lastReplyBy,ip,words,createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
+
+ clearIPs: acc.Update(t).Set("ip=''").Where("ip!=''").Stmt(),
+ lockTen: acc.Update(t).Set("is_closed=1").Where("tid IN(" + inqbuild2(10) + ")").Stmt(),
}, acc.FirstError()
}
func (s *DefaultTopicStore) DirtyGet(id int) *Topic {
- t, err := s.cache.Get(id)
- if err == nil {
+ t, e := s.cache.Get(id)
+ if e == nil {
return t
}
- t, err = s.BypassGet(id)
- if err == nil {
+ t, e = s.BypassGet(id)
+ if e == nil {
_ = s.cache.Set(t)
return t
}
@@ -87,26 +96,26 @@ func (s *DefaultTopicStore) DirtyGet(id int) *Topic {
}
// TODO: Log weird cache errors?
-func (s *DefaultTopicStore) Get(id int) (t *Topic, err error) {
- t, err = s.cache.Get(id)
- if err == nil {
+func (s *DefaultTopicStore) Get(id int) (t *Topic, e error) {
+ t, e = s.cache.Get(id)
+ if e == nil {
return t, nil
}
- t, err = s.BypassGet(id)
- if err == nil {
+ t, e = s.BypassGet(id)
+ if e == nil {
_ = s.cache.Set(t)
}
- return t, err
+ return t, e
}
// BypassGet will always bypass the cache and pull the topic directly from the database
func (s *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
t := &Topic{ID: id}
- err := s.get.QueryRow(id).Scan(&t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
- if err == nil {
+ e := s.get.QueryRow(id).Scan(&t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
+ if e == nil {
t.Link = BuildTopicURL(NameToSlug(t.Title), id)
}
- return t, err
+ return t, e
}
/*func (s *DefaultTopicStore) GetByUser(uid int) (list map[int]*Topic, err error) {
@@ -119,7 +128,7 @@ func (s *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
}*/
// TODO: Avoid duplicating much of this logic from user_store.go
-func (s *DefaultTopicStore) BulkGetMap(ids []int) (list map[int]*Topic, err error) {
+func (s *DefaultTopicStore) BulkGetMap(ids []int) (list map[int]*Topic, e error) {
idCount := len(ids)
list = make(map[int]*Topic)
if idCount == 0 {
@@ -143,68 +152,115 @@ func (s *DefaultTopicStore) BulkGetMap(ids []int) (list map[int]*Topic, err erro
if len(ids) == 0 {
return list, nil
} else if len(ids) == 1 {
- topic, err := s.Get(ids[0])
- if err != nil {
- return list, err
+ t, e := s.Get(ids[0])
+ if e != nil {
+ return list, e
}
- list[topic.ID] = topic
+ list[t.ID] = t
return list, nil
}
idList, q := inqbuild(ids)
- rows, err := qgen.NewAcc().Select("topics").Columns("tid,title,content,createdBy,createdAt,lastReplyBy,lastReplyAt,lastReplyID,is_closed,sticky,parentID,ip,views,postCount,likeCount,attachCount,poll,data").Where("tid IN(" + q + ")").Query(idList...)
- if err != nil {
- return list, err
+ rows, e := qgen.NewAcc().Select("topics").Columns("tid,title,content,createdBy,createdAt,lastReplyBy,lastReplyAt,lastReplyID,is_closed,sticky,parentID,ip,views,postCount,likeCount,attachCount,poll,data").Where("tid IN(" + q + ")").Query(idList...)
+ if e != nil {
+ return list, e
}
defer rows.Close()
for rows.Next() {
t := &Topic{}
- err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
- if err != nil {
- return list, err
+ e := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.CreatedAt, &t.LastReplyBy, &t.LastReplyAt, &t.LastReplyID, &t.IsClosed, &t.Sticky, &t.ParentID, &t.IP, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data)
+ if e != nil {
+ return list, e
}
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
- s.cache.Set(t)
+ _ = s.cache.Set(t)
list[t.ID] = t
}
- if err = rows.Err(); err != nil {
- return list, err
+ if e = rows.Err(); e != nil {
+ return list, e
}
// Did we miss any topics?
if idCount > len(list) {
var sidList string
- for _, id := range ids {
- _, ok := list[id]
- if !ok {
- sidList += strconv.Itoa(id) + ","
+ for i, id := range ids {
+ if _, ok := list[id]; !ok {
+ if i == 0 {
+ sidList += strconv.Itoa(id)
+ } else {
+ sidList += ","+strconv.Itoa(id)
+ }
}
}
if sidList != "" {
- sidList = sidList[0 : len(sidList)-1]
- err = errors.New("Unable to find topics with the following IDs: " + sidList)
+ e = errors.New("Unable to find topics with the following IDs: " + sidList)
}
}
- return list, err
+ return list, e
}
func (s *DefaultTopicStore) Reload(id int) error {
- topic, err := s.BypassGet(id)
- if err == nil {
- _ = s.cache.Set(topic)
+ t, e := s.BypassGet(id)
+ if e == nil {
+ _ = s.cache.Set(t)
} else {
_ = s.cache.Remove(id)
}
TopicListThaw.Thaw()
- return err
+ return e
}
func (s *DefaultTopicStore) Exists(id int) bool {
return s.exists.QueryRow(id).Scan(&id) == nil
}
+func (s *DefaultTopicStore) ClearIPs() error {
+ _, e := s.clearIPs.Exec()
+ return e
+}
+
+func (s *DefaultTopicStore) LockMany(tids []int) (e error) {
+ tc, i := Topics.GetCache(), 0
+ singles := func() error {
+ for ; i < len(tids); i++ {
+ _, e := topicStmts.lock.Exec(tids[i])
+ if e != nil {
+ return e
+ }
+ }
+ return nil
+ }
+
+ if len(tids) < 10 {
+ if e = singles(); e != nil {
+ return e
+ }
+ if tc != nil {
+ _ = tc.RemoveMany(tids)
+ }
+ TopicListThaw.Thaw()
+ return nil
+ }
+
+ for ; (i + 10) < len(tids); i += 10 {
+ _, e := s.lockTen.Exec(tids[i], tids[i+1], tids[i+2], tids[i+3], tids[i+4], tids[i+5], tids[i+6], tids[i+7], tids[i+8], tids[i+9])
+ if e != nil {
+ return e
+ }
+ }
+
+ if e = singles(); e != nil {
+ return e
+ }
+ if tc != nil {
+ _ = tc.RemoveMany(tids)
+ }
+ TopicListThaw.Thaw()
+ return nil
+}
+
func (s *DefaultTopicStore) Create(fid int, name, content string, uid int, ip string) (tid int, err error) {
if name == "" {
return 0, ErrNoTitle
diff --git a/gen_router.go b/gen_router.go
index 0c26930f..16ff5c92 100644
--- a/gen_router.go
+++ b/gen_router.go
@@ -924,13 +924,6 @@ func (r *GenRouter) SuspiciousRequest(req *http.Request, pre string) {
// TODO: SetDefaultPath
// TODO: GetDefaultPath
func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- // HTTP/1.1 hanging conn fix
- /*if req.ProtoMajor == 1 && c.Dev.ExpFix1 {
- defer func() {
- //io.Copy(ioutil.Discard, req.Body)
- req.Body.Close()
- }()
- }*/
malformedRequest := func(typ int) {
w.WriteHeader(200) // 400
w.Write([]byte(""))
diff --git a/parser_test.go b/parser_test.go
index 8102d3a1..03e38482 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -248,6 +248,7 @@ func TestParser(t *testing.T) {
l.Add("ddd ddd //a ", "ddd ddd a ")
l.Add("https://"+url, ""+url+"")
l.Add("https://t", "t")
+ l.Add("https://en.wikipedia.org/wiki/First_they_came_...", "en.wikipedia.org/wiki/First_they_came_...") // this frequently fails in some chat clients, we should make sure that doesn't happen here
l.Add("http://"+url, ""+url+"")
l.Add("#http://"+url, "#http://"+url)
l.Add("@http://"+url, "[Invalid Profile]ttp://"+url)
diff --git a/router_gen/main.go b/router_gen/main.go
index 5bbf2c6d..d37618d2 100644
--- a/router_gen/main.go
+++ b/router_gen/main.go
@@ -507,13 +507,6 @@ func (r *GenRouter) SuspiciousRequest(req *http.Request, pre string) {
// TODO: SetDefaultPath
// TODO: GetDefaultPath
func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- // HTTP/1.1 hanging conn fix
- /*if req.ProtoMajor == 1 && c.Dev.ExpFix1 {
- defer func() {
- //io.Copy(ioutil.Discard, req.Body)
- req.Body.Close()
- }()
- }*/
malformedRequest := func(typ int) {
w.WriteHeader(200) // 400
w.Write([]byte(""))