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 }