File: cache.go

package info (click to toggle)
golang-github-cue-lang-cue 0.14.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 19,644 kB
  • sloc: makefile: 20; sh: 15
file content (173 lines) | stat: -rw-r--r-- 5,446 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Package modcache provides a file-based cache for modules.
//
// WARNING: THIS PACKAGE IS EXPERIMENTAL.
// ITS API MAY CHANGE AT ANY TIME.
package modcache

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"

	"github.com/rogpeppe/go-internal/lockedfile"

	"cuelang.org/go/internal/robustio"
	"cuelang.org/go/mod/module"
)

var errNotCached = fmt.Errorf("not in cache")

// readDiskModFile reads a cached go.mod file from disk,
// returning the name of the cache file and the result.
// If the read fails, the caller can use
// writeDiskModFile(file, data) to write a new cache entry.
func (c *Cache) readDiskModFile(mv module.Version) (file string, data []byte, err error) {
	return c.readDiskCache(mv, "mod")
}

// writeDiskModFile writes a cue.mod/module.cue cache entry.
// The file name must have been returned by a previous call to readDiskModFile.
func (c *Cache) writeDiskModFile(ctx context.Context, file string, text []byte) error {
	return c.writeDiskCache(ctx, file, text)
}

// readDiskCache is the generic "read from a cache file" implementation.
// It takes the revision and an identifying suffix for the kind of data being cached.
// It returns the name of the cache file and the content of the file.
// If the read fails, the caller can use
// writeDiskCache(file, data) to write a new cache entry.
func (c *Cache) readDiskCache(mv module.Version, suffix string) (file string, data []byte, err error) {
	file, err = c.cachePath(mv, suffix)
	if err != nil {
		return "", nil, errNotCached
	}
	data, err = robustio.ReadFile(file)
	if err != nil {
		return file, nil, errNotCached
	}
	return file, data, nil
}

// writeDiskCache is the generic "write to a cache file" implementation.
// The file must have been returned by a previous call to readDiskCache.
func (c *Cache) writeDiskCache(ctx context.Context, file string, data []byte) error {
	if file == "" {
		return nil
	}
	// Make sure directory for file exists.
	if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil {
		return err
	}

	// Write the file to a temporary location, and then rename it to its final
	// path to reduce the likelihood of a corrupt file existing at that final path.
	f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0666)
	if err != nil {
		return err
	}
	defer func() {
		// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
		// some other process may have created a new file with the same name after
		// the rename completed.
		if err != nil {
			f.Close()
			os.Remove(f.Name())
		}
	}()

	if _, err := f.Write(data); err != nil {
		return err
	}
	if err := f.Close(); err != nil {
		return err
	}
	if err := robustio.Rename(f.Name(), file); err != nil {
		return err
	}
	return nil
}

// downloadDir returns the directory for storing.
// An error will be returned if the module path or version cannot be escaped.
// An error satisfying [errors.Is](err, [fs.ErrNotExist]) will be returned
// along with the directory if the directory does not exist or if the directory
// is not completely populated.
func (c *Cache) downloadDir(m module.Version) (string, error) {
	if !m.IsCanonical() {
		return "", fmt.Errorf("non-semver module version %q", m.Version())
	}
	enc, err := module.EscapePath(m.BasePath())
	if err != nil {
		return "", err
	}
	encVer, err := module.EscapeVersion(m.Version())
	if err != nil {
		return "", err
	}

	// Check whether the directory itself exists.
	dir := filepath.Join(c.dir, "extract", enc+"@"+encVer)
	if fi, err := os.Stat(dir); os.IsNotExist(err) {
		return dir, err
	} else if err != nil {
		return dir, &downloadDirPartialError{dir, err}
	} else if !fi.IsDir() {
		return dir, &downloadDirPartialError{dir, errors.New("not a directory")}
	}

	// Check if a .partial file exists. This is created at the beginning of
	// a download and removed after the zip is extracted.
	partialPath, err := c.cachePath(m, "partial")
	if err != nil {
		return dir, err
	}
	if _, err := os.Stat(partialPath); err == nil {
		return dir, &downloadDirPartialError{dir, errors.New("not completely extracted")}
	} else if !os.IsNotExist(err) {
		return dir, err
	}
	return dir, nil
}

func (c *Cache) cachePath(m module.Version, suffix string) (string, error) {
	if !m.IsValid() || m.Version() == "" {
		return "", fmt.Errorf("non-semver module version %q", m)
	}
	esc, err := module.EscapePath(m.BasePath())
	if err != nil {
		return "", err
	}
	encVer, err := module.EscapeVersion(m.Version())
	if err != nil {
		return "", err
	}
	return filepath.Join(c.dir, "download", esc, "/@v", encVer+"."+suffix), nil
}

// downloadDirPartialError is returned by DownloadDir if a module directory
// exists but was not completely populated.
//
// downloadDirPartialError is equivalent to fs.ErrNotExist.
type downloadDirPartialError struct {
	Dir string
	Err error
}

func (e *downloadDirPartialError) Error() string     { return fmt.Sprintf("%s: %v", e.Dir, e.Err) }
func (e *downloadDirPartialError) Is(err error) bool { return err == fs.ErrNotExist }

// lockVersion locks a file within the module cache that guards the downloading
// and extraction of module data for the given module version.
func (c *Cache) lockVersion(mod module.Version) (unlock func(), err error) {
	path, err := c.cachePath(mod, "lock")
	if err != nil {
		return nil, err
	}
	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
		return nil, err
	}
	return lockedfile.MutexAt(path).Lock()
}