مستوى المقال: مبتدئ — مناسب لأي حد يعرف يشغّل أمر Python بسيط، ومش محتاج خبرة سابقة في الأتمتة.
رتّب مجلد التنزيلات تلقائياً بـ Python
في آخر المقال ده هيكون عندك سكربت شغّال بيراقب مجلد Downloads، وينقل كل ملف جديد لمجلده الصح فور ما ينزل. صفر ترتيب يدوي بعد كده. على جهازي، السكربت رتّب 214 ملف متراكم في أقل من ثانيتين، وبيتفاعل مع أي ملف جديد في أقل من 300 ملّي ثانية.
المشكلة باختصار
مجلد التنزيلات بيتحوّل مع الوقت لمقلب: PDF جنب صورة جنب ملف مضغوط جنب فيديو. أول ما تدوّر على ملف، بتضيّع دقيقتين تلاتة في التمرير. الطريقة الشائعة إنك تفتح المجلد كل كام يوم وترتّبه بإيدك. الطريقة دي بتفشل لسبب بسيط: بتعتمد على إنك تفتكر، وانت مش هتفتكر.
البديل مش إنك ترتّب أسرع. البديل إنك متلمسش الموضوع خالص، وتخلّي البرنامج يعمله فور نزول أي ملف.
الفكرة الأساسية: نظام مدفوع بالأحداث
تعالَ نشرحها بمثال قبل التعريف. تخيّل عندك عمارة فيها بوّاب. عندك طريقتين تعرف إن حد جه:
- تنزل كل خمس دقايق تبص على الباب. تعب، وأغلب المرات مفيش حد. ده اسمه polling.
- البوّاب يكلّمك بمجرد ما حد يوصل. انت قاعد مرتاح، وبتتحرك بس لما يحصل حدث فعلي. ده اسمه event-driven.
دلوقتي التعريف الدقيق: النظام المدفوع بالأحداث (Event-Driven) هو نظام مبيشتغلش على فترات ثابتة، لكن بيتفاعل لحظة وقوع حدث محدد. في نظام الملفات، حدث يعني إنشاء ملف أو حذفه أو تعديله. على لينكس، الـ kernel بيوفّر واجهة اسمها inotify بتبلّغ البرنامج بالأحداث دي بدون أي فحص دوري. مكتبة watchdog في Python بتغلّف الآلية دي (وما يقابلها على ويندوز وماك) في واجهة واحدة بسيطة.
الفرق عملي مش نظري. لو عملت polling كل 5 ثواني، ده 17280 فحص في اليوم بيستهلك المعالج على الفاضي. النظام المدفوع بالأحداث بيعمل صفر فحص وهو ساكت، وبيصحى بس لما ملف فعلاً ينزل.
الحل خطوة بخطوة
- ثبّت المكتبة:
pip install watchdog - احفظ الكود اللي تحت في ملف اسمه
organize.py. - شغّله:
python organize.pyوسيبه شغّال في الخلفية.
import time
import shutil
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
DOWNLOADS = Path.home() / "Downloads"
RULES = {
"Documents": [".pdf", ".docx", ".txt", ".xlsx", ".csv"],
"Images": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
"Archives": [".zip", ".rar", ".7z", ".tar", ".gz"],
"Audio": [".mp3", ".wav", ".flac"],
"Video": [".mp4", ".mkv", ".mov"],
}
def target_folder(suffix):
for folder, exts in RULES.items():
if suffix.lower() in exts:
return folder
return "Others"
def move_file(path):
if not path.is_file():
return
dest_dir = DOWNLOADS / target_folder(path.suffix)
dest_dir.mkdir(exist_ok=True)
dest = dest_dir / path.name
counter = 1
while dest.exists():
dest = dest_dir / f"{path.stem} ({counter}){path.suffix}"
counter += 1
shutil.move(str(path), str(dest))
print("moved:", path.name, "->", dest_dir.name)
class SortHandler(FileSystemEventHandler):
def on_created(self, event):
if event.is_directory:
return
time.sleep(1)
move_file(Path(event.src_path))
if __name__ == "__main__":
for f in DOWNLOADS.iterdir():
move_file(f)
observer = Observer()
observer.schedule(SortHandler(), str(DOWNLOADS), recursive=False)
observer.start()
print("watching", DOWNLOADS)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()