2015-12-18 04:14:39 +00:00
|
|
|
package goldsmith
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2018-12-08 19:18:51 +00:00
|
|
|
"hash/crc32"
|
2015-12-18 04:14:39 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
2016-01-13 04:12:50 +00:00
|
|
|
"path/filepath"
|
2018-12-08 19:18:51 +00:00
|
|
|
"strings"
|
2016-06-11 23:24:06 +00:00
|
|
|
"time"
|
2015-12-18 04:14:39 +00:00
|
|
|
)
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// File represents in-memory or on-disk files in a chain.
|
2018-12-08 19:18:51 +00:00
|
|
|
type File struct {
|
|
|
|
sourcePath string
|
|
|
|
dataPath string
|
|
|
|
|
2016-01-01 08:47:28 +00:00
|
|
|
Meta map[string]interface{}
|
2015-12-18 07:06:28 +00:00
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
hashValue uint32
|
|
|
|
hashValid bool
|
|
|
|
|
2016-06-11 23:24:06 +00:00
|
|
|
reader *bytes.Reader
|
|
|
|
size int64
|
|
|
|
modTime time.Time
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
2016-06-11 23:24:06 +00:00
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Path returns the file path relative to the source directory.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Path() string {
|
|
|
|
return file.sourcePath
|
2015-12-18 04:14:39 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Name returns the base name of the file.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Name() string {
|
|
|
|
return path.Base(file.sourcePath)
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Dir returns the containing directory of the file.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Dir() string {
|
|
|
|
return path.Dir(file.sourcePath)
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Ext returns the extension of the file.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Ext() string {
|
|
|
|
return path.Ext(file.sourcePath)
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Size returns the file length in bytes.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Size() int64 {
|
|
|
|
return file.size
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// ModTime returns the time of the file's last modification.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) ModTime() time.Time {
|
|
|
|
return file.modTime
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Read reads file data into the provided buffer.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Read(data []byte) (int, error) {
|
|
|
|
if err := file.load(); err != nil {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
return file.reader.Read(data)
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Write writes file data into the provided writer.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) WriteTo(writer io.Writer) (int64, error) {
|
|
|
|
if err := file.load(); err != nil {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
return file.reader.WriteTo(writer)
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 20:43:25 +00:00
|
|
|
// Seek updates the file pointer to the desired position.
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) Seek(offset int64, whence int) (int64, error) {
|
|
|
|
if file.reader == nil && offset == 0 && (whence == os.SEEK_SET || whence == os.SEEK_CUR) {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
if err := file.load(); err != nil {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
return file.reader.Seek(offset, whence)
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) export(targetDir string) error {
|
|
|
|
targetPath := filepath.Join(targetDir, file.sourcePath)
|
2019-04-07 00:32:53 +00:00
|
|
|
|
|
|
|
if len(file.dataPath) == 0 {
|
|
|
|
if targetInfo, err := os.Stat(targetPath); err == nil && targetInfo.ModTime().After(file.ModTime()) {
|
|
|
|
return nil
|
|
|
|
}
|
2016-01-12 08:04:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
if err := os.MkdirAll(path.Dir(targetPath), 0755); err != nil {
|
2015-12-18 04:14:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
fw, err := os.Create(targetPath)
|
2015-12-18 04:14:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-12-18 10:49:52 +00:00
|
|
|
defer fw.Close()
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
if file.reader == nil {
|
|
|
|
fr, err := os.Open(file.dataPath)
|
2015-12-18 10:49:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fr.Close()
|
|
|
|
|
|
|
|
if _, err := io.Copy(fw, fr); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2018-12-10 01:00:46 +00:00
|
|
|
if _, err := file.Seek(0, os.SEEK_SET); err != nil {
|
2015-12-20 14:18:58 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-12-08 19:18:51 +00:00
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
if _, err := file.WriteTo(fw); err != nil {
|
2015-12-18 10:49:52 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-12-18 04:14:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) load() error {
|
|
|
|
if file.reader != nil {
|
2015-12-18 04:14:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
data, err := ioutil.ReadFile(file.dataPath)
|
2015-12-18 04:14:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
file.reader = bytes.NewReader(data)
|
2015-12-18 04:14:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
func (file *File) hash() (uint32, error) {
|
|
|
|
if file.hashValid {
|
|
|
|
return file.hashValue, nil
|
2018-12-08 19:18:51 +00:00
|
|
|
}
|
2016-06-11 23:24:06 +00:00
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
if err := file.load(); err != nil {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
2016-06-11 23:24:06 +00:00
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
offset, err := file.Seek(0, os.SEEK_CUR)
|
2018-12-08 19:18:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2015-12-18 04:14:39 +00:00
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
if _, err := file.Seek(0, os.SEEK_SET); err != nil {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
2015-12-29 12:08:41 +00:00
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
hasher := crc32.NewIEEE()
|
2018-12-10 01:00:46 +00:00
|
|
|
if _, err := io.Copy(hasher, file.reader); err != nil {
|
2018-12-08 19:18:51 +00:00
|
|
|
return 0, err
|
2015-12-19 12:14:16 +00:00
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
if _, err := file.Seek(offset, os.SEEK_SET); err != nil {
|
2015-12-18 04:14:39 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:00:46 +00:00
|
|
|
file.hashValue = hasher.Sum32()
|
|
|
|
file.hashValid = true
|
|
|
|
return file.hashValue, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type FilesByPath []*File
|
|
|
|
|
|
|
|
func (file FilesByPath) Len() int {
|
|
|
|
return len(file)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (file FilesByPath) Swap(i, j int) {
|
|
|
|
file[i], file[j] = file[j], file[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (file FilesByPath) Less(i, j int) bool {
|
|
|
|
return strings.Compare(file[i].Path(), file[j].Path()) < 0
|
2015-12-18 07:06:28 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
type fileInfo struct {
|
|
|
|
os.FileInfo
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanPath(path string) string {
|
|
|
|
if filepath.IsAbs(path) {
|
|
|
|
var err error
|
|
|
|
if path, err = filepath.Rel("/", path); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2015-12-18 07:06:28 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
return filepath.Clean(path)
|
2015-12-18 07:06:28 +00:00
|
|
|
}
|
2015-12-20 14:18:58 +00:00
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
func scanDir(rootDir string, infos chan fileInfo) {
|
|
|
|
defer close(infos)
|
2015-12-20 14:18:58 +00:00
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err == nil {
|
|
|
|
infos <- fileInfo{FileInfo: info, path: path}
|
|
|
|
}
|
2015-12-20 14:18:58 +00:00
|
|
|
|
2018-12-08 19:18:51 +00:00
|
|
|
return err
|
|
|
|
})
|
2015-12-20 14:18:58 +00:00
|
|
|
}
|