Agate/archive/archive.go
Alexander Lazarenko 7e9cea9227
Add initial implementation of Agate snapshot library
Introduces core functionality for the Agate library, including snapshot creation, restoration, listing, and deletion. Adds examples for basic usage, gRPC proto definitions, and build/configuration files such as `go.mod` and `Makefile`. The implementation establishes the framework for store integration and placeholder server functionality.
2025-04-24 02:44:16 +03:00

156 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package archive
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
type ArchiveEntryInfo struct {
Path string // Относительный путь внутри архива
Size uint64 // Размер файла в байтах (0 для директорий)
IsDir bool // Является ли запись директорией
}
var ErrFileNotFoundInArchive = errors.New("file not found in archive")
// CreateArchive создает ZIP-архив по пути targetPath из содержимого sourceDir.
func CreateArchive(sourceDir, targetPath string) error {
info, err := os.Stat(sourceDir)
if err != nil {
return fmt.Errorf("failed to stat source directory %s: %w", sourceDir, err)
}
if !info.IsDir() {
return fmt.Errorf("source %s is not a directory", sourceDir)
}
// Создаем файл для ZIP-архива
outFile, err := os.Create(targetPath)
if err != nil {
return fmt.Errorf("failed to create target archive file %s: %w", targetPath, err)
}
defer outFile.Close()
// Создаем zip.Writer
zipWriter := zip.NewWriter(outFile)
defer zipWriter.Close()
// Рекурсивно проходим по директории sourceDir
err = filepath.Walk(sourceDir, func(filePath string, fileInfo os.FileInfo, walkErr error) error {
if walkErr != nil {
return fmt.Errorf("error walking path %s: %w", filePath, walkErr)
}
// Пропускаем саму директорию sourceDir
if filePath == sourceDir {
return nil
}
// Создаем относительный путь для записи в архиве
relativePath := strings.TrimPrefix(filePath, sourceDir+string(filepath.Separator))
relativePath = filepath.ToSlash(relativePath)
// Проверяем, является ли текущий элемент директорией
if fileInfo.IsDir() {
// Для директорий добавляем "/" в конец
_, err = zipWriter.Create(relativePath + "/")
if err != nil {
return fmt.Errorf("failed to create directory entry %s in archive: %w", relativePath, err)
}
return nil
}
// Открываем файл для чтения
var fileToArchive *os.File
fileToArchive, err = os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file %s for archiving: %w", filePath, err)
}
defer fileToArchive.Close()
// Создаем запись в архиве
var zipEntryWriter io.Writer
zipEntryWriter, err = zipWriter.Create(relativePath)
if err != nil {
return fmt.Errorf("failed to create entry %s in archive: %w", relativePath, err)
}
// Копируем содержимое файла в запись архива
if _, err = io.Copy(zipEntryWriter, fileToArchive); err != nil {
return fmt.Errorf("failed to copy file content %s to archive: %w", filePath, err)
}
return nil
})
if err != nil {
os.Remove(targetPath)
return fmt.Errorf("failed during directory walk for archiving %s: %w", sourceDir, err)
}
return nil
}
// ListArchiveContents читает ZIP-архив и возвращает информацию о его содержимом.
func ListArchiveContents(archivePath string) ([]ArchiveEntryInfo, error) {
// Открываем ZIP-архив
zipReader, err := zip.OpenReader(archivePath)
if err != nil {
return nil, fmt.Errorf("failed to open archive file %s: %w", archivePath, err)
}
defer zipReader.Close()
var entries []ArchiveEntryInfo
// Проходим по всем файлам и директориям внутри архива
for _, file := range zipReader.File {
entry := ArchiveEntryInfo{
Path: file.Name,
Size: file.UncompressedSize64,
IsDir: file.FileInfo().IsDir(),
}
entries = append(entries, entry)
}
return entries, nil
}
// ExtractFileFromArchive извлекает один файл из ZIP-архива.
func ExtractFileFromArchive(archivePath, filePathInArchive string, writer io.Writer) error {
// Открываем ZIP-архив
zipReader, err := zip.OpenReader(archivePath)
if err != nil {
return fmt.Errorf("failed to open archive file %s: %w", archivePath, err)
}
defer zipReader.Close()
// Ищем файл в архиве
for _, file := range zipReader.File {
if filepath.ToSlash(file.Name) == filepath.ToSlash(filePathInArchive) {
// Проверяем, что это не директория
if file.FileInfo().IsDir() {
return fmt.Errorf("entry %s in archive is a directory", filePathInArchive)
}
// Открываем файл для чтения
readCloser, err := file.Open()
if err != nil {
return fmt.Errorf("failed to open file %s in archive: %w", filePathInArchive, err)
}
defer readCloser.Close()
// Копируем содержимое файла в writer
if _, err := io.Copy(writer, readCloser); err != nil {
return fmt.Errorf("failed to copy content of %s from archive: %w", filePathInArchive, err)
}
return nil
}
}
return ErrFileNotFoundInArchive
}