بناء مدير ملفات بإسخدام بايثون

بناء مدير ملفات بإسخدام بايثون

عندما تبدأ في بناء مشروع صغير بلغة Python، قد يبدو لك أن “مدير ملفات” مجرد أداة بسيطة تقوم بعرض الملفات داخل مجلد معين. لكن الحقيقة أن هذا النوع من المشاريع يفتح لك بابًا واسعًا جدًا لتعلّم أشياء مهمة ومفيدة في البرمجة: التعامل مع النظام، قراءة المسارات، إنشاء الملفات، حذفها، نسخها، نقلها، البحث عنها، عرض المعلومات الخاصة بها، ومعالجة الأخطاء بطريقة ذكية. وفي الوقت نفسه، هو مشروع عملي جدًا، لأنك ستشعر منذ أول تشغيل أن ما كتبته ليس مجرد تمارين نظرية، بل أداة حقيقية يمكن استخدامها يوميًا.

في هذا المقال سنبني File Manager في Python بأسلوب تدريجي، من الفكرة الأساسية إلى نسخة أكثر نضجًا واحترافية. سنعتمد على مكتبات Python القياسية مثل pathlib وos وshutil، وسنكتب أمثلة واضحة ومباشرة، مع شرح هادئ ومفهوم. الهدف هنا ليس فقط أن تنسخ الكود، بل أن تفهم كيف يعمل ولماذا صُمّم بهذه الطريقة. لأن البرمجة الحقيقية تبدأ عندما تعرف “لماذا”، وليس فقط “كيف”.

سنفترض أننا نبني مدير ملفات يعمل من الطرفية Terminal، ثم سنضيف عليه ميزات تجعل التجربة أقرب إلى أداة فعلية: التنقل بين المجلدات، عرض الملفات بشكل منظم، إنشاء مجلدات وملفات، إعادة التسمية، الحذف، النسخ، النقل، البحث، وقراءة بعض البيانات الأساسية مثل الحجم وتاريخ التعديل. وفي نهاية المقال ستجد نسخة كاملة يمكن تطويرها لاحقًا إلى واجهة رسومية باستخدام Tkinter أو PyQt إذا أردت.

ما هو File Manager في Python؟

مدير الملفات هو برنامج يتيح لك التحكم في الملفات والمجلدات داخل نظام التشغيل. بدل أن تعتمد فقط على مستعرض الملفات التقليدي، يمكنك إنشاء أداة خاصة بك تنفذ أوامر مثل:

  • عرض محتويات مجلد معين

  • الانتقال إلى مجلد آخر

  • إنشاء ملف جديد أو مجلد جديد

  • حذف ملف أو مجلد

  • نسخ ملف أو مجلد

  • نقل ملف أو مجلد

  • إعادة تسمية عنصر

  • البحث عن ملف معين

  • عرض معلومات تفصيلية عن العناصر

في Python، هذه العمليات سهلة نسبيًا لأن اللغة توفر مكتبات قوية جدًا للتعامل مع الملفات. أهمها:

  • pathlib للتعامل مع المسارات بطريقة حديثة وأنيقة

  • os للتعامل مع النظام والملفات

  • shutil للنسخ والنقل والحذف على مستوى أعلى

  • datetime لعرض التواريخ

  • argparse إذا أردت تحويل المشروع إلى أداة سطر أوامر أكثر احترافية

والجميل في Python أنها تجعل الكود واضحًا وقابلًا للقراءة، وهذا مهم جدًا في مشاريع مثل مدير الملفات حيث يكون التنظيم والنظافة البرمجية جزءًا من قوة الأداة نفسها.

لماذا هذا المشروع مفيد جدًا؟

قد يسأل البعض: لماذا أحتاج إلى بناء File Manager من الأساس، بينما النظام لديه مدير ملفات جاهز؟ والجواب ببساطة أن هذا المشروع ليس فقط من أجل “الاستخدام النهائي”، بل من أجل “الفهم العميق”.

عندما تبني مدير ملفات بنفسك، فأنت تتعلم كيف تتعامل مع أشياء ستحتاجها في مشاريع كثيرة لاحقًا، مثل أنظمة النسخ الاحتياطي، أدوات تنظيم الصور، برامج إدارة المستندات، أدوات تحميل الملفات، لوحات التحكم الإدارية، وأتمتة المهام داخل السيرفرات. كذلك، هذا النوع من المشاريع يدرّبك على التعامل مع الأخطاء التي تحدث كثيرًا في الواقع: ملف غير موجود، صلاحيات مفقودة، اسم مسار غير صالح، مجلد محذوف أثناء التنفيذ، أو محاولة نسخ ملف ضخم بطريقة غير مناسبة.

ومن زاوية أخرى، هذا المشروع يمنحك شعورًا جميلًا بالإنجاز. لأنك لا تبني شيئًا نظريًا فقط، بل تكتب برنامجًا يلمس أرض الواقع مباشرة. وهذا النوع من المشاريع يترك أثرًا قويًا في أي متعلم، خاصة عندما يبدأ يرى النتائج بعينيه.

