Skip to content

Commit a0dc6ac

Browse files
committed
add go routines
1 parent 06b148a commit a0dc6ac

File tree

4 files changed

+131
-51
lines changed

4 files changed

+131
-51
lines changed

build.bat

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
windres -o resources.syso resources.rc
2+
go build -o piarch.exe

compressor/compressor.go

Lines changed: 113 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,101 +7,170 @@ import (
77
"fmt"
88
"os"
99
"path"
10+
"sync"
1011

1112
"file-compressor/utils"
1213
)
1314

15+
// Compress compresses the specified files into a single compressed file.
1416
func Compress(filenameStrs []string, outputDir *string, password *string) error {
15-
17+
// Check if there are files to compress
1618
if len(filenameStrs) == 0 {
1719
return errors.New("no files to compress")
1820
}
1921

22+
// Set default output directory if not provided
2023
if *outputDir == "" {
2124
*outputDir = path.Dir(filenameStrs[0])
2225
}
2326

27+
// Prepare to store files' content
2428
var files []utils.File
2529

26-
// Read files
30+
// Use a wait group to synchronize goroutines
31+
var wg sync.WaitGroup
32+
var errMutex sync.Mutex // Mutex to handle errors safely
33+
34+
// Channel to receive errors from goroutines
35+
errChan := make(chan error, len(filenameStrs))
36+
37+
// Read files and store their content concurrently
2738
for _, filename := range filenameStrs {
28-
// Read file content and append to files
29-
if _, err := os.Stat(filename); os.IsNotExist(err) {
30-
return errors.New("file does not exist")
31-
}
39+
wg.Add(1)
40+
go func(filename string) {
41+
defer wg.Done()
42+
43+
// Check if the file exists
44+
if _, err := os.Stat(filename); os.IsNotExist(err) {
45+
errChan <- fmt.Errorf("file does not exist: %s", filename)
46+
return
47+
}
48+
49+
// Read file content
50+
content, err := os.ReadFile(filename)
51+
if err != nil {
52+
errChan <- fmt.Errorf("failed to read file %s: %w", filename, err)
53+
return
54+
}
55+
56+
// Store file information (name and content)
57+
files = append(files, utils.File{
58+
Name: path.Base(filename),
59+
Content: content,
60+
})
61+
}(filename)
62+
}
3263

33-
content, err := os.ReadFile(filename)
64+
// Wait for all goroutines to finish
65+
wg.Wait()
66+
close(errChan)
67+
68+
// Check for errors from goroutines
69+
for err := range errChan {
3470
if err != nil {
35-
return err
71+
errMutex.Lock()
72+
defer errMutex.Unlock()
73+
return err // Return the first error encountered
3674
}
37-
38-
files = append(files, utils.File{
39-
Name: path.Base(filename),
40-
Content: content,
41-
})
4275
}
4376

44-
// Compress files
77+
// Compress files using Huffman coding
4578
compressedFile := Zip(files)
4679

47-
var err error
48-
49-
compressedFile.Content, err = utils.Encrypt(compressedFile.Content, *password)
80+
// Encrypt compressed content if password is provided
81+
if password != nil && *password != "" {
82+
var err error
83+
compressedFile.Content, err = utils.Encrypt(compressedFile.Content, *password)
84+
if err != nil {
85+
return fmt.Errorf("encryption error: %w", err)
86+
}
87+
}
5088

89+
// Write compressed file to the output directory
90+
err := os.WriteFile(*outputDir+"/"+compressedFile.Name, compressedFile.Content, 0644)
5191
if err != nil {
52-
return err
92+
return fmt.Errorf("failed to write compressed file: %w", err)
5393
}
5494

55-
// Write compressed file in the current directory + /compressed directory
56-
os.WriteFile(*outputDir + "/" + compressedFile.Name, compressedFile.Content, 0644)
57-
5895
return nil
5996
}
6097

