#include "base/files/file_path_watcher.h"
#include <windows.h>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "base/win/object_watcher.h"
namespace base {
namespace {
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
public base::win::ObjectWatcher::Delegate {
public:
FilePathWatcherImpl() = default;
FilePathWatcherImpl(const FilePathWatcherImpl&) = delete;
FilePathWatcherImpl& operator=(const FilePathWatcherImpl&) = delete;
~FilePathWatcherImpl() override;
bool Watch(const FilePath& path,
Type type,
const FilePathWatcher::Callback& callback) override;
void Cancel() override;
void OnObjectSignaled(HANDLE object) override;
private:
[[nodiscard]] static bool SetupWatchHandle(const FilePath& dir,
bool recursive,
HANDLE* handle);
[[nodiscard]] bool UpdateWatch();
void DestroyWatch();
FilePathWatcher::Callback callback_;
FilePath target_;
raw_ptr<bool> was_deleted_ptr_ = nullptr;
HANDLE handle_ = INVALID_HANDLE_VALUE;
base::win::ObjectWatcher watcher_;
Type type_ = Type::kNonRecursive;
Time last_modified_;
Time first_notification_;
};
FilePathWatcherImpl::~FilePathWatcherImpl() {
DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
if (was_deleted_ptr_)
*was_deleted_ptr_ = true;
}
bool FilePathWatcherImpl::Watch(const FilePath& path,
Type type,
const FilePathWatcher::Callback& callback) {
DCHECK(target_.value().empty());
set_task_runner(SequencedTaskRunner::GetCurrentDefault());
callback_ = callback;
target_ = path;
type_ = type;
File::Info file_info;
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
if (GetFileInfo(target_, &file_info)) {
last_modified_ = file_info.last_modified;
first_notification_ = Time::Now();
}
if (!UpdateWatch())
return false;
watcher_.StartWatchingOnce(handle_, this);
return true;
}
void FilePathWatcherImpl::Cancel() {
if (callback_.is_null()) {
set_cancelled();
return;
}
DCHECK(task_runner()->RunsTasksInCurrentSequence());
set_cancelled();
if (handle_ != INVALID_HANDLE_VALUE)
DestroyWatch();
callback_.Reset();
}
void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK_EQ(object, handle_);
DCHECK(!was_deleted_ptr_);
bool was_deleted = false;
was_deleted_ptr_ = &was_deleted;
if (!UpdateWatch()) {
callback_.Run(target_, true );
return;
}
File::Info file_info;
bool file_exists = false;
{
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
file_exists = GetFileInfo(target_, &file_info);
}
if (type_ == Type::kRecursive) {
callback_.Run(target_, false);
} else if (file_exists && (last_modified_.is_null() ||
last_modified_ != file_info.last_modified)) {
last_modified_ = file_info.last_modified;
first_notification_ = Time::Now();
callback_.Run(target_, false);
} else if (file_exists && last_modified_ == file_info.last_modified &&
!first_notification_.is_null()) {
if (Time::Now() - first_notification_ > Seconds(1)) {
first_notification_ = Time();
}
callback_.Run(target_, false);
} else if (!file_exists && !last_modified_.is_null()) {
last_modified_ = Time();
callback_.Run(target_, false);
}
if (!was_deleted) {
watcher_.StartWatchingOnce(handle_, this);
was_deleted_ptr_ = nullptr;
}
}
bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
bool recursive,
HANDLE* handle) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
*handle = FindFirstChangeNotification(
dir.value().c_str(), recursive,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
if (*handle != INVALID_HANDLE_VALUE) {
if (!DirectoryExists(dir)) {
FindCloseChangeNotification(*handle);
*handle = INVALID_HANDLE_VALUE;
}
return true;
}
DWORD error_code = GetLastError();
if (error_code != ERROR_FILE_NOT_FOUND &&
error_code != ERROR_PATH_NOT_FOUND &&
error_code != ERROR_ACCESS_DENIED &&
error_code != ERROR_SHARING_VIOLATION &&
error_code != ERROR_DIRECTORY) {
DPLOG(ERROR) << "FindFirstChangeNotification failed for "
<< dir.value();
return false;
}
return true;
}
bool FilePathWatcherImpl::UpdateWatch() {
if (handle_ != INVALID_HANDLE_VALUE)
DestroyWatch();
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
std::vector<FilePath> child_dirs;
FilePath watched_path(target_);
while (true) {
if (!SetupWatchHandle(watched_path, type_ == Type::kRecursive, &handle_))
return false;
if (handle_ != INVALID_HANDLE_VALUE)
break;
child_dirs.push_back(watched_path.BaseName());
FilePath parent(watched_path.DirName());
if (parent == watched_path) {
DLOG(ERROR) << "Reached the root directory";
return false;
}
watched_path = parent;
}
while (!child_dirs.empty()) {
watched_path = watched_path.Append(child_dirs.back());
child_dirs.pop_back();
HANDLE temp_handle = INVALID_HANDLE_VALUE;
if (!SetupWatchHandle(watched_path, type_ == Type::kRecursive,
&temp_handle)) {
return false;
}
if (temp_handle == INVALID_HANDLE_VALUE)
break;
FindCloseChangeNotification(handle_);
handle_ = temp_handle;
}
return true;
}
void FilePathWatcherImpl::DestroyWatch() {
watcher_.StopWatching();
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
FindCloseChangeNotification(handle_);
handle_ = INVALID_HANDLE_VALUE;
}
}
FilePathWatcher::FilePathWatcher() {
DETACH_FROM_SEQUENCE(sequence_checker_);
impl_ = std::make_unique<FilePathWatcherImpl>();
}
}