Looking at the last part of the filelistener project, the next thing for us to do is:
"Write a file listener that iterates through an input directory in a given interval and triggers a purely virtual function for someone else to override"
Here is the approach to implement these features, which I recommend saving as include/filelistener.hpp:
#ifndef FILELISTENER_HPP #define FILELISTENER_HPP /* Link with the following libraries: -lboost_filesystem -lpthread */ #include "datastructures.hpp" #include <boost/asio.hpp> #include <boost/optional.hpp> #include <boost/filesystem/operations.hpp> #include <boost/date_time/posix_time/posix_time.hpp> /** * Listens on a directory and triggers on parsed files. * * @author nijansen * * The listener will monitor the input directory in a given interval. All files within * the input directory will be read; if they can be parsed, the listener will call a * purely virtual trigger function and pass the parsed information. * * The listener is not recursive: Directories within the input directory will be * ignored. If files cannot be parsed, the listener will move them into an "ignored" * directory within the input directory. * * The listener will not throw exceptions on parsing failures. It will throw if it * lacks the necessary rights to read/write within the input directory. Exceptions * within the purely virtual trigger function must be caught by the derived class; the * listener will NOT catch those. This is intended to prevent unexpected behavior. * * The listener itself is thread safe. It is strongly recommended to use one io * service for each thread. Several listener objects may be registered to one io * service if they are in the same thread. * * @warning Do not set up multiple listener objects on the same input directory! */ class FileListener { public: /** * Constructor that registers to an Boost Asio io service. * * The derived class must overload this constructor and call the parent constructor * with a valid io service. * * @param[in] io_service Asio io service the listener will register to. */ FileListener(boost::asio::io_service &io_service); /** * Sets the input directory. * * @param[in] input Must be a valid (existing) directory. * @return true if successful, false if invalid. */ bool set_input_directory(const boost::filesystem::path &input); /** * Sets the listening interval. * * @param[in] interval_ms Listening interval in milliseconds. */ void set_listening_interval(long interval_ms); /** * Tries to start the listener service. * * @return true if successful. * * @warning The input directory and listening interval must be set! * @warning You must run the io service after this call! */ bool start_service(); /** * Purely virtual trigger function for derived classes. * * Derived classes must implement this function. It will be called by the * listener when a file was parsed successfully. The return value determines * whether the file will be deleted. * * @param[in] tournament Parsed data from the file. * @return 0 if successful (file is deleted!), any other value if failure. * * @warning On success return value, the file will be deleted permanently! */ virtual int trigger(const datastructures::Tournament &tournament) = 0; private: /** * Iterates through the input directory, and triggers on parsed files. */ void listen(); /** Input directory; default: unset. **/ boost::optional<boost::filesystem::path> input_; /** Listening interval; default: unset. **/ boost::optional<boost::posix_time::time_duration> interval_; /** Timer to listen in intervals. **/ boost::asio::deadline_timer timer_; }; #endif // FILELISTENER_HPP
And here is the respective implementation source/filelistener.cpp:
#include "filelistener.hpp" #include <boost/bind.hpp> FileListener::FileListener(boost::asio::io_service &io_service) : timer_(io_service) {} bool FileListener::set_input_directory(const boost::filesystem::path &input) { // Only directories are valid input paths if(boost::filesystem::is_directory(input)) { input_ = input; return true; } return false; } void FileListener::set_listening_interval(long interval_ms) { interval_ = boost::posix_time::milliseconds(interval_ms); } bool FileListener::start_service() { // Only start the server if the input path and listening interval have been set if(input_ && interval_) { // Set the timer timer_.expires_from_now(*interval_); timer_.async_wait(boost::bind(&FileListener::listen, this)); return true; } return false; } void FileListener::listen() { // Iterate through the input directory for(boost::filesystem::directory_iterator file(*input_); file != boost::filesystem::directory_iterator(); file++) { boost::filesystem::path current_file = file->path(); // Ignore directories if(boost::filesystem::is_directory(current_file)) { continue; } // Try to parse the file datastructures::Tournament tournament; try { tournament.load(current_file.native()); } catch(...) { // For performance reasons, move files that failed parsing into input/ignored, // so we will not try to parse them over and over again boost::filesystem::path ignored = *input_/"ignored"; if(!boost::filesystem::exists(ignored)) { boost::filesystem::create_directory(ignored); } boost::filesystem::copy_file(current_file, ignored/current_file.filename()); boost::filesystem::remove(current_file); continue; } // Call the trigger function of the derived class if(trigger(tournament) == 0) { // Only delete the file if the trigger function submitted the success value; // the file will trigger again later otherwise boost::filesystem::remove(current_file); } } // Reset the timer timer_.expires_from_now(*interval_); timer_.async_wait(boost::bind(&FileListener::listen, this)); }
This leaves us with only one more step for this project: an example implementation. Coming up in a future post, of course.
No comments:
Post a Comment