98+
99+
// Decompress decompresses the specified compressed file into individual files.
61100
func Decompress(filenameStrs []string, outputDir *string, password *string) error {
101+
// Check if there are files to decompress
62102
if len(filenameStrs) == 0 {
63103
return errors.New("no files to decompress")
64104
}
65105

106+
// Set default output directory if not provided
66107
if *outputDir == "" {
67108
*outputDir = path.Dir(filenameStrs[0])
68109
}
69110

70-
// Read compressed file
71-
if _, err := os.Stat(filenameStrs[0]); os.IsNotExist(err) {
72-
return errors.New("file does not exist")
73-
}
74-
75-
content, err := os.ReadFile(filenameStrs[0])
111+
// Read compressed file content
112+
compressedContent, err := os.ReadFile(filenameStrs[0])
76113
if err != nil {
77-
return err
114+
return fmt.Errorf("failed to read compressed file %s: %w", filenameStrs[0], err)
78115
}
79116

80-
content, err = utils.Decrypt(content, *password)
81-
if err != nil {
82-
return err
117+
// Decrypt compressed content if password is provided
118+
if password != nil && *password != "" {
119+
compressedContent, err = utils.Decrypt(compressedContent, *password)
120+
if err != nil {
121+
return fmt.Errorf("decryption error: %w", err)
122+
}
83123
}
84124

85-
// Unzip file
125+
// Decompress file using Huffman coding
86126
files := Unzip(utils.File{
87127
Name: path.Base(filenameStrs[0]),
88-
Content: content,
128+
Content: compressedContent,
89129
})
90130

91-
// Write decompressed files
131+
// Use a wait group to synchronize goroutines
132+
var wg sync.WaitGroup
133+
var errMutex sync.Mutex // Mutex to handle errors safely
134+
135+
// Channel to receive errors from goroutines
136+
errChan := make(chan error, len(files))
137+
138+
// Write decompressed files to the output directory concurrently
92139
for _, file := range files {
93-
utils.ColorPrint(utils.GREEN, fmt.Sprintf("Decompressed file: %s\n", file.Name))
94-
os.WriteFile(*outputDir + "/" + file.Name, file.Content, 0644)
140+
wg.Add(1)
141+
go func(file utils.File) {
142+
defer wg.Done()
143+
144+
err := os.WriteFile(*outputDir+"/"+file.Name, file.Content, 0644)
145+
if err != nil {
146+
errChan <- fmt.Errorf("failed to write decompressed file %s: %w", file.Name, err)
147+
return
148+
}
149+
utils.ColorPrint(utils.GREEN, fmt.Sprintf("Decompressed file: %s\n", file.Name))
150+
}(file)
151+
}
152+
153+
// Wait for all goroutines to finish
154+
wg.Wait()
155+
close(errChan)
156+
157+
// Check for errors from goroutines
158+
for err := range errChan {
159+
if err != nil {
160+
errMutex.Lock()
161+
defer errMutex.Unlock()
162+
return err // Return the first error encountered
163+
}
95164
}
96165

97166
return nil
98167
}
99168

100-
169+
// Zip compresses files using Huffman coding and returns a compressed file object.
101170
func Zip(files []utils.File) utils.File {
102171
var buf bytes.Buffer
103172

104-
// Write header count of files
173+
// Write the number of files in the header
105174
binary.Write(&buf, binary.BigEndian, uint32(len(files)))
106175

107176
// Create raw content buffer
@@ -142,11 +211,12 @@ func Zip(files []utils.File) utils.File {
142211
}
143212

144213
return utils.File{
145-
Name: "compressed.bin",
146-
Content: buf.Bytes(),
214+
Name: "compressed.bin",
215+
Content: buf.Bytes(),
147216
}
148217
}
149218

219+
// Unzip decompresses a compressed file using Huffman coding and returns individual files.
150220
func Unzip(file utils.File) []utils.File {
151221
var files []utils.File
152222

@@ -211,7 +281,7 @@ func Unzip(file utils.File) []utils.File {
211281
return files
212282
}
213283

214-
284+
// compressData compresses data using Huffman codes.
215285
func compressData(data []byte, codes map[rune]string) []byte {
216286
var buf bytes.Buffer
217287
var bitBuffer uint64
@@ -238,6 +308,7 @@ func compressData(data []byte, codes map[rune]string) []byte {
238308
return buf.Bytes()
239309
}
240310

311+
// decompressData decompresses data using Huffman codes.
241312
func decompressData(data []byte, root *Node) []byte {
242313
var buf bytes.Buffer
243314
if root == nil {
@@ -260,4 +331,4 @@ func decompressData(data []byte, root *Node) []byte {
260331
}
261332
}
262333
return buf.Bytes()
263-
}
334+
}

compressor/huffman.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,45 @@ import (
44
"container/heap"
55
)
66

7-
// Huffman coding structures
7+
// Node represents a node in the Huffman tree
88
type Node struct {
9-
char rune
10-
freq int
11-
left *Node
12-
right *Node
9+
char rune // Character stored in the node
10+
freq int // Frequency of the character
11+
left *Node // Left child node
12+
right *Node // Right child node
1313
}
1414

15+
// PriorityQueue implements heap.Interface and holds Nodes
1516
type PriorityQueue []*Node
1617

18+
// Len returns the number of items in the priority queue
1719
func (pq PriorityQueue) Len() int { return len(pq) }
1820

21+
// Less defines the ordering of items in the priority queue based on frequency
1922
func (pq PriorityQueue) Less(i, j int) bool {
2023
return pq[i].freq < pq[j].freq
2124
}
2225

26+
// Swap swaps two items in the priority queue
2327
func (pq PriorityQueue) Swap(i, j int) {
2428
pq[i], pq[j] = pq[j], pq[i]
2529
}
2630

31+
// Push adds an item (Node) to the priority queue
2732
func (pq *PriorityQueue) Push(x interface{}) {
2833
*pq = append(*pq, x.(*Node))
2934
}
3035

36+
// Pop removes and returns the highest priority item (Node) from the priority queue
3137
func (pq *PriorityQueue) Pop() interface{} {
3238
old := *pq
3339
n := len(old)
3440
item := old[n-1]
35-
*pq = old[0:n-1]
41+
*pq = old[0 : n-1]
3642
return item
3743
}
3844

45+
// buildHuffmanTree builds the Huffman tree from character frequencies
3946
func buildHuffmanTree(freq map[rune]int) *Node {
4047
pq := make(PriorityQueue, len(freq))
4148
i := 0
@@ -64,6 +71,7 @@ func buildHuffmanTree(freq map[rune]int) *Node {
6471
return nil
6572
}
6673

74+
// buildHuffmanCodes builds Huffman codes (bit strings) for each character
6775
func buildHuffmanCodes(root *Node) map[rune]string {
6876
codes := make(map[rune]string)
6977
var build func(node *Node, code string)
@@ -82,8 +90,7 @@ func buildHuffmanCodes(root *Node) map[rune]string {
8290
return codes
8391
}
8492

85-
86-
93+
// rebuildHuffmanTree reconstructs the Huffman tree from Huffman codes
8794
func rebuildHuffmanTree(codes map[rune]string) *Node {
8895
var root *Node
8996
for char, code := range codes {
@@ -107,4 +114,4 @@ func rebuildHuffmanTree(codes map[rune]string) *Node {
107114
current.char = char
108115
}
109116
return root
110-
}
117+
}

test/zip/compressed.bin

38.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)