blob: 3466cad3a93a297ad2fbf021d47cd1000a6e21e5 [file] [log] [blame]
// 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