الأدوات التي سنستخدمها

سنستخدم مكتبات Python المدمجة، وبالتالي لن تحتاج إلى تثبيت أشياء كثيرة. هذا من مزايا المشروع أيضًا، لأنه يظل خفيفًا وسهل التشغيل.

المكتبات الأساسية

from pathlib import Path
import os
import shutil
from datetime import datetime

لماذا pathlib بدل الاعتماد الكلي على os.path؟

لأن pathlib يجعل التعامل مع المسارات أكثر أناقة وأوضح في القراءة. بدل أن تكتب سلسلة طويلة من الدوال، ستتعامل مع المسار ككائن Object. وهذا يسهّل عليك كتابة كود منظم وقابل للتوسع.

مثال بسيط:

from pathlib import Path

current_dir = Path.cwd()
print(current_dir)
print(current_dir.exists())
print(current_dir.is_dir())

هنا لم نعد نتعامل مع المسار كنص عادي، بل ككائن يملك خصائص ودوال تساعدنا مباشرة.

فكرة المشروع

سنصمم مدير ملفات بسيط لكنه مفيد جدًا. سيحتوي على وظائف مثل:

  • عرض الملفات والمجلدات

  • عرض حجم الملف

  • عرض تاريخ التعديل

  • الانتقال بين المجلدات

  • إنشاء ملف أو مجلد

  • إعادة تسمية

  • حذف

  • نسخ

  • نقل

  • بحث

وسنبني واجهة نصية Menu داخل الطرفية، حتى يكون المشروع سهل التجربة. بعد ذلك يمكن تحويله إلى واجهة رسومية لاحقًا.

هيكل المشروع

يمكنك البدء بملف واحد فقط، مثل:

file_manager.py

لكن من الأفضل أن تنظم الكود داخل أكثر من جزء إذا بدأ المشروع يكبر. ومع ذلك، حتى الآن سنبقيه في ملف واحد لتسهيل الفهم.

الجزء الأول: عرض محتويات المجلد

هذه هي الخطوة الأولى الطبيعية. نريد أن نقرأ محتويات مجلد ونطبعها بشكل مرتب.

from pathlib import Path

def list_directory(path="."):
    directory = Path(path)

    if not directory.exists():
        print("المسار غير موجود.")
        return

    if not directory.is_dir():
        print("المسار ليس مجلدًا.")
        return

    print(f"\nمحتويات المجلد: {directory.resolve()}\n")
    for item in directory.iterdir():
        if item.is_dir():
            print(f"[DIR]  {item.name}")
        else:
            print(f"[FILE] {item.name}")

ماذا يفعل هذا الكود؟

هذا الكود يستقبل مسارًا، ثم يتحقق هل هو موجود أم لا. إذا لم يكن موجودًا، يعرض رسالة مناسبة. إذا كان موجودًا لكنه ليس مجلدًا، يعرض تنبيهًا. وإذا كان مجلدًا فعلًا، يطبع محتوياته واحدة واحدة.

الخطوة الصغيرة هنا مهمة جدًا، لأن التحقق من الحالة قبل التنفيذ هو ما يجعل برنامجك محترمًا من ناحية السلوك. المستخدم لا يحب أن يرى أخطاء غامضة، بل يريد رسالة واضحة تخبره ماذا حدث.

الجزء الثاني: الانتقال إلى مجلد آخر

في مدير الملفات، التنقل بين المجلدات هو قلب التجربة. يمكننا كتابة دالة تعطيك المسار الجديد:

from pathlib import Path

def change_directory(current_path, new_path):
    new_directory = Path(new_path)

    if not new_directory.is_absolute():
        new_directory = current_path / new_directory

    if new_directory.exists() and new_directory.is_dir():
        return new_directory.resolve()
    else:
        print("المجلد غير موجود.")
        return current_path

مثال استعمال

current_dir = Path.cwd()
current_dir = change_directory(current_dir, "Downloads")
print(current_dir)

هنا لو كان Downloads مجلدًا داخل المسار الحالي، سينتقل إليه البرنامج. ولو لم يكن موجودًا، سيبقى في نفس المكان.

الجزء الثالث: إنشاء مجلد جديد

إنشاء المجلدات عملية أساسية جدًا.

from pathlib import Path

def create_folder(folder_path):
    folder = Path(folder_path)

    try:
        folder.mkdir(parents=True, exist_ok=False)
        print(f"تم إنشاء المجلد: {folder}")
    except FileExistsError:
        print("المجلد موجود بالفعل.")
    except Exception as e:
        print(f"حدث خطأ أثناء إنشاء المجلد: {e}")

شرح بسيط

  • parents=True يعني إنشاء المجلدات الأب إذا كانت غير موجودة

  • exist_ok=False يعني أن البرنامج سيرفض إنشاء المجلد إذا كان موجودًا أصلًا

