kawipiko/sources/cmd/archiver/archiver.go

991 lines
30 KiB
Go
Raw Normal View History

package archiver
import "crypto/sha256"
import "encoding/hex"
import "encoding/json"
import "flag"
import "fmt"
import "io"
import "io/ioutil"
import "log"
import "net/http"
import "path/filepath"
import "os"
import "sort"
import "strings"
import "syscall"
import "time"
import "github.com/colinmarc/cdb"
import "go.etcd.io/bbolt"
import . "github.com/volution/kawipiko/lib/common"
import . "github.com/volution/kawipiko/lib/archiver"
type context struct {
cdbWriter *cdb.Writer
cdbWriteCount int
cdbWriteKeySize int
cdbWriteDataSize int
storedFilePaths []string
storedFolderPaths []string
storedDataMeta map[string]bool
storedDataContent map[string]bool
storedDataContentMeta map[string]map[string]string
storedFiles map[[2]uint64][2]string
storedKeys map[string]string
archivedReferences uint
compress string
compressCache *bbolt.DB
dataUncompressedCount int
dataUncompressedSize int
dataCompressedCount int
dataCompressedSize int
includeIndex bool
includeStripped bool
includeCache bool
includeEtag bool
includeFileListing bool
includeFolderListing bool
progress bool
progressStarted time.Time
progressLast time.Time
debug bool
}
func archiveFile (_context *context, _pathResolved string, _pathInArchive string, _name string) (error) {
var _file *os.File
if _file_0, _error := os.Open (_pathResolved); _error == nil {
_file = _file_0
} else {
return _error
}
defer _file.Close ()
var _fileId [2]uint64
if _stat, _error := _file.Stat (); _error == nil {
_stat := _stat.Sys()
if _stat, _ok := _stat.(*syscall.Stat_t); _ok {
_fileId = [2]uint64 { uint64 (_stat.Dev), uint64 (_stat.Ino) }
} else {
return fmt.Errorf ("[6578d2d7] failed `stat`-ing: `%s`!", _pathResolved)
}
}
var _wasStored bool
var _fingerprintContent string
var _fingerprintMeta string
if _fingerprints, _wasStored_0 := _context.storedFiles[_fileId]; _wasStored_0 {
_fingerprintContent = _fingerprints[0]
_fingerprintMeta = _fingerprints[1]
_wasStored = true
}
var _dataContent []byte
var _dataMeta map[string]string
var _dataMetaRaw []byte
if ! _wasStored {
if _dataContent_0, _error := ioutil.ReadAll (_file); _error == nil {
_dataContent = _dataContent_0
} else {
return _error
}
if _fingerprintContent_0, _dataContent_0, _dataMeta_0, _error := prepareDataContent (_context, _pathResolved, _pathInArchive, _name, _dataContent, ""); _error != nil {
return _error
} else {
_fingerprintContent = _fingerprintContent_0
_dataContent = _dataContent_0
_dataMeta = _dataMeta_0
}
if _fingerprintMeta_0, _dataMetaRaw_0, _error := prepareDataMeta (_context, _dataMeta); _error != nil {
return _error
} else {
_fingerprintMeta = _fingerprintMeta_0
_dataMetaRaw = _dataMetaRaw_0
}
}
if _context.includeStripped {
for _, _suffix := range StripSuffixes {
if strings.HasSuffix (_pathInArchive, _suffix) {
_pathInArchive := _pathInArchive [: len (_pathInArchive) - len (_suffix)]
if _error := archiveReference (_context, NamespaceFilesContent, _pathInArchive, _fingerprintContent, _fingerprintMeta); _error != nil {
return _error
}
break
}
}
}
if _error := archiveReference (_context, NamespaceFilesContent, _pathInArchive, _fingerprintContent, _fingerprintMeta); _error != nil {
return _error
}
if _dataMetaRaw != nil {
if _error := archiveDataMeta (_context, _fingerprintMeta, _dataMetaRaw); _error != nil {
return _error
}
}
if _dataContent != nil {
if _error := archiveDataContent (_context, _fingerprintContent, _dataContent); _error != nil {
return _error
}
}
if ! _wasStored {
_context.storedFiles[_fileId] = [2]string { _fingerprintContent, _fingerprintMeta }
}
return nil
}
func archiveFolder (_context *context, _pathResolved string, _pathInArchive string, _names []string, _stats map[string]os.FileInfo) (error) {
type Entry struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Size uint64 `json:"size,omitempty"`
}
type Folder struct {
Entries []Entry `json:"entries,omitempty"`
Indices []string `json:"indices,omitempty"`
}
_entries := make ([]Entry, 0, len (_names))
if _context.includeFolderListing {
for _, _name := range _names {
_entry := Entry {
Name : _name,
Type : "unknown",
}
_stat := _stats[_name]
_statMode := _stat.Mode ()
if _statMode.IsRegular () {
_entry.Type = "file"
_entry.Size = uint64 (_stat.Size ())
} else if _statMode.IsDir () {
_entry.Type = "folder"
}
_entries = append (_entries, _entry)
}
}
_indexNames := make ([]string, 0, 4)
if _context.includeIndex {
var _indexNameFirst string
for _, _indexName := range IndexNames {
_indexNameFound := sort.SearchStrings (_names, _indexName)
if _indexNameFound == len (_names) {
continue
}
if _names[_indexNameFound] != _indexName {
continue
}
_stat := _stats[_indexName]
_statMode := _stat.Mode ()
if ! _statMode.IsRegular () {
continue
}
if _indexNameFirst == "" {
_indexNameFirst = _indexName
}
_indexNames = append (_indexNames, _indexName)
}
if _indexNameFirst != "" {
_indexPathResolved := filepath.Join (_pathResolved, _indexNameFirst)
_indexPathInArchive := _pathInArchive + "/"
if _pathInArchive == "/" {
_indexPathInArchive = "/"
}
archiveFile (_context, _indexPathResolved, _indexPathInArchive, _indexNameFirst)
}
}
if ! _context.includeFolderListing {
return nil
}
_folder := Folder {
Entries : _entries,
Indices : _indexNames,
}
var _data []byte
if _data_0, _error := json.Marshal (&_folder); _error == nil {
_data = _data_0
} else {
return _error
}
if _, _, _error := archiveReferenceAndData (_context, NamespaceFoldersContent, _pathResolved, _pathInArchive, "", _data, MimeTypeJson); _error != nil {
return _error
}
return nil
}
func archiveReferenceAndData (_context *context, _namespace string, _pathResolved string, _pathInArchive string, _name string, _dataContent []byte, _dataType string) (string, string, error) {
var _fingerprintContent string
var _fingerprintMeta string
var _dataMeta map[string]string
var _dataMetaRaw []byte
if _fingerprintContent_0, _dataContent_0, _dataMeta_0, _error := prepareDataContent (_context, _pathResolved, _pathInArchive, _name, _dataContent, _dataType); _error != nil {
return "", "", _error
} else {
_fingerprintContent = _fingerprintContent_0
_dataContent = _dataContent_0
_dataMeta = _dataMeta_0
}
if _fingerprintMeta_0, _dataMetaRaw_0, _error := prepareDataMeta (_context, _dataMeta); _error != nil {
return "", "", _error
} else {
_fingerprintMeta = _fingerprintMeta_0
_dataMetaRaw = _dataMetaRaw_0
}
if _error := archiveReference (_context, _namespace, _pathInArchive, _fingerprintContent, _fingerprintMeta); _error != nil {
return "", "", _error
}
if _dataMetaRaw != nil {
if _error := archiveDataMeta (_context, _fingerprintMeta, _dataMetaRaw); _error != nil {
return "", "", _error
}
}
if _dataContent != nil {
if _error := archiveDataContent (_context, _fingerprintContent, _dataContent); _error != nil {
return "", "", _error
}
}
return _fingerprintContent, _fingerprintMeta, nil
}
func archiveDataContent (_context *context, _fingerprintContent string, _dataContent []byte) (error) {
if _wasStored, _ := _context.storedDataContent[_fingerprintContent]; _wasStored {
return fmt.Errorf ("[256cde78] data content already stored: `%s`!", _fingerprintContent)
}
{
var _key string
if _key_0, _error := prepareKey (_context, NamespaceDataContent, _fingerprintContent); _error == nil {
_key = fmt.Sprintf ("%s:%s", NamespaceDataContent, _key_0)
} else {
return _error
}
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [085d83ec] data-content ++ `%s` %d\n", _key, len (_dataContent))
}
if _error := _context.cdbWriter.Put ([]byte (_key), _dataContent); _error != nil {
return _error
}
_context.cdbWriteCount += 1
_context.cdbWriteKeySize += len (_key)
_context.cdbWriteDataSize += len (_dataContent)
}
_context.storedDataContent[_fingerprintContent] = true
return nil
}
func archiveDataMeta (_context *context, _fingerprintMeta string, _dataMeta []byte) (error) {
if _wasStored, _ := _context.storedDataMeta[_fingerprintMeta]; _wasStored {
return fmt.Errorf ("[2918c4e2] data meta already stored: `%s`!", _fingerprintMeta)
}
{
var _key string
if _key_0, _error := prepareKey (_context, NamespaceDataMetadata, _fingerprintMeta); _error == nil {
_key = fmt.Sprintf ("%s:%s", NamespaceDataMetadata, _key_0)
} else {
return _error
}
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [07737b98] data-meta ++ `%s` %d\n", _key, len (_dataMeta))
}
if _error := _context.cdbWriter.Put ([]byte (_key), _dataMeta); _error != nil {
return _error
}
_context.cdbWriteCount += 1
_context.cdbWriteKeySize += len (_key)
_context.cdbWriteDataSize += len (_dataMeta)
}
_context.storedDataMeta[_fingerprintMeta] = true
return nil
}
func archiveReference (_context *context, _namespace string, _pathInArchive string, _fingerprintContent string, _fingerprintMeta string) (error) {
switch _namespace {
case NamespaceFilesContent :
_context.storedFilePaths = append (_context.storedFilePaths, _pathInArchive)
case NamespaceFoldersContent :
_context.storedFolderPaths = append (_context.storedFolderPaths, _pathInArchive)
default :
return fmt.Errorf ("[051a102a]")
}
_context.archivedReferences += 1
_key := fmt.Sprintf ("%s:%s", _namespace, _pathInArchive)
var _keyMeta, _keyContent string
if _key_0, _error := prepareKey (_context, NamespaceDataMetadata, _fingerprintMeta); _error == nil {
_keyMeta = _key_0
} else {
return _error
}
if _key_0, _error := prepareKey (_context, NamespaceDataContent, _fingerprintContent); _error == nil {
_keyContent = _key_0
} else {
return _error
}
_references := fmt.Sprintf ("%s:%s", _keyMeta, _keyContent)
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [2b9c053a] reference ++ `%s` :: `%s` -> `%s` ~ `%s`\n", _namespace, _pathInArchive, _keyMeta, _keyContent)
}
if _error := _context.cdbWriter.Put ([]byte (_key), []byte (_references)); _error != nil {
return _error
}
_context.cdbWriteCount += 1
_context.cdbWriteKeySize += len (_key)
_context.cdbWriteDataSize += len (_references)
if _context.progress {
if _context.archivedReferences <= 1 {
_context.progressLast = time.Now ()
}
if ((_context.archivedReferences % 1000) == 0) || (((_context.archivedReferences % 10) == 0) && (time.Since (_context.progressLast) .Seconds () >= 6)) {
2021-12-15 15:36:09 +00:00
log.Printf ("[ii] [5193276e] pogress -- %0.2f minutes -- %d files, %d folders, %0.2f MiB (%0.2f MiB/s) -- %d compressed (%0.2f MiB, %0.2f%%) -- %d records (%0.2f MiB)\n",
time.Since (_context.progressStarted) .Minutes (),
len (_context.storedFilePaths),
len (_context.storedFolderPaths),
float32 (_context.dataUncompressedSize) / 1024 / 1024,
float64 (_context.dataUncompressedSize) / 1024 / 1024 / (time.Since (_context.progressStarted) .Seconds () + 0.001),
_context.dataCompressedCount,
float32 (_context.dataUncompressedSize - _context.dataCompressedSize) / 1024 / 1024,
(float32 (_context.dataUncompressedSize - _context.dataCompressedSize) / float32 (_context.dataUncompressedSize) * 100),
_context.cdbWriteCount,
float32 (_context.cdbWriteKeySize + _context.cdbWriteDataSize) / 1024 / 1024,
)
_context.progressLast = time.Now ()
}
}
return nil
}
func prepareDataContent (_context *context, _pathResolved string, _pathInArchive string, _name string, _dataContent []byte, _dataType string) (string, []byte, map[string]string, error) {
_fingerprintContentRaw := sha256.Sum256 (_dataContent)
_fingerprintContent := hex.EncodeToString (_fingerprintContentRaw[:])
if _wasStored, _ := _context.storedDataContent[_fingerprintContent]; _wasStored {
_dataMeta := _context.storedDataContentMeta[_fingerprintContent]
return _fingerprintContent, nil, _dataMeta, nil
}
if (_dataType == "") && (_name != "") {
_extension := filepath.Ext (_name)
if _extension != "" {
_extension = _extension[1:]
}
_dataType, _ = MimeTypesByExtension[_extension]
}
if _dataType == "" {
_dataType = http.DetectContentType (_dataContent)
}
if _dataType == "" {
_dataType = MimeTypeRaw
}
_dataEncoding := "identity"
_dataUncompressedSize := len (_dataContent)
_dataSize := _dataUncompressedSize
var _compressAlgorithm string
var _compressEncoding string
if _algorithm_0, _encoding_0, _error := CompressEncoding (_context.compress); _error == nil {
_compressAlgorithm = _algorithm_0
_compressEncoding = _encoding_0
} else {
return "", nil, nil, _error
}
if _compressAlgorithm != "identity" {
var _dataCompressed []byte
var _dataCompressedCached bool
if _context.compressCache != nil {
_cacheTxn, _error := _context.compressCache.Begin (false)
if _error != nil {
AbortError (_error, "[91a5b78a] unexpected compression cache error!")
}
_cacheBucket := _cacheTxn.Bucket ([]byte (_compressAlgorithm))
if _cacheBucket != nil {
_dataCompressed = _cacheBucket.Get (_fingerprintContentRaw[:])
_dataCompressedCached = _dataCompressed != nil
}
if _error := _cacheTxn.Rollback (); _error != nil {
AbortError (_error, "[a06cfe46] unexpected compression cache error!")
}
}
if _dataCompressed == nil {
if _data_0, _error := Compress (_dataContent, _compressAlgorithm); _error == nil {
_dataCompressed = _data_0
} else {
return "", nil, nil, _error
}
}
_dataCompressedSize := len (_dataCompressed)
_dataCompressedDelta := _dataUncompressedSize - _dataCompressedSize
2021-12-15 15:36:09 +00:00
_dataCompressedRatio := float32 (_dataCompressedDelta) * 100 / float32 (_dataUncompressedSize)
_accepted := false
_accepted = _accepted || ((_dataUncompressedSize > (1024 * 1024)) && (_dataCompressedRatio >= 5))
_accepted = _accepted || ((_dataUncompressedSize > (64 * 1024)) && (_dataCompressedRatio >= 10))
_accepted = _accepted || ((_dataUncompressedSize > (32 * 1024)) && (_dataCompressedRatio >= 15))
_accepted = _accepted || ((_dataUncompressedSize > (16 * 1024)) && (_dataCompressedRatio >= 20))
_accepted = _accepted || ((_dataUncompressedSize > (8 * 1024)) && (_dataCompressedRatio >= 25))
_accepted = _accepted || ((_dataUncompressedSize > (4 * 1024)) && (_dataCompressedRatio >= 30))
_accepted = _accepted || ((_dataUncompressedSize > (2 * 1024)) && (_dataCompressedRatio >= 35))
_accepted = _accepted || ((_dataUncompressedSize > (1 * 1024)) && (_dataCompressedRatio >= 40))
_accepted = _accepted || (_dataCompressedRatio >= 90)
if _accepted {
_dataContent = _dataCompressed
_dataEncoding = _compressEncoding
_dataSize = _dataCompressedSize
}
if (_context.compressCache != nil) && !_dataCompressedCached && _accepted {
_cacheTxn, _error := _context.compressCache.Begin (true)
if _error != nil {
AbortError (_error, "[ddbe6a70] unexpected compression cache error!")
}
_cacheBucket := _cacheTxn.Bucket ([]byte (_compressAlgorithm))
if _cacheBucket == nil {
if _bucket_0, _error := _cacheTxn.CreateBucket ([]byte (_compressAlgorithm)); _error == nil {
_cacheBucket = _bucket_0
} else {
AbortError (_error, "[b7766792] unexpected compression cache error!")
}
}
if _error := _cacheBucket.Put (_fingerprintContentRaw[:], _dataCompressed); _error != nil {
AbortError (_error, "[51d57220] unexpected compression cache error!")
}
if _error := _cacheTxn.Commit (); _error != nil {
AbortError (_error, "[a47c7c10] unexpected compression cache error!")
}
}
if _dataSize < _dataUncompressedSize {
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [271e48d6] compress -- %.1f%% %d (%d) `%s`\n", _dataCompressedRatio, _dataCompressedSize, _dataCompressedDelta, _pathInArchive)
}
} else {
2021-12-15 15:36:09 +00:00
if _context.debug || _context.progress {
log.Printf ("[dd] [2174c2d6] compress-NOK -- %.1f%% %d (%d) `%s`\n", _dataCompressedRatio, _dataCompressedSize, _dataCompressedDelta, _pathInArchive)
}
}
} else {
if _context.debug && (_context.compress != "identity") {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [a9d7a281] compress-NOK -- %d `%s`\n", _dataUncompressedSize, _pathInArchive)
}
}
_context.dataUncompressedSize += _dataUncompressedSize
_context.dataUncompressedCount += 1
_context.dataCompressedSize += _dataSize
if _dataSize != _dataUncompressedSize {
_context.dataCompressedCount += 1
}
_dataMeta := make (map[string]string, 16)
// _dataMeta["Content-Length"] = fmt.Sprintf ("%d", _dataSize)
_dataMeta["Content-Type"] = _dataType
_dataMeta["Content-Encoding"] = _dataEncoding
if _context.includeCache {
_dataMeta["Cache-Control"] = "public, immutable, max-age=3600"
}
if _context.includeEtag {
_dataMeta["ETag"] = _fingerprintContent
}
_context.storedDataContentMeta[_fingerprintContent] = _dataMeta
return _fingerprintContent, _dataContent, _dataMeta, nil
}
func prepareDataMeta (_context *context, _dataMeta map[string]string) (string, []byte, error) {
var _dataMetaRaw []byte
if _dataMetaRaw_0, _error := MetadataEncode (_dataMeta); _error == nil {
_dataMetaRaw = _dataMetaRaw_0
} else {
return "", nil, _error
}
_fingerprintMetaRaw := sha256.Sum256 (_dataMetaRaw)
_fingerprintMeta := hex.EncodeToString (_fingerprintMetaRaw[:])
if _wasStored, _ := _context.storedDataMeta[_fingerprintMeta]; _wasStored {
return _fingerprintMeta, nil, nil
}
return _fingerprintMeta, _dataMetaRaw, nil
}
func prepareKey (_context *context, _namespace string, _fingerprint string) (string, error) {
_qualified := fmt.Sprintf ("%s:%s", _namespace, _fingerprint)
if _key, _found := _context.storedKeys[_qualified]; _found {
return _key, nil
}
_keyIndex := len (_context.storedKeys) + 1
if _keyIndex >= (1 << 32) {
return "", fmt.Errorf ("[aba09b4d] maximum stored keys reached!")
}
_key := fmt.Sprintf ("%x", _keyIndex)
_context.storedKeys[_qualified] = _key
return _key, nil
}
func walkPath (_context *context, _pathResolved string, _pathInArchive string, _name string, _recursed map[string]uint, _recurse bool) (os.FileInfo, error) {
if _recursed == nil {
_recursed = make (map[string]uint, 128)
}
_pathOriginal := _pathResolved
var _stat os.FileInfo
if _stat_0, _error := os.Lstat (_pathResolved); _error == nil {
_stat = _stat_0
} else {
return nil, _error
}
_statMode := _stat.Mode ()
_isSymlink := false
if (_stat.Mode () & os.ModeSymlink) != 0 {
_isSymlink = true
if _stat_0, _error := os.Stat (_pathResolved); _error == nil {
_stat = _stat_0
} else {
return nil, _error
}
}
_statMode = _stat.Mode ()
if ! _recurse {
return _stat, nil
}
if _isSymlink {
if _pathResolved_0, _error := filepath.EvalSymlinks (_pathResolved); _error == nil {
_pathResolved = _pathResolved_0
} else {
return nil, _error
}
}
if _isSymlink && _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [7eed523e] symlink -- `%s` -> `%s`\n", _pathInArchive, _pathResolved)
}
if _statMode.IsRegular () {
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [da429eaa] file -- `%s`\n", _pathInArchive)
}
if _error := archiveFile (_context, _pathResolved, _pathInArchive, _name); _error != nil {
return nil, _error
}
return _stat, nil
} else if _statMode.IsDir () {
_wasRecursed, _ := _recursed[_pathResolved]
if _wasRecursed > 0 {
log.Printf ("[ww] [2e1744c9] detected directory loop for `%s` resolving to `%s`; ignoring!\n", _pathOriginal, _pathResolved)
return _stat, nil
}
_recursed[_pathResolved] = _wasRecursed + 1
_childsName := make ([]string, 0, 16)
_childsPathResolved := make (map[string]string, 16)
_childsPathInArchive := make (map[string]string, 16)
_childsStat := make (map[string]os.FileInfo, 16)
var _wildcardName string
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [2d22d910] folder |> `%s`\n", _pathInArchive)
}
if _stream, _error := os.Open (_pathResolved); _error == nil {
defer _stream.Close ()
_loop : for {
switch _buffer, _error := _stream.Readdir (1024); _error {
case nil :
2021-12-15 15:36:09 +00:00
if _context.debug && (len (_buffer) > 0) {
log.Printf ("[dd] [d4c30c66] folder |~ `%s` %d\n", _pathInArchive, len (_buffer))
}
for _, _childStat := range _buffer {
_childName := _childStat.Name ()
_childPathResolved := filepath.Join (_pathResolved, _childName)
_childPathInArchive := filepath.Join (_pathInArchive, _childName)
if _childStat_0, _error := walkPath (_context, _childPathResolved, _childPathInArchive, _childName, _recursed, false); _error == nil {
_childStat = _childStat_0
} else {
return nil, _error
}
_childsPathResolved[_childName] = _childPathResolved
_childsPathInArchive[_childName] = _childPathInArchive
_childsStat[_childName] = _childStat
if strings.HasPrefix (_childName, "_wildcard.") {
_wildcardName = _childName
continue
}
if ShouldSkipName (_childName) {
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [f11b5ba1] skip !! `%s`\n", _childPathInArchive)
}
continue
}
_childsName = append (_childsName, _childName)
}
case io.EOF :
break _loop
default :
return nil, _error
}
}
}
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [2d61b965] folder |< `%s`\n", _pathInArchive)
}
sort.Strings (_childsName)
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [a4475a48] folder -- `%s`\n", _pathInArchive)
}
if _error := archiveFolder (_context, _pathResolved, _pathInArchive, _childsName, _childsStat); _error != nil {
return nil, _error
}
if _wildcardName != "" {
_childPathInArchive := filepath.Join (_pathInArchive, "*")
if _, _error := walkPath (_context, _childsPathResolved[_wildcardName], _childPathInArchive, _wildcardName, _recursed, true); _error != nil {
return nil, _error
}
}
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [ce1fe181] folder |> `%s`\n", _pathInArchive)
}
for _, _childName := range _childsName {
if _childsStat[_childName] .Mode () .IsRegular () {
if _, _error := walkPath (_context, _childsPathResolved[_childName], _childsPathInArchive[_childName], _childName, _recursed, true); _error != nil {
return nil, _error
}
}
}
for _, _childName := range _childsName {
if _childsStat[_childName] .Mode () .IsDir () {
if _, _error := walkPath (_context, _childsPathResolved[_childName], _childsPathInArchive[_childName], _childName, _recursed, true); _error != nil {
return nil, _error
}
}
}
for _, _childName := range _childsName {
if (! _childsStat[_childName] .Mode () .IsRegular ()) && (! _childsStat[_childName] .Mode () .IsDir ()) {
if _, _error := walkPath (_context, _childsPathResolved[_childName], _childsPathInArchive[_childName], _childName, _recursed, true); _error != nil {
return nil, _error
}
}
}
if _context.debug {
2021-12-15 15:36:09 +00:00
log.Printf ("[dd] [6b7de03b] folder |< `%s`\n", _pathInArchive)
}
_recursed[_pathResolved] = _wasRecursed
return _stat, nil
} else {
return nil, fmt.Errorf ("[d9b836d7] unexpected file type for `%s`: `%s`!", _pathResolved, _statMode)
}
}
func Main () () {
Main_0 (main_0)
}
func main_0 () (error) {
var _sourcesFolder string
var _archiveFile string
var _compress string
var _compressCache string
var _includeIndex bool
var _includeStripped bool
var _includeCache bool
var _includeEtag bool
var _includeFileListing bool
var _includeFolderListing bool
var _progress bool
var _debug bool
{
_flags := flag.NewFlagSet ("kawipiko-archiver", flag.ContinueOnError)
_flags.Usage = func () () {
fmt.Fprintf (os.Stderr, "%s",
`
==== kawipiko -- blazingly fast static HTTP server ====
| Documentation, issues and sources:
| * https://github.com/volution/kawipiko
| Authors:
| * Ciprian Dorin Craciun
| ciprian@volution.ro
| ciprian.craciun@gmail.com
| https://volution.ro/ciprian
-----------------------------------------------------------
kawipiko-archiver
--sources <path>
--archive <path>
--compress <gzip | zopfli | brotli | identity>
--compress-cache <path>
--exclude-index
--exclude-strip
--exclude-cache
--include-etag
--exclude-file-listing
--include-folder-listing
--progress
--debug
** for details see:
https://github.com/volution/kawipiko#kawipiko-archiver
`)
}
_sourcesFolder_0 := _flags.String ("sources", "", "")
_archiveFile_0 := _flags.String ("archive", "", "")
_compress_0 := _flags.String ("compress", "", "")
_compressCache_0 := _flags.String ("compress-cache", "", "")
_excludeIndex_0 := _flags.Bool ("exclude-index", false, "")
_excludeStripped_0 := _flags.Bool ("exclude-strip", false, "")
_excludeCache_0 := _flags.Bool ("exclude-cache", false, "")
_includeEtag_0 := _flags.Bool ("include-etag", false, "")
_excludeFileListing_0 := _flags.Bool ("exclude-file-listing", false, "")
_includeFolderListing_0 := _flags.Bool ("include-folder-listing", false, "")
_progress_0 := _flags.Bool ("progress", false, "")
_debug_0 := _flags.Bool ("debug", false, "")
FlagsParse (_flags, 0, 0)
_sourcesFolder = *_sourcesFolder_0
_archiveFile = *_archiveFile_0
_compress = *_compress_0
_compressCache = *_compressCache_0
_includeIndex = ! *_excludeIndex_0
_includeStripped = ! *_excludeStripped_0
_includeCache = ! *_excludeCache_0
_includeEtag = *_includeEtag_0
_includeFileListing = ! *_excludeFileListing_0
_includeFolderListing = *_includeFolderListing_0
_progress = *_progress_0
_debug = *_debug_0
if _sourcesFolder == "" {
AbortError (nil, "[515ee462] expected sources folder argument!")
}
if _archiveFile == "" {
AbortError (nil, "[5e8da985] expected archive file argument!")
}
}
var _cdbWriter *cdb.Writer
if _db_0, _error := cdb.Create (_archiveFile); _error == nil {
_cdbWriter = _db_0
} else {
AbortError (_error, "[85234ba0] failed creating archive (while opening)!")
}
var _compressCacheDb *bbolt.DB
if _compressCache != "" {
_options := bbolt.Options {
PageSize : 16 * 1024,
InitialMmapSize : 1 * 1024 * 1024,
NoFreelistSync : true,
NoSync : true,
}
if _db_0, _error := bbolt.Open (_compressCache, 0600, &_options); _error == nil {
_compressCacheDb = _db_0
} else {
AbortError (_error, "[eaff07f6] failed opening compression cache!")
}
}
_context := & context {
cdbWriter : _cdbWriter,
storedFilePaths : make ([]string, 0, 16 * 1024),
storedFolderPaths : make ([]string, 0, 16 * 1024),
storedDataMeta : make (map[string]bool, 16 * 1024),
storedDataContent : make (map[string]bool, 16 * 1024),
storedDataContentMeta : make (map[string]map[string]string, 16 * 1024),
storedFiles : make (map[[2]uint64][2]string, 16 * 1024),
storedKeys : make (map[string]string, 16 * 1024),
compress : _compress,
compressCache : _compressCacheDb,
includeIndex : _includeIndex,
includeStripped : _includeStripped,
includeCache : _includeCache,
includeEtag : _includeEtag,
includeFileListing : _includeFileListing,
includeFolderListing : _includeFolderListing,
progress : _progress,
debug : _debug,
}
_context.progressStarted = time.Now ()
if _error := _context.cdbWriter.Put ([]byte (NamespaceSchemaVersion), []byte (CurrentSchemaVersion)); _error != nil {
AbortError (_error, "[43228812] failed writing archive!")
}
_context.cdbWriteCount += 1
_context.cdbWriteKeySize += len (NamespaceSchemaVersion)
_context.cdbWriteDataSize += len (CurrentSchemaVersion)
if _, _error := walkPath (_context, _sourcesFolder, "/", filepath.Base (_sourcesFolder), nil, true); _error != nil {
AbortError (_error, "[b6a19ef4] failed walking folder!")
}
if _includeFileListing {
_buffer := make ([]byte, 0, 1024 * 1024)
for _, _path := range _context.storedFilePaths {
_buffer = append (_buffer, _path ...)
_buffer = append (_buffer, '\n')
}
if _error := _context.cdbWriter.Put ([]byte (NamespaceFilesIndex), _buffer); _error != nil {
AbortError (_error, "[1dbdde05] failed writing archive!")
}
_context.cdbWriteCount += 1
_context.cdbWriteKeySize += len (NamespaceFilesIndex)
_context.cdbWriteDataSize += len (_buffer)
}
if _includeFolderListing {
_buffer := make ([]byte, 0, 1024 * 1024)
for _, _path := range _context.storedFolderPaths {
_buffer = append (_buffer, _path ...)
_buffer = append (_buffer, '\n')
}
if _error := _context.cdbWriter.Put ([]byte (NamespaceFoldersIndex), _buffer); _error != nil {
AbortError (_error, "[e2dd2de0] failed writing archive!")
}
_context.cdbWriteCount += 1
_context.cdbWriteKeySize += len (NamespaceFilesIndex)
_context.cdbWriteDataSize += len (_buffer)
}
if _error := _context.cdbWriter.Close (); _error != nil {
AbortError (_error, "[bbfb8478] failed creating archive (while closing)!")
}
_context.cdbWriter = nil
if _context.compressCache != nil {
if _error := _context.compressCache.Close (); _error != nil {
AbortError (_error, "[53cbe28d] failed closing compression cache!")
}
}
if true {
2021-12-15 15:36:09 +00:00
log.Printf ("[ii] [56f63575] completed -- %0.2f minutes -- %d files, %d folders, %0.2f MiB (%0.2f MiB/s) -- %d compressed (%0.2f MiB, %0.2f%%) -- %d records (%0.2f MiB)\n",
time.Since (_context.progressStarted) .Minutes (),
len (_context.storedFilePaths),
len (_context.storedFolderPaths),
float32 (_context.dataUncompressedSize) / 1024 / 1024,
float64 (_context.dataUncompressedSize) / 1024 / 1024 / (time.Since (_context.progressStarted) .Seconds () + 0.001),
_context.dataCompressedCount,
float32 (_context.dataUncompressedSize - _context.dataCompressedSize) / 1024 / 1024,
(float32 (_context.dataUncompressedSize - _context.dataCompressedSize) / float32 (_context.dataUncompressedSize) * 100),
_context.cdbWriteCount,
float32 (_context.cdbWriteKeySize + _context.cdbWriteDataSize) / 1024 / 1024,
)
}
return nil
}