/* * Copyright 2004-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Distributed under the terms of the MIT License. */ #include "DataEditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TRACE # undef TRACE #endif //#define TRACE_DATA_EDITOR #ifdef TRACE_DATA_EDITOR # define TRACE(x) printf x #else # define TRACE(x) ; #endif class StateWatcher { public: StateWatcher(DataEditor &editor); ~StateWatcher(); private: DataEditor &fEditor; bool fCouldUndo; bool fCouldRedo; bool fWasModified; }; 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 bool Merge(DataChange *change); 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 bool Merge(DataChange *change); 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; }; //--------------------- #ifdef TRACE_DATA_EDITOR #define DUMPED_BLOCK_SIZE 16 static void dump_block(const uint8 *buffer, int size, const char *prefix) { for (int i = 0; i < size;) { int start = i; printf(prefix); for (; i < start + DUMPED_BLOCK_SIZE; i++) { if (!(i % 4)) printf(" "); if (i >= size) printf(" "); else printf("%02x", *(unsigned char *)(buffer + i)); } printf(" "); for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) { if (i < size) { char c = buffer[i]; if (c < 30) printf("."); else printf("%c", c); } else break; } printf("\n"); } } #endif // TRACE_DATA_EDITOR static int CompareCaseInsensitive(const uint8 *a, const uint8 *b, size_t size) { for (size_t i = 0; i < size; i++) { uint8 diff = tolower(a[i]) - tolower(b[i]); if (diff) return diff; } return 0; } static int CompareOffsets(const off_t *a, const off_t *b) { if (*a < *b) return -1; else if (*a > *b) return 1; return 0; } // #pragma mark - StateWatcher::StateWatcher(DataEditor &editor) : fEditor(editor) { fCouldUndo = editor.CanUndo(); fCouldRedo = editor.CanRedo(); fWasModified = editor.IsModified(); } StateWatcher::~StateWatcher() { BMessage update; if (fCouldRedo != fEditor.CanRedo()) update.AddBool("can_redo", fEditor.CanRedo()); if (fCouldUndo != fEditor.CanUndo()) update.AddBool("can_undo", fEditor.CanUndo()); if (fWasModified != fEditor.IsModified()) update.AddBool("modified", fEditor.IsModified()); // only send an update if we have to report anything if (!update.IsEmpty()) fEditor.SendNotices(kMsgDataEditorStateChange, &update); } // #pragma mark - DataChange::~DataChange() { } bool DataChange::Merge(DataChange *change) { return false; } // #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 = bufferOffset + bufferSize - offset; } void ReplaceChange::Apply(off_t bufferOffset, uint8 *buffer, size_t bufferSize) { // is it in our range? if (fOffset > (bufferOffset + (off_t)bufferSize) || (fOffset + (off_t)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; #ifdef TRACE_DATA_EDITOR printf("Apply %p (buffer offset = %lld):\n", this, bufferOffset); dump_block(buffer + offset - bufferOffset, size, "old:"); dump_block(fNewData + dataOffset, size, "new:"); #endif // 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 > (off_t)bufferSize || fOffset + (off_t)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; #ifdef TRACE_DATA_EDITOR printf("Revert %p (buffer offset = %lld):\n", this, bufferOffset); dump_block(buffer + offset - bufferOffset, size, "old:"); dump_block(fOldData + dataOffset, size, "new:"); #endif // now we can safely revert the buffer! memcpy(buffer + offset - bufferOffset, fOldData + dataOffset, size); } bool ReplaceChange::Merge(DataChange *_change) { ReplaceChange *change = dynamic_cast(_change); if (change == NULL) return false; if (change->fOffset + change->fSize == fOffset + fSize && change->fSize == 1) { // this is a special case - the new change changed the last byte of // the old change: we do this since the same byte is changed twice // in hex mode editing. fNewData[fSize - 1] = change->fNewData[0]; #ifdef TRACE_DATA_EDITOR printf("Merge one byte %p (offset = %lld, size = %lu):\n", this, fOffset, fSize); dump_block(fOldData, fSize, "old:"); dump_block(fNewData, fSize, "new:"); #endif return true; } // are the changes adjacent? if (change->fOffset + (off_t)change->fSize != fOffset && fOffset + (off_t)fSize != change->fOffset) return false; // okay, we can try to merge the two changes uint8 *newData = fOffset < change->fOffset ? fNewData : change->fNewData; size_t size = fSize + change->fSize; if ((newData = (uint8 *)realloc(newData, size)) == NULL) return false; uint8 *oldData = fOffset < change->fOffset ? fOldData : change->fOldData; if ((oldData = (uint8 *)realloc(oldData, size)) == NULL) { fNewData = (uint8 *)realloc(newData, fSize); // if this fails, we can't do anything about it return false; } if (fOffset < change->fOffset) { memcpy(newData + fSize, change->fNewData, change->fSize); memcpy(oldData + fSize, change->fOldData, change->fSize); } else { memcpy(newData + change->fSize, fNewData, fSize); memcpy(oldData + change->fSize, fOldData, fSize); change->fNewData = fNewData; change->fOldData = fOldData; // transfer ownership, so that they will be deleted with the change fOffset = change->fOffset; } fNewData = newData; fOldData = oldData; fSize = size; #ifdef TRACE_DATA_EDITOR printf("Merge %p (offset = %lld, size = %lu):\n", this, fOffset, fSize); dump_block(fOldData, fSize, "old:"); dump_block(fNewData, fSize, "new:"); #endif return true; } void ReplaceChange::GetRange(off_t /*fileSize*/, off_t &_offset, off_t &_size) { _offset = fOffset; _size = fSize; } // #pragma mark - DataEditor::DataEditor() : BLocker("data view"), fAttribute(NULL) { } DataEditor::DataEditor(entry_ref &ref, const char *attribute) : BLocker("data view"), fAttribute(NULL) { SetTo(ref, attribute); } DataEditor::DataEditor(BEntry &entry, const char *attribute) : BLocker("data view"), fAttribute(NULL) { SetTo(entry, attribute); } DataEditor::DataEditor(const DataEditor &editor) : BLocker("data view"), fAttribute(NULL) { } DataEditor::~DataEditor() { free((void*)fAttribute); } 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) { fSize = 0; fLastChange = fFirstChange = NULL; fChangesFromSaved = 0; fView = NULL; fRealViewOffset = 0; fViewOffset = 0; fRealViewSize = fViewSize = fBlockSize = 512; free((void*)fAttribute); if (attribute != NULL) fAttribute = strdup(attribute); else fAttribute = NULL; struct stat stat; status_t status = entry.GetStat(&stat); if (status < B_OK) return status; entry.GetRef(&fAttributeRef); bool isFileSystem = false; if (entry.IsDirectory()) { // we redirect root directories to their volumes BDirectory directory(&entry); if (directory.InitCheck() == B_OK && directory.IsRootDirectory()) { fs_info info; if (fs_stat_dev(stat.st_dev, &info) != 0) return errno; status = entry.SetTo(info.device_name); if (status < B_OK) return status; entry.GetStat(&stat); fBlockSize = info.block_size; if (fBlockSize > 0 && fBlockSize <= 65536) isFileSystem = true; } } 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; } else fIsReadOnly = false; entry.GetRef(&fRef); fIsDevice = S_ISBLK(stat.st_mode) || S_ISCHR(stat.st_mode); if (IsAttribute()) { BNode node(&fAttributeRef); attr_info info; status = node.GetAttrInfo(fAttribute, &info); if (status != B_OK) { fFile.Unset(); return status; } fSize = info.size; fType = info.type; } else if (fIsDevice) { device_geometry geometry; int device = fFile.Dup(); if (device < 0 || ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(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; if (fSize < 0) fSize = 0; if (!isFileSystem) fBlockSize = geometry.bytes_per_sector; } else if (entry.IsDirectory() || entry.IsSymLink()) { fSize = 0; fIsReadOnly = true; } else { status = fFile.GetSize(&fSize); if (status < B_OK) { fFile.Unset(); return status; } } if (fBlockSize == 0) fBlockSize = 512; fRealViewSize = fViewSize = fBlockSize; fNeedsUpdate = true; return B_OK; } status_t DataEditor::InitCheck() { return fFile.InitCheck(); } void DataEditor::AddChange(DataChange *change) { if (change == NULL) return; StateWatcher watcher(*this); // update state observers RemoveRedos(); change->Apply(fRealViewOffset, fView, fRealViewSize); SendNotices(change); // update observers // try to join changes if (fLastChange == NULL || !fLastChange->Merge(change)) { fChanges.AddItem(change); fLastChange = change; fChangesFromSaved++; } else delete change; } status_t DataEditor::Replace(off_t offset, const uint8 *data, size_t length) { if (IsReadOnly()) return B_NOT_ALLOWED; BAutolock locker(this); if (offset >= fSize) return B_BAD_VALUE; if (offset + (off_t)length > fSize) length = fSize - offset; 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 (IsReadOnly()) return B_NOT_ALLOWED; BAutolock locker(this); // not yet implemented // ToDo: this needs some changes to the whole change mechanism return B_ERROR; } status_t DataEditor::Insert(off_t offset, const uint8 *text, size_t length) { if (IsReadOnly()) return B_NOT_ALLOWED; BAutolock locker(this); // not yet implemented // ToDo: this needs some changes to the whole change mechanism return B_ERROR; } void DataEditor::ApplyChanges() { if (fLastChange == NULL && fFirstChange == NULL) return; int32 firstIndex = fFirstChange != NULL ? fChanges.IndexOf(fFirstChange) + 1 : 0; int32 lastIndex = fChanges.IndexOf(fLastChange); if (fChangesFromSaved >= 0) { // ascend changes TRACE(("ApplyChanges(): ascend from %ld to %ld\n", firstIndex, lastIndex)); for (int32 i = firstIndex; i <= lastIndex; i++) { DataChange *change = fChanges.ItemAt(i); change->Apply(fRealViewOffset, fView, fRealViewSize); } } else { // descend changes TRACE(("ApplyChanges(): descend from %ld to %ld\n", firstIndex - 1, lastIndex)); for (int32 i = firstIndex - 1; i > lastIndex; i--) { DataChange *change = fChanges.ItemAt(i); change->Revert(fRealViewOffset, fView, fRealViewSize); } } } status_t DataEditor::Save() { BAutolock locker(this); if (!IsModified()) return B_OK; StateWatcher watcher(*this); // Do we need to ascend or descend the list of changes? int32 firstIndex = fFirstChange != NULL ? fChanges.IndexOf(fFirstChange) + 1 : 0; int32 lastIndex = fChanges.IndexOf(fLastChange); if (fChangesFromSaved < 0 && firstIndex != lastIndex) { // swap indices ASSERT(firstIndex > lastIndex); int32 temp = firstIndex - 1; firstIndex = lastIndex; lastIndex = temp; } if (firstIndex < 0) firstIndex = 0; if (lastIndex > fChanges.CountItems() - 1) lastIndex = fChanges.CountItems(); // Collect ranges of data we need to write. // ToDo: This is a very simple implementation and could be drastically // improved by having items that save ranges, not just offsets. If Insert() // and Remove() are going to be implemented, it should be improved that // way to reduce the memory footprint to something acceptable. BObjectList list; for (int32 i = firstIndex; i <= lastIndex; i++) { DataChange *change = fChanges.ItemAt(i); off_t offset, size; change->GetRange(FileSize(), offset, size); offset -= offset % BlockSize(); while (size > 0) { list.BinaryInsertCopyUnique(offset, CompareOffsets); offset += BlockSize(); size -= BlockSize(); } } // read in data and apply changes, write it back to disk off_t oldOffset = fViewOffset; for (int32 i = 0; i < list.CountItems(); i++) { off_t offset = *list.ItemAt(i); // load the data into our view SetViewOffset(offset, false); if (fNeedsUpdate) { status_t status = Update(); if (status < B_OK) return status; } // save back to disk // don't try to change the file size size_t size = fRealViewSize; if (fRealViewOffset + (off_t)fRealViewSize > (off_t)fSize) size = fSize - fRealViewOffset; ssize_t bytesWritten; if (IsAttribute()) { bytesWritten = fFile.WriteAttr(fAttribute, fType, fRealViewOffset, fView, size); } else bytesWritten = fFile.WriteAt(fRealViewOffset, fView, size); if (bytesWritten < B_OK) return bytesWritten; } // update state SetViewOffset(oldOffset, false); if (fNeedsUpdate) Update(); fChangesFromSaved = 0; fFirstChange = fLastChange; return B_OK; } /** 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. */ void DataEditor::RemoveRedos() { int32 start = fChanges.IndexOf(fLastChange) + 1; for (int32 i = fChanges.CountItems(); i-- > start; ) { DataChange *change = fChanges.RemoveItemAt(i); delete change; } } status_t DataEditor::Undo() { BAutolock locker(this); if (!CanUndo()) return B_ERROR; StateWatcher watcher(*this); // update state observers DataChange *undoChange = fLastChange; int32 index = fChanges.IndexOf(undoChange); fChangesFromSaved--; undoChange->Revert(fRealViewOffset, fView, fRealViewSize); if (index > 0) fLastChange = fChanges.ItemAt(index - 1); else fLastChange = NULL; // update observers SendNotices(undoChange); return B_OK; } status_t DataEditor::Redo() { BAutolock locker(this); if (!CanRedo()) return B_ERROR; StateWatcher watcher(*this); // update state observers int32 index = fChanges.IndexOf(fLastChange); fLastChange = fChanges.ItemAt(index + 1); fChangesFromSaved++; fLastChange->Apply(fRealViewOffset, fView, fRealViewSize); // update observers SendNotices(fLastChange); 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) { // ToDo: implement me! //fSize = size; //return B_OK; return B_ERROR; } status_t DataEditor::SetViewOffset(off_t offset, bool sendNotices) { BAutolock locker(this); if (fView == NULL) { status_t status = SetViewSize(fViewSize); if (status < B_OK) return status; } if (offset < 0 || offset > fSize) return B_BAD_VALUE; offset = (offset / fViewSize) * fViewSize; if (offset == fViewOffset) return B_OK; fViewOffset = offset; fRealViewOffset = (fViewOffset / fBlockSize) * fBlockSize; fNeedsUpdate = true; if (sendNotices) { BMessage update; update.AddInt64("offset", fViewOffset); SendNotices(kMsgDataEditorParameterChange, &update); } return B_OK; } status_t DataEditor::SetViewOffset(off_t offset) { return SetViewOffset(offset, true); } status_t DataEditor::SetViewSize(size_t size, bool sendNotices) { BAutolock locker(this); size_t realSize = (size + fBlockSize - 1) & ~(fBlockSize - 1); // round to the next multiple of block size if (realSize == fRealViewSize && fViewSize == size && fView != NULL) return B_OK; if (realSize == 0) return B_BAD_VALUE; if (realSize != fRealViewSize || fView == NULL) { uint8 *view = (uint8 *)realloc(fView, realSize); if (view == NULL) return B_NO_MEMORY; fView = view; fRealViewSize = realSize; } fViewSize = size; fNeedsUpdate = true; // let's correct the view offset if necessary if (fViewOffset % size) SetViewOffset(fViewOffset); if (sendNotices) { BMessage update; update.AddInt32("view_size", size); SendNotices(kMsgDataEditorParameterChange, &update); } return B_OK; } status_t DataEditor::SetViewSize(size_t size) { return SetViewSize(size, true); } status_t DataEditor::SetBlockSize(size_t size) { BAutolock locker(this); fBlockSize = size; status_t status = SetViewOffset(fViewOffset, false); // this will trigger an update of the real view offset if (status == B_OK) status = SetViewSize(fViewSize, false); BMessage update; update.AddInt32("block_size", size); update.AddInt64("offset", fViewOffset); SendNotices(kMsgDataEditorParameterChange, &update); return status; } status_t DataEditor::Update() { ssize_t bytesRead; if (IsAttribute()) { BNode node(&fAttributeRef); bytesRead = node.ReadAttr(fAttribute, fType, fRealViewOffset, fView, fRealViewSize); } else bytesRead = fFile.ReadAt(fRealViewOffset, fView, fRealViewSize); if (bytesRead < B_OK) return bytesRead; if ((size_t)bytesRead < fRealViewSize) { // make sure the rest of data is cleared memset(fView + bytesRead, 0, fRealViewSize - bytesRead); } ApplyChanges(); fNeedsUpdate = false; return B_OK; } status_t DataEditor::UpdateIfNeeded(bool *_updated) { 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::ForceUpdate() { BAutolock locker(this); status_t status = B_OK; off_t newSize = fSize; if (IsAttribute()) { // update attribute size (we ignore the type for now) attr_info info; status = fFile.GetAttrInfo(fAttribute, &info); if (status != B_OK) { // The attribute may have just been removed before // it gets rewritten, so we don't do anything // else here (we just set the file size to 0) newSize = 0; } else newSize = info.size; } else if (!IsDevice()) { // update file size if (fFile.GetSize(&newSize) != B_OK) return B_ERROR; } if (fSize != newSize) { fSize = newSize; // update observers BMessage update; update.AddInt64("file_size", newSize); SendNotices(kMsgDataEditorParameterChange, &update); } if (fView == NULL) status = SetViewOffset(fViewOffset); else { BMessage update; update.AddInt64("offset", fViewOffset); update.AddInt64("size", fViewSize); SendNotices(kMsgDataEditorUpdate, &update); } if (status == B_OK) status = Update(); 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; if (fView == NULL) return B_NO_INIT; *_buffer = fView + fViewOffset - fRealViewOffset; return B_OK; } off_t DataEditor::Find(off_t startPosition, const uint8 *data, size_t dataSize, bool caseInsensitive, bool cyclic, BMessenger progressMonitor, volatile bool *stop) { if (data == NULL || dataSize == 0) return B_BAD_VALUE; if (startPosition < 0) startPosition = 0; BAutolock locker(this); typedef int (*compare_func)(const uint8 *a, const uint8 *b, size_t size); compare_func compareFunc; if (caseInsensitive) compareFunc = CompareCaseInsensitive; else compareFunc = (compare_func)memcmp; bool savedIsReadOnly = fIsReadOnly; fIsReadOnly = true; off_t savedOffset = fViewOffset; off_t position = (startPosition / fRealViewSize) * fRealViewSize; size_t firstByte = startPosition % fRealViewSize; size_t matchLastOffset = 0; off_t foundAt = B_ENTRY_NOT_FOUND; bool noStop = false; if (stop == NULL) stop = &noStop; // start progress monitor { BMessage progress(kMsgDataEditorFindProgress); progress.AddBool("running", true); progressMonitor.SendMessage(&progress); } bigtime_t lastReport = 0; off_t blocks = fSize; if (!cyclic) blocks -= position; blocks = (blocks + fRealViewSize - 1) / fRealViewSize; for (; blocks-- > 0 && !*stop; position += fRealViewSize) { if (position > fSize) position = 0; SetViewOffset(position, false); if (fNeedsUpdate) Update(); bigtime_t current = system_time(); if (lastReport + 500000LL < current) { // report the progress two times a second BMessage progress(kMsgDataEditorFindProgress); progress.AddInt64("position", position); progressMonitor.SendMessage(&progress); lastReport = current; } // search for data in current block if (matchLastOffset != 0) { // we had a partial match in the previous block, let's // check if it is a whole match if (!compareFunc(fView, data + matchLastOffset, dataSize - matchLastOffset)) { matchLastOffset = 0; break; } foundAt = B_ENTRY_NOT_FOUND; matchLastOffset = 0; } for (size_t i = firstByte; i < fRealViewSize; i++) { if (position + (off_t)(i + dataSize) > (off_t)fSize) break; if (!compareFunc(fView + i, data, 1)) { // one byte matches, compare the rest size_t size = dataSize - 1; size_t offset = i + 1; if (offset + size > fRealViewSize) size = fRealViewSize - offset; if (size == 0 || !compareFunc(fView + offset, data + 1, size)) { foundAt = position + i; if (size != dataSize - 1) { // only a partial match, we have to check the start // of the next block matchLastOffset = size + 1; } break; } } } if (foundAt >= 0 && matchLastOffset == 0) break; firstByte = 0; } fIsReadOnly = savedIsReadOnly; if (foundAt >= 0 && matchLastOffset != 0) foundAt = B_ENTRY_NOT_FOUND; // stop progress monitor { BMessage progress(kMsgDataEditorFindProgress); progress.AddBool("running", false); progress.AddInt64("position", foundAt >= 0 ? foundAt : savedOffset); progressMonitor.SendMessage(&progress); } SetViewOffset(savedOffset, false); // this is to ensure that we're sending an update when // we're set to the found data offset if (foundAt < 0 && *stop) return B_INTERRUPTED; return foundAt; } void DataEditor::SendNotices(DataChange *change) { off_t offset, size; change->GetRange(FileSize(), offset, size); // update observer BMessage update; update.AddInt64("offset", offset); update.AddInt64("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)); }