From f30198a05113232c63144c30e753e7d983dccf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Fri, 4 May 2007 16:02:04 +0000 Subject: [PATCH] * Implemented PathMonitor class - untested, but compiles. * The API is just a proposal at this time, please comment. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21020 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/storage/PathMonitor.h | 40 +++ src/kits/storage/PathMonitor.cpp | 493 ++++++++++++++++++++++++++ 2 files changed, 533 insertions(+) create mode 100644 headers/private/storage/PathMonitor.h create mode 100644 src/kits/storage/PathMonitor.cpp diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h new file mode 100644 index 0000000000..03bb0d34f1 --- /dev/null +++ b/headers/private/storage/PathMonitor.h @@ -0,0 +1,40 @@ +/* + * Copyright 2007, Haiku Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _PATH_MONITOR_H +#define _PATH_MONITOR_H + + +#include + + +// additional flags (combined with those in NodeMonitor.h) +#define B_WATCH_FILES_ONLY 0x0100 +#define B_WATCH_RECURSIVELY 0x0200 + +#define B_PATH_MONITOR '_PMN' + +namespace BPrivate { + +class PathHandler; + +class BPathMonitor { + public: + BPathMonitor(); + BPathMonitor(const char* path, uint32 flags, BMessenger target); + ~BPathMonitor(); + + status_t InitCheck() const; + status_t SetTo(const char* path, uint32 flags, BMessenger target); + status_t SetTarget(BMessenger target); + void Unset(); + + private: + PathHandler* fHandler; + status_t fStatus; +}; + +} // namespace BPrivate + +#endif // _PATH_MONITOR_H diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp new file mode 100644 index 0000000000..2bcfcd9ea3 --- /dev/null +++ b/src/kits/storage/PathMonitor.cpp @@ -0,0 +1,493 @@ +/* + * Copyright 2007, Haiku Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace BPrivate; +using namespace std; + + +#define WATCH_NODE_FLAG_MASK 0x00ff + +typedef set DirectorySet; + +namespace BPrivate { + +class PathHandler : public BHandler { + public: + PathHandler(const char* path, uint32 flags, BMessenger target); + virtual ~PathHandler(); + + status_t InitCheck() const; + void SetTarget(BMessenger target); + void Quit(); + + virtual void MessageReceived(BMessage* message); + + private: + bool _IsContained(const node_ref& nodeRef) const; + bool _IsContained(BEntry& entry) const; + bool _HasDirectory(const node_ref& nodeRef) const; + void _NotifyTarget(BMessage* message) const; + status_t _AddDirectory(BEntry& entry); + status_t _RemoveDirectory(const node_ref& nodeRef); + status_t _RemoveDirectory(BEntry& entry); + + BPath fPath; + int32 fPathLength; + BMessenger fTarget; + uint32 fFlags; + status_t fStatus; + bool fOwnsLooper; + DirectorySet fDirectories; +}; + +} + + +static status_t +set_entry(node_ref& nodeRef, const char* name, BEntry& entry) +{ + entry_ref ref; + ref.device = nodeRef.device; + ref.directory = nodeRef.node; + + status_t status = ref.set_name(name); + if (status != B_OK) + return status; + + return entry.SetTo(&ref, true); +} + + +bool +operator<(const node_ref& a, const node_ref& b) +{ + if (a.device < b.device) + return true; + if (a.device == b.device && a.node < b.node) + return true; + + return false; +} + + +// #pragma mark - + + +PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target) + : BHandler(path), + fPath(path, NULL, true), + fTarget(target), + fFlags(flags), + fOwnsLooper(false) +{ + fPathLength = strlen(fPath.Path()); + + BPath first(path); + node_ref nodeRef; + + while (true) { + // try to find the first part of the path that exists + BDirectory directory; + fStatus = directory.SetTo(first.Path()); + if (fStatus == B_OK) { + fStatus = directory.GetNodeRef(&nodeRef); + if (fStatus == B_OK) + break; + } + + if (first.GetParent(&first) != B_OK) { + fStatus = B_ERROR; + break; + } + } + + if (fStatus < B_OK) + return; + + if (be_app != NULL) { + be_app->AddHandler(this); + } else { + // TODO: only have a single global looper! + BLooper* looper = new BLooper("PathMonitor looper"); + looper->Run(); + looper->AddHandler(this); + fOwnsLooper = true; + } + + fStatus = watch_node(&nodeRef, flags & WATCH_NODE_FLAG_MASK, this); +} + + +PathHandler::~PathHandler() +{ +} + + +status_t +PathHandler::InitCheck() const +{ + return fStatus; +} + + +void +PathHandler::SetTarget(BMessenger target) +{ + fTarget = target; +} + + +void +PathHandler::Quit() +{ + BMessenger me(this); + me.SendMessage(B_QUIT_REQUESTED); +} + + +void +PathHandler::MessageReceived(BMessage* message) +{ + switch (message->what) { + case B_NODE_MONITOR: + { + int32 opcode; + if (message->FindInt32("opcode", &opcode) != B_OK) + return; + + switch (opcode) { + case B_ENTRY_CREATED: + { + const char* name; + node_ref nodeRef; + if (message->FindInt32("device", &nodeRef.device) != B_OK + || message->FindInt64("directory", &nodeRef.node) != B_OK + || message->FindString("name", &name) != B_OK) + break; + + BEntry entry; + if (set_entry(nodeRef, name, entry) != B_OK) + break; + + bool notify = true; + + if (entry.IsDirectory()) { + // a new directory to watch for us + if (_AddDirectory(entry) != B_OK + || (fFlags & B_WATCH_FILES_ONLY) != 0) + notify = false; + } + + if (notify && _IsContained(entry)) { + message->AddBool("added", true); + _NotifyTarget(message); + } + break; + } + + case B_ENTRY_MOVED: + { + // has the entry been moved into a monitored directory or has + // it been removed from one? + const char* name; + node_ref nodeRef; + uint64 fromNode; + uint64 node; + if (message->FindInt32("device", &nodeRef.device) != B_OK + || message->FindInt64("to directory", &nodeRef.node) != B_OK + || message->FindInt64("from directory", (int64 *)&fromNode) != B_OK + || message->FindInt64("node", (int64 *)&node) != B_OK + || message->FindString("name", &name) != B_OK) + break; + + BEntry entry; + if (set_entry(nodeRef, name, entry) != B_OK) + break; + + bool wasAdded = false; + bool wasRemoved = false; + bool notify = true; + + if (_HasDirectory(nodeRef)) { + // something has been added to our watched directories + + // test, if the source directory is one of ours as well + nodeRef.node = fromNode; + bool hasFromDirectory = _HasDirectory(nodeRef); + + if (entry.IsDirectory()) { + if (!hasFromDirectory) { + // there is a new directory to watch for us + _AddDirectory(entry); + wasAdded = true; + } + if ((fFlags & B_WATCH_FILES_ONLY) != 0) + notify = false; + } else if (!hasFromDirectory) { + // file has been added + wasAdded = true; + } + } else { + // and entry has been removed from our directories + wasRemoved = true; + + if (entry.IsDirectory()) { + _RemoveDirectory(entry); + if ((fFlags & B_WATCH_FILES_ONLY) != 0) + notify = false; + } + } + + if (notify && _IsContained(entry)) { + if (wasAdded) + message->AddBool("added", true); + if (wasRemoved) + message->AddBool("removed", true); + + _NotifyTarget(message); + } + break; + } + + case B_ENTRY_REMOVED: + { + node_ref nodeRef; + uint64 directoryNode; + if (message->FindInt32("device", &nodeRef.device) != B_OK + || message->FindInt64("directory", (int64 *)&directoryNode) != B_OK + || message->FindInt64("node", &nodeRef.node) != B_OK) + break; + + bool notify = true; + + if (_HasDirectory(nodeRef)) { + // the directory has been removed, so we remove it as well + _RemoveDirectory(nodeRef); + if ((fFlags & B_WATCH_FILES_ONLY) != 0) + notify = false; + } + + nodeRef.node = directoryNode; + if (notify && _IsContained(nodeRef)) { + message->AddBool("removed", true); + _NotifyTarget(message); + } + break; + } + + default: + _NotifyTarget(message); + break; + } + break; + } + + case B_QUIT_REQUESTED: + { + BLooper* looper = Looper(); + + stop_watching(this); + looper->RemoveHandler(this); + delete this; + + if (fOwnsLooper) + looper->Quit(); + return; + } + + default: + BHandler::MessageReceived(message); + break; + } +} + + +bool +PathHandler::_IsContained(const node_ref& nodeRef) const +{ + BDirectory directory(&nodeRef); + if (directory.InitCheck() != B_OK) + return false; + + BEntry entry; + if (directory.GetEntry(&entry) != B_OK) + return false; + + return _IsContained(entry); +} + + +bool +PathHandler::_IsContained(BEntry& entry) const +{ + BPath path; + if (entry.GetPath(&path) != B_OK) + return false; + + return strncmp(path.Path(), fPath.Path(), fPathLength) == 0; +} + + +bool +PathHandler::_HasDirectory(const node_ref& nodeRef) const +{ + DirectorySet::const_iterator iterator = fDirectories.find(nodeRef); + return iterator != fDirectories.end(); +} + + +void +PathHandler::_NotifyTarget(BMessage* message) const +{ + BMessage update(*message); + update.what = B_PATH_MONITOR; + fTarget.SendMessage(&update); +} + + +status_t +PathHandler::_AddDirectory(BEntry& entry) +{ + node_ref nodeRef; + status_t status = entry.GetNodeRef(&nodeRef); + if (status != B_OK) + return status; + + // check if we are already know this directory + + if (_HasDirectory(nodeRef)) + return B_OK; + + uint32 flags; + if (_IsContained(entry)) + flags = fFlags & WATCH_NODE_FLAG_MASK; + else + flags = B_WATCH_DIRECTORY; + + status = watch_node(&nodeRef, flags, this); + if (status != B_OK) + return status; + + fDirectories.insert(nodeRef); + +#if 0 + BEntry parent; + if (entry.GetParent(&parent) == B_OK + && !_IsContained(parent)) { + // TODO: remove parent from watched directories + } +#endif + return B_OK; +} + + +status_t +PathHandler::_RemoveDirectory(const node_ref& nodeRef) +{ + DirectorySet::iterator iterator = fDirectories.find(nodeRef); + if (iterator == fDirectories.end()) + return B_ENTRY_NOT_FOUND; + + fDirectories.erase(iterator); + return B_OK; +} + + +status_t +PathHandler::_RemoveDirectory(BEntry& entry) +{ + node_ref nodeRef; + status_t status = entry.GetNodeRef(&nodeRef); + if (status != B_OK) + return status; + + return _RemoveDirectory(nodeRef); +} + + +// #pragma mark - + + +BPathMonitor::BPathMonitor() + : + fHandler(NULL), + fStatus(B_NO_INIT) +{ +} + + +BPathMonitor::BPathMonitor(const char* path, uint32 flags, BMessenger target) + : + fHandler(NULL), + fStatus(B_NO_INIT) +{ + SetTo(path, flags, target); +} + + +BPathMonitor::~BPathMonitor() +{ + Unset(); +} + + +status_t +BPathMonitor::InitCheck() const +{ + return fStatus; +} + + +status_t +BPathMonitor::SetTo(const char* path, uint32 flags, BMessenger target) +{ + Unset(); + + fHandler = new PathHandler(path, flags, target); + status_t status = fHandler->InitCheck(); + if (status < B_OK) + Unset(); + + return fStatus = status; +} + + +status_t +BPathMonitor::SetTarget(BMessenger target) +{ + if (fStatus < B_OK) + return B_NO_INIT; + + fHandler->SetTarget(target); + return B_OK; +} + + +void +BPathMonitor::Unset() +{ + if (fHandler != NULL) { + fHandler->Quit(); + fHandler = NULL; + } + fStatus = B_NO_INIT; +} + +//} // namespace BPrivate