nginx-ui/internal/backup/backup_zip.go
2025-03-29 19:52:50 +08:00

290 lines
6.6 KiB
Go

package backup
import (
"archive/zip"
"bytes"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
"github.com/uozi-tech/cosy"
)
// createZipArchive creates a zip archive from a directory
func createZipArchive(zipPath, srcDir string) error {
// Create a new zip file
zipFile, err := os.Create(zipPath)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipFile, err.Error())
}
defer zipFile.Close()
// Create a new zip writer
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// Walk through all files in the source directory
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Get relative path
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
// Skip if it's the source directory itself
if relPath == "." {
return nil
}
// Check if it's a symlink
if info.Mode()&os.ModeSymlink != 0 {
// Get target of symlink
linkTarget, err := os.Readlink(path)
if err != nil {
return cosy.WrapErrorWithParams(ErrReadSymlink, err.Error())
}
// Create symlink entry in zip
header := &zip.FileHeader{
Name: relPath,
Method: zip.Deflate,
}
header.SetMode(info.Mode())
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
}
// Write link target as content (common way to store symlinks in zip)
_, err = writer.Write([]byte(linkTarget))
if err != nil {
return cosy.WrapErrorWithParams(ErrCopyContent, relPath)
}
return nil
}
// Create zip header
header, err := zip.FileInfoHeader(info)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipHeader, err.Error())
}
// Set relative path as name
header.Name = relPath
if info.IsDir() {
header.Name += "/"
}
// Set compression method
header.Method = zip.Deflate
// Create zip entry writer
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
}
// Skip if it's a directory
if info.IsDir() {
return nil
}
// Open source file
source, err := os.Open(path)
if err != nil {
return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
}
defer source.Close()
// Copy to zip
_, err = io.Copy(writer, source)
return err
})
return err
}
// createZipArchiveFromFiles creates a zip archive from a list of files
func createZipArchiveFromFiles(zipPath string, files []string) error {
// Create a new zip file
zipFile, err := os.Create(zipPath)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipFile, err.Error())
}
defer zipFile.Close()
// Create a new zip writer
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// Add each file to the zip
for _, file := range files {
// Get file info
info, err := os.Stat(file)
if err != nil {
return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
}
// Create zip header
header, err := zip.FileInfoHeader(info)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipHeader, err.Error())
}
// Set base name as header name
header.Name = filepath.Base(file)
// Set compression method
header.Method = zip.Deflate
// Create zip entry writer
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
}
// Open source file
source, err := os.Open(file)
if err != nil {
return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
}
defer source.Close()
// Copy to zip
_, err = io.Copy(writer, source)
if err != nil {
return cosy.WrapErrorWithParams(ErrCopyContent, file)
}
}
return nil
}
// calculateFileHash calculates the SHA-256 hash of a file
func calculateFileHash(filePath string) (string, error) {
// Open file
file, err := os.Open(filePath)
if err != nil {
return "", cosy.WrapErrorWithParams(ErrReadFile, filePath)
}
defer file.Close()
// Create hash
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", cosy.WrapErrorWithParams(ErrCalculateHash, err.Error())
}
// Return hex hash
return hex.EncodeToString(hash.Sum(nil)), nil
}
// createZipArchiveToBuffer creates a zip archive of files in the specified directory
// and writes the zip content to the provided buffer
func createZipArchiveToBuffer(buffer *bytes.Buffer, sourceDir string) error {
// Create a zip writer that writes to the buffer
zipWriter := zip.NewWriter(buffer)
defer zipWriter.Close()
// Walk through all files in the source directory
err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip the source directory itself
if path == sourceDir {
return nil
}
// Get the relative path to the source directory
relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return err
}
// Check if it's a symlink
if info.Mode()&os.ModeSymlink != 0 {
// Get target of symlink
linkTarget, err := os.Readlink(path)
if err != nil {
return cosy.WrapErrorWithParams(ErrReadSymlink, err.Error())
}
// Create symlink entry in zip
header := &zip.FileHeader{
Name: relPath,
Method: zip.Deflate,
}
header.SetMode(info.Mode())
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
}
// Write link target as content
_, err = writer.Write([]byte(linkTarget))
if err != nil {
return cosy.WrapErrorWithParams(ErrCopyContent, relPath)
}
return nil
}
// Create a zip header from the file info
header, err := zip.FileInfoHeader(info)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipHeader, err.Error())
}
// Set the name to be relative to the source directory
header.Name = relPath
// Set the compression method
if !info.IsDir() {
header.Method = zip.Deflate
}
// Create the entry in the zip file
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
}
// If it's a directory, we're done
if info.IsDir() {
return nil
}
// Open the source file
file, err := os.Open(path)
if err != nil {
return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
}
defer file.Close()
// Copy the file contents to the zip entry
_, err = io.Copy(writer, file)
if err != nil {
return cosy.WrapErrorWithParams(ErrCopyContent, relPath)
}
return nil
})
if err != nil {
return err
}
// Close the zip writer to ensure all data is written
return zipWriter.Close()
}