هذا مهم لأنك قد لا تريد أن يمرّ الأمر بصمت إذا كان المجلد موجودًا بالفعل. أحيانًا يكون الخطأ مفيدًا، لأنه يخبرك أن هناك شيئًا يجب مراجعته.

الجزء الرابع: إنشاء ملف جديد

الآن ننتقل إلى إنشاء ملف نصي جديد.

from pathlib import Path

def create_file(file_path, content=""):
    file = Path(file_path)

    try:
        file.parent.mkdir(parents=True, exist_ok=True)
        file.write_text(content, encoding="utf-8")
        print(f"تم إنشاء الملف: {file}")
    except Exception as e:
        print(f"حدث خطأ أثناء إنشاء الملف: {e}")

مثال

create_file("notes/todo.txt", "اشترِ الخبز\nراجع الكود\nاقرأ المقال")

هذا الكود ينشئ ملفًا داخل مجلد notes، وإن لم يكن المجلد موجودًا فسيتم إنشاؤه تلقائيًا. هذه لمسة جميلة جدًا لأن المستخدم لا يحتاج إلى إنشاء كل شيء يدويًا.

الجزء الخامس: إعادة تسمية ملف أو مجلد

إعادة التسمية مهمة جدًا في أي File Manager.

from pathlib import Path

def rename_item(old_path, new_name):
    old_item = Path(old_path)

    if not old_item.exists():
        print("العنصر غير موجود.")
        return

    new_item = old_item.with_name(new_name)

    try:
        old_item.rename(new_item)
        print(f"تمت إعادة التسمية إلى: {new_item.name}")
    except Exception as e:
        print(f"حدث خطأ أثناء إعادة التسمية: {e}")

مثال

rename_item("notes/todo.txt", "tasks.txt")

سيتم تغيير الاسم من todo.txt إلى tasks.txt داخل نفس المجلد.

الجزء السادس: حذف ملف

الحذف يجب أن يكون حذرًا، لأنه عملية حساسة. لذلك لا بد أن تفصل بين حذف الملف وحذف المجلد.

from pathlib import Path

def delete_file(file_path):
    file = Path(file_path)

    if not file.exists():
        print("الملف غير موجود.")
        return

    if file.is_file():
        try:
            file.unlink()
            print("تم حذف الملف بنجاح.")
        except Exception as e:
            print(f"حدث خطأ أثناء الحذف: {e}")
    else:
        print("المسار ليس ملفًا.")

لماذا استخدمنا unlink()؟

لأنها الطريقة المناسبة لحذف الملفات في pathlib. وهي بسيطة وواضحة.

الجزء السابع: حذف مجلد

حذف المجلد يختلف، لأنه قد يكون فارغًا أو يحتوي على ملفات.

from pathlib import Path
import shutil

def delete_folder(folder_path):
    folder = Path(folder_path)

    if not folder.exists():
        print("المجلد غير موجود.")
        return

    if folder.is_dir():
        try:
            shutil.rmtree(folder)
            print("تم حذف المجلد ومحتوياته.")
        except Exception as e:
            print(f"حدث خطأ أثناء حذف المجلد: {e}")
    else:
        print("المسار ليس مجلدًا.")

ملاحظة مهمة

shutil.rmtree() يحذف المجلد بكل ما بداخله. لهذا يجب أن يكون المستخدم واعيًا قبل تنفيذ هذه العملية. في التطبيقات الحقيقية، من الأفضل أن تسأل المستخدم للتأكيد قبل الحذف النهائي.

الجزء الثامن: نسخ ملف

النسخ من أكثر العمليات استخدامًا.

from pathlib import Path
import shutil

def copy_file(source, destination):
    src = Path(source)
    dest = Path(destination)

    if not src.exists():
        print("الملف المصدر غير موجود.")
        return

    try:
        if dest.is_dir():
            dest = dest / src.name

        dest.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src, dest)
        print(f"تم نسخ الملف إلى: {dest}")
    except Exception as e:
        print(f"حدث خطأ أثناء النسخ: {e}")

لماذا copy2؟

لأنها لا تنسخ الملف فقط، بل تحاول أيضًا الاحتفاظ ببعض البيانات الوصفية مثل أوقات التعديل.

الجزء التاسع: نقل ملف أو مجلد

النقل يشبه النسخ، لكن مع إزالة العنصر من مكانه الأصلي.

from pathlib import Path
import shutil

def move_item(source, destination):
    src = Path(source)
    dest = Path(destination)

    if not src.exists():
        print("العنصر المصدر غير موجود.")
        return

    try:
        if dest.is_dir():
            dest = dest / src.name

        dest.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(src), str(dest))
        print(f"تم نقل العنصر إلى: {dest}")
    except Exception as e:
        print(f"حدث خطأ أثناء النقل: {e}")

ملاحظة

استخدمنا str() لأن shutil.move قد يكون أكثر ارتياحًا مع النصوص في بعض الحالات، رغم أن Path في Python الحديثة يعمل بشكل جيد جدًا في الكثير من الدوال.

