واژه watchdog به معنی "سگ نگهبان" است و از دید یک برنامه رایانه ای، ابزاری است که بر فعالیت های سیستم فایل و تغییرات اعمال شده بر روی آن نظارت دارد و هر تغییری مانند ساخت یک فایل (دایرکتوری)، تغییر نام، پاک شدن یا تغییر محتوا را گزارش می دهد.
در لینوکس API ای به نام inotify هست که ابزاری برای زبان سی و سی پلاس پلاس که اجازه می دهد تا یک سیستم فایل را نظارت کنیم. توجه کنید که در لینوکس، یک سیستم فایل توسط یک دایرکتوری برای پیمایش در دسترس است.
در واقع پس از سوار کردن سیستم فایلی مانند ext4 یا btrfs یا ntfs بر روی هارد دیسک یا پارتیشن، توسط دستوری به نام mount هست که توسط آن یک دایرکتوری بر روی یک سیستم فایل (هارد دیسک یا پارتیشنی از آن) سوار می شود و از این پس از درون دسکتاپ یا خط فرمان به آن دسترسی خواهیم داشت.
واسط برنامه نویسی inotify بر پایه زبان سی نوشته شده است و در برنامه های سی و سی پلاس پلاس کاربرد دارد. نسخه پایتونی آن به نام ماژول pyinotify نیز در دسترس است ولی می خواهم در این نوشته ماژول دیگری به نام watchdog را به کار ببرم.
# on linux, mac and unix mkdir watchdog_test cd watchdog_test virtualenv env -p python3 source env/bin/activate
استفاده از watchdog برای پایتون بسیار ساده است. فرض بر این است که شما می خواهید یک دایرکتوری اشتراکی روی یک سرور را مانیتور کنید که آیا کارمندان شرکت روی آن چیزی که مد نظر شما بوده، مثلا یک فایل پی دی اف از یک گزارش را در ساعتی مشخص ریخته اند یا نه. آیامی توانیم بفهمیم کدام شخص فایل را کپی کرده؟ آیا می توانیم یک لاگ (Log) از تغییرات درون داریرکتوری مورد نظر ثبت کنیم.
import sys import time import logging from watchdog.observers import Observer from watchdog.events import LoggingEventHandler
در کد بالا تعدادی از ماژول های مورد نیاز برنامه ساده مان را پیوست کرده ایم. در ادامه خواهید دید که از sys برای دریافت ورودی از خط فرمان کمک گرفته ایم. در واقع ورودی یک تک رشته است که نام یا مسیری به یک دایرکتوری است.
از تابع ()sleep ماژول time برای به خواب بردن و از نو بیدار کردن برنامه یا بهتر است بگوییم ناظر سیستم فایل استفاده می کنیم. در واقع می خواهیم هر n ثاینه یک بار ناظر دایرکتوری مورد نظر را بررسی می کند که آیا تغییری در آن رخ داده است.
ماژول logging برای ثبت رویدادهای درون یک برنامه مورد استفاده قرار می گیرد. این مورد را در سیستم عامل ها، برنامه ها، پایگاه داده ها و حتی برنامه های وب دیده اید. ماژول هایی که فرایند لاگ را انجام می دهند، دو چیز را نیاز دارند: ۱) چه اطلاعاتی و چه سطحی از اطلاعات لاگ شود و ۲) در کجا لاگ شود.
در کدی که در این نوشته مربوط به لاگ کردن است، در واقع لاگ ها بر روی خروجی استاندارد یا همان Standard Output یا به عبارت بهتر خط فرمان سیستم عامل نشان داده می شود. ولی محل خروجی می تواند یک فایل روی دیسک نیز باشد.
رویداد (Event) چیست؟ به ساده ترین زبان و بی توجه به زمینه برنامه، رویداد چیزی است که رخ می دهد و در برابر آن باید یک پاسخ یا Action انجام شود. در اینجا رویدادها تغییراتی است که درون سیستم فایل یا داریرکتوری (یا فایل) رخ می دهند و ما می خواهیم یک پاسخ به آن بدهیم. این پاسخ یا Action توسط watchdog و کلاس های آن انجام می شود. در بالا چهار تغییر را گفته ام و در ادامه در زیر از نو آنها را بازنویسی خواهم کرد.
این کلاس بخش اصلی watchdog است، جایی که زمانبدی نظارت بر دایرکتوری مربوطه انجام می شود.
observer = Observer() observer.schedule(event_handler, path, recursive=True) observer.start() print("Hello")
در کد بالا یک نمونه از کلاس Observer ساخته ایم و سپس متد ()schedule را با سه پارامتر فراخوانی می کنیم. فراخوانی نخست متغیری است به نام event_handler که در ادامه نشان می دهم که از نوع کلاس LoggingEventHandler است.
در واقع پارامتر اول ()schedule متغیری است که به یکی از کلاس های اداره کننده رویدادها (Event Handler) اشاره می کند. پارامتر دوم مسیر دایرکتوری است که می خواهیم زمانبدی نظارت بر روی آن باشد و توجه کنید نظارت بر رویدادها توسط کلاس های Event Handler انجام می شود (نه Observer).
پارامتر سوم recursive=True است که اجازه می دهد تا برگشتی (Recursive) و تو در تو زیر دایرکتوری های درون دایرکتوری ما نیز مورد هدف قرار گیرند. توجه کنید مقدار recursive به صورت پیش فرض برابر با False است و برگشتی بودن نیز غیر فعال است. در نهایت متد ()start از کلاس Observer فراخوانی شده است.
event_handler = LoggingEventHandler()
در کدها بالا متغیری به نام event_handler از یکی از چندین کلاس اداره کننده رویدادها به نام LoggingEventHandler ساخته می شود. در پایان کد، خط های زیر نوشته شده اند.
try: while True: time.sleep(10) except KeyboardInterrupt: observer.stop() print("Goodbye") observer.join()
در یک حلقه while True و تا زمانی که Ctrl+c (خاتمه دادن به برنامه در خط فرمان) کلیک نشده باشد، پس برنامه هر ۱۰ ثانیه یک بار بررسی می کند که آیا تغییری در دایرکتوری رخ داده است. در صورتی که Ctrl+C کلیک شود، پس استثنای KeyboardInterrupt رخ داده و تابع ()stop از کلاس Observer فراخوانی شده و فرایند نظارت پایان می یابد.
توجه کنید که کلاس Observer پیاده سازی نخ ها (Thread) است و دو متد ()stop و ()join مربوط به مفاهیم threading هستند. تابع ()join باعث می شود تا نخی که کدهای ما در آن فراخوانی شده اند به حالت انتظار برود تا زمانی که نخ اصلی thread1 خاتمه پیدا کند.
در کد زیر متد ()basicConfig فراخوانی شده تا لاگ کردن در سطح logging و برای نمایش لاگ ها به صورت بر خط و بر روی خط فرمان آماده شوند. بدین معنی که هر بار یک تغییر مانند کپی کردن یا ساخت یک فایل یا دایرکتوری درون دایرکتوری مربوطه، یک مطابق با نوع رویداد نمایش داده می شود.
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
لیست []argv فهرستی از ورودی ها را در خود نگه می دارد. پس از آنکه شما کد را درون فایل py. نوشتید، برای نمونه با دستور python3 YOUR_PYTHON.py /path/to/your/directory آن را اجرا می کنید پس ورودی به پایتون در اینجا ۲ تا است، یکی نام خود فایل پایتون و دیگری نام دایرکتوری یا مسیر منتهی به آن است.
path = sys.argv[1] if len(sys.argv) > 1 else '.'
بنابراین [1]sys.argv یعنی دومین پارامتر درون لیست []argv که در اینجا همان نام یا مسیر دایرکتوری است. پس بررسی می شود که اگر اندازه لیست []argv بیشتر از ۱ باشد، یعنی نام فایل py. و نام یا مسیر دایرکتوری تعیین شده باشد، پس داریکتوری مشخص شده به متغیر path ریخته شود وگرنه اندازه لیست کمتر از ۲ یا برابر ۱ است و از این رو نام دایرکتوری مشخص نشده پس دایرکتوری جاری (Current Directory) که با نقطه پایان خط مشخص شده است به عنوان دایرکتوری ما تعیین می شود.
توجه کنید این کد کمبود دارد و شما باید با ماژول os.path بررسی کنید اصلا آیا مسیر یا دایرکتوری وجود دارد؟
کد استفاده شده در این نوشته