@@ -7,14 +7,19 @@ import (
77 "os"
88 "path/filepath"
99 "runtime"
10+ "sort"
1011 "sync"
12+ "time"
1113
1214 "github.com/go-openapi/strfmt"
1315
16+ "github.com/ActiveState/cli/internal/constants"
1417 "github.com/ActiveState/cli/internal/errs"
1518 "github.com/ActiveState/cli/internal/fileutils"
1619 "github.com/ActiveState/cli/internal/installation/storage"
1720 "github.com/ActiveState/cli/internal/logging"
21+ configMediator "github.com/ActiveState/cli/internal/mediators/config"
22+ "github.com/ActiveState/cli/internal/multilog"
1823 "github.com/ActiveState/cli/internal/sliceutils"
1924 "github.com/ActiveState/cli/internal/smartlink"
2025)
@@ -24,7 +29,8 @@ const (
2429)
2530
2631type depotConfig struct {
27- Deployments map [strfmt.UUID ][]deployment `json:"deployments"`
32+ Deployments map [strfmt.UUID ][]deployment `json:"deployments"`
33+ Cache map [strfmt.UUID ]* artifactInfo `json:"cache"`
2834}
2935
3036type deployment struct {
@@ -41,6 +47,12 @@ const (
4147 deploymentTypeCopy = "copy"
4248)
4349
50+ type artifactInfo struct {
51+ InUse bool `json:"inUse"`
52+ Size int64 `json:"size"`
53+ LastAccessTime int64 `json:"lastAccessTime"`
54+ }
55+
4456type ErrVolumeMismatch struct {
4557 DepotVolume string
4658 PathVolume string
@@ -55,8 +67,15 @@ type depot struct {
5567 depotPath string
5668 artifacts map [strfmt.UUID ]struct {}
5769 fsMutex * sync.Mutex
70+ cacheSize int64
71+ }
72+
73+ func init () {
74+ configMediator .RegisterOption (constants .RuntimeCacheSizeConfigKey , configMediator .Int , 500 )
5875}
5976
77+ const MB int64 = 1024 * 1024
78+
6079func newDepot (runtimePath string ) (* depot , error ) {
6180 depotPath := filepath .Join (storage .CachePath (), depotName )
6281
@@ -73,6 +92,7 @@ func newDepot(runtimePath string) (*depot, error) {
7392 result := & depot {
7493 config : depotConfig {
7594 Deployments : map [strfmt.UUID ][]deployment {},
95+ Cache : map [strfmt.UUID ]* artifactInfo {},
7696 },
7797 depotPath : depotPath ,
7898 artifacts : map [strfmt.UUID ]struct {}{},
@@ -122,6 +142,10 @@ func newDepot(runtimePath string) (*depot, error) {
122142 return result , nil
123143}
124144
145+ func (d * depot ) SetCacheSize (mb int ) {
146+ d .cacheSize = int64 (mb ) * MB
147+ }
148+
125149func (d * depot ) Exists (id strfmt.UUID ) bool {
126150 _ , ok := d .artifacts [id ]
127151 return ok
@@ -196,6 +220,7 @@ func (d *depot) DeployViaLink(id strfmt.UUID, relativeSrc, absoluteDest string)
196220 Files : files .RelativePaths (),
197221 RelativeSrc : relativeSrc ,
198222 })
223+ d .recordUse (id )
199224
200225 return nil
201226}
@@ -253,10 +278,28 @@ func (d *depot) DeployViaCopy(id strfmt.UUID, relativeSrc, absoluteDest string)
253278 Files : files .RelativePaths (),
254279 RelativeSrc : relativeSrc ,
255280 })
281+ d .recordUse (id )
256282
257283 return nil
258284}
259285
286+ func (d * depot ) recordUse (id strfmt.UUID ) {
287+ // Ensure a cache entry for this artifact exists and then update its last access time.
288+ if _ , exists := d .config .Cache [id ]; ! exists {
289+ size , err := fileutils .GetDirSize (d .Path (id ))
290+ if err != nil {
291+ multilog .Error ("Could not get artifact size on disk: %v" , err )
292+ size = 0
293+ }
294+ logging .Debug ("Recording artifact '%s' with size %.1f MB" , id .String (), float64 (size )/ float64 (MB ))
295+ d .config .Cache [id ] = & artifactInfo {Size : size }
296+ } else {
297+ logging .Debug ("Recording use of artifact '%s'" , id .String ())
298+ }
299+ d .config .Cache [id ].InUse = true
300+ d .config .Cache [id ].LastAccessTime = time .Now ().Unix ()
301+ }
302+
260303func (d * depot ) Undeploy (id strfmt.UUID , relativeSrc , path string ) error {
261304 d .fsMutex .Lock ()
262305 defer d .fsMutex .Unlock ()
@@ -372,14 +415,14 @@ func (d *depot) getSharedFilesToRedeploy(id strfmt.UUID, deploy deployment, path
372415// Save will write config changes to disk (ie. links between depot artifacts and runtimes that use it).
373416// It will also delete any stale artifacts which are not used by any runtime.
374417func (d * depot ) Save () error {
375- // Delete artifacts that are no longer used
418+ // Mark artifacts that are no longer used and remove the old ones.
376419 for id := range d .artifacts {
377420 if deployments , ok := d .config .Deployments [id ]; ! ok || len (deployments ) == 0 {
378- if err := os .RemoveAll (d .Path (id )); err != nil {
379- return errs .Wrap (err , "failed to remove stale artifact" )
380- }
421+ d .config .Cache [id ].InUse = false
422+ logging .Debug ("Artifact '%s' is no longer in use" , id .String ())
381423 }
382424 }
425+ d .removeStaleArtifacts ()
383426
384427 // Write config file changes to disk
385428 configFile := filepath .Join (d .depotPath , depotFile )
@@ -422,3 +465,39 @@ func someFilesExist(filePaths []string, basePath string) bool {
422465 }
423466 return false
424467}
468+
469+ // removeStaleArtifacts iterates over all unused artifacts in the depot, sorts them by last access
470+ // time, and removes them until the size of cached artifacts is under the limit.
471+ func (d * depot ) removeStaleArtifacts () {
472+ type artifact struct {
473+ id strfmt.UUID
474+ info * artifactInfo
475+ }
476+ var totalSize int64
477+ unusedArtifacts := make ([]* artifact , 0 )
478+
479+ for id , info := range d .config .Cache {
480+ if ! info .InUse {
481+ totalSize += info .Size
482+ unusedArtifacts = append (unusedArtifacts , & artifact {id : id , info : info })
483+ }
484+ }
485+ logging .Debug ("There are %d unused artifacts totaling %.1f MB in size" , len (unusedArtifacts ), float64 (totalSize )/ float64 (MB ))
486+
487+ sort .Slice (unusedArtifacts , func (i , j int ) bool {
488+ return unusedArtifacts [i ].info .LastAccessTime < unusedArtifacts [j ].info .LastAccessTime
489+ })
490+
491+ for _ , artifact := range unusedArtifacts {
492+ if totalSize <= d .cacheSize {
493+ break // done
494+ }
495+ logging .Debug ("Removing cached artifact '%s', last accessed on %s" , artifact .id .String (), time .Unix (artifact .info .LastAccessTime , 0 ).Format (time .UnixDate ))
496+ if err := os .RemoveAll (d .Path (artifact .id )); err == nil {
497+ totalSize -= artifact .info .Size
498+ } else {
499+ multilog .Error ("Could not delete old artifact: %v" , err )
500+ }
501+ delete (d .config .Cache , artifact .id )
502+ }
503+ }
0 commit comments