- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List

# FileChangeChecker.h

    00001 /*
    00002  *  Phusion Passenger - http://www.modrails.com/
    00003  *  Copyright (c) 2010 Phusion
    00004  *
    00005  *  "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
    00006  *
    00007  *  Permission is hereby granted, free of charge, to any person obtaining a copy
    00008  *  of this software and associated documentation files (the "Software"), to deal
    00009  *  in the Software without restriction, including without limitation the rights
    00010  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    00011  *  copies of the Software, and to permit persons to whom the Software is
    00012  *  furnished to do so, subject to the following conditions:
    00013  *
    00014  *  The above copyright notice and this permission notice shall be included in
    00015  *  all copies or substantial portions of the Software.
    00016  *
    00017  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    00018  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    00019  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    00020  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    00021  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    00022  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    00023  *  THE SOFTWARE.
    00024  */
    00025 #ifndef _PASSENGER_CHANGE_FILE_CHECKER_H_
    00026 #define _PASSENGER_CHANGE_FILE_CHECKER_H_
    00027
    00028 #include <string>
    00029
    00030 #include <boost/thread.hpp>
    00031 #include <errno.h>
    00032
    00033 #include "CachedFileStat.hpp"
    00034 #include "SystemTime.h"
    00035
    00036 namespace Passenger {
    00037
    00038 using namespace std;
    00039 using namespace oxt;
    00040
    00041 /**
    00042  * A utility class for checking for file changes. Example:
    00043  *
    00044  * @code
    00045  * FileChangeChecker checker;
    00046  * checker.changed("foo.txt");   // false
    00047  * writeToFile("foo.txt");
    00048  * checker.changed("foo.txt");   // true
    00049  * checker.changed("foo.txt");   // false
    00050  * @endcode
    00051  *
    00052  * FileChangeChecker uses stat() to retrieve file information. It also
    00053  * supports throttling in order to limit the number of actual stat() calls.
    00054  * This can improve performance on systems where disk I/O is a problem.
    00055  */
    00056 class FileChangeChecker {
    00057 private:
    00058         struct Entry {
    00059                 string filename;
    00060                 time_t lastMtime;
    00061                 time_t lastCtime;
    00062
    00063                 Entry(const string &filename) {
    00064                         this->filename  = filename;
    00065                         this->lastMtime = 0;
    00066                         this->lastCtime = 0;
    00067                 }
    00068         };
    00069
    00070         typedef shared_ptr<Entry> EntryPtr;
    00071         typedef list<EntryPtr> EntryList;
    00072         typedef map<string, EntryList::iterator> EntryMap;
    00073
    00074         CachedFileStat cstat;
    00075         mutable boost::mutex lock;
    00076         unsigned int maxSize;
    00077         EntryList entries;
    00078         EntryMap fileToEntry;
    00079
    00080 public:
    00081         /**
    00082          * Create a FileChangeChecker object.
    00083          *
    00084          * @param maxSize The maximum size of the internal file list. A size of 0 means unlimited.
    00085          */
    00086         FileChangeChecker(unsigned int maxSize = 0)
    00087                 : cstat(maxSize)
    00088         {
    00089                 this->maxSize = maxSize;
    00090         }
    00091
    00092         /**
    00093          * Checks whether, since the last call to changed() with this filename,
    00094          * the file's timestamp has changed or whether the file has been created
    00095          * or removed. If the stat() call fails for any other reason (e.g. the
    00096          * directory is not readable) then this method will return false.
    00097          *
    00098          * If this method was called with this filename for the first time, or if
    00099          * information about this file has since been removed from the internal
    00100          * file list, then this method will return whether the file is stat()-able.
    00101          * That is, if the file doesn't exist then it will return false, but if
    00102          * the directory is not readable then it will also return false.
    00103          *
    00104          * @param filename The file to check. Note that two different filename
    00105          *                 strings are treated as two different files, so you should
    00106          *                 use absolute filenames if you change working directory
    00107          *                 often.
    00108          * @param throttleRate When set to a non-zero value, throttling will be
    00109          *                     enabled. stat() will be called at most once per
    00110          *                     throttleRate seconds.
    00111          * @throws TimeRetrievalException Something went wrong while retrieving the
    00112          *         system time.
    00113          * @throws boost::thread_interrupted
    00114          */
    00115         bool changed(const string &filename, unsigned int throttleRate = 0) {
    00116                 boost::unique_lock<boost::mutex> l(lock);
    00117                 EntryMap::iterator it(fileToEntry.find(filename));
    00118                 EntryPtr entry;
    00119                 struct stat buf;
    00120                 bool result, newEntry = false;
    00121                 int ret;
    00122
    00123                 if (it == fileToEntry.end()) {
    00124                         // Filename not in file list.
    00125                         // If file list is full, remove the least recently used
    00126                         // file list entry.
    00127                         if (maxSize != 0 && fileToEntry.size() == maxSize) {
    00128                                 EntryList::iterator listEnd(entries.end());
    00129                                 listEnd--;
    00130                                 string filename((*listEnd)->filename);
    00131                                 entries.pop_back();
    00132                                 fileToEntry.erase(filename);
    00133                         }
    00134
    00135                         // Add to file list as most recently used.
    00136                         entry = EntryPtr(new Entry(filename));
    00137                         entries.push_front(entry);
    00138                         fileToEntry[filename] = entries.begin();
    00139                         newEntry = true;
    00140                 } else {
    00141                         // Filename is in file list.
    00142                         entry = *it->second;
    00143
    00144                         // Mark this entry as most recently used.
    00145                         entries.erase(it->second);
    00146                         entries.push_front(entry);
    00147                         fileToEntry[filename] = entries.begin();
    00148                 }
    00149
    00150                 ret = cstat.stat(filename, &buf, throttleRate);
    00151                 if (newEntry) {
    00152                         // The file's information isn't in the file list.
    00153                         if (ret == -1) {
    00154                                 entry->lastMtime = 0;
    00155                                 entry->lastCtime = 0;
    00156                                 return false;
    00157                         } else {
    00158                                 entry->lastMtime = buf.st_mtime;
    00159                                 entry->lastCtime = buf.st_ctime;
    00160                                 return true;
    00161                         }
    00162                 } else {
    00163                         // The file's information was already in the file list.
    00164                         if (ret == -1 && errno == ENOENT) {
    00165                                 result = entry->lastMtime != 0 || entry->lastCtime != 0;
    00166                                 entry->lastMtime = 0;
    00167                                 entry->lastCtime = 0;
    00168                         } else if (ret == -1) {
    00169                                 result = false;
    00170                         } else {
    00171                                 result = entry->lastMtime != buf.st_mtime || entry->lastCtime != buf.st_ctime;
    00172                                 entry->lastMtime = buf.st_mtime;
    00173                                 entry->lastCtime = buf.st_ctime;
    00174                         }
    00175                         return result;
    00176                 }
    00177         }
    00178
    00179         /**
    00180          * Change the maximum size of the internal file list.
    00181          *
    00182          * A size of 0 means unlimited.
    00183          */
    00184         void setMaxSize(unsigned int maxSize) {
    00185                 boost::unique_lock<boost::mutex> l(lock);
    00186                 if (maxSize != 0) {
    00187                         int toRemove = fileToEntry.size() - maxSize;
    00188                         for (int i = 0; i < toRemove; i++) {
    00189                                 string filename(entries.back()->filename);
    00190                                 entries.pop_back();
    00191                                 fileToEntry.erase(filename);
    00192                         }
    00193                 }
    00194                 this->maxSize = maxSize;
    00195                 cstat.setMaxSize(maxSize);
    00196         }
    00197
    00198         /**
    00199          * Returns whether <tt>filename</tt> is in the internal file list.
    00200          */
    00201         bool knows(const string &filename) const {
    00202                 boost::unique_lock<boost::mutex> l(lock);
    00203                 return fileToEntry.find(filename) != fileToEntry.end();
    00204         }
    00205 };
    00206
    00207 } // namespace Passenger
    00208
    00209 #endif /* _PASSENGER_CHANGE_FILE_CHECKER_H_ */