الجزء العاشر: عرض معلومات الملف

هذا الجزء مهم جدًا، لأنه يجعل مدير الملفات أكثر فائدة. نريد أن نعرض:

  • الاسم

  • النوع

  • الحجم

  • تاريخ التعديل

  • هل هو ملف أم مجلد

from pathlib import Path
from datetime import datetime

def get_file_info(file_path):
    file = Path(file_path)

    if not file.exists():
        print("العنصر غير موجود.")
        return

    size = file.stat().st_size
    modified_time = datetime.fromtimestamp(file.stat().st_mtime)

    print("\nمعلومات العنصر:")
    print(f"الاسم: {file.name}")
    print(f"المسار الكامل: {file.resolve()}")
    print(f"النوع: {'مجلد' if file.is_dir() else 'ملف'}")
    print(f"الحجم: {size} بايت")
    print(f"آخر تعديل: {modified_time}")

هذا النوع من التفاصيل يضيف قيمة حقيقية للأداة. فالمستخدم لا يريد فقط رؤية الاسم، بل يريد أن يفهم ما الذي يتعامل معه.

الجزء الحادي عشر: حساب حجم المجلد

حجم المجلد ليس مباشرًا مثل حجم الملف، لأنك تحتاج إلى جمع أحجام كل الملفات بداخله.

from pathlib import Path

def get_folder_size(folder_path):
    folder = Path(folder_path)

    if not folder.exists():
        print("المجلد غير موجود.")
        return 0

    if not folder.is_dir():
        print("المسار ليس مجلدًا.")
        return 0

    total_size = 0

    for item in folder.rglob("*"):
        if item.is_file():
            total_size += item.stat().st_size

    return total_size

مثال

size = get_folder_size("my_project")
print(f"الحجم الكلي: {size} بايت")

وهنا يمكن لاحقًا تحويل البايت إلى KB أو MB أو GB، حتى يكون العرض أجمل للعين.

تحويل الحجم إلى صيغة مفهومة

def format_size(size_in_bytes):
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if size_in_bytes < 1024:
            return f"{size_in_bytes:.2f} {unit}"
        size_in_bytes /= 1024
    return f"{size_in_bytes:.2f} PB"

مثال

print(format_size(1536))      # 1.50 KB
print(format_size(1048576))   # 1.00 MB

هذا التفصيل الصغير يغير تجربة المستخدم كثيرًا. أحيانًا الفرق بين برنامج “عادي” وبرنامج “مريح” يكون في هذه اللمسات البسيطة.

الجزء الثاني عشر: البحث عن ملف داخل مجلد

ميزة البحث مهمة جدًا، خصوصًا عندما يكون لديك عدد كبير من الملفات.

from pathlib import Path

def search_files(directory, keyword):
    directory = Path(directory)

    if not directory.exists():
        print("المجلد غير موجود.")
        return []

    results = []

    for item in directory.rglob("*"):
        if keyword.lower() in item.name.lower():
            results.append(item)

    return results

مثال استعمال

matches = search_files(".", "report")
for match in matches:
    print(match)

هذا الكود يبحث في جميع المجلدات الفرعية أيضًا، وهذا مفيد جدًا عند التعامل مع مشاريع كبيرة أو أرشيف ملفات قديم.

الجزء الثالث عشر: فلترة الملفات حسب الامتداد

أحيانًا نريد فقط صورًا أو ملفات Python أو مستندات PDF.

from pathlib import Path

def filter_by_extension(directory, extension):
    directory = Path(directory)

    if not directory.exists():
        print("المجلد غير موجود.")
        return []

    if not extension.startswith("."):
        extension = "." + extension

    results = [item for item in directory.iterdir() if item.is_file() and item.suffix.lower() == extension.lower()]
    return results

مثال

python_files = filter_by_extension(".", "py")
for f in python_files:
    print(f)

هذه الميزة مفيدة جدًا للعرض السريع والتنظيم.

الجزء الرابع عشر: عرض العناصر بشكل مرتب

عرض الملفات بطريقة جميلة يجعل الأداة أسهل في الاستخدام.

from pathlib import Path
from datetime import datetime

