@@ -7,101 +7,170 @@ import (
7
7
"fmt"
8
8
"os"
9
9
"path"
10
+ "sync"
10
11
11
12
"file-compressor/utils"
12
13
)
13
14
15
+ // Compress compresses the specified files into a single compressed file.
14
16
func Compress (filenameStrs []string , outputDir * string , password * string ) error {
15
-
17
+ // Check if there are files to compress
16
18
if len (filenameStrs ) == 0 {
17
19
return errors .New ("no files to compress" )
18
20
}
19
21
22
+ // Set default output directory if not provided
20
23
if * outputDir == "" {
21
24
* outputDir = path .Dir (filenameStrs [0 ])
22
25
}
23
26
27
+ // Prepare to store files' content
24
28
var files []utils.File
25
29
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
27
38
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
+ }
32
63
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 {
34
70
if err != nil {
35
- return err
71
+ errMutex .Lock ()
72
+ defer errMutex .Unlock ()
73
+ return err // Return the first error encountered
36
74
}
37
-
38
- files = append (files , utils.File {
39
- Name : path .Base (filename ),
40
- Content : content ,
41
- })
42
75
}
43
76
44
- // Compress files
77
+ // Compress files using Huffman coding
45
78
compressedFile := Zip (files )
46
79
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
+ }
50
88
89
+ // Write compressed file to the output directory
90
+ err := os .WriteFile (* outputDir + "/" + compressedFile .Name , compressedFile .Content , 0644 )
51
91
if err != nil {
52
- return err
92
+ return fmt . Errorf ( "failed to write compressed file: %w" , err )
53
93
}
54
94
55
- // Write compressed file in the current directory + /compressed directory
56
- os .WriteFile (* outputDir + "/" + compressedFile .Name , compressedFile .Content , 0644 )
57
-
58
95
return nil
59
96
}
60
97
98
+
99
+ // Decompress decompresses the specified compressed file into individual files.
61
100
func Decompress (filenameStrs []string , outputDir * string , password * string ) error {
101
+ // Check if there are files to decompress
62
102
if len (filenameStrs ) == 0 {
63
103
return errors .New ("no files to decompress" )
64
104
}
65
105
106
+ // Set default output directory if not provided
66
107
if * outputDir == "" {
67
108
* outputDir = path .Dir (filenameStrs [0 ])
68
109
}
69
110
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 ])
76
113
if err != nil {
77
- return err
114
+ return fmt . Errorf ( "failed to read compressed file %s: %w" , filenameStrs [ 0 ], err )
78
115
}
79
116
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
+ }
83
123
}
84
124
85
- // Unzip file
125
+ // Decompress file using Huffman coding
86
126
files := Unzip (utils.File {
87
127
Name : path .Base (filenameStrs [0 ]),
88
- Content : content ,
128
+ Content : compressedContent ,
89
129
})
90
130
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
92
139
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
+ }
95
164
}
96
165
97
166
return nil
98
167
}
99
168
100
-
169
+ // Zip compresses files using Huffman coding and returns a compressed file object.
101
170
func Zip (files []utils.File ) utils.File {
102
171
var buf bytes.Buffer
103
172
104
- // Write header count of files
173
+ // Write the number of files in the header
105
174
binary .Write (& buf , binary .BigEndian , uint32 (len (files )))
106
175
107
176
// Create raw content buffer
@@ -142,11 +211,12 @@ func Zip(files []utils.File) utils.File {
142
211
}
143
212
144
213
return utils.File {
145
- Name : "compressed.bin" ,
146
- Content : buf .Bytes (),
214
+ Name : "compressed.bin" ,
215
+ Content : buf .Bytes (),
147
216
}
148
217
}
149
218
219
+ // Unzip decompresses a compressed file using Huffman coding and returns individual files.
150
220
func Unzip (file utils.File ) []utils.File {
151
221
var files []utils.File
152
222
@@ -211,7 +281,7 @@ func Unzip(file utils.File) []utils.File {
211
281
return files
212
282
}
213
283
214
-
284
+ // compressData compresses data using Huffman codes.
215
285
func compressData (data []byte , codes map [rune ]string ) []byte {
216
286
var buf bytes.Buffer
217
287
var bitBuffer uint64
@@ -238,6 +308,7 @@ func compressData(data []byte, codes map[rune]string) []byte {
238
308
return buf .Bytes ()
239
309
}
240
310
311
+ // decompressData decompresses data using Huffman codes.
241
312
func decompressData (data []byte , root * Node ) []byte {
242
313
var buf bytes.Buffer
243
314
if root == nil {
@@ -260,4 +331,4 @@ func decompressData(data []byte, root *Node) []byte {
260
331
}
261
332
}
262
333
return buf .Bytes ()
263
- }
334
+ }
0 commit comments