github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/avatars/fullcaching.go (about)

     1  package avatars
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/url"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/lru"
    16  	"github.com/keybase/client/go/protocol/keybase1"
    17  )
    18  
    19  type avatarLoadPair struct {
    20  	name      string
    21  	format    keybase1.AvatarFormat
    22  	path      string
    23  	remoteURL *string
    24  }
    25  
    26  type avatarLoadSpec struct {
    27  	hits   []avatarLoadPair
    28  	misses []avatarLoadPair
    29  	stales []avatarLoadPair
    30  }
    31  
    32  func (a avatarLoadSpec) details(l []avatarLoadPair) (names []string, formats []keybase1.AvatarFormat) {
    33  	fmap := make(map[keybase1.AvatarFormat]bool)
    34  	umap := make(map[string]bool)
    35  	for _, m := range l {
    36  		umap[m.name] = true
    37  		fmap[m.format] = true
    38  	}
    39  	for u := range umap {
    40  		names = append(names, u)
    41  	}
    42  	for f := range fmap {
    43  		formats = append(formats, f)
    44  	}
    45  	return names, formats
    46  }
    47  
    48  func (a avatarLoadSpec) missDetails() ([]string, []keybase1.AvatarFormat) {
    49  	return a.details(a.misses)
    50  }
    51  
    52  func (a avatarLoadSpec) staleDetails() ([]string, []keybase1.AvatarFormat) {
    53  	return a.details(a.stales)
    54  }
    55  
    56  func (a avatarLoadSpec) staleKnownURL(name string, format keybase1.AvatarFormat) *string {
    57  	for _, stale := range a.stales {
    58  		if stale.name == name && stale.format == format {
    59  			return stale.remoteURL
    60  		}
    61  	}
    62  	return nil
    63  }
    64  
    65  type populateArg struct {
    66  	name   string
    67  	format keybase1.AvatarFormat
    68  	url    keybase1.AvatarUrl
    69  }
    70  
    71  type remoteFetchArg struct {
    72  	names   []string
    73  	formats []keybase1.AvatarFormat
    74  	cb      chan keybase1.LoadAvatarsRes
    75  	errCb   chan error
    76  }
    77  
    78  type lruEntry struct {
    79  	Path string
    80  	URL  *string
    81  }
    82  
    83  func (l lruEntry) GetPath() string {
    84  	return l.Path
    85  }
    86  
    87  type FullCachingSource struct {
    88  	libkb.Contextified
    89  	sync.Mutex
    90  	started              bool
    91  	diskLRU              *lru.DiskLRU
    92  	diskLRUCleanerCancel context.CancelFunc
    93  	staleThreshold       time.Duration
    94  	simpleSource         libkb.AvatarLoaderSource
    95  
    96  	populateCacheCh chan populateArg
    97  
    98  	prepareDirs sync.Once
    99  
   100  	usersMissBatch  func(interface{})
   101  	teamsMissBatch  func(interface{})
   102  	usersStaleBatch func(interface{})
   103  	teamsStaleBatch func(interface{})
   104  
   105  	// testing
   106  	populateSuccessCh chan struct{}
   107  	tempDir           string
   108  }
   109  
   110  var _ libkb.AvatarLoaderSource = (*FullCachingSource)(nil)
   111  
   112  func NewFullCachingSource(g *libkb.GlobalContext, staleThreshold time.Duration, size int) *FullCachingSource {
   113  	s := &FullCachingSource{
   114  		Contextified:   libkb.NewContextified(g),
   115  		diskLRU:        lru.NewDiskLRU("avatars", 1, size),
   116  		staleThreshold: staleThreshold,
   117  		simpleSource:   NewSimpleSource(),
   118  	}
   119  	batcher := func(intBatched interface{}, intSingle interface{}) interface{} {
   120  		reqs, _ := intBatched.([]remoteFetchArg)
   121  		single, _ := intSingle.(remoteFetchArg)
   122  		return append(reqs, single)
   123  	}
   124  	reset := func() interface{} {
   125  		return []remoteFetchArg{}
   126  	}
   127  	actor := func(loadFn func(libkb.MetaContext, []string, []keybase1.AvatarFormat) (keybase1.LoadAvatarsRes, error)) func(interface{}) {
   128  		return func(intBatched interface{}) {
   129  			reqs, _ := intBatched.([]remoteFetchArg)
   130  			s.makeRemoteFetchRequests(reqs, loadFn)
   131  		}
   132  	}
   133  	usersMissBatch, _ := libkb.ThrottleBatch(
   134  		actor(s.simpleSource.LoadUsers), batcher, reset, 100*time.Millisecond, false,
   135  	)
   136  	teamsMissBatch, _ := libkb.ThrottleBatch(
   137  		actor(s.simpleSource.LoadTeams), batcher, reset, 100*time.Millisecond, false,
   138  	)
   139  	usersStaleBatch, _ := libkb.ThrottleBatch(
   140  		actor(s.simpleSource.LoadUsers), batcher, reset, 5000*time.Millisecond, false,
   141  	)
   142  	teamsStaleBatch, _ := libkb.ThrottleBatch(
   143  		actor(s.simpleSource.LoadTeams), batcher, reset, 5000*time.Millisecond, false,
   144  	)
   145  	s.usersMissBatch = usersMissBatch
   146  	s.teamsMissBatch = teamsMissBatch
   147  	s.usersStaleBatch = usersStaleBatch
   148  	s.teamsStaleBatch = teamsStaleBatch
   149  	return s
   150  }
   151  
   152  func (c *FullCachingSource) makeRemoteFetchRequests(reqs []remoteFetchArg,
   153  	loadFn func(libkb.MetaContext, []string, []keybase1.AvatarFormat) (keybase1.LoadAvatarsRes, error)) {
   154  	mctx := libkb.NewMetaContextBackground(c.G())
   155  	namesSet := make(map[string]bool)
   156  	formatsSet := make(map[keybase1.AvatarFormat]bool)
   157  	for _, req := range reqs {
   158  		for _, name := range req.names {
   159  			namesSet[name] = true
   160  		}
   161  		for _, format := range req.formats {
   162  			formatsSet[format] = true
   163  		}
   164  	}
   165  	genErrors := func(err error) {
   166  		for _, req := range reqs {
   167  			req.errCb <- err
   168  		}
   169  	}
   170  	extractRes := func(req remoteFetchArg, ires keybase1.LoadAvatarsRes) (res keybase1.LoadAvatarsRes) {
   171  		res.Picmap = make(map[string]map[keybase1.AvatarFormat]keybase1.AvatarUrl)
   172  		for _, name := range req.names {
   173  			iformats, ok := ires.Picmap[name]
   174  			if !ok {
   175  				continue
   176  			}
   177  			if _, ok := res.Picmap[name]; !ok {
   178  				res.Picmap[name] = make(map[keybase1.AvatarFormat]keybase1.AvatarUrl)
   179  			}
   180  			for _, format := range req.formats {
   181  				res.Picmap[name][format] = iformats[format]
   182  			}
   183  		}
   184  		return res
   185  	}
   186  	names := make([]string, 0, len(namesSet))
   187  	formats := make([]keybase1.AvatarFormat, 0, len(formatsSet))
   188  	for name := range namesSet {
   189  		names = append(names, name)
   190  	}
   191  	for format := range formatsSet {
   192  		formats = append(formats, format)
   193  	}
   194  	c.debug(mctx, "makeRemoteFetchRequests: names: %d formats: %d", len(names), len(formats))
   195  	res, err := loadFn(mctx, names, formats)
   196  	if err != nil {
   197  		genErrors(err)
   198  		return
   199  	}
   200  	for _, req := range reqs {
   201  		req.cb <- extractRes(req, res)
   202  	}
   203  }
   204  
   205  func (c *FullCachingSource) StartBackgroundTasks(mctx libkb.MetaContext) {
   206  	defer mctx.Trace("FullCachingSource.StartBackgroundTasks", nil)()
   207  	c.Lock()
   208  	defer c.Unlock()
   209  	if c.started {
   210  		return
   211  	}
   212  	c.started = true
   213  	go c.monitorAppState(mctx)
   214  	c.populateCacheCh = make(chan populateArg, 100)
   215  	for i := 0; i < 10; i++ {
   216  		go c.populateCacheWorker(mctx)
   217  	}
   218  	mctx, cancel := mctx.WithContextCancel()
   219  	c.diskLRUCleanerCancel = cancel
   220  	go lru.CleanOutOfSyncWithDelay(mctx, c.diskLRU, c.getCacheDir(mctx), 10*time.Second)
   221  }
   222  
   223  func (c *FullCachingSource) StopBackgroundTasks(mctx libkb.MetaContext) {
   224  	defer mctx.Trace("FullCachingSource.StopBackgroundTasks", nil)()
   225  	c.Lock()
   226  	defer c.Unlock()
   227  	if !c.started {
   228  		return
   229  	}
   230  	c.started = false
   231  	close(c.populateCacheCh)
   232  	if c.diskLRUCleanerCancel != nil {
   233  		c.diskLRUCleanerCancel()
   234  	}
   235  	if err := c.diskLRU.Flush(mctx.Ctx(), mctx.G()); err != nil {
   236  		c.debug(mctx, "StopBackgroundTasks: unable to flush diskLRU %v", err)
   237  	}
   238  }
   239  
   240  func (c *FullCachingSource) debug(m libkb.MetaContext, msg string, args ...interface{}) {
   241  	m.Debug("Avatars.FullCachingSource: %s", fmt.Sprintf(msg, args...))
   242  }
   243  
   244  func (c *FullCachingSource) avatarKey(name string, format keybase1.AvatarFormat) string {
   245  	return fmt.Sprintf("%s:%s", name, format.String())
   246  }
   247  
   248  func (c *FullCachingSource) isStale(m libkb.MetaContext, item lru.DiskLRUEntry) bool {
   249  	return m.G().GetClock().Now().Sub(item.Ctime) > c.staleThreshold
   250  }
   251  
   252  func (c *FullCachingSource) monitorAppState(m libkb.MetaContext) {
   253  	c.debug(m, "monitorAppState: starting up")
   254  	state := keybase1.MobileAppState_FOREGROUND
   255  	for {
   256  		state = <-m.G().MobileAppState.NextUpdate(&state)
   257  		if state == keybase1.MobileAppState_BACKGROUND {
   258  			c.debug(m, "monitorAppState: backgrounded")
   259  			if err := c.diskLRU.Flush(m.Ctx(), m.G()); err != nil {
   260  				c.debug(m, "monitorAppState: unable to flush diskLRU %v", err)
   261  			}
   262  		}
   263  	}
   264  }
   265  
   266  func (c *FullCachingSource) processLRUHit(entry lru.DiskLRUEntry) (res lruEntry) {
   267  	var ok bool
   268  	if _, ok = entry.Value.(map[string]interface{}); ok {
   269  		jstr, _ := json.Marshal(entry.Value)
   270  		_ = json.Unmarshal(jstr, &res)
   271  		return res
   272  	}
   273  	path, _ := entry.Value.(string)
   274  	res.Path = path
   275  	return res
   276  }
   277  
   278  func (c *FullCachingSource) specLoad(m libkb.MetaContext, names []string, formats []keybase1.AvatarFormat) (res avatarLoadSpec, err error) {
   279  	for _, name := range names {
   280  		for _, format := range formats {
   281  			key := c.avatarKey(name, format)
   282  			found, ientry, err := c.diskLRU.Get(m.Ctx(), m.G(), key)
   283  			if err != nil {
   284  				return res, err
   285  			}
   286  			lp := avatarLoadPair{
   287  				name:   name,
   288  				format: format,
   289  			}
   290  
   291  			// If we found something in the index, let's make sure we have it on the disk as well.
   292  			entry := c.processLRUHit(ientry)
   293  			if found {
   294  				lp.path = c.normalizeFilenameFromCache(m, entry.Path)
   295  				lp.remoteURL = entry.URL
   296  				var file *os.File
   297  				if file, err = os.Open(lp.path); err != nil {
   298  					c.debug(m, "specLoad: error loading hit: file: %s err: %s", lp.path, err)
   299  					if err := c.diskLRU.Remove(m.Ctx(), m.G(), key); err != nil {
   300  						c.debug(m, "specLoad: unable to remove from LRU %v", err)
   301  					}
   302  					// Not a true hit if we don't have it on the disk as well
   303  					found = false
   304  				} else {
   305  					file.Close()
   306  				}
   307  			}
   308  			if found {
   309  				if c.isStale(m, ientry) {
   310  					res.stales = append(res.stales, lp)
   311  				} else {
   312  					res.hits = append(res.hits, lp)
   313  				}
   314  			} else {
   315  				res.misses = append(res.misses, lp)
   316  			}
   317  		}
   318  	}
   319  	return res, nil
   320  }
   321  
   322  func (c *FullCachingSource) getCacheDir(m libkb.MetaContext) string {
   323  	if len(c.tempDir) > 0 {
   324  		return c.tempDir
   325  	}
   326  	return filepath.Join(m.G().GetCacheDir(), "avatars")
   327  }
   328  
   329  func (c *FullCachingSource) getFullFilename(fileName string) string {
   330  	return fileName + ".avatar"
   331  }
   332  
   333  // normalizeFilenameFromCache substitutes the existing cache dir value into the
   334  // file path since it's possible for the path to the cache dir to change,
   335  // especially on mobile.
   336  func (c *FullCachingSource) normalizeFilenameFromCache(mctx libkb.MetaContext, file string) string {
   337  	file = filepath.Base(file)
   338  	return filepath.Join(c.getCacheDir(mctx), file)
   339  }
   340  
   341  func (c *FullCachingSource) commitAvatarToDisk(m libkb.MetaContext, data io.ReadCloser, previousPath string) (path string, err error) {
   342  	c.prepareDirs.Do(func() {
   343  		err := os.MkdirAll(c.getCacheDir(m), os.ModePerm)
   344  		c.debug(m, "creating directory for avatars %q: %v", c.getCacheDir(m), err)
   345  	})
   346  
   347  	var file *os.File
   348  	shouldRename := false
   349  	if len(previousPath) > 0 {
   350  		// We already have the image, let's re-use the same file
   351  		c.debug(m, "commitAvatarToDisk: using previous path: %s", previousPath)
   352  		if file, err = os.OpenFile(previousPath, os.O_RDWR, os.ModeAppend); err != nil {
   353  			// NOTE: Even if we don't have this file anymore (e.g. user
   354  			// raced us to remove it manually), OpenFile will not error
   355  			// out, but create a new file on given path.
   356  			return path, err
   357  		}
   358  		path = file.Name()
   359  	} else {
   360  		if file, err = os.CreateTemp(c.getCacheDir(m), "avatar"); err != nil {
   361  			return path, err
   362  		}
   363  		shouldRename = true
   364  	}
   365  	_, err = io.Copy(file, data)
   366  	file.Close()
   367  	if err != nil {
   368  		return path, err
   369  	}
   370  	// Rename with correct extension
   371  	if shouldRename {
   372  		path = c.getFullFilename(file.Name())
   373  		if err = os.Rename(file.Name(), path); err != nil {
   374  			return path, err
   375  		}
   376  	}
   377  	return path, nil
   378  }
   379  
   380  func (c *FullCachingSource) removeFile(m libkb.MetaContext, ent *lru.DiskLRUEntry) {
   381  	if ent != nil {
   382  		lentry := c.processLRUHit(*ent)
   383  		file := c.normalizeFilenameFromCache(m, lentry.GetPath())
   384  		if err := os.Remove(file); err != nil {
   385  			c.debug(m, "removeFile: failed to remove: file: %s err: %s", file, err)
   386  		} else {
   387  			c.debug(m, "removeFile: successfully removed: %s", file)
   388  		}
   389  	}
   390  }
   391  
   392  func (c *FullCachingSource) populateCacheWorker(m libkb.MetaContext) {
   393  	for arg := range c.populateCacheCh {
   394  		c.debug(m, "populateCacheWorker: fetching: name: %s format: %s url: %s", arg.name,
   395  			arg.format, arg.url)
   396  		// Grab image data first
   397  		url := arg.url.String()
   398  		resp, err := libkb.ProxyHTTPGet(m.G(), m.G().GetEnv(), url, "FullCachingSource: Avatar")
   399  		if err != nil {
   400  			c.debug(m, "populateCacheWorker: failed to download avatar: %s", err)
   401  			continue
   402  		}
   403  		// Find any previous path we stored this image at on the disk
   404  		var previousEntry lruEntry
   405  		var previousPath string
   406  		key := c.avatarKey(arg.name, arg.format)
   407  		found, ent, err := c.diskLRU.Get(m.Ctx(), m.G(), key)
   408  		if err != nil {
   409  			c.debug(m, "populateCacheWorker: failed to read previous entry in LRU: %s", err)
   410  			err = libkb.DiscardAndCloseBody(resp)
   411  			if err != nil {
   412  				c.debug(m, "populateCacheWorker: error closing body: %+v", err)
   413  			}
   414  			continue
   415  		}
   416  		if found {
   417  			previousEntry = c.processLRUHit(ent)
   418  			previousPath = c.normalizeFilenameFromCache(m, previousEntry.Path)
   419  		}
   420  
   421  		// Save to disk
   422  		path, err := c.commitAvatarToDisk(m, resp.Body, previousPath)
   423  		discardErr := libkb.DiscardAndCloseBody(resp)
   424  		if discardErr != nil {
   425  			c.debug(m, "populateCacheWorker: error closing body: %+v", discardErr)
   426  		}
   427  		if err != nil {
   428  			c.debug(m, "populateCacheWorker: failed to write to disk: %s", err)
   429  			continue
   430  		}
   431  		v := lruEntry{
   432  			Path: path,
   433  			URL:  &url,
   434  		}
   435  		evicted, err := c.diskLRU.Put(m.Ctx(), m.G(), key, v)
   436  		if err != nil {
   437  			c.debug(m, "populateCacheWorker: failed to put into LRU: %s", err)
   438  			continue
   439  		}
   440  		// Remove any evicted file (if there is one)
   441  		c.removeFile(m, evicted)
   442  
   443  		if c.populateSuccessCh != nil {
   444  			c.populateSuccessCh <- struct{}{}
   445  		}
   446  	}
   447  }
   448  
   449  func (c *FullCachingSource) dispatchPopulateFromRes(m libkb.MetaContext, res keybase1.LoadAvatarsRes,
   450  	spec avatarLoadSpec) {
   451  	c.Lock()
   452  	defer c.Unlock()
   453  	if !c.started {
   454  		return
   455  	}
   456  	for name, rec := range res.Picmap {
   457  		for format, url := range rec {
   458  			if url != "" {
   459  				knownURL := spec.staleKnownURL(name, format)
   460  				if knownURL == nil || *knownURL != url.String() {
   461  					c.populateCacheCh <- populateArg{
   462  						name:   name,
   463  						format: format,
   464  						url:    url,
   465  					}
   466  				} else {
   467  					c.debug(m, "dispatchPopulateFromRes: skipping name: %s format: %s, stale known", name,
   468  						format)
   469  				}
   470  			}
   471  		}
   472  	}
   473  }
   474  
   475  func (c *FullCachingSource) makeURL(m libkb.MetaContext, path string) keybase1.AvatarUrl {
   476  	raw := fmt.Sprintf("file://%s", fileUrlize(path))
   477  	u, err := url.Parse(raw)
   478  	if err != nil {
   479  		c.debug(m, "makeURL: invalid URL: %s", err)
   480  		return keybase1.MakeAvatarURL("")
   481  	}
   482  	final := fmt.Sprintf("file://%s", u.EscapedPath())
   483  	return keybase1.MakeAvatarURL(final)
   484  }
   485  
   486  func (c *FullCachingSource) mergeRes(res *keybase1.LoadAvatarsRes, m keybase1.LoadAvatarsRes) {
   487  	for username, rec := range m.Picmap {
   488  		for format, url := range rec {
   489  			res.Picmap[username][format] = url
   490  		}
   491  	}
   492  }
   493  
   494  func (c *FullCachingSource) loadNames(m libkb.MetaContext, names []string, formats []keybase1.AvatarFormat,
   495  	users bool) (res keybase1.LoadAvatarsRes, err error) {
   496  	loadSpec, err := c.specLoad(m, names, formats)
   497  	if err != nil {
   498  		return res, err
   499  	}
   500  	c.debug(m, "loadNames: hits: %d stales: %d misses: %d", len(loadSpec.hits), len(loadSpec.stales),
   501  		len(loadSpec.misses))
   502  
   503  	// Fill in the hits
   504  	allocRes(&res, names)
   505  	for _, hit := range loadSpec.hits {
   506  		res.Picmap[hit.name][hit.format] = c.makeURL(m, hit.path)
   507  	}
   508  	// Fill in stales
   509  	for _, stale := range loadSpec.stales {
   510  		res.Picmap[stale.name][stale.format] = c.makeURL(m, stale.path)
   511  	}
   512  
   513  	// Go get the misses
   514  	missNames, missFormats := loadSpec.missDetails()
   515  	if len(missNames) > 0 {
   516  		var loadRes keybase1.LoadAvatarsRes
   517  		cb := make(chan keybase1.LoadAvatarsRes, 1)
   518  		errCb := make(chan error, 1)
   519  		arg := remoteFetchArg{
   520  			names:   missNames,
   521  			formats: missFormats,
   522  			cb:      cb,
   523  			errCb:   errCb,
   524  		}
   525  		if users {
   526  			c.usersMissBatch(arg)
   527  		} else {
   528  			c.teamsMissBatch(arg)
   529  		}
   530  		select {
   531  		case loadRes = <-cb:
   532  		case err = <-errCb:
   533  		}
   534  		if err == nil {
   535  			c.mergeRes(&res, loadRes)
   536  			c.dispatchPopulateFromRes(m, loadRes, loadSpec)
   537  		} else {
   538  			c.debug(m, "loadNames: failed to load server miss reqs: %s", err)
   539  		}
   540  	}
   541  	// Spawn off a goroutine to reload stales
   542  	staleNames, staleFormats := loadSpec.staleDetails()
   543  	if len(staleNames) > 0 {
   544  		go func() {
   545  			m := m.BackgroundWithLogTags()
   546  			c.debug(m, "loadNames: spawning stale background load: names: %d",
   547  				len(staleNames))
   548  			var loadRes keybase1.LoadAvatarsRes
   549  			cb := make(chan keybase1.LoadAvatarsRes, 1)
   550  			errCb := make(chan error, 1)
   551  			arg := remoteFetchArg{
   552  				names:   staleNames,
   553  				formats: staleFormats,
   554  				cb:      cb,
   555  				errCb:   errCb,
   556  			}
   557  			if users {
   558  				c.usersStaleBatch(arg)
   559  			} else {
   560  				c.teamsStaleBatch(arg)
   561  			}
   562  			select {
   563  			case loadRes = <-cb:
   564  			case err = <-errCb:
   565  			}
   566  			if err == nil {
   567  				c.dispatchPopulateFromRes(m, loadRes, loadSpec)
   568  			} else {
   569  				c.debug(m, "loadNames: failed to load server stale reqs: %s", err)
   570  			}
   571  		}()
   572  	}
   573  	return res, nil
   574  }
   575  
   576  func (c *FullCachingSource) clearName(m libkb.MetaContext, name string, formats []keybase1.AvatarFormat) (err error) {
   577  	for _, format := range formats {
   578  		key := c.avatarKey(name, format)
   579  		found, ent, err := c.diskLRU.Get(m.Ctx(), m.G(), key)
   580  		if err != nil {
   581  			return err
   582  		}
   583  		if found {
   584  			c.removeFile(m, &ent)
   585  			if err := c.diskLRU.Remove(m.Ctx(), m.G(), key); err != nil {
   586  				return err
   587  			}
   588  		}
   589  	}
   590  	return nil
   591  }
   592  
   593  func (c *FullCachingSource) LoadUsers(m libkb.MetaContext, usernames []string, formats []keybase1.AvatarFormat) (res keybase1.LoadAvatarsRes, err error) {
   594  	defer m.Trace("FullCachingSource.LoadUsers", &err)()
   595  	return c.loadNames(m, usernames, formats, true)
   596  }
   597  
   598  func (c *FullCachingSource) LoadTeams(m libkb.MetaContext, teams []string, formats []keybase1.AvatarFormat) (res keybase1.LoadAvatarsRes, err error) {
   599  	defer m.Trace("FullCachingSource.LoadTeams", &err)()
   600  	return c.loadNames(m, teams, formats, false)
   601  }
   602  
   603  func (c *FullCachingSource) ClearCacheForName(m libkb.MetaContext, name string, formats []keybase1.AvatarFormat) (err error) {
   604  	defer m.Trace(fmt.Sprintf("FullCachingSource.ClearCacheForUser(%q,%v)", name, formats), &err)()
   605  	return c.clearName(m, name, formats)
   606  }
   607  
   608  func (c *FullCachingSource) OnDbNuke(m libkb.MetaContext) error {
   609  	if c.diskLRU != nil {
   610  		if err := c.diskLRU.CleanOutOfSync(m, c.getCacheDir(m)); err != nil {
   611  			c.debug(m, "unable to run clean: %v", err)
   612  		}
   613  	}
   614  	return nil
   615  }