def list_directory_details(path="."):
    directory = Path(path)

    if not directory.exists():
        print("المسار غير موجود.")
        return

    print(f"\n{'الاسم':<30} {'النوع':<10} {'الحجم':<15} {'آخر تعديل'}")
    print("-" * 80)

    for item in directory.iterdir():
        item_type = "مجلد" if item.is_dir() else "ملف"
        size = "-"
        modified = datetime.fromtimestamp(item.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")

        if item.is_file():
            size = format_size(item.stat().st_size)
        elif item.is_dir():
            size = format_size(get_folder_size(item))

        print(f"{item.name:<30} {item_type:<10} {size:<15} {modified}")

هنا بدأنا نقترب من تجربة File Manager حقيقية، لأن العرض المنظم يعطي انطباعًا واضحًا ومريحًا.

الجزء الخامس عشر: بناء قائمة تفاعلية في الطرفية

الآن سنجمع كل شيء في قائمة تفاعلية بسيطة. هذه هي اللحظة التي يبدأ فيها المشروع يشعر بأنه “برنامج” بالفعل.

from pathlib import Path

def main():
    current_dir = Path.cwd()

    while True:
        print("\n" + "=" * 40)
        print(f"المجلد الحالي: {current_dir}")
        print("=" * 40)
        print("1. عرض المحتويات")
        print("2. الانتقال إلى مجلد")
        print("3. إنشاء مجلد")
        print("4. إنشاء ملف")
        print("5. إعادة تسمية عنصر")
        print("6. حذف ملف")
        print("7. حذف مجلد")
        print("8. نسخ ملف")
        print("9. نقل عنصر")
        print("10. معلومات عنصر")
        print("11. بحث")
        print("12. الخروج")

        choice = input("اختر رقمًا: ").strip()

        if choice == "1":
            list_directory_details(current_dir)

        elif choice == "2":
            new_path = input("أدخل المسار الجديد: ").strip()
            current_dir = change_directory(current_dir, new_path)

        elif choice == "3":
            folder_name = input("اسم المجلد الجديد: ").strip()
            create_folder(current_dir / folder_name)

        elif choice == "4":
            file_name = input("اسم الملف الجديد: ").strip()
            content = input("محتوى الملف (اختياري): ")
            create_file(current_dir / file_name, content)

        elif choice == "5":
            old_name = input("المسار القديم: ").strip()
            new_name = input("الاسم الجديد: ").strip()
            rename_item(current_dir / old_name, new_name)

        elif choice == "6":
            file_name = input("اسم الملف المراد حذفه: ").strip()
            delete_file(current_dir / file_name)

        elif choice == "7":
            folder_name = input("اسم المجلد المراد حذفه: ").strip()
            delete_folder(current_dir / folder_name)

        elif choice == "8":
            source = input("مسار الملف المصدر: ").strip()
            destination = input("مسار الوجهة: ").strip()
            copy_file(current_dir / source, current_dir / destination)

        elif choice == "9":
            source = input("مسار العنصر المصدر: ").strip()
            destination = input("مسار الوجهة: ").strip()
            move_item(current_dir / source, current_dir / destination)

        elif choice == "10":
            item_name = input("مسار العنصر: ").strip()
            get_file_info(current_dir / item_name)

        elif choice == "11":
            keyword = input("كلمة البحث: ").strip()
            results = search_files(current_dir, keyword)
            if results:
                print("\nالنتائج:")
                for result in results:
                    print(result)
            else:
                print("لم يتم العثور على أي نتائج.")

        elif choice == "12":
            print("وداعًا!")
            break

        else:
            print("خيار غير صحيح، حاول مرة أخرى.")

if __name__ == "__main__":
    main()

هذه النسخة تصلح كنقطة انطلاق ممتازة. هي ليست “نهائية” طبعًا، لكنها تمثل نواة حقيقية لمدير ملفات عملي. والأجمل أنك تستطيع تطويرها في أي اتجاه تريده.

تحسينات مهمة جدًا على المشروع

المشروع الآن جيد، لكنه ما زال يحتاج إلى بعض اللمسات حتى يصبح أكثر احترافية. على سبيل المثال:

1) التأكيد قبل الحذف

من الأفضل أن تسأل المستخدم قبل تنفيذ الحذف النهائي.

def confirm_delete():
    answer = input("هل أنت متأكد من الحذف؟ (y/n): ").strip().lower()
    return answer == "y"

ثم تستعملها قبل الحذف.

2) دعم المسارات النسبية والمطلقة بشكل أفضل

أحيانًا المستخدم يكتب اسمًا بسيطًا، وأحيانًا يكتب مسارًا كاملًا. من الجيد أن يكون البرنامج مرنًا في الحالتين.

3) تحسين التعامل مع الأخطاء

بدل رسالة عامة مثل “حدث خطأ”، من الأفضل أحيانًا عرض السبب بشكل واضح، خصوصًا أثناء التطوير.

4) تلوين النصوص في الطرفية

يمكنك استخدام مكتبات مثل colorama لإظهار الرسائل بشكل أجمل، لكن هذا اختياري.

5) إضافة سلة مهملات بدل الحذف المباشر

هذا مستوى أرقى بكثير من الحذف النهائي، لأنه يمنع فقدان الملفات عن طريق الخطأ.

نسخة أكثر احترافية: استخدام كلاس FileManager

عندما يكبر المشروع، يصبح من الأفضل تنظيمه داخل class بدل الاعتماد على دوال متفرقة فقط. هذا يعطيك بنية أوضح.

from pathlib import Path
import shutil
from datetime import datetime

