package filesystem import ( "context" "fmt" "io" "os" "path/filepath" "unprism.ru/KRBL/agate" "unprism.ru/KRBL/agate/store" ) const blobExtension = ".zip" // fileSystemStore реализует интерфейс store.BlobStore с использованием локальной файловой системы. type fileSystemStore struct { baseDir string // Директория для хранения блобов (архивов) } // NewFileSystemStore создает новое хранилище блобов в указанной директории. func NewFileSystemStore(baseDir string) (store.BlobStore, error) { // Убедимся, что директория существует if err := os.MkdirAll(baseDir, 0755); err != nil { return nil, fmt.Errorf("failed to create base directory %s for filesystem blob store: %w", baseDir, err) } return &fileSystemStore{baseDir: baseDir}, nil } // getBlobPath формирует полный путь к файлу блоба. func (fs *fileSystemStore) getBlobPath(snapshotID string) string { // Используем ID снапшота в качестве имени файла return filepath.Join(fs.baseDir, snapshotID+blobExtension) } // StoreBlob сохраняет данные из reader в файл в baseDir. func (fs *fileSystemStore) StoreBlob(ctx context.Context, snapshotID string, reader io.Reader) (string, error) { blobPath := fs.getBlobPath(snapshotID) // Создаем или перезаписываем файл file, err := os.Create(blobPath) if err != nil { return "", fmt.Errorf("failed to create blob file %s: %w", blobPath, err) } defer file.Close() // Гарантируем закрытие файла // Копируем данные из ридера в файл _, err = io.Copy(file, reader) if err != nil { // Если произошла ошибка копирования, удаляем неполный файл os.Remove(blobPath) return "", fmt.Errorf("failed to write data to blob file %s: %w", blobPath, err) } // Возвращаем путь к созданному файлу return blobPath, nil } // RetrieveBlob открывает файл блоба и возвращает его как io.ReadCloser. func (fs *fileSystemStore) RetrieveBlob(ctx context.Context, snapshotID string) (io.ReadCloser, error) { blobPath := fs.getBlobPath(snapshotID) // Открываем файл для чтения file, err := os.Open(blobPath) if err != nil { if os.IsNotExist(err) { // Если файл не найден, возвращаем кастомную ошибку return nil, agate.ErrNotFound } return nil, fmt.Errorf("failed to open blob file %s: %w", blobPath, err) } // Возвращаем открытый файл (*os.File реализует io.ReadCloser) return file, nil } // DeleteBlob удаляет файл блоба из файловой системы. func (fs *fileSystemStore) DeleteBlob(ctx context.Context, snapshotID string) error { blobPath := fs.getBlobPath(snapshotID) // Удаляем файл err := os.Remove(blobPath) if err != nil { if os.IsNotExist(err) { // Если файл и так не существует, это не ошибка return nil } // Если произошла другая ошибка при удалении return fmt.Errorf("failed to delete blob file %s: %w", blobPath, err) } return nil } // GetBlobPath возвращает путь к файлу блоба, если он существует. func (fs *fileSystemStore) GetBlobPath(ctx context.Context, snapshotID string) (string, error) { blobPath := fs.getBlobPath(snapshotID) // Проверяем существование файла if _, err := os.Stat(blobPath); err != nil { if os.IsNotExist(err) { return "", agate.ErrNotFound } return "", fmt.Errorf("failed to stat blob file %s: %w", blobPath, err) } // Файл существует, возвращаем путь return blobPath, nil }