@@ -5,11 +5,13 @@ import (
5
5
"fmt"
6
6
"os"
7
7
"path"
8
- "sync"
9
8
"path/filepath"
9
+ "strings"
10
+ "sync"
11
+ "sync/atomic"
10
12
11
- "file-compressor/utils"
12
13
"file-compressor/encryption"
14
+ "file-compressor/utils"
13
15
)
14
16
15
17
// Compress compresses the specified files or folders into a single compressed file.
@@ -26,6 +28,8 @@ func Compress(filenameStrs []string, outputDir *string, password *string) error
26
28
27
29
// Prepare to store files' content
28
30
var files []utils.File
31
+ var originalSize int64
32
+ var compressedSize int64
29
33
30
34
// Use a wait group to synchronize goroutines
31
35
var wg sync.WaitGroup
@@ -34,40 +38,52 @@ func Compress(filenameStrs []string, outputDir *string, password *string) error
34
38
// Channel to receive errors from goroutines
35
39
errChan := make (chan error , len (filenameStrs ))
36
40
37
- // Process each input file or folder
41
+ // Function to handle compression of a single file or directory
42
+ compressFileOrFolder := func (filePath string ) error {
43
+ // Check if the file or folder exists
44
+ info , err := os .Stat (filePath )
45
+ if os .IsNotExist (err ) {
46
+ return fmt .Errorf ("file or folder does not exist: %s" , filePath )
47
+ }
48
+
49
+ if info .IsDir () {
50
+ utils .ColorPrint (utils .YELLOW , fmt .Sprintf ("Compressing folder (%s)\n " , filePath ))
51
+ err := compressFolderRecursive (filePath , & files , & originalSize )
52
+ if err != nil {
53
+ return err
54
+ }
55
+ } else {
56
+ utils .ColorPrint (utils .YELLOW , fmt .Sprintf ("Compressing file (%s)\n " , filePath ))
57
+ // Read file content
58
+ content , err := os .ReadFile (filePath )
59
+ if err != nil {
60
+ return fmt .Errorf ("failed to read file %s: %w" , filePath , err )
61
+ }
62
+
63
+ // Store file information (name and content)
64
+ files = append (files , utils.File {
65
+ Name : path .Base (filePath ),
66
+ Content : content ,
67
+ })
68
+
69
+ // Increment original size
70
+ atomic .AddInt64 (& originalSize , info .Size ())
71
+ }
72
+ return nil
73
+ }
74
+
75
+ // Process each input file or folder recursively
38
76
for _ , filename := range filenameStrs {
39
77
wg .Add (1 )
40
- go func (filename string ) {
78
+ file := filename
79
+ go func (filePath string ) {
41
80
defer wg .Done ()
42
81
43
- // Check if the file or folder exists
44
- info , err := os .Stat (filename )
45
- if os .IsNotExist (err ) {
46
- errChan <- fmt .Errorf ("file or folder does not exist: %s" , filename )
47
- return
48
- }
49
-
50
- // Handle directory recursively
51
- if info .IsDir () {
52
- err := compressFolderRecursive (filename , & files )
53
- if err != nil {
54
- errChan <- err
55
- }
56
- } else {
57
- // Read file content
58
- content , err := os .ReadFile (filename )
59
- if err != nil {
60
- errChan <- fmt .Errorf ("failed to read file %s: %w" , filename , err )
61
- return
62
- }
63
-
64
- // Store file information (name and content)
65
- files = append (files , utils.File {
66
- Name : path .Base (filename ),
67
- Content : content ,
68
- })
82
+ err := compressFileOrFolder (filePath )
83
+ if err != nil {
84
+ errChan <- err
69
85
}
70
- }(filename )
86
+ }(file )
71
87
}
72
88
73
89
// Wait for all goroutines to finish
@@ -84,50 +100,58 @@ func Compress(filenameStrs []string, outputDir *string, password *string) error
84
100
}
85
101
86
102
// Compress files using Huffman coding
87
- compressedFile := Zip (files )
103
+ compressedFile , err := Zip (files )
104
+ if err != nil {
105
+ return err
106
+ }
88
107
89
108
// Encrypt compressed content if password is provided
90
109
if password != nil && * password != "" {
91
- var err error
92
110
compressedFile .Content , err = encryption .Encrypt (compressedFile .Content , * password )
93
111
if err != nil {
94
112
return fmt .Errorf ("encryption error: %w" , err )
95
113
}
96
114
}
97
115
98
- //check if the output file and directory exists
116
+ // Check if the output directory exists; create if not
99
117
if _ , err := os .Stat (* outputDir ); os .IsNotExist (err ) {
100
118
err := os .MkdirAll (* outputDir , os .ModePerm )
101
119
if err != nil {
102
120
return fmt .Errorf ("failed to create output directory: %w" , err )
103
121
}
104
122
}
105
123
106
- // if the output file already exists, rename it with file(N).bin
107
- if _ , err := os .Stat (* outputDir + "/" + compressedFile .Name + ".bin" ); err == nil {
108
- count := 1
109
- for {
110
- if _ , err := os .Stat (* outputDir + "/" + compressedFile .Name + fmt .Sprintf ("(%d).bin" , count )); os .IsNotExist (err ) {
111
- compressedFile .Name = compressedFile .Name + fmt .Sprintf ("(%d).bin" , count )
112
- break
113
- }
114
- count ++
115
- }
116
- } else if os .IsNotExist (err ) {
117
- compressedFile .Name = compressedFile .Name + ".bin"
124
+ // Check if the output file already exists; rename if necessary
125
+ if _ , err := os .Stat (* outputDir + "/" + compressedFile .Name ); err == nil {
126
+ InvalidateFileName (& compressedFile , outputDir )
118
127
}
119
128
120
129
// Write compressed file to the output directory
121
- err : = os .WriteFile (* outputDir + "/" + compressedFile .Name , compressedFile .Content , 0644 )
130
+ err = os .WriteFile (* outputDir + "/" + compressedFile .Name , compressedFile .Content , 0644 )
122
131
if err != nil {
123
132
return fmt .Errorf ("failed to write compressed file: %w" , err )
124
133
}
125
134
135
+ //get the file size
136
+ compressedFileInfo , err := os .Stat (* outputDir + "/" + compressedFile .Name )
137
+ if err != nil {
138
+ return fmt .Errorf ("failed to get compressed file info: %w" , err )
139
+ }
140
+
141
+ compressedSize = compressedFileInfo .Size ()
142
+
143
+ // Calculate compression ratio
144
+ compressionRatio := float64 (compressedSize ) / float64 (originalSize )
145
+
146
+ // Print compression statistics
147
+ utils .ColorPrint (utils .GREEN , fmt .Sprintf ("Compression complete: Original Size: %s, Compressed Size: %s, Compression Ratio: %.2f%%\n " ,
148
+ utils .FileSize (originalSize ), utils .FileSize (compressedSize ), compressionRatio * 100 ))
149
+
126
150
return nil
127
151
}
128
152
129
153
// Function to recursively compress a folder and its contents
130
- func compressFolderRecursive (folderPath string , files * []utils.File ) error {
154
+ func compressFolderRecursive (folderPath string , files * []utils.File , originalSize * int64 ) error {
131
155
// Traverse the folder contents
132
156
err := filepath .Walk (folderPath , func (filePath string , info os.FileInfo , err error ) error {
133
157
if err != nil {
@@ -140,16 +164,19 @@ func compressFolderRecursive(folderPath string, files *[]utils.File) error {
140
164
return fmt .Errorf ("failed to read file %s: %w" , filePath , err )
141
165
}
142
166
143
- filename , err := filepath .Rel (folderPath , filePath )
167
+ // Store file information (relative path and content)
168
+ relativePath , err := filepath .Rel (folderPath , filePath )
144
169
if err != nil {
145
170
return fmt .Errorf ("failed to get relative path for file %s: %w" , filePath , err )
146
171
}
147
172
148
- // Store file information (relative path and content)
149
173
* files = append (* files , utils.File {
150
- Name : filepath .ToSlash (filename ), // Store relative path
174
+ Name : filepath .ToSlash (relativePath ), // Store relative path
151
175
Content : content ,
152
176
})
177
+
178
+ // Increment original size and compressed size
179
+ atomic .AddInt64 (originalSize , info .Size ())
153
180
}
154
181
return nil
155
182
})
@@ -161,6 +188,7 @@ func compressFolderRecursive(folderPath string, files *[]utils.File) error {
161
188
return nil
162
189
}
163
190
191
+
164
192
// Decompress decompresses the specified compressed file into individual files or folders.
165
193
func Decompress (filenameStrs []string , outputDir * string , password * string ) error {
166
194
// Check if there are files to decompress
@@ -188,11 +216,15 @@ func Decompress(filenameStrs []string, outputDir *string, password *string) erro
188
216
}
189
217
190
218
// Decompress file using Huffman coding
191
- files := Unzip (utils.File {
219
+ files , err := Unzip (utils.File {
192
220
Name : path .Base (filenameStrs [0 ]),
193
221
Content : compressedContent ,
194
222
})
195
223
224
+ if err != nil {
225
+ return err
226
+ }
227
+
196
228
// Use a wait group to synchronize goroutines
197
229
var wg sync.WaitGroup
198
230
var errMutex sync.Mutex // Mutex to handle errors safely
@@ -214,13 +246,18 @@ func Decompress(filenameStrs []string, outputDir *string, password *string) erro
214
246
return
215
247
}
216
248
249
+ // Check if the file already exists, rename it with file_N
250
+ if _ , err := os .Stat (filepath .Join (* outputDir , file .Name )); err == nil {
251
+ InvalidateFileName (& file , outputDir )
252
+ }
253
+
217
254
// Write decompressed file content
218
255
err = os .WriteFile (filepath .Join (* outputDir , file .Name ), file .Content , 0644 )
219
256
if err != nil {
220
257
errChan <- fmt .Errorf ("failed to write decompressed file %s: %w" , file .Name , err )
221
258
return
222
259
}
223
- utils .ColorPrint (utils .GREEN , fmt .Sprintf ("Decompressed file: %s\n " , file .Name ))
260
+ utils .ColorPrint (utils .YELLOW , fmt .Sprintf ("Decompressed file: %s\n " , file .Name ))
224
261
}(file )
225
262
}
226
263
@@ -238,4 +275,20 @@ func Decompress(filenameStrs []string, outputDir *string, password *string) erro
238
275
}
239
276
240
277
return nil
241
- }
278
+ }
279
+
280
+ func InvalidateFileName (file * utils.File , outputDir * string ) {
281
+ fileExt := path .Ext (file .Name )
282
+ //extract the file name without the extension
283
+ filename := path .Base (file .Name )
284
+ filename = strings .TrimSuffix (filename , fileExt )
285
+ count := 1
286
+ for {
287
+ if _ , err := os .Stat (filepath .Join (* outputDir , filepath .Dir (file .Name ), filename + fmt .Sprintf ("_%d%s" , count , fileExt ))); os .IsNotExist (err ) {
288
+ utils .ColorPrint (utils .PURPLE , fmt .Sprintf ("File %s already exists, renaming to %s\n " , file .Name , filename + fmt .Sprintf ("_%d%s" , count , fileExt )))
289
+ file .Name = filepath .Dir (file .Name ) + "/" + filename + fmt .Sprintf ("_%d%s" , count , fileExt )
290
+ break
291
+ }
292
+ count ++
293
+ }
294
+ }
0 commit comments