class FileManager:
    def __init__(self, start_path=None):
        self.current_path = Path(start_path) if start_path else Path.cwd()

    def list_items(self):
        if not self.current_path.exists():
            print("المسار الحالي غير موجود.")
            return

        for item in self.current_path.iterdir():
            item_type = "DIR" if item.is_dir() else "FILE"
            print(f"[{item_type}] {item.name}")

    def change_directory(self, new_path):
        new_dir = Path(new_path)

        if not new_dir.is_absolute():
            new_dir = self.current_path / new_dir

        if new_dir.exists() and new_dir.is_dir():
            self.current_path = new_dir.resolve()
            print(f"تم الانتقال إلى: {self.current_path}")
        else:
            print("المجلد غير موجود.")

    def make_folder(self, name):
        try:
            (self.current_path / name).mkdir(parents=True, exist_ok=False)
            print("تم إنشاء المجلد.")
        except FileExistsError:
            print("المجلد موجود بالفعل.")

    def make_file(self, name, content=""):
        try:
            file_path = self.current_path / name
            file_path.parent.mkdir(parents=True, exist_ok=True)
            file_path.write_text(content, encoding="utf-8")
            print("تم إنشاء الملف.")
        except Exception as e:
            print(f"خطأ: {e}")

    def info(self, name):
        item = self.current_path / name
        if not item.exists():
            print("العنصر غير موجود.")
            return

        stat = item.stat()
        print(f"الاسم: {item.name}")
        print(f"النوع: {'مجلد' if item.is_dir() else 'ملف'}")
        print(f"الحجم: {stat.st_size} بايت")
        print(f"آخر تعديل: {datetime.fromtimestamp(stat.st_mtime)}")

استخدام الكلاس يجعل المشروع أكثر قابلية للتوسع. يمكنك الآن إضافة طرق جديدة بسهولة، مثل copy_item وmove_item وdelete_item وsearch.

مثال عملي كامل

إليك مثالًا صغيرًا يبيّن كيف تستخدم الكلاس:

fm = FileManager()

fm.list_items()
fm.make_folder("demo_folder")
fm.make_file("demo_folder/hello.txt", "Hello from Python!")
fm.info("demo_folder/hello.txt")
fm.change_directory("demo_folder")
fm.list_items()

هذا المثال وحده كفيل بأن يمنحك تصورًا جيدًا عن سهولة الاستخدام عندما يصبح المشروع منظمًا.

كيف تجعل مدير الملفات يدعم الواجهة الرسومية؟

إذا أردت تطوير المشروع لاحقًا، فهناك خيارات ممتازة:

Tkinter

مكتبة مدمجة في Python، ممتازة للمشاريع البسيطة والمتوسطة. يمكنك إنشاء نافذة تعرض الملفات، أزرار للحذف والنسخ، وقائمة للتنقل داخل المجلدات.

PyQt أو PySide

خيار أقوى وأكثر احترافية، مناسب إذا كنت تريد واجهة حديثة جدًا وتفاعلية.

customtkinter

لو أردت مظهرًا أجمل من Tkinter التقليدي، فهذه مكتبة جيدة جدًا من حيث الشكل وسهولة الاستخدام.

الانتقال من CLI إلى GUI خطوة طبيعية جدًا لهذا المشروع، لأن منطق الملفات نفسه يبقى كما هو، فقط تتغير طريقة العرض والتفاعل.

أخطاء شائعة يجب الانتباه لها

عند بناء مدير ملفات، هناك أخطاء يقع فيها كثير من المبتدئين. وهذه بعض منها:

1) حذف المجلدات دون تأكيد

هذا قد يسبب فقدان بيانات مهمّة جدًا.

2) عدم التحقق من وجود المسار

قد يؤدي ذلك إلى أخطاء مزعجة للمستخدم.

3) خلط المسارات النسبية والمطلقة

خصوصًا عندما يبدأ البرنامج من مجلد معين ثم ينتقل المستخدم لمجلد آخر.

4) نسيان الترميز utf-8

عند التعامل مع نصوص عربية أو ملفات تحتوي على أحرف غير لاتينية، هذا أمر مهم جدًا.

5) تجاهل الصلاحيات

أحيانًا يكون الملف موجودًا لكن النظام يمنعك من تعديله أو حذفه.

6) عدم التعامل مع الملفات الكبيرة

إذا بدأت المشروع يتعامل مع ملفات ضخمة، ستحتاج إلى التفكير في الأداء والذاكرة.

لمسة واقعية: كيف يفكر المطوّر أثناء البناء؟

من الجميل أن ننظر إلى المشروع ليس فقط كأكواد، بل كرحلة. في البداية تبدأ بفكرة صغيرة: “أريد أن أرى الملفات داخل مجلد”. ثم تضيف الانتقال. ثم الإنشاء. ثم الحذف. ثم تقول في نفسك: “لماذا لا أضيف بحثًا؟ ولماذا لا أعرض الحجم؟ ولماذا لا أرتب النتائج؟”

