Skip to content

Commit b66fdf6

Browse files
committed
Refactor compression and decompression functions for improved clarity and maintainability
- Introduced `collectFiles` function to streamline file collection logic in `ReadAndCompressFiles`. - Refactored `Zip` function to utilize `processFileCompression` for handling individual file compression. - Enhanced `Unzip` function with `processFile` for better file decompression management. - Updated error handling to return more informative messages and maintain consistent return types. - Improved progress reporting during file operations for better user feedback.
1 parent 8648610 commit b66fdf6

File tree

4 files changed

+163
-193
lines changed

4 files changed

+163
-193
lines changed

compressor/compress.go

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -107,66 +107,36 @@ func Compress(filenameStrs []string, outputDir, algorithm string, progressCallba
107107
return fileName, fileMeta, err
108108
}
109109

110-
// ReadAndCompressFiles reads a list of files, compresses them using the specified algorithm,
111-
// and writes the compressed data to the provided output writer.
112-
//
113-
// Parameters:
114-
// - filenameStrs: A slice of strings containing the file paths to be read and compressed.
115-
// - output: An io.Writer where the compressed data will be written.
116-
// - algorithm: A string specifying the compression algorithm to use.
117-
// - progressCallback: A callback function to report progress during compression.
118-
//
119-
// Returns:
120-
// - uint64: The total size of the original uncompressed files.
121-
// - error: An error if any occurs during the process.
122-
//
123-
// The function performs the following steps:
124-
// 1. Iterates over the provided file paths.
125-
// 2. Retrieves file information and checks if the file is a directory.
126-
// 3. If the file is a directory, it recursively walks through the directory to gather file data.
127-
// 4. If the file is not a directory, it opens the file and appends its data to a slice.
128-
// 5. Writes the specified compression algorithm to the output.
129-
// 6. Compresses the gathered file data using the specified algorithm and writes the compressed data to the output.
130-
//
131-
// Supported compression algorithms:
132-
// - utils.HUFFMAN: Uses Huffman coding for compression.
133-
//
134-
// Errors:
135-
// - Returns an error if any file cannot be opened, read, or if compression fails.
136-
func ReadAndCompressFiles(filenameStrs []string, output io.Writer, algorithm string, progressCallback utils.ProgressCallback) (uint64, error) {
137-
var err error
138-
fileDataArr := []utils.FileData{}
110+
// collectFiles gathers file information and returns the total size and file data array
111+
func collectFiles(filenameStrs []string, progressCallback utils.ProgressCallback) (uint64, []utils.FileData, error) {
112+
fileDataArr := make([]utils.FileData, 0)
139113
originalSize := uint64(0)
140114

141-
// First pass: collect file information
142115
for i, filenameStr := range filenameStrs {
143116
fileInfo, err := os.Stat(filenameStr)
144117
if err != nil {
145-
return 0, fmt.Errorf("failed to get file info: %v", err)
118+
return 0, nil, fmt.Errorf("failed to get file info: %v", err)
146119
}
147120

148121
originalSize += uint64(fileInfo.Size())
149122

150123
if fileInfo.IsDir() {
151124
if err := walkDir(filenameStr, &fileDataArr); err != nil {
152-
return 0, err
125+
return 0, nil, err
153126
}
154127
} else {
155128
file, err := os.Open(filenameStr)
156129
if err != nil {
157-
return 0, fmt.Errorf(constants.FILE_OPEN_ERROR, err)
130+
return 0, nil, fmt.Errorf(constants.FILE_OPEN_ERROR, err)
158131
}
159132

160-
fileData := utils.FileData{
133+
fileDataArr = append(fileDataArr, utils.FileData{
161134
Name: filenameStr,
162135
Size: fileInfo.Size(),
163136
Reader: file,
164-
}
165-
166-
fileDataArr = append(fileDataArr, fileData)
137+
})
167138
}
168139

169-
// Report progress for file collection
170140
if progressCallback != nil {
171141
utils.UpdateProgress(utils.ProgressInfo{
172142
TotalFiles: len(filenameStrs),
@@ -176,12 +146,19 @@ func ReadAndCompressFiles(filenameStrs []string, output io.Writer, algorithm str
176146
}
177147
}
178148

179-
// Write the compression algorithm to the output
149+
return originalSize, fileDataArr, nil
150+
}
151+
152+
func ReadAndCompressFiles(filenameStrs []string, output io.Writer, algorithm string, progressCallback utils.ProgressCallback) (uint64, error) {
153+
originalSize, fileDataArr, err := collectFiles(filenameStrs, progressCallback)
154+
if err != nil {
155+
return 0, err
156+
}
157+
180158
if err := writeAlgorithm(output, algorithm); err != nil {
181159
return 0, err
182160
}
183161

184-
// Second pass: compress files
185162
switch utils.Algorithm(algorithm) {
186163
case utils.HUFFMAN:
187164
err = hfc.Zip(fileDataArr, output, progressCallback)
0 Bytes
Binary file not shown.

compressor/hfc/io.go

Lines changed: 78 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -438,74 +438,66 @@ func decompressRemainingBits(remainingBits uint32, remainingBitsLen uint8, numOf
438438
return nil
439439
}
440440

441-
// Zip compresses data using Huffman coding and writes the compressed data to the output stream.
442-
// Zip compresses multiple files and writes the compressed data to the provided output writer.
443-
// It first generates compression codes for the files, writes the number of files, and then processes each file individually.
444-
// For each file, it compresses and writes the file name, writes a placeholder for the compressed size, compresses the file data,
445-
// and then updates the placeholder with the actual compressed size.
446-
//
447-
// Parameters:
448-
// - files: A slice of utils.FileData representing the files to be compressed.
449-
// - output: An io.Writer where the compressed data will be written.
450-
// - progressCallback: A callback function to report progress.
451-
//
452-
// Returns:
453-
// - error: An error if any step in the compression process fails.
454-
func Zip(files []utils.FileData, output io.Writer, progressCallback utils.ProgressCallback) error {
455-
codes, err := generateCodes(&files, output)
456-
if err != nil {
457-
return fmt.Errorf("error preparing codes: %w", err)
441+
// processFile handles the compression of a single file
442+
func processFileCompression(file utils.FileData, output io.Writer, codes map[rune]string, progressCallback utils.ProgressCallback) error {
443+
// Report progress for current file
444+
if progressCallback != nil {
445+
utils.UpdateProgress(utils.ProgressInfo{
446+
TotalFiles: 1,
447+
CurrentFile: 1,
448+
CurrentFileSize: file.Size,
449+
Message: fmt.Sprintf("Compressing file: %s", filepath.Base(file.Name)),
450+
}, progressCallback)
451+
}
452+
453+
// Compress and write the file name
454+
if err := writeFileName(file.Name, output, codes); err != nil {
455+
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
458456
}
459457

460-
// Write the number of files
461-
if err := writeNumOfFiles(uint64(len(files)), output); err != nil {
458+
// Write placeholder for compressed size
459+
if err := binary.Write(output, binary.LittleEndian, uint64(0)); err != nil {
462460
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
463461
}
464462

465-
for i, file := range files {
466-
reader := file.Reader
463+
// Compress and write the data
464+
compressedLen, err := compressData(file.Reader, output, codes)
465+
if err != nil {
466+
return fmt.Errorf(constants.ERROR_COMPRESS, err)
467+
}
467468

468-
// Report progress for current file
469-
if progressCallback != nil {
470-
utils.UpdateProgress(utils.ProgressInfo{
471-
TotalFiles: len(files),
472-
CurrentFile: i + 1,
473-
CurrentFileSize: file.Size,
474-
Message: fmt.Sprintf("Compressing file: %s", filepath.Base(file.Name)),
475-
}, progressCallback)
476-
}
469+
// Seek back to write the actual compressed size
470+
if _, err := output.(io.Seeker).Seek(-int64(compressedLen+8), io.SeekCurrent); err != nil {
471+
return fmt.Errorf("error seeking back to write the compressed size: %w", err)
472+
}
477473

478-
//Compress and write the file name
479-
if err := writeFileName(file.Name, output, codes); err != nil {
480-
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
481-
}
474+
if err := binary.Write(output, binary.LittleEndian, compressedLen); err != nil {
475+
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
476+
}
482477

483-
//write 64 bit 0 for the compressed size
484-
if err := binary.Write(output, binary.LittleEndian, uint64(0)); err != nil {
485-
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
486-
}
478+
// Seek back to the end
479+
if _, err := output.(io.Seeker).Seek(0, io.SeekEnd); err != nil {
480+
return fmt.Errorf("error seeking to the end of the file: %w", err)
481+
}
487482

488-
//Compress and write the data
489-
compressedLen, err := compressData(reader, output, codes)
490-
if err != nil {
491-
return fmt.Errorf(constants.ERROR_COMPRESS, err)
492-
}
483+
return nil
484+
}
493485

494-
//seek back to compressedLen bytes and write the compressed size
495-
if _, err := output.(io.Seeker).Seek(-int64(compressedLen+8), io.SeekCurrent); err != nil {
496-
return fmt.Errorf("error seeking back to write the compressed size: %w", err)
497-
}
486+
func Zip(files []utils.FileData, output io.Writer, progressCallback utils.ProgressCallback) error {
487+
codes, err := generateCodes(&files, output)
488+
if err != nil {
489+
return fmt.Errorf("error preparing codes: %w", err)
490+
}
498491

499-
if err := binary.Write(output, binary.LittleEndian, compressedLen); err != nil {
500-
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
501-
}
492+
if err := writeNumOfFiles(uint64(len(files)), output); err != nil {
493+
return fmt.Errorf(constants.FILE_WRITE_ERROR, err)
494+
}
502495

503-
//seek back to the end of the file
504-
if _, err := output.(io.Seeker).Seek(0, io.SeekEnd); err != nil {
505-
return fmt.Errorf("error seeking to the end of the file: %w", err)
496+
for i, file := range files {
497+
if err := processFileCompression(file, output, codes, progressCallback); err != nil {
498+
return err
506499
}
507500

508-
// Report progress after file is compressed
509501
if progressCallback != nil {
510502
utils.UpdateProgress(utils.ProgressInfo{
511503
TotalFiles: len(files),
@@ -631,10 +623,36 @@ func readFileName(input io.Reader, codes map[rune]string) (string, error) {
631623
return name, nil
632624
}
633625

634-
// Unzip decompresses data from the provided io.Reader and writes the decompressed files to the specified output path.
626+
// processFile handles the decompression of a single file
627+
func processFile(input io.Reader, outputPath string, fileName string, codes map[rune]string, progressCallback utils.ProgressCallback) (string, error) {
628+
fileName = filepath.Join(outputPath, fileName)
629+
dir := filepath.Dir(fileName)
630+
631+
if err := utils.MakeOutputDir(dir); err != nil {
632+
return "", fmt.Errorf(constants.ERROR_CREATE_DIR, err)
633+
}
634+
635+
outputFile, err := os.Create(fileName)
636+
if err != nil {
637+
return "", fmt.Errorf(constants.FILE_CREATE_ERROR, err)
638+
}
639+
defer outputFile.Close()
640+
641+
var compressedSize uint64
642+
if err := binary.Read(input, binary.LittleEndian, &compressedSize); err != nil {
643+
return "", fmt.Errorf(constants.FILE_READ_ERROR, err)
644+
}
645+
646+
if err := decompressData(input, outputFile, codes, compressedSize); err != nil {
647+
return "", fmt.Errorf(constants.ERROR_DECOMPRESS, err)
648+
}
649+
650+
return fileName, nil
651+
}
652+
635653
func Unzip(input io.Reader, outputPath string, progressCallback utils.ProgressCallback) ([]string, error) {
636654
if outputPath == "" {
637-
outputPath = "." // Use the current directory if no output path is provided
655+
outputPath = "."
638656
}
639657

640658
codes, err := ReadHuffmanCodes(input)
@@ -651,24 +669,14 @@ func Unzip(input io.Reader, outputPath string, progressCallback utils.ProgressCa
651669
return nil, errors.New("no files to decompress")
652670
}
653671

654-
filePaths := []string{}
672+
filePaths := make([]string, 0, numOfFiles)
655673

656674
for i := uint64(0); i < numOfFiles; i++ {
657-
// get the file name
658675
fileName, err := readFileName(input, codes)
659676
if err != nil {
660677
return nil, err
661678
}
662679

663-
fileName = filepath.Join(outputPath, fileName)
664-
665-
dir := filepath.Dir(fileName)
666-
667-
if err := utils.MakeOutputDir(dir); err != nil {
668-
return nil, fmt.Errorf(constants.ERROR_CREATE_DIR, err)
669-
}
670-
671-
// Report progress for current file
672680
if progressCallback != nil {
673681
utils.UpdateProgress(utils.ProgressInfo{
674682
TotalFiles: int(numOfFiles),
@@ -677,28 +685,13 @@ func Unzip(input io.Reader, outputPath string, progressCallback utils.ProgressCa
677685
}, progressCallback)
678686
}
679687

680-
// writer
681-
outputFile, err := os.Create(fileName)
688+
filePath, err := processFile(input, outputPath, fileName, codes, progressCallback)
682689
if err != nil {
683-
return nil, fmt.Errorf(constants.FILE_CREATE_ERROR, err)
684-
}
685-
686-
// read the compressed size
687-
var compressedSize uint64
688-
if err := binary.Read(input, binary.LittleEndian, &compressedSize); err != nil {
689-
return nil, fmt.Errorf(constants.FILE_READ_ERROR, err)
690-
}
691-
692-
err = decompressData(input, outputFile, codes, compressedSize)
693-
if err != nil {
694-
return nil, fmt.Errorf(constants.ERROR_DECOMPRESS, err)
690+
return nil, err
695691
}
696692

697-
outputFile.Close()
698-
699-
filePaths = append(filePaths, fileName)
693+
filePaths = append(filePaths, filePath)
700694

701-
// Report progress after file is decompressed
702695
if progressCallback != nil {
703696
utils.UpdateProgress(utils.ProgressInfo{
704697
TotalFiles: int(numOfFiles),

0 commit comments

Comments
 (0)