1
- import os
2
1
import argparse
3
2
import time
4
3
import os
5
4
import lz4 .block
6
5
import zlib
7
6
import threading
8
- from concurrent .futures import ThreadPoolExecutor
7
+ import sys
8
+ import multiprocessing
9
+ import queue
10
+ from functools import partial
11
+
9
12
10
13
class Color :
11
14
RED = '\033 [31m'
@@ -14,28 +17,35 @@ class Color:
14
17
YELLOW = '\033 [33m'
15
18
RESET = '\033 [0m'
16
19
20
+
17
21
class Meta :
18
22
NAME = 'PyDVPL'
19
- VERSION = '0.1 .0'
20
- DATE = '13 /03/2024'
23
+ VERSION = '0.2 .0'
24
+ DATE = '14 /03/2024'
21
25
DEV = 'RifsxD'
22
26
REPO = 'https://github.com/rifsxd/pydvpl'
23
27
INFO = 'A CLI Tool Coded In Python3 To Convert WoTB ( Dava ) SmartDLC DVPL File Based On LZ4 High Compression.'
24
28
29
+
25
30
output_lock = threading .Lock ()
26
31
32
+ # Define a thread-safe queue to store processed files
33
+ processed_queue = queue .Queue ()
34
+
27
35
DVPL_FOOTER_SIZE = 20
28
36
DVPL_TYPE_NONE = 0
29
37
DVPL_TYPE_LZ4 = 2
30
38
DVPL_FOOTER = b"DVPL"
31
39
40
+
32
41
class DVPLFooter :
33
42
def __init__ (self , original_size , compressed_size , crc32 , type_val ):
34
43
self .original_size = original_size
35
44
self .compressed_size = compressed_size
36
45
self .crc32 = crc32
37
46
self .type = type_val
38
47
48
+
39
49
def createDVPLFooter (input_size , compressed_size , crc32_val , type_val ):
40
50
result = bytearray (DVPL_FOOTER_SIZE )
41
51
result [:4 ] = input_size .to_bytes (4 , 'little' )
@@ -45,6 +55,7 @@ def createDVPLFooter(input_size, compressed_size, crc32_val, type_val):
45
55
result [16 :] = DVPL_FOOTER
46
56
return result
47
57
58
+
48
59
def readDVPLFooter (buffer ):
49
60
if len (buffer ) < DVPL_FOOTER_SIZE :
50
61
raise ValueError (Color .RED + "InvalidDVPLFooter: Buffer size is smaller than expected" + Color .RESET )
@@ -61,11 +72,13 @@ def readDVPLFooter(buffer):
61
72
62
73
return DVPLFooter (original_size , compressed_size , crc32_val , type_val )
63
74
75
+
64
76
def CompressDVPL (buffer ):
65
77
compressed_block = lz4 .block .compress (buffer , store_size = False )
66
78
footer_buffer = createDVPLFooter (len (buffer ), len (compressed_block ), zlib .crc32 (compressed_block ), DVPL_TYPE_LZ4 )
67
79
return compressed_block + footer_buffer
68
80
81
+
69
82
def DecompressDVPL (buffer ):
70
83
footer_data = readDVPLFooter (buffer )
71
84
target_block = buffer [:- DVPL_FOOTER_SIZE ]
@@ -88,22 +101,39 @@ def DecompressDVPL(buffer):
88
101
else :
89
102
raise ValueError (Color .RED + "UNKNOWN DVPL FORMAT" + Color .RESET )
90
103
91
- def ConvertDVPLFiles (directory_or_file , config ):
104
+
105
+ def print_progress_bar (processed_files , total_files ):
106
+ with output_lock :
107
+ progress = min (processed_files .value / total_files , 1.0 ) # Ensure progress doesn't exceed 100%
108
+ bar_length = 50
109
+ filled_length = int (bar_length * progress )
110
+ bar = '=' * filled_length + '-' * (bar_length - filled_length )
111
+ percentage = progress * 100
112
+ sys .stdout .write ('\r Processing: [{:<50}] {:.2f}%' .format (bar , percentage ))
113
+ sys .stdout .flush ()
114
+
115
+
116
+ def count_total_files (directory ):
117
+ total_files = 0
118
+ for root , dirs , files in os .walk (directory ):
119
+ total_files += len (files )
120
+ return total_files
121
+
122
+
123
+ def ConvertDVPLFiles (directory_or_file , config , total_files , processed_files ):
92
124
success_count = 0
93
125
failure_count = 0
94
126
ignored_count = 0
95
127
96
128
if os .path .isdir (directory_or_file ):
97
129
dir_list = os .listdir (directory_or_file )
98
- with ThreadPoolExecutor () as executor :
99
- future_results = []
100
- for dir_item in dir_list :
101
- future_results .append (executor .submit (ConvertDVPLFiles , os .path .join (directory_or_file , dir_item ), config ))
102
- for future in future_results :
103
- succ , fail , ignored = future .result ()
104
- success_count += succ
105
- failure_count += fail
106
- ignored_count += ignored
130
+ for dir_item in dir_list :
131
+ succ , fail , ignored = ConvertDVPLFiles (os .path .join (directory_or_file , dir_item ), config , total_files , processed_files )
132
+ success_count += succ
133
+ failure_count += fail
134
+ ignored_count += ignored
135
+ processed_files .value += 1
136
+ print_progress_bar (processed_files , total_files )
107
137
else :
108
138
is_decompression = config .mode == "decompress" and directory_or_file .endswith (".dvpl" )
109
139
is_compression = config .mode == "compress" and not directory_or_file .endswith (".dvpl" )
@@ -147,22 +177,24 @@ def ConvertDVPLFiles(directory_or_file, config):
147
177
148
178
return success_count , failure_count , ignored_count
149
179
150
- def VerifyDVPLFiles (directory_or_file , config ):
180
+
181
+
182
+ def VerifyDVPLFiles (directory_or_file , config , total_files , processed_files ):
151
183
success_count = 0
152
184
failure_count = 0
153
185
ignored_count = 0
154
186
155
187
if os .path .isdir (directory_or_file ):
156
188
dir_list = os .listdir (directory_or_file )
157
- with ThreadPoolExecutor () as executor :
158
- future_results = []
159
- for dir_item in dir_list :
160
- future_results . append ( executor . submit ( VerifyDVPLFiles , os . path . join ( directory_or_file , dir_item ), config ))
161
- for future in future_results :
162
- succ , fail , ignored = future . result ()
163
- success_count += succ
164
- failure_count += fail
165
- ignored_count += ignored
189
+ for dir_item in dir_list :
190
+ succ , fail , ignored = VerifyDVPLFiles ( os . path . join ( directory_or_file , dir_item ), config , total_files ,
191
+ processed_files )
192
+ success_count += succ
193
+ failure_count += fail
194
+ ignored_count += ignored
195
+ if processed_files . value < total_files : # Ensure processed files count does not exceed total files
196
+ processed_files . value += 1
197
+ print_progress_bar ( processed_files , total_files )
166
198
else :
167
199
is_dvpl_file = directory_or_file .endswith (".dvpl" )
168
200
@@ -187,7 +219,8 @@ def VerifyDVPLFiles(directory_or_file, config):
187
219
188
220
if config .verbose :
189
221
with output_lock :
190
- print (f"{ Color .GREEN } \n File{ Color .RESET } { file_path } has been successfully { Color .GREEN } verified.{ Color .RESET } " )
222
+ print (
223
+ f"{ Color .GREEN } \n File{ Color .RESET } { file_path } has been successfully { Color .GREEN } verified.{ Color .RESET } " )
191
224
192
225
success_count += 1
193
226
except Exception as e :
@@ -203,13 +236,18 @@ def VerifyDVPLFiles(directory_or_file, config):
203
236
204
237
return success_count , failure_count , ignored_count
205
238
239
+
206
240
def ParseCommandLineArgs ():
207
241
parser = argparse .ArgumentParser ()
208
- parser .add_argument ("-m" , "--mode" , help = "mode can be 'compress' / 'decompress' / 'help' (for an extended help guide)." )
209
- parser .add_argument ("-k" , "--keep-originals" , action = "store_true" , help = "keep original files after compression/decompression." )
210
- parser .add_argument ("-v" , "--verbose" , action = "store_true" , help = "shows verbose information for all processed files." )
242
+ parser .add_argument ("-m" , "--mode" ,
243
+ help = "mode can be 'compress' / 'decompress' / 'help' (for an extended help guide)." )
244
+ parser .add_argument ("-k" , "--keep-originals" , action = "store_true" ,
245
+ help = "keep original files after compression/decompression." )
246
+ parser .add_argument ("-v" , "--verbose" , action = "store_true" ,
247
+ help = "shows verbose information for all processed files." )
211
248
parser .add_argument ("-p" , "--path" , help = "directory/files path to process. Default is the current directory." )
212
- parser .add_argument ("-i" , "--ignore" , default = "" , help = "Comma-separated list of file extensions to ignore during compression." )
249
+ parser .add_argument ("-i" , "--ignore" , default = "" ,
250
+ help = "Comma-separated list of file extensions to ignore during compression." )
213
251
args = parser .parse_args ()
214
252
215
253
if not args .mode :
@@ -220,6 +258,7 @@ def ParseCommandLineArgs():
220
258
221
259
return args
222
260
261
+
223
262
def PrintHelpMessage ():
224
263
print ('''pydvpl [--mode] [--keep-originals] [--path]
225
264
@@ -263,12 +302,13 @@ def PrintHelpMessage():
263
302
$ pydvpl --mode compress --path /path/to/decompress --ignore exe,dll
264
303
265
304
$ pydvpl --mode compress --path /path/to/decompress --ignore test.exe,test.txt
266
-
305
+
267
306
$ pydvpl --mode verify -path /path/to/verify
268
307
269
308
$ pydvpl --mode verify -path /path/to/verify/verify.yaml.dvpl
270
309
''' )
271
310
311
+
272
312
def PrintElapsedTime (elapsed_time ):
273
313
if elapsed_time < 1 :
274
314
print (f"\n Processing took { Color .GREEN } { int (elapsed_time * 1000 )} ms{ Color .RESET } \n " )
@@ -277,6 +317,7 @@ def PrintElapsedTime(elapsed_time):
277
317
else :
278
318
print (f"\n Processing took { Color .RED } { int (elapsed_time / 60 )} min{ Color .RESET } \n " )
279
319
320
+
280
321
def main ():
281
322
print (f"\n { Color .BLUE } • Name:{ Color .RESET } { Meta .NAME } " )
282
323
print (f"{ Color .BLUE } • Version:{ Color .RESET } { Meta .VERSION } " )
@@ -288,22 +329,41 @@ def main():
288
329
start_time = time .time ()
289
330
config = ParseCommandLineArgs ()
290
331
332
+ total_files = count_total_files (config .path )
333
+ manager = multiprocessing .Manager ()
334
+ processed_files = manager .Value ('i' , 0 ) # Define processed_files using a Manager
335
+
291
336
try :
292
337
if config .mode in ["compress" , "decompress" ]:
293
- success_count , failure_count , ignored_count = ConvertDVPLFiles ( config . path , config )
294
- print ( f" \n { Color . GREEN } { config . mode . upper () } FINISHED { Color . RESET } . Successful conversions: { Color . GREEN } { success_count } { Color . RESET } , Failed conversions: { Color . RED } { failure_count } { Color . RESET } , Ignored files: { Color . YELLOW } { ignored_count } { Color . RESET } " )
338
+ process_func = partial ( ConvertDVPLFiles , config = config , total_files = total_files ,
339
+ processed_files = processed_files )
295
340
elif config .mode == "verify" :
296
- success_count , failure_count , ignored_count = VerifyDVPLFiles (config .path , config )
297
- print (f"\n { Color .GREEN } { config .mode .upper ()} FINISHED{ Color .RESET } . Successful verifications: { Color .GREEN } { success_count } { Color .RESET } , Failed verifications: { Color .RED } { failure_count } { Color .RESET } , Ignored files: { Color .YELLOW } { ignored_count } { Color .RESET } " )
298
- elif config .mode == "help" :
299
- PrintHelpMessage ()
341
+ process_func = partial (VerifyDVPLFiles , config = config , total_files = total_files ,
342
+ processed_files = processed_files )
300
343
else :
301
344
raise ValueError ("Incorrect mode selected. Use '--help' for information." )
345
+
346
+ with multiprocessing .Pool () as pool :
347
+ results = pool .map (process_func , [config .path ])
348
+
349
+ success_count = sum (result [0 ] for result in results )
350
+ failure_count = sum (result [1 ] for result in results )
351
+ ignored_count = sum (result [2 ] for result in results )
352
+
353
+ if config .mode in ["compress" , "decompress" ]:
354
+ print (
355
+ f"\n \n { Color .GREEN } { config .mode .upper ()} FINISHED{ Color .RESET } . Successful conversions: { Color .GREEN } { success_count } { Color .RESET } , Failed conversions: { Color .RED } { failure_count } { Color .RESET } , Ignored files: { Color .YELLOW } { ignored_count } { Color .RESET } " )
356
+ elif config .mode == "verify" :
357
+ print (
358
+ f"\n \n { Color .GREEN } { config .mode .upper ()} FINISHED{ Color .RESET } . Successful verifications: { Color .GREEN } { success_count } { Color .RESET } , Failed verifications: { Color .RED } { failure_count } { Color .RESET } , Ignored files: { Color .YELLOW } { ignored_count } { Color .RESET } " )
359
+ elif config .mode == "help" :
360
+ PrintHelpMessage ()
302
361
except Exception as e :
303
- print (f"\n { Color .RED } { config .mode .upper ()} FAILED{ Color .RESET } : { e } \n " )
362
+ print (f"\n \n { Color .RED } { config .mode .upper ()} FAILED{ Color .RESET } : { e } \n " )
304
363
305
364
elapsed_time = time .time () - start_time
306
365
PrintElapsedTime (elapsed_time )
307
366
367
+
308
368
if __name__ == "__main__" :
309
369
main ()
0 commit comments