@@ -7,14 +7,18 @@ 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"
1822 "github.com/ActiveState/cli/internal/sliceutils"
1923 "github.com/ActiveState/cli/internal/smartlink"
2024)
@@ -24,7 +28,8 @@ const (
2428)
2529
2630type depotConfig struct {
27- Deployments map [strfmt.UUID ][]deployment `json:"deployments"`
31+ Deployments map [strfmt.UUID ][]deployment `json:"deployments"`
32+ Cache map [strfmt.UUID ]* artifactInfo `json:"cache"`
2833}
2934
3035type deployment struct {
@@ -41,6 +46,14 @@ const (
4146 deploymentTypeCopy = "copy"
4247)
4348
49+ type artifactInfo struct {
50+ InUse bool `json:"inUse"`
51+ Size int64 `json:"size"`
52+ LastAccessTime int64 `json:"lastAccessTime"`
53+
54+ id strfmt.UUID // for convenience when removing stale artifacts; should NOT have json tag
55+ }
56+
4457type ErrVolumeMismatch struct {
4558 DepotVolume string
4659 PathVolume string
@@ -55,8 +68,15 @@ type depot struct {
5568 depotPath string
5669 artifacts map [strfmt.UUID ]struct {}
5770 fsMutex * sync.Mutex
71+ cacheSize int64
72+ }
73+
74+ func init () {
75+ configMediator .RegisterOption (constants .RuntimeCacheSizeConfigKey , configMediator .Int , 500 )
5876}
5977
78+ const MB int64 = 1024 * 1024
79+
6080func newDepot (runtimePath string ) (* depot , error ) {
6181 depotPath := filepath .Join (storage .CachePath (), depotName )
6282
@@ -73,6 +93,7 @@ func newDepot(runtimePath string) (*depot, error) {
7393 result := & depot {
7494 config : depotConfig {
7595 Deployments : map [strfmt.UUID ][]deployment {},
96+ Cache : map [strfmt.UUID ]* artifactInfo {},
7697 },
7798 depotPath : depotPath ,
7899 artifacts : map [strfmt.UUID ]struct {}{},
@@ -122,6 +143,10 @@ func newDepot(runtimePath string) (*depot, error) {
122143 return result , nil
123144}
124145
146+ func (d * depot ) SetCacheSize (mb int ) {
147+ d .cacheSize = int64 (mb ) * MB
148+ }
149+
125150func (d * depot ) Exists (id strfmt.UUID ) bool {
126151 _ , ok := d .artifacts [id ]
127152 return ok
@@ -196,6 +221,10 @@ func (d *depot) DeployViaLink(id strfmt.UUID, relativeSrc, absoluteDest string)
196221 Files : files .RelativePaths (),
197222 RelativeSrc : relativeSrc ,
198223 })
224+ err = d .recordUse (id )
225+ if err != nil {
226+ return errs .Wrap (err , "Could not record artifact use" )
227+ }
199228
200229 return nil
201230}
@@ -253,7 +282,25 @@ func (d *depot) DeployViaCopy(id strfmt.UUID, relativeSrc, absoluteDest string)
253282 Files : files .RelativePaths (),
254283 RelativeSrc : relativeSrc ,
255284 })
285+ err = d .recordUse (id )
286+ if err != nil {
287+ return errs .Wrap (err , "Could not record artifact use" )
288+ }
289+
290+ return nil
291+ }
256292
293+ func (d * depot ) recordUse (id strfmt.UUID ) error {
294+ // Ensure a cache entry for this artifact exists and then update its last access time.
295+ if _ , exists := d .config .Cache [id ]; ! exists {
296+ size , err := fileutils .GetDirSize (d .Path (id ))
297+ if err != nil {
298+ return errs .Wrap (err , "Could not get artifact size on disk" )
299+ }
300+ d .config .Cache [id ] = & artifactInfo {Size : size , id : id }
301+ }
302+ d .config .Cache [id ].InUse = true
303+ d .config .Cache [id ].LastAccessTime = time .Now ().Unix ()
257304 return nil
258305}
259306
@@ -372,14 +419,17 @@ func (d *depot) getSharedFilesToRedeploy(id strfmt.UUID, deploy deployment, path
372419// Save will write config changes to disk (ie. links between depot artifacts and runtimes that use it).
373420// It will also delete any stale artifacts which are not used by any runtime.
374421func (d * depot ) Save () error {
375- // Delete artifacts that are no longer used
422+ // Mark artifacts that are no longer used and remove the old ones.
376423 for id := range d .artifacts {
377424 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- }
425+ d .config .Cache [id ].InUse = false
426+ logging .Debug ("Artifact '%s' is no longer in use" , id .String ())
381427 }
382428 }
429+ err := d .removeStaleArtifacts ()
430+ if err != nil {
431+ return errs .Wrap (err , "Could not remove stale artifacts" )
432+ }
383433
384434 // Write config file changes to disk
385435 configFile := filepath .Join (d .depotPath , depotFile )
@@ -422,3 +472,40 @@ func someFilesExist(filePaths []string, basePath string) bool {
422472 }
423473 return false
424474}
475+
476+ // removeStaleArtifacts iterates over all unused artifacts in the depot, sorts them by last access
477+ // time, and removes them until the size of cached artifacts is under the limit.
478+ func (d * depot ) removeStaleArtifacts () error {
479+ var totalSize int64
480+ unusedArtifacts := make ([]* artifactInfo , 0 )
481+
482+ for _ , info := range d .config .Cache {
483+ if ! info .InUse {
484+ totalSize += info .Size
485+ unusedArtifacts = append (unusedArtifacts , info )
486+ }
487+ }
488+ logging .Debug ("There are %d unused artifacts totaling %.1f MB in size" , len (unusedArtifacts ), float64 (totalSize )/ float64 (MB ))
489+
490+ sort .Slice (unusedArtifacts , func (i , j int ) bool {
491+ return unusedArtifacts [i ].LastAccessTime < unusedArtifacts [j ].LastAccessTime
492+ })
493+
494+ var rerr error
495+ for _ , artifact := range unusedArtifacts {
496+ if totalSize <= d .cacheSize {
497+ break // done
498+ }
499+ if err := os .RemoveAll (d .Path (artifact .id )); err == nil {
500+ totalSize -= artifact .Size
501+ } else {
502+ if err := errs .Wrap (err , "Could not delete old artifact" ); rerr == nil {
503+ rerr = err
504+ } else {
505+ rerr = errs .Pack (rerr , err )
506+ }
507+ }
508+ delete (d .config .Cache , artifact .id )
509+ }
510+ return rerr
511+ }
0 commit comments