mirror of
https://github.com/nxshock/backuper.git
synced 2024-11-27 00:11:01 +05:00
252 lines
6.5 KiB
Go
252 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/klauspost/compress/zstd"
|
|
)
|
|
|
|
func (b *Config) fileList(fileNames chan FileInfo) {
|
|
errorCount := 0
|
|
|
|
for _, mask := range b.Patterns {
|
|
if mask.Recursive {
|
|
err := filepath.WalkDir(mask.Path, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
errorCount++
|
|
b.logf(Error, "Ошибка при поиске файлов: %v\n", err)
|
|
if b.StopOnAnyError {
|
|
return fmt.Errorf("ошибка при переборе файлов: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
path = filepath.ToSlash(path)
|
|
|
|
if !mask.Recursive && filepath.Dir(path) != mask.Path {
|
|
return nil
|
|
}
|
|
|
|
if isFilePathMatchPatterns(mask.FilePathPatternList, path) && isFileNameMatchPatterns(mask.FileNamePatternList, path) {
|
|
if !isFilePathMatchPatterns(b.GlobalExcludeFilePathPatterns, path) && !isFileNameMatchPatterns(b.GlobalExcludeFileNamePatterns, path) {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
errorCount++
|
|
b.logf(Error, "get file info error: %v", err)
|
|
if b.StopOnAnyError {
|
|
return fmt.Errorf("get file info error: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
file := FileInfo{
|
|
filePath: path,
|
|
ModificationTime: info.ModTime(),
|
|
fileSize: info.Size()}
|
|
fileNames <- file
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
b.logf(Error, "get file list error: %v\n", err)
|
|
}
|
|
} else {
|
|
allFilesAndDirs, err := filepath.Glob(filepath.Join(mask.Path, "*"))
|
|
if err != nil {
|
|
errorCount++
|
|
b.logf(Error, "get file list error: %v\n", err)
|
|
}
|
|
|
|
for _, fileOrDirPath := range allFilesAndDirs {
|
|
info, err := os.Stat(fileOrDirPath)
|
|
if err != nil {
|
|
errorCount++
|
|
b.logf(Error, "get object info error: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if info.IsDir() {
|
|
continue
|
|
}
|
|
|
|
if isFilePathMatchPatterns(mask.FilePathPatternList, fileOrDirPath) && isFileNameMatchPatterns(mask.FileNamePatternList, fileOrDirPath) {
|
|
if !isFilePathMatchPatterns(b.GlobalExcludeFilePathPatterns, fileOrDirPath) && !isFileNameMatchPatterns(b.GlobalExcludeFileNamePatterns, fileOrDirPath) {
|
|
file := FileInfo{
|
|
filePath: fileOrDirPath,
|
|
ModificationTime: info.ModTime()}
|
|
fileNames <- file
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if errorCount > 0 {
|
|
b.logf(Error, "Ошибок: %d\n", errorCount)
|
|
}
|
|
|
|
close(fileNames)
|
|
}
|
|
|
|
func (b *Config) FullBackup() error {
|
|
return b.doBackup(make(Index))
|
|
}
|
|
|
|
func (b *Config) IncrementalBackup() error {
|
|
index, err := b.index(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return b.doBackup(index)
|
|
}
|
|
|
|
func (b *Config) doBackup(index Index) error {
|
|
var suffix string
|
|
if len(index) == 0 {
|
|
suffix = "f" // Full backup - полный бекап
|
|
} else {
|
|
suffix = "i" // Инкрементальный бекап
|
|
}
|
|
|
|
filePath := filepath.Join(filepath.Dir(b.filePath), b.FileName+"_"+time.Now().Local().Format(defaulFileNameTimeFormat)+suffix+defaultExt)
|
|
|
|
var err error
|
|
filePath, err = filepath.Abs(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("ошибка при создании файла архива: %v", err)
|
|
}
|
|
b.logf(Info, "Creating new file %s...", filepath.Base(filePath))
|
|
|
|
resultArchiveFile, err := os.Create(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("ошибка при создании файла архива: %v", err)
|
|
}
|
|
|
|
compressor, err := zstd.NewWriter(resultArchiveFile, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
|
|
if err != nil {
|
|
return fmt.Errorf("ошибка при создании инициализации архиватора: %v", err)
|
|
}
|
|
|
|
tarWriter := tar.NewWriter(compressor)
|
|
|
|
b.log(Info, "Copying files...")
|
|
|
|
addedFileIndex := make(Index)
|
|
|
|
i := 0 // processed file count
|
|
addSize := int64(0) // added bytes
|
|
for k := range b.planChan(index) {
|
|
i++
|
|
addSize += k.fileSize
|
|
err := b.addFileToTarWriter(k.filePath, tarWriter)
|
|
if err != nil {
|
|
b.logf(Error, "add file error %s: %v\n", k.filePath, err)
|
|
if b.StopOnAnyError {
|
|
compressor.Close()
|
|
resultArchiveFile.Close()
|
|
os.Remove(filePath)
|
|
return fmt.Errorf("add file error: %v", err) // TODO: организовать закрытие и удаление частичного файла
|
|
}
|
|
}
|
|
addedFileIndex.AddFile(k.filePath, filepath.Base(filePath), k.ModificationTime)
|
|
}
|
|
|
|
err = tarWriter.Close()
|
|
if err != nil {
|
|
compressor.Close()
|
|
resultArchiveFile.Close()
|
|
os.Remove(filePath)
|
|
return fmt.Errorf("close tar file error: %v", err)
|
|
}
|
|
|
|
err = compressor.Close()
|
|
if err != nil {
|
|
resultArchiveFile.Close()
|
|
os.Remove(filePath)
|
|
return fmt.Errorf("close compressor error: %v", err)
|
|
}
|
|
|
|
err = resultArchiveFile.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("close file error: %v", err)
|
|
}
|
|
|
|
if i == 0 {
|
|
b.logf(Info, "No new or updated files found.")
|
|
} else if i == 1 {
|
|
b.logf(Info, "%d file added, %s.", i, sizeToApproxHuman(addSize))
|
|
} else {
|
|
b.logf(Info, "%d files added, %s.", i, sizeToApproxHuman(addSize))
|
|
}
|
|
|
|
// если не было обновлений, удалить пустой файл
|
|
if i == 0 {
|
|
err = os.Remove(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// если были обновления - обновить индексный файл
|
|
if i > 0 {
|
|
for fileName, fileHistory := range addedFileIndex {
|
|
for _, historyItem := range fileHistory {
|
|
index.AddFile(fileName, historyItem.ArchiveFileName, historyItem.ModificationTime)
|
|
}
|
|
}
|
|
|
|
err = index.Save(filepath.Join(filepath.Dir(b.filePath), indexFileName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Config) addFileToTarWriter(filePath string, tarWriter *tar.Writer) error {
|
|
b.logf(Debug, "Adding file %s...\n", filePath)
|
|
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not open file '%s', got error '%s'", filePath, err.Error())
|
|
}
|
|
defer file.Close()
|
|
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return fmt.Errorf("Could not get stat for file '%s', got error '%s'", filePath, err.Error())
|
|
}
|
|
|
|
header := &tar.Header{
|
|
Format: tar.FormatGNU,
|
|
Name: filepath.ToSlash(filePath),
|
|
Size: stat.Size(),
|
|
ModTime: stat.ModTime()}
|
|
|
|
err = tarWriter.WriteHeader(header)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not write header for file '%s', got error '%s'", filePath, err.Error())
|
|
}
|
|
|
|
_, err = io.Copy(tarWriter, file)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not copy the file '%s' data to the tarball, got error '%s'", filePath, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|