11#!/data/data/com.tool.tree/files/home/termux/bin/python
22
3- # Tóm tắt:
4- # Script kiểm tra các thay đổi của file trong thư mục lớn trên Android/Termux.
5- # - Dùng ThreadPoolExecutor (không sử dụng multiprocessing.SemLock).
6- # - Quét file theo luồng (generator) để giảm tiêu thụ bộ nhớ.
7- # - Lưu kết quả hash, kích thước, thời gian chỉnh sửa vào file JSON.
8- # - Bỏ qua hash file khi kích thước và thời gian không đổi (tiết kiệm thời gian).
9- # - Đọc file với bộ đệm 1 MB.
10- # - Số luồng tối đa mặc định: min(32, CPU*2). Cho phép cấu hình qua tùy chọn --workers.
11- # - Loại trừ đuôi file mặc định: .bak, .tmp, .log. Có thể tùy chỉnh qua --exclude.
12- # - Có tùy chọn --force để tính toán lại tất cả file.
13- # - Lưu file database một cách nguyên tử (ghi vào tạm rồi replace).
14- # - Xử lý đường dẫn Unicode và lỗi I/O một cách chắc chắn.
15- # - In ra thay đổi theo định dạng: new:, fixed:, deleted:.
16- # - Bảng tham số khuyến nghị:
17- # Buffer đọc | Số luồng tối đa | Chunk size (executor.map)
18- # 1048576 (1 MB) | min(32, CPU*2) | 64
19- #
20- # Ví dụ sử dụng:
21- # python script.py /duong/dan/thu_muc data.json --exclude=.bak,.tmp --force
22-
233import os
244import sys
255import json
266import argparse
277import hashlib
28- from concurrent .futures import ThreadPoolExecutor , as_completed
8+ from concurrent .futures import ThreadPoolExecutor
299
3010def compute_hash (file_path , buffer_size = 1048576 ):
3111 """
@@ -70,47 +50,41 @@ def save_hash_db(hash_db_path, hash_db):
7050
7151def iter_files (root_dir , excluded_exts ):
7252 """
73- Yield đường dẫn file trong thư mục (không đệ quy).
53+ Yield đường dẫn file trong thư mục và các thư mục con (có đệ quy).
7454 Loại trừ các file có đuôi trong excluded_exts.
7555 """
76- for entry in os .scandir (root_dir ):
77- if not entry .is_file ():
78- continue
79- name = entry .name
80- if any (name .lower ().endswith (ext ) for ext in excluded_exts ):
81- continue
82- yield entry .path
56+ for dirpath , _ , filenames in os .walk (root_dir ):
57+ for name in filenames :
58+ if any (name .lower ().endswith (ext ) for ext in excluded_exts ):
59+ continue
60+ yield os .path .join (dirpath , name )
8361
8462def scan_directory (root_dir , excluded_exts , previous_db , force = False , buffer_size = 1048576 , max_workers = None ):
8563 """
8664 Quét thư mục, tính hash cho file mới/đã thay đổi, kết hợp với database trước đó.
8765 Trả về dict mới {rel_path: {"hash":..., "size":..., "mtime":...}}.
8866 """
8967 current_db = {}
90- # Chuẩn bị công việc: xác định file cần hash (generator)
9168 def gen_tasks ():
9269 for file_path in iter_files (root_dir , excluded_exts ):
9370 rel = os .path .relpath (file_path , root_dir )
9471 try :
9572 st = os .stat (file_path )
9673 except Exception :
97- continue # bỏ qua file không thể truy xuất
74+ continue
9875 size = st .st_size
9976 mtime = st .st_mtime_ns
10077 prev = previous_db .get (rel )
10178 if not force and prev and prev .get ("size" ) == size and prev .get ("mtime" ) == mtime :
102- # Không thay đổi, dùng hash cũ
10379 current_db [rel ] = {"hash" : prev .get ("hash" ), "size" : size , "mtime" : mtime }
10480 else :
105- # Cần tính hash
10681 yield file_path
10782
10883 if max_workers is None :
10984 cpu = os .cpu_count () or 1
11085 max_workers = min (32 , cpu * 2 )
11186 try :
11287 with ThreadPoolExecutor (max_workers = max_workers ) as executor :
113- # executor.map với chunksize giới hạn số tasks đồng thời
11488 for file_path , file_hash in executor .map (lambda p : compute_hash (p , buffer_size ), gen_tasks (), chunksize = 64 ):
11589 rel = os .path .relpath (file_path , root_dir )
11690 try :
@@ -120,10 +94,7 @@ def gen_tasks():
12094 except Exception :
12195 size = None
12296 mtime = None
123- if file_hash :
124- current_db [rel ] = {"hash" : file_hash , "size" : size , "mtime" : mtime }
125- else :
126- current_db [rel ] = {"hash" : None , "size" : size , "mtime" : mtime }
97+ current_db [rel ] = {"hash" : file_hash , "size" : size , "mtime" : mtime }
12798 except KeyboardInterrupt :
12899 print ("Program paused (interrupted by user)..." , file = sys .stderr )
129100 return current_db
@@ -138,7 +109,6 @@ def check_changes(root_dir, hash_db_path, exclude_list, force=False, buffer_size
138109 previous_db = load_hash_db (hash_db_path )
139110 current_db = scan_directory (root_dir , exclude_list , previous_db , force , buffer_size , max_workers )
140111
141- # In kết quả so sánh
142112 for rel , info in current_db .items ():
143113 if rel not in previous_db :
144114 print (f"new: { rel } " )
@@ -148,7 +118,6 @@ def check_changes(root_dir, hash_db_path, exclude_list, force=False, buffer_size
148118 if rel not in current_db :
149119 print (f"deleted: { rel } " )
150120
151- # Lưu database mới
152121 save_hash_db (hash_db_path , current_db )
153122
154123def main ():
0 commit comments