github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/forum_perms_store.go (about)

     1  package common
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"sync"
     7  
     8  	qgen "github.com/Azareal/Gosora/query_gen"
     9  )
    10  
    11  var FPStore ForumPermsStore
    12  
    13  type ForumPermsStore interface {
    14  	Init() error
    15  	GetAllMap() (bigMap map[int]map[int]*ForumPerms)
    16  	Get(fid, gid int) (fp *ForumPerms, err error)
    17  	GetCopy(fid, gid int) (fp ForumPerms, err error)
    18  	ReloadAll() error
    19  	Reload(id int) error
    20  }
    21  
    22  type ForumPermsCache interface {
    23  }
    24  
    25  type MemoryForumPermsStore struct {
    26  	getByForum      *sql.Stmt
    27  	getByForumGroup *sql.Stmt
    28  
    29  	evenForums map[int]map[int]*ForumPerms
    30  	oddForums  map[int]map[int]*ForumPerms // [fid][gid]*ForumPerms
    31  	evenLock   sync.RWMutex
    32  	oddLock    sync.RWMutex
    33  }
    34  
    35  func NewMemoryForumPermsStore() (*MemoryForumPermsStore, error) {
    36  	acc := qgen.NewAcc()
    37  	fp := "forums_permissions"
    38  	return &MemoryForumPermsStore{
    39  		getByForum:      acc.Select(fp).Columns("gid,permissions").Where("fid=?").Orderby("gid ASC").Prepare(),
    40  		getByForumGroup: acc.Select(fp).Columns("permissions").Where("fid=? AND gid=?").Prepare(),
    41  
    42  		evenForums: make(map[int]map[int]*ForumPerms),
    43  		oddForums:  make(map[int]map[int]*ForumPerms),
    44  	}, acc.FirstError()
    45  }
    46  
    47  func (s *MemoryForumPermsStore) Init() error {
    48  	DebugLog("Initialising the forum perms store")
    49  	return s.ReloadAll()
    50  }
    51  
    52  // TODO: Optimise this?
    53  func (s *MemoryForumPermsStore) ReloadAll() error {
    54  	DebugLog("Reloading the forum perms")
    55  	fids, e := Forums.GetAllIDs()
    56  	if e != nil {
    57  		return e
    58  	}
    59  	for _, fid := range fids {
    60  		if e := s.reload(fid); e != nil {
    61  			return e
    62  		}
    63  	}
    64  	if e := s.recalcCanSeeAll(); e != nil {
    65  		return e
    66  	}
    67  	TopicListThaw.Thaw()
    68  	return nil
    69  }
    70  
    71  func (s *MemoryForumPermsStore) parseForumPerm(perms []byte) (pperms *ForumPerms, e error) {
    72  	DebugDetail("perms: ", string(perms))
    73  	pperms = BlankForumPerms()
    74  	e = json.Unmarshal(perms, &pperms)
    75  	pperms.ExtData = make(map[string]bool)
    76  	pperms.Overrides = true
    77  	return pperms, e
    78  }
    79  
    80  func (s *MemoryForumPermsStore) Reload(fid int) error {
    81  	e := s.reload(fid)
    82  	if e != nil {
    83  		return e
    84  	}
    85  	if e = s.recalcCanSeeAll(); e != nil {
    86  		return e
    87  	}
    88  	TopicListThaw.Thaw()
    89  	return nil
    90  }
    91  
    92  // TODO: Need a more thread-safe way of doing this. Possibly with sync.Map?
    93  func (s *MemoryForumPermsStore) reload(fid int) error {
    94  	DebugLogf("Reloading the forum permissions for forum #%d", fid)
    95  	rows, err := s.getByForum.Query(fid)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	defer rows.Close()
   100  
   101  	forumPerms := make(map[int]*ForumPerms)
   102  	for rows.Next() {
   103  		var gid int
   104  		var perms []byte
   105  		err := rows.Scan(&gid, &perms)
   106  		if err != nil {
   107  			return err
   108  		}
   109  
   110  		DebugLog("gid:", gid)
   111  		DebugLogf("perms: %+v\n", perms)
   112  		pperms, err := s.parseForumPerm(perms)
   113  		if err != nil {
   114  			return err
   115  		}
   116  		DebugLogf("pperms: %+v\n", pperms)
   117  		forumPerms[gid] = pperms
   118  	}
   119  	DebugLogf("forumPerms: %+v\n", forumPerms)
   120  
   121  	if fid%2 == 0 {
   122  		s.evenLock.Lock()
   123  		s.evenForums[fid] = forumPerms
   124  		s.evenLock.Unlock()
   125  	} else {
   126  		s.oddLock.Lock()
   127  		s.oddForums[fid] = forumPerms
   128  		s.oddLock.Unlock()
   129  	}
   130  	return nil
   131  }
   132  
   133  func (s *MemoryForumPermsStore) recalcCanSeeAll() error {
   134  	groups, err := Groups.GetAll()
   135  	if err != nil {
   136  		return err
   137  	}
   138  	fids, err := Forums.GetAllIDs()
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	gc, ok := Groups.(GroupCache)
   144  	if !ok {
   145  		TopicListThaw.Thaw()
   146  		return nil
   147  	}
   148  
   149  	// A separate loop to avoid contending on the odd-even locks as much
   150  	fForumPerms := make(map[int]map[int]*ForumPerms)
   151  	for _, fid := range fids {
   152  		var forumPerms map[int]*ForumPerms
   153  		var ok bool
   154  		if fid%2 == 0 {
   155  			s.evenLock.RLock()
   156  			forumPerms, ok = s.evenForums[fid]
   157  			s.evenLock.RUnlock()
   158  		} else {
   159  			s.oddLock.RLock()
   160  			forumPerms, ok = s.oddForums[fid]
   161  			s.oddLock.RUnlock()
   162  		}
   163  		if ok {
   164  			fForumPerms[fid] = forumPerms
   165  		}
   166  	}
   167  
   168  	// TODO: Can we recalculate CanSee without calculating every other forum?
   169  	for _, g := range groups {
   170  		DebugLogf("Updating the forum permissions for Group #%d", g.ID)
   171  		canSee := []int{}
   172  		for _, fid := range fids {
   173  			DebugDetailf("Forum #%+v\n", fid)
   174  			forumPerms, ok := fForumPerms[fid]
   175  			if !ok {
   176  				continue
   177  			}
   178  			fp, ok := forumPerms[g.ID]
   179  			if !ok {
   180  				if g.Perms.ViewTopic {
   181  					canSee = append(canSee, fid)
   182  				}
   183  				continue
   184  			}
   185  
   186  			if fp.Overrides {
   187  				if fp.ViewTopic {
   188  					canSee = append(canSee, fid)
   189  				}
   190  			} else if g.Perms.ViewTopic {
   191  				canSee = append(canSee, fid)
   192  			}
   193  			//DebugDetail("g.ID: ", g.ID)
   194  			DebugDetailf("forumPerm: %+v\n", fp)
   195  			DebugDetail("canSee: ", canSee)
   196  		}
   197  		DebugDetailf("canSee (length %d): %+v \n", len(canSee), canSee)
   198  		gc.SetCanSee(g.ID, canSee)
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  // ! Throughput on this might be bad due to the excessive locking
   205  func (s *MemoryForumPermsStore) GetAllMap() (bigMap map[int]map[int]*ForumPerms) {
   206  	bigMap = make(map[int]map[int]*ForumPerms)
   207  	s.evenLock.RLock()
   208  	for fid, subMap := range s.evenForums {
   209  		bigMap[fid] = subMap
   210  	}
   211  	s.evenLock.RUnlock()
   212  	s.oddLock.RLock()
   213  	for fid, subMap := range s.oddForums {
   214  		bigMap[fid] = subMap
   215  	}
   216  	s.oddLock.RUnlock()
   217  	return bigMap
   218  }
   219  
   220  // TODO: Add a hook here and have plugin_guilds use it
   221  // TODO: Check if the forum exists?
   222  // TODO: Fix the races
   223  // TODO: Return BlankForumPerms() when the forum permission set doesn't exist?
   224  func (s *MemoryForumPermsStore) Get(fid, gid int) (fp *ForumPerms, err error) {
   225  	var fmap map[int]*ForumPerms
   226  	var ok bool
   227  	if fid%2 == 0 {
   228  		s.evenLock.RLock()
   229  		fmap, ok = s.evenForums[fid]
   230  		s.evenLock.RUnlock()
   231  	} else {
   232  		s.oddLock.RLock()
   233  		fmap, ok = s.oddForums[fid]
   234  		s.oddLock.RUnlock()
   235  	}
   236  	if !ok {
   237  		return fp, ErrNoRows
   238  	}
   239  
   240  	fp, ok = fmap[gid]
   241  	if !ok {
   242  		return fp, ErrNoRows
   243  	}
   244  	return fp, nil
   245  }
   246  
   247  // TODO: Check if the forum exists?
   248  // TODO: Fix the races
   249  func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fp ForumPerms, e error) {
   250  	fPermsPtr, e := s.Get(fid, gid)
   251  	if e != nil {
   252  		return fp, e
   253  	}
   254  	return *fPermsPtr, nil
   255  }