| // Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "p2p/server/file_watcher.h" |
| |
| #include <gio/gio.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| |
| #include <base/logging.h> |
| |
| using std::string; |
| using std::vector; |
| |
| using base::FilePath; |
| |
| namespace p2p { |
| |
| namespace server { |
| |
| class FileWatcherGLib : public FileWatcher { |
| public: |
| FileWatcherGLib(const FilePath& dir, const string& file_extension); |
| FileWatcherGLib(const FileWatcherGLib&) = delete; |
| FileWatcherGLib& operator=(const FileWatcherGLib&) = delete; |
| |
| ~FileWatcherGLib() override; |
| |
| const vector<FilePath>& files() const override; |
| |
| const FilePath& dir() const override; |
| const string& file_extension() const override; |
| |
| void SetChangedCallback(FileWatcherCallback changed_callback) override; |
| |
| bool Init(); |
| |
| private: |
| // Looks at all files in |dir_| and sees if anything has |
| // changed. Usually called from the file monitoring callback from |
| // the kernel (inotify via GFileMonitor). The |changed_file| |
| // parameter should be NULL unless the kernel indicates the given |
| // file has changed. Updates the |files_| member. |
| void ReloadDir(const FilePath* changed_file); |
| |
| // Used for handling the GFileMonitor::changed GLib signal. |
| static void OnMonitorChanged(GFileMonitor* monitor, |
| GFile* file, |
| GFile* other_file, |
| GFileMonitorEvent event_type, |
| gpointer user_data); |
| |
| // The directory we monitor. |
| FilePath dir_; |
| |
| // The file extension for files we are interested in, e.g. ".p2p". |
| string file_extension_; |
| |
| // The callback set by the user of our callback. |
| FileWatcherCallback changed_callback_; |
| |
| // The current set of files in |dir_|. Is updated by ReloadDir(). |
| vector<FilePath> files_; |
| |
| // The GLib abstraction used to interface with the kernel's inotify |
| // subsystem. |
| GFileMonitor* monitor_; |
| }; |
| |
| const FilePath& FileWatcherGLib::dir() const { |
| return dir_; |
| } |
| |
| const string& FileWatcherGLib::file_extension() const { |
| return file_extension_; |
| } |
| |
| void FileWatcherGLib::OnMonitorChanged(GFileMonitor* monitor, |
| GFile* file, |
| GFile* other_file, |
| GFileMonitorEvent event_type, |
| gpointer user_data) { |
| FileWatcherGLib* file_watcher = reinterpret_cast<FileWatcherGLib*>(user_data); |
| |
| // Ignore hints |
| if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) |
| return; |
| |
| // Ignore files not matching the extension |
| gchar* file_name = g_file_get_path(file); |
| if (!g_str_has_suffix(file_name, file_watcher->file_extension_.c_str())) { |
| g_free(file_name); |
| return; |
| } |
| |
| VLOG(2) << "OnMonitorChanged, event_type=" << event_type |
| << " file=" << file_name; |
| FilePath path(file_name); |
| file_watcher->ReloadDir(&path); |
| g_free(file_name); |
| } |
| |
| FileWatcherGLib::FileWatcherGLib(const FilePath& dir, |
| const string& file_extension) |
| : dir_(dir), file_extension_(file_extension) {} |
| |
| bool FileWatcherGLib::Init() { |
| GFile* file; |
| GError* error = NULL; |
| |
| file = g_file_new_for_path(dir_.value().c_str()); |
| monitor_ = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, |
| NULL, /* GCancellable */ |
| &error); |
| if (monitor_ == NULL) { |
| LOG(ERROR) << "Error monitoring directory " << dir_.value() << ": " |
| << error->message << "(" << error->code << ", " |
| << g_quark_to_string(error->domain) << ")"; |
| g_clear_error(&error); |
| return false; |
| } else { |
| g_signal_connect(monitor_, "changed", G_CALLBACK(OnMonitorChanged), this); |
| } |
| |
| ReloadDir(NULL); |
| |
| g_clear_object(&file); |
| return true; |
| } |
| |
| FileWatcherGLib::~FileWatcherGLib() { |
| if (monitor_ != NULL) { |
| g_signal_handlers_disconnect_by_func(monitor_, (gpointer)OnMonitorChanged, |
| this); |
| g_file_monitor_cancel(monitor_); |
| g_clear_object(&monitor_); |
| } |
| } |
| |
| void FileWatcherGLib::SetChangedCallback(FileWatcherCallback changed_callback) { |
| changed_callback_ = changed_callback; |
| } |
| |
| static void diff_sorted_vectors( |
| const vector<FilePath>::iterator& a_first, |
| const vector<FilePath>::iterator& a_last, |
| const vector<FilePath>::iterator& b_first, |
| const vector<FilePath>::iterator& b_last, |
| vector<FilePath>* added, // in b, not in a |
| vector<FilePath>* removed, // in a, not in b |
| vector<FilePath>* unchanged) { // in both a and b |
| vector<FilePath>::const_iterator ai = a_first; |
| vector<FilePath>::const_iterator bi = b_first; |
| |
| while (ai != a_last && bi != b_last) { |
| int order = ai->value().compare(bi->value()); |
| if (order < 0) { |
| // *ai > *bi |
| removed->push_back(*ai); |
| ++ai; |
| } else if (order > 0) { |
| // *ai < *bi |
| added->push_back(*bi); |
| ++bi; |
| } else { |
| // *ai == *bi |
| unchanged->push_back(*bi); |
| ++ai; |
| ++bi; |
| } |
| } |
| |
| while (ai != a_last) { |
| removed->push_back(*ai); |
| ++ai; |
| } |
| |
| while (bi != b_last) { |
| added->push_back(*bi); |
| ++bi; |
| } |
| } |
| |
| void FileWatcherGLib::ReloadDir(const FilePath* changed_file) { |
| GDir* dir; |
| GError* error = NULL; |
| const char* name; |
| vector<FilePath> new_files; |
| |
| VLOG(2) << "in ReloadDir(), dir=" << dir_.value() << "file=" |
| << (changed_file != NULL ? changed_file->value() : "(none)"); |
| |
| dir = g_dir_open(dir_.value().c_str(), 0, &error); |
| if (dir == NULL) { |
| LOG(ERROR) << "Error opening directory " << dir_.value() << ": " |
| << error->message << "(" << error->code << ", " |
| << g_quark_to_string(error->domain) << ")"; |
| return; |
| } |
| |
| while ((name = g_dir_read_name(dir)) != NULL) { |
| if (file_extension_.length() > 0 && |
| g_str_has_suffix(name, file_extension_.c_str())) { |
| new_files.push_back(dir_.Append(name)); |
| } |
| } |
| g_dir_close(dir); |
| |
| vector<FilePath> added; |
| vector<FilePath> removed; |
| vector<FilePath> unchanged; |
| std::sort(new_files.begin(), new_files.end()); |
| // TODO(zeuthen): actually unnecessary to sort files_ |
| std::sort(files_.begin(), files_.end()); |
| diff_sorted_vectors(files_.begin(), files_.end(), new_files.begin(), |
| new_files.end(), &added, &removed, &unchanged); |
| files_ = new_files; |
| |
| if (!changed_callback_.is_null()) { |
| for (auto const& i : removed) { |
| VLOG(2) << "Emitting kFileRemoved for file " << i.value(); |
| changed_callback_.Run(i, FileWatcher::EventType::kFileRemoved); |
| } |
| for (auto const& i : unchanged) { |
| if (changed_file != NULL && *changed_file == i) { |
| VLOG(2) << "Emitting kFileChanged for file " << i.value(); |
| changed_callback_.Run(i, FileWatcher::EventType::kFileChanged); |
| } |
| } |
| for (auto const& i : added) { |
| VLOG(2) << "Emitting kFileAdded for file " << i.value(); |
| changed_callback_.Run(i, FileWatcher::EventType::kFileAdded); |
| } |
| } |
| } |
| |
| const vector<FilePath>& FileWatcherGLib::files() const { |
| return files_; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| FileWatcher* FileWatcher::Construct(const FilePath& dir, |
| const string& file_extension) { |
| FileWatcherGLib* instance = new FileWatcherGLib(dir, file_extension); |
| if (!instance->Init()) { |
| delete instance; |
| return NULL; |
| } |
| return instance; |
| } |
| |
| } // namespace server |
| |
| } // namespace p2p |