Files
haiku-beta6/src/apps/diskprobe/DataEditor.cpp
T
Axel Dörfler 66fcb84e31 Now maintains the undo/redo state watcher, and also the data update
containing the range of affected bytes.
Added missing initialization of fLastChange and fFirstChange.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@6733 a95241bf-73f2-0310-859d-f6bbb57e9c96
2004-02-24 16:54:28 +00:00

677 lines
12 KiB
C++

/*
** Copyright 2004, Axel Dörfler, [email protected]. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
#include "DataEditor.h"
#include <Autolock.h>
#include <NodeMonitor.h>
#include <Drivers.h>
#include <fs_attr.h>
#include <string.h>
#include <unistd.h>
class DataChange {
public:
virtual ~DataChange();
virtual void Apply(off_t offset, uint8 *buffer, size_t size) = 0;
virtual void Revert(off_t offset, uint8 *buffer, size_t size) = 0;
virtual void GetRange(off_t fileSize, off_t &_offset, off_t &_size) = 0;
};
class ReplaceChange : public DataChange {
public:
ReplaceChange(off_t offset, const uint8 *data, size_t size);
~ReplaceChange();
virtual void Apply(off_t offset, uint8 *buffer, size_t size);
virtual void Revert(off_t offset, uint8 *buffer, size_t size);
virtual void GetRange(off_t fileSize, off_t &_offset, off_t &_size);
private:
void Normalize(off_t bufferOffset, size_t bufferSize,
off_t &offset, size_t &dataOffset, size_t &size);
uint8 *fNewData;
uint8 *fOldData;
size_t fSize;
off_t fOffset;
};
DataChange::~DataChange()
{
}
// #pragma mark -
ReplaceChange::ReplaceChange(off_t offset, const uint8 *data, size_t size)
{
fOffset = offset;
fNewData = (uint8 *)malloc(size);
fOldData = (uint8 *)malloc(size);
if (fNewData != NULL && fOldData != NULL) {
memcpy(fNewData, data, size);
fSize = size;
} else
fSize = 0;
}
ReplaceChange::~ReplaceChange()
{
free(fNewData);
free(fOldData);
}
/** Normalizes the supplied offset & size pair to be limited by
* the buffer offset and size.
* All parameters must have been initialized before calling this
* method.
*/
void
ReplaceChange::Normalize(off_t bufferOffset, size_t bufferSize, off_t &offset,
size_t &dataOffset, size_t &size)
{
if (fOffset < bufferOffset) {
offset = bufferOffset;
dataOffset = bufferOffset - fOffset;
size -= dataOffset;
}
if (offset + size > bufferOffset + bufferSize)
size = offset - bufferOffset + offset;
}
void
ReplaceChange::Apply(off_t bufferOffset, uint8 *buffer, size_t bufferSize)
{
// is it in our range?
if (fOffset - bufferOffset > bufferSize || fOffset + fSize < bufferOffset)
return;
// don't change anything outside the supplied buffer
off_t offset = fOffset;
size_t dataOffset = 0;
size_t size = fSize;
Normalize(bufferOffset, bufferSize, offset, dataOffset, size);
if (size == 0)
return;
// now we can safely exchange the buffer!
memcpy(fOldData + dataOffset, buffer + offset - bufferOffset, size);
memcpy(buffer + offset - bufferOffset, fNewData + dataOffset, size);
}
void
ReplaceChange::Revert(off_t bufferOffset, uint8 *buffer, size_t bufferSize)
{
// is it in our range?
if (fOffset - bufferOffset > bufferSize || fOffset + fSize < bufferOffset)
return;
// don't change anything outside the supplied buffer
off_t offset = fOffset;
size_t dataOffset = 0;
size_t size = fSize;
Normalize(bufferOffset, bufferSize, offset, dataOffset, size);
if (size == 0)
return;
// now we can safely revert the buffer!
memcpy(buffer + offset - bufferOffset, fOldData + dataOffset, size);
}
void
ReplaceChange::GetRange(off_t /*fileSize*/, off_t &_offset, off_t &_size)
{
_offset = fOffset;
_size = fSize;
}
// #pragma mark -
DataEditor::DataEditor()
: BLocker("data view")
{
}
DataEditor::DataEditor(entry_ref &ref, const char *attribute)
: BLocker("data view")
{
SetTo(ref, attribute);
}
DataEditor::DataEditor(BEntry &entry, const char *attribute)
: BLocker("data view")
{
SetTo(entry, attribute);
}
DataEditor::DataEditor(const DataEditor &editor)
: BLocker("data view")
{
}
DataEditor::~DataEditor()
{
}
status_t
DataEditor::SetTo(const char *path, const char *attribute)
{
BEntry entry(path);
return SetTo(entry, attribute);
}
status_t
DataEditor::SetTo(entry_ref &ref, const char *attribute)
{
BEntry entry(&ref);
return SetTo(entry, attribute);
}
status_t
DataEditor::SetTo(BEntry &entry, const char *attribute)
{
status_t status = fFile.SetTo(&entry, B_READ_WRITE);
if (status < B_OK) {
// try to open read only
status = fFile.SetTo(&entry, B_READ_ONLY);
if (status < B_OK)
return status;
fIsReadOnly = true;
}
entry.GetRef(&fRef);
struct stat stat;
stat.st_mode = 0;
fFile.GetStat(&stat);
fIsDevice = (stat.st_mode & (S_IFBLK | S_IFCHR)) != 0;
if (attribute != NULL)
fAttribute = strdup(attribute);
else
fAttribute = NULL;
fBlockSize = 512;
if (IsAttribute()) {
attr_info info;
status = fFile.GetAttrInfo(fAttribute, &info);
if (status != B_OK)
return status;
fSize = info.size;
fType = info.type;
} else if (fIsDevice) {
// ToDo: is there any other possibility to issue a ioctl() from a BFile?
device_geometry geometry;
int device = fFile.Dup();
if (device < 0 || ioctl(device, B_GET_GEOMETRY, &geometry) < 0) {
if (device >= 0)
close(device);
fFile.Unset();
return B_ERROR;
}
close(device);
fSize = 1LL * geometry.head_count * geometry.cylinder_count
* geometry.sectors_per_track * geometry.bytes_per_sector;
fBlockSize = geometry.bytes_per_sector;
} else {
status = fFile.GetSize(&fSize);
if (status < B_OK) {
fFile.Unset();
return status;
}
}
fLastChange = fFirstChange = NULL;
fView = NULL;
fRealViewOffset = 0;
fViewOffset = 0;
fRealViewSize = fViewSize = fBlockSize;
fNeedsUpdate = true;
return B_OK;
}
status_t
DataEditor::SetToAttribute(const char *attribute)
{
status_t status = InitCheck();
if (status < B_OK)
return status;
fAttribute = attribute;
// ToDo: attributes are not yet supported
return B_ERROR;
}
status_t
DataEditor::InitCheck()
{
if (fAttribute != NULL)
// ToDo: for now!
return B_ERROR;
return fFile.InitCheck();
}
void
DataEditor::AddChange(DataChange *change)
{
if (change == NULL)
return;
bool removed = RemoveRedos();
bool changed = !CanUndo();
fChanges.AddItem(change);
fLastChange = change;
fLastChange->Apply(fRealViewOffset, fView, fRealViewSize);
// update observers
SendNotices(fLastChange);
BMessage update;
if (removed)
update.AddBool("can_redo", false);
if (changed)
update.AddBool("can_undo", true);
SendNotices(kMsgDataEditorStateChange, &update);
}
status_t
DataEditor::Replace(off_t offset, const uint8 *data, size_t length)
{
if (!IsLocked())
debugger("DataEditor: view not locked");
if (fNeedsUpdate) {
status_t status = Update();
if (status < B_OK)
return status;
}
ReplaceChange *change = new ReplaceChange(offset, data, length);
AddChange(change);
return B_OK;
}
status_t
DataEditor::Remove(off_t offset, off_t length)
{
if (!IsLocked())
debugger("DataEditor: view not locked");
// not yet implemented
return B_ERROR;
}
status_t
DataEditor::Insert(off_t offset, const uint8 *text, size_t length)
{
if (!IsLocked())
debugger("DataEditor: view not locked");
// not yet implemented
return B_ERROR;
}
void
DataEditor::ApplyChanges()
{
if (fLastChange == NULL)
return;
int32 count = fChanges.IndexOf(fLastChange) + 1;
for (int32 i = 0; i < count; i++) {
DataChange *change = fChanges.ItemAt(i);
change->Apply(fRealViewOffset, fView, fRealViewSize);
}
}
/** This method will be called by DataEditor::AddChange()
* immediately before a change is applied.
* It removes all pending redo nodes from the list that would
* come after the current change.
*/
bool
DataEditor::RemoveRedos()
{
if (fLastChange == NULL)
return false;
int32 start = fChanges.IndexOf(fLastChange) + 1;
bool removed = false;
for (int32 i = fChanges.CountItems(); i-- > start; ) {
DataChange *change = fChanges.RemoveItemAt(i);
delete change;
removed = true;
}
return removed;
}
status_t
DataEditor::Undo()
{
BAutolock locker(this);
if (!CanUndo())
return B_ERROR;
bool couldRedo = CanRedo();
DataChange *undoChange = fLastChange;
int32 index = fChanges.IndexOf(undoChange);
undoChange->Revert(fRealViewOffset, fView, fRealViewSize);
if (index > 0)
fLastChange = fChanges.ItemAt(index - 1);
else
fLastChange = NULL;
// update observers
SendNotices(undoChange);
BMessage update;
if (!couldRedo)
update.AddBool("can_redo", true);
if (!CanUndo())
update.AddBool("can_undo", false);
SendNotices(kMsgDataEditorStateChange, &update);
return B_OK;
}
status_t
DataEditor::Redo()
{
BAutolock locker(this);
if (!CanRedo())
return B_ERROR;
bool couldUndo = CanUndo();
int32 index = fChanges.IndexOf(fLastChange);
fLastChange = fChanges.ItemAt(index + 1);
fLastChange->Apply(fRealViewOffset, fView, fRealViewSize);
// update observers
SendNotices(fLastChange);
BMessage update;
if (!CanRedo())
update.AddBool("can_redo", false);
if (!couldUndo)
update.AddBool("can_undo", true);
SendNotices(kMsgDataEditorStateChange, &update);
return B_OK;
}
bool
DataEditor::CanUndo() const
{
return fLastChange != NULL;
}
bool
DataEditor::CanRedo() const
{
return fChanges.IndexOf(fLastChange) < fChanges.CountItems() - 1;
}
status_t
DataEditor::SetFileSize(off_t size)
{
fSize = size;
return B_OK;
}
status_t
DataEditor::SetViewOffset(off_t offset)
{
if (fView == NULL) {
status_t status = SetViewSize(fViewSize);
if (status < B_OK)
return status;
}
fRealViewOffset = (offset / fBlockSize) * fBlockSize;
fViewOffset = offset;
fNeedsUpdate = true;
SendNotices(kMsgDataEditorOffsetChange);
return B_OK;
}
status_t
DataEditor::SetViewSize(size_t size)
{
size_t realSize = (size + fBlockSize - 1) & ~(fBlockSize - 1);
// round to the next multiple of block size
if (realSize == fRealViewSize && fView != NULL) {
fViewSize = size;
return B_OK;
}
if (realSize == 0)
return B_BAD_VALUE;
uint8 *view = (uint8 *)realloc(fView, realSize);
if (view == NULL)
return B_NO_MEMORY;
fView = view;
fRealViewSize = realSize;
fViewSize = size;
fNeedsUpdate = true;
return B_OK;
}
void
DataEditor::SetBlockSize(size_t size)
{
fBlockSize = size;
}
status_t
DataEditor::Update()
{
ssize_t bytesRead;
if (IsAttribute())
bytesRead = fFile.ReadAttr(fAttribute, fType, fRealViewOffset, fView, fRealViewSize);
else
bytesRead = fFile.ReadAt(fRealViewOffset, fView, fRealViewSize);
if (bytesRead < B_OK)
return bytesRead;
ApplyChanges();
fNeedsUpdate = false;
return B_OK;
}
status_t
DataEditor::UpdateIfNeeded(bool *_updated = NULL)
{
if (!fNeedsUpdate) {
if (_updated)
*_updated = false;
return B_OK;
}
status_t status = B_OK;
if (fView == NULL)
status = SetViewOffset(fViewOffset);
if (status == B_OK && fNeedsUpdate) {
status = Update();
if (status == B_OK && _updated)
*_updated = true;
}
return status;
}
status_t
DataEditor::GetViewBuffer(const uint8 **_buffer)
{
if (!IsLocked())
debugger("DataEditor: view not locked");
status_t status = UpdateIfNeeded();
if (status < B_OK)
return status;
*_buffer = fView + fViewOffset - fRealViewOffset;
return B_OK;
}
void
DataEditor::SendNotices(DataChange *change)
{
off_t offset, size;
change->GetRange(FileSize(), offset, size);
// update observer
BMessage update;
update.AddInt64("offset", offset);
update.AddInt32("size", size);
SendNotices(kMsgDataEditorUpdate, &update);
}
void
DataEditor::SendNotices(uint32 what, BMessage *message)
{
if (fObservers.CountItems() == 0)
return;
BMessage *notice;
if (message) {
notice = new BMessage(*message);
notice->what = what;
} else
notice = new BMessage(what);
for (int32 i = fObservers.CountItems(); i-- > 0;) {
BMessenger *messenger = fObservers.ItemAt(i);
messenger->SendMessage(notice);
}
delete notice;
}
status_t
DataEditor::StartWatching(BMessenger target)
{
BAutolock locker(this);
node_ref node;
status_t status = fFile.GetNodeRef(&node);
if (status < B_OK)
return status;
fObservers.AddItem(new BMessenger(target));
return watch_node(&node, B_WATCH_STAT | B_WATCH_ATTR, target);
}
status_t
DataEditor::StartWatching(BHandler *handler, BLooper *looper)
{
return StartWatching(BMessenger(handler, looper));
}
void
DataEditor::StopWatching(BMessenger target)
{
BAutolock locker(this);
for (int32 i = fObservers.CountItems(); i-- > 0;) {
BMessenger *messenger = fObservers.ItemAt(i);
if (*messenger == target) {
fObservers.RemoveItemAt(i);
delete messenger;
break;
}
}
stop_watching(target);
}
void
DataEditor::StopWatching(BHandler *handler, BLooper *looper)
{
StopWatching(BMessenger(handler, looper));
}