وهكذا يتحول المشروع من أداة صغيرة جدًا إلى برنامج له شخصية. وهذه هي متعة البرمجة الحقيقية. ليس المهم فقط أن يعمل البرنامج، بل أن تشعر أن كل جزء فيه موجود لسبب واضح. عندما تصل إلى هذه المرحلة، ستبدأ تفكر مثل المطورين المحترفين: كيف أجعل الكود أوضح؟ كيف أجعل المستخدم يشعر بالراحة؟ كيف أتجنب الخطأ قبل أن يحدث؟

هذا التفكير هو ما يصنع الفرق.

نسخة متقدمة: دمج كل العمليات في ملف واحد

الآن سأعطيك نسخة شاملة نسبيًا تجمع أهم الوظائف في تصميم واحد، ويمكنك تطويرها لاحقًا.

from pathlib import Path
import shutil
from datetime import datetime

def format_size(size_in_bytes):
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if size_in_bytes < 1024:
            return f"{size_in_bytes:.2f} {unit}"
        size_in_bytes /= 1024
    return f"{size_in_bytes:.2f} PB"

def get_folder_size(folder_path):
    folder = Path(folder_path)
    total_size = 0
    if folder.exists() and folder.is_dir():
        for item in folder.rglob("*"):
            if item.is_file():
                total_size += item.stat().st_size
    return total_size

def list_directory_details(path="."):
    directory = Path(path)
    if not directory.exists():
        print("المسار غير موجود.")
        return

    print(f"\n{'الاسم':<30} {'النوع':<10} {'الحجم':<15} {'آخر تعديل'}")
    print("-" * 80)

    for item in directory.iterdir():
        item_type = "مجلد" if item.is_dir() else "ملف"
        modified = datetime.fromtimestamp(item.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")

        if item.is_file():
            size = format_size(item.stat().st_size)
        else:
            size = format_size(get_folder_size(item))

        print(f"{item.name:<30} {item_type:<10} {size:<15} {modified}")

def change_directory(current_path, new_path):
    new_directory = Path(new_path)
    if not new_directory.is_absolute():
        new_directory = current_path / new_directory

    if new_directory.exists() and new_directory.is_dir():
        return new_directory.resolve()
    else:
        print("المجلد غير موجود.")
        return current_path

def create_folder(folder_path):
    folder = Path(folder_path)
    try:
        folder.mkdir(parents=True, exist_ok=False)
        print(f"تم إنشاء المجلد: {folder}")
    except FileExistsError:
        print("المجلد موجود بالفعل.")
    except Exception as e:
        print(f"حدث خطأ: {e}")

def create_file(file_path, content=""):
    file = Path(file_path)
    try:
        file.parent.mkdir(parents=True, exist_ok=True)
        file.write_text(content, encoding="utf-8")
        print(f"تم إنشاء الملف: {file}")
    except Exception as e:
        print(f"حدث خطأ: {e}")

def rename_item(old_path, new_name):
    old_item = Path(old_path)
    if not old_item.exists():
        print("العنصر غير موجود.")
        return

    new_item = old_item.with_name(new_name)

    try:
        old_item.rename(new_item)
        print("تمت إعادة التسمية بنجاح.")
    except Exception as e:
        print(f"حدث خطأ: {e}")

def delete_file(file_path):
    file = Path(file_path)
    if file.exists() and file.is_file():
        try:
            file.unlink()
            print("تم حذف الملف.")
        except Exception as e:
            print(f"حدث خطأ: {e}")
    else:
        print("الملف غير موجود أو المسار ليس ملفًا.")

def delete_folder(folder_path):
    folder = Path(folder_path)
    if folder.exists() and folder.is_dir():
        try:
            shutil.rmtree(folder)
            print("تم حذف المجلد.")
        except Exception as e:
            print(f"حدث خطأ: {e}")
    else:
        print("المجلد غير موجود أو المسار ليس مجلدًا.")

def copy_file(source, destination):
    src = Path(source)
    dest = Path(destination)

    if not src.exists():
        print("المصدر غير موجود.")
        return

    try:
        if dest.is_dir():
            dest = dest / src.name
        dest.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src, dest)
        print("تم النسخ بنجاح.")
    except Exception as e:
        print(f"حدث خطأ: {e}")

def move_item(source, destination):
    src = Path(source)
    dest = Path(destination)

    if not src.exists():
        print("المصدر غير موجود.")
        return

    try:
        if dest.is_dir():
            dest = dest / src.name
        dest.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(src), str(dest))
        print("تم النقل بنجاح.")
    except Exception as e:
        print(f"حدث خطأ: {e}")

def get_file_info(file_path):
    file = Path(file_path)
    if not file.exists():
        print("العنصر غير موجود.")
        return

    stat = file.stat()
    print("\nمعلومات العنصر:")
    print(f"الاسم: {file.name}")
    print(f"المسار: {file.resolve()}")
    print(f"النوع: {'مجلد' if file.is_dir() else 'ملف'}")
    print(f"الحجم: {format_size(stat.st_size)}")
    print(f"آخر تعديل: {datetime.fromtimestamp(stat.st_mtime)}")

def search_files(directory, keyword):
    directory = Path(directory)
    if not directory.exists():
        print("المجلد غير موجود.")
        return []

    results = []
    for item in directory.rglob("*"):
        if keyword.lower() in item.name.lower():
            results.append(item)
    return results

def main():
    current_dir = Path.cwd()

    while True:
        print("\n" + "=" * 50)
        print(f"File Manager - المجلد الحالي: {current_dir}")
        print("=" * 50)
        print("1) عرض المحتويات")
        print("2) الانتقال إلى مجلد")
        print("3) إنشاء مجلد")
        print("4) إنشاء ملف")
        print("5) إعادة تسمية")
        print("6) حذف ملف")
        print("7) حذف مجلد")
        print("8) نسخ ملف")
        print("9) نقل عنصر")
        print("10) معلومات عنصر")
        print("11) بحث")
        print("12) خروج")

        choice = input("اختر: ").strip()

        if choice == "1":
            list_directory_details(current_dir)
        elif choice == "2":
            new_path = input("أدخل المسار: ").strip()
            current_dir = change_directory(current_dir, new_path)
        elif choice == "3":
            name = input("اسم المجلد: ").strip()
            create_folder(current_dir / name)
        elif choice == "4":
            name = input("اسم الملف: ").strip()
            content = input("محتوى الملف: ")
            create_file(current_dir / name, content)
        elif choice == "5":
            old_path = input("المسار الحالي: ").strip()
            new_name = input("الاسم الجديد: ").strip()
            rename_item(current_dir / old_path, new_name)
        elif choice == "6":
            name = input("اسم الملف: ").strip()
            delete_file(current_dir / name)
        elif choice == "7":
            name = input("اسم المجلد: ").strip()
            delete_folder(current_dir / name)
        elif choice == "8":
            source = input("المصدر: ").strip()
            dest = input("الوجهة: ").strip()
            copy_file(current_dir / source, current_dir / dest)
        elif choice == "9":
            source = input("المصدر: ").strip()
            dest = input("الوجهة: ").strip()
            move_item(current_dir / source, current_dir / dest)
        elif choice == "10":
            item = input("اسم العنصر: ").strip()
            get_file_info(current_dir / item)
        elif choice == "11":
            keyword = input("كلمة البحث: ").strip()
            results = search_files(current_dir, keyword)
            if results:
                print("\nالنتائج:")
                for r in results:
                    print(r)
            else:
                print("لا توجد نتائج.")
        elif choice == "12":
            print("تم الخروج.")
            break
        else:
            print("خيار غير صحيح.")

if __name__ == "__main__":
    main()

كيف تطور هذا المشروع بعد ذلك؟

بعد أن يصبح لديك الأساس، يمكنك أن تبني عليه أشياء أجمل بكثير:

  • إضافة واجهة رسومية

  • دعم السحب والإفلات Drag and Drop

  • إضافة سلة مهملات

  • عرض الملفات بصور مصغرة للصور

  • دعم فتح الملفات مباشرة

  • حفظ سجل العمليات

  • دعم المفضلة للمجلدات الأكثر استخدامًا

  • إضافة بحث متقدم حسب الاسم والامتداد والحجم والتاريخ

  • تصدير قائمة الملفات إلى CSV أو JSON

  • دمج البرنامج مع قاعدة بيانات صغيرة لتخزين الإعدادات

هذه الأفكار ليست بعيدة. بالعكس، كلها طبيعية جدًا عندما تنجح في بناء الأساس.

خاتمة

بناء File Manager in Python ليس مجرد تطبيق صغير، بل هو مشروع تعليمي وعملي غني جدًا. ستتعلّم منه التعامل مع المسارات، الملفات، المجلدات، الأخطاء، والعمليات اليومية التي يعتمد عليها أي نظام حقيقي. والأهم من ذلك أنك ستبدأ تفهم كيف يفكر المطوّر عندما يحاول تحويل فكرة بسيطة إلى أداة نافعة ومريحة.

قد يبدو المشروع في البداية بسيطًا، لكنه في الحقيقة يعلّمك الكثير: الصبر، التنظيم، احترام أخطاء النظام، والانتباه للتفاصيل الصغيرة التي تصنع فرقًا كبيرًا. ومع كل دالة تكتبها، ستشعر أنك تبني شيئًا مفيدًا بالفعل، لا مجرد تمرين دراسي.

وإذا أردت، يمكنك اعتبار هذا المقال نقطة البداية فقط. لأن أفضل ما في Python أنها تسمح لك أن تبدأ صغيرًا، ثم تكبر مشروعك خطوة خطوة، دون أن تشعر أن الطريق معقد أو مغلق. وهذا بالضبط ما يجعلها لغة محبوبة لدى الكثير من المطورين.

#file manager in python #Python file manager #مدير ملفات بايثون #بناء مدير ملفات بايثون #مشروع بايثون مدير ملفات #Python os module #pathlib python #shutil python