1/*
2 * Copyright 2004-2007, Axel D��rfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "DataEditor.h"
8
9#include <Autolock.h>
10#include <NodeMonitor.h>
11#include <Debug.h>
12#include <Directory.h>
13#include <Drivers.h>
14#include <fs_attr.h>
15#include <fs_info.h>
16
17#include <stdlib.h>
18#include <string.h>
19#include <unistd.h>
20#include <ctype.h>
21#include <errno.h>
22
23
24#ifdef TRACE
25#	undef TRACE
26#endif
27
28//#define TRACE_DATA_EDITOR
29#ifdef TRACE_DATA_EDITOR
30#	define TRACE(x) printf x
31#else
32#	define TRACE(x) ;
33#endif
34
35
36class StateWatcher {
37	public:
38		StateWatcher(DataEditor &editor);
39		~StateWatcher();
40
41	private:
42		DataEditor	&fEditor;
43		bool		fCouldUndo;
44		bool		fCouldRedo;
45		bool		fWasModified;
46};
47
48class DataChange {
49	public:
50		virtual ~DataChange();
51
52		virtual void Apply(off_t offset, uint8 *buffer, size_t size) = 0;
53		virtual void Revert(off_t offset, uint8 *buffer, size_t size) = 0;
54		virtual bool Merge(DataChange *change);
55		virtual void GetRange(off_t fileSize, off_t &_offset, off_t &_size) = 0;
56};
57
58class ReplaceChange : public DataChange {
59	public:
60		ReplaceChange(off_t offset, const uint8 *data, size_t size);
61		~ReplaceChange();
62
63		virtual void Apply(off_t offset, uint8 *buffer, size_t size);
64		virtual void Revert(off_t offset, uint8 *buffer, size_t size);
65		virtual bool Merge(DataChange *change);
66		virtual void GetRange(off_t fileSize, off_t &_offset, off_t &_size);
67
68	private:
69		void Normalize(off_t bufferOffset, size_t bufferSize,
70			off_t &offset, size_t &dataOffset, size_t &size);
71
72		uint8 	*fNewData;
73		uint8 	*fOldData;
74		size_t	fSize;
75		off_t	fOffset;
76};
77
78
79//---------------------
80
81
82#ifdef TRACE_DATA_EDITOR
83
84#define DUMPED_BLOCK_SIZE 16
85
86static void
87dump_block(const uint8 *buffer, int size, const char *prefix)
88{
89	for (int i = 0; i < size;) {
90		int start = i;
91
92		printf(prefix);
93		for (; i < start + DUMPED_BLOCK_SIZE; i++) {
94			if (!(i % 4))
95				printf(" ");
96
97			if (i >= size)
98				printf("  ");
99			else
100				printf("%02x", *(unsigned char *)(buffer + i));
101		}
102		printf("  ");
103
104		for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) {
105			if (i < size) {
106				char c = buffer[i];
107
108				if (c < 30)
109					printf(".");
110				else
111					printf("%c", c);
112			} else
113				break;
114		}
115		printf("\n");
116	}
117}
118#endif	// TRACE_DATA_EDITOR
119
120
121static int
122CompareCaseInsensitive(const uint8 *a, const uint8 *b, size_t size)
123{
124	for (size_t i = 0; i < size; i++) {
125		uint8 diff = tolower(a[i]) - tolower(b[i]);
126		if (diff)
127			return diff;
128	}
129
130	return 0;
131}
132
133
134static int
135CompareOffsets(const off_t *a, const off_t *b)
136{
137	if (*a < *b)
138		return -1;
139	else if (*a > *b)
140		return 1;
141
142	return 0;
143}
144
145
146//	#pragma mark -
147
148
149StateWatcher::StateWatcher(DataEditor &editor)
150	:
151	fEditor(editor)
152{
153	fCouldUndo = editor.CanUndo();
154	fCouldRedo = editor.CanRedo();
155	fWasModified = editor.IsModified();
156}
157
158
159StateWatcher::~StateWatcher()
160{
161	BMessage update;
162	if (fCouldRedo != fEditor.CanRedo())
163		update.AddBool("can_redo", fEditor.CanRedo());
164	if (fCouldUndo != fEditor.CanUndo())
165		update.AddBool("can_undo", fEditor.CanUndo());
166	if (fWasModified != fEditor.IsModified())
167		update.AddBool("modified", fEditor.IsModified());
168
169	// only send an update if we have to report anything
170	if (!update.IsEmpty())
171		fEditor.SendNotices(kMsgDataEditorStateChange, &update);
172}
173
174
175//	#pragma mark -
176
177
178DataChange::~DataChange()
179{
180}
181
182
183bool
184DataChange::Merge(DataChange *change)
185{
186	return false;
187}
188
189
190//	#pragma mark -
191
192
193ReplaceChange::ReplaceChange(off_t offset, const uint8 *data, size_t size)
194{
195	fOffset = offset;
196
197	fNewData = (uint8 *)malloc(size);
198	fOldData = (uint8 *)malloc(size);
199	if (fNewData != NULL && fOldData != NULL) {
200		memcpy(fNewData, data, size);
201		fSize = size;
202	} else
203		fSize = 0;
204}
205
206
207ReplaceChange::~ReplaceChange()
208{
209	free(fNewData);
210	free(fOldData);
211}
212
213
214/** Normalizes the supplied offset & size pair to be limited by
215 *	the buffer offset and size.
216 *	All parameters must have been initialized before calling this
217 *	method.
218 */
219
220void
221ReplaceChange::Normalize(off_t bufferOffset, size_t bufferSize, off_t &offset,
222	size_t &dataOffset, size_t &size)
223{
224	if (fOffset < bufferOffset) {
225		offset = bufferOffset;
226		dataOffset = bufferOffset - fOffset;
227		size -= dataOffset;
228	}
229
230	if (offset + size > bufferOffset + bufferSize)
231		size = bufferOffset + bufferSize - offset;
232}
233
234
235void
236ReplaceChange::Apply(off_t bufferOffset, uint8 *buffer, size_t bufferSize)
237{
238	// is it in our range?
239	if (fOffset > (bufferOffset + (off_t)bufferSize)
240		|| (fOffset + (off_t)fSize) < bufferOffset) {
241		return;
242	}
243
244	// don't change anything outside the supplied buffer
245	off_t offset = fOffset;
246	size_t dataOffset = 0;
247	size_t size = fSize;
248	Normalize(bufferOffset, bufferSize, offset, dataOffset, size);
249	if (size == 0)
250		return;
251
252#ifdef TRACE_DATA_EDITOR
253	printf("Apply %p (buffer offset = %lld):\n", this, bufferOffset);
254	dump_block(buffer + offset - bufferOffset, size, "old:");
255	dump_block(fNewData + dataOffset, size, "new:");
256#endif
257
258	// now we can safely exchange the buffer!
259	memcpy(fOldData + dataOffset, buffer + offset - bufferOffset, size);
260	memcpy(buffer + offset - bufferOffset, fNewData + dataOffset, size);
261}
262
263
264void
265ReplaceChange::Revert(off_t bufferOffset, uint8 *buffer, size_t bufferSize)
266{
267	// is it in our range?
268	if (fOffset - bufferOffset > (off_t)bufferSize
269		|| fOffset + (off_t)fSize < bufferOffset) {
270		return;
271	}
272
273	// don't change anything outside the supplied buffer
274	off_t offset = fOffset;
275	size_t dataOffset = 0;
276	size_t size = fSize;
277	Normalize(bufferOffset, bufferSize, offset, dataOffset, size);
278	if (size == 0)
279		return;
280
281#ifdef TRACE_DATA_EDITOR
282	printf("Revert %p (buffer offset = %lld):\n", this, bufferOffset);
283	dump_block(buffer + offset - bufferOffset, size, "old:");
284	dump_block(fOldData + dataOffset, size, "new:");
285#endif
286
287	// now we can safely revert the buffer!
288	memcpy(buffer + offset - bufferOffset, fOldData + dataOffset, size);
289}
290
291
292bool
293ReplaceChange::Merge(DataChange *_change)
294{
295	ReplaceChange *change = dynamic_cast<ReplaceChange *>(_change);
296	if (change == NULL)
297		return false;
298
299	if (change->fOffset + change->fSize == fOffset + fSize
300		&& change->fSize == 1) {
301		// this is a special case - the new change changed the last byte of
302		// the old change: we do this since the same byte is changed twice
303		// in hex mode editing.
304		fNewData[fSize - 1] = change->fNewData[0];
305#ifdef TRACE_DATA_EDITOR
306		printf("Merge one byte %p (offset = %lld, size = %lu):\n", this, fOffset,
307			fSize);
308		dump_block(fOldData, fSize, "old:");
309		dump_block(fNewData, fSize, "new:");
310#endif
311		return true;
312	}
313
314	// are the changes adjacent?
315
316	if (change->fOffset + (off_t)change->fSize != fOffset
317		&& fOffset + (off_t)fSize != change->fOffset)
318		return false;
319
320	// okay, we can try to merge the two changes
321
322	uint8 *newData = fOffset < change->fOffset ? fNewData : change->fNewData;
323	size_t size = fSize + change->fSize;
324
325	if ((newData = (uint8 *)realloc(newData, size)) == NULL)
326		return false;
327
328	uint8 *oldData = fOffset < change->fOffset ? fOldData : change->fOldData;
329	if ((oldData = (uint8 *)realloc(oldData, size)) == NULL) {
330		fNewData = (uint8 *)realloc(newData, fSize);
331			// if this fails, we can't do anything about it
332		return false;
333	}
334
335	if (fOffset < change->fOffset) {
336		memcpy(newData + fSize, change->fNewData, change->fSize);
337		memcpy(oldData + fSize, change->fOldData, change->fSize);
338	} else {
339		memcpy(newData + change->fSize, fNewData, fSize);
340		memcpy(oldData + change->fSize, fOldData, fSize);
341		change->fNewData = fNewData;
342		change->fOldData = fOldData;
343			// transfer ownership, so that they will be deleted with the change
344		fOffset = change->fOffset;
345	}
346
347	fNewData = newData;
348	fOldData = oldData;
349	fSize = size;
350
351#ifdef TRACE_DATA_EDITOR
352	printf("Merge %p (offset = %lld, size = %lu):\n", this, fOffset, fSize);
353	dump_block(fOldData, fSize, "old:");
354	dump_block(fNewData, fSize, "new:");
355#endif
356
357	return true;
358}
359
360
361void
362ReplaceChange::GetRange(off_t /*fileSize*/, off_t &_offset, off_t &_size)
363{
364	_offset = fOffset;
365	_size = fSize;
366}
367
368
369//	#pragma mark -
370
371
372DataEditor::DataEditor()
373	:
374	BLocker("data view"),
375	fAttribute(NULL)
376{
377}
378
379
380DataEditor::DataEditor(entry_ref &ref, const char *attribute)
381	:
382	BLocker("data view"),
383	fAttribute(NULL)
384{
385	SetTo(ref, attribute);
386}
387
388
389DataEditor::DataEditor(BEntry &entry, const char *attribute)
390	:
391	BLocker("data view"),
392	fAttribute(NULL)
393{
394	SetTo(entry, attribute);
395}
396
397
398DataEditor::DataEditor(const DataEditor &editor)
399	:
400	BLocker("data view"),
401	fAttribute(NULL)
402{
403}
404
405
406DataEditor::~DataEditor()
407{
408	free((void*)fAttribute);
409}
410
411
412status_t
413DataEditor::SetTo(const char *path, const char *attribute)
414{
415	BEntry entry(path);
416	return SetTo(entry, attribute);
417}
418
419
420status_t
421DataEditor::SetTo(entry_ref &ref, const char *attribute)
422{
423	BEntry entry(&ref);
424	return SetTo(entry, attribute);
425}
426
427
428status_t
429DataEditor::SetTo(BEntry &entry, const char *attribute)
430{
431	fSize = 0;
432	fLastChange = fFirstChange = NULL;
433	fChangesFromSaved = 0;
434	fView = NULL;
435	fRealViewOffset = 0;
436	fViewOffset = 0;
437	fRealViewSize = fViewSize = fBlockSize = 512;
438
439	free((void*)fAttribute);
440	if (attribute != NULL)
441		fAttribute = strdup(attribute);
442	else
443		fAttribute = NULL;
444
445	struct stat stat;
446	status_t status = entry.GetStat(&stat);
447	if (status < B_OK)
448		return status;
449
450	entry.GetRef(&fAttributeRef);
451
452	bool isFileSystem = false;
453
454	if (entry.IsDirectory()) {
455		// we redirect root directories to their volumes
456		BDirectory directory(&entry);
457		if (directory.InitCheck() == B_OK && directory.IsRootDirectory()) {
458			fs_info info;
459			if (fs_stat_dev(stat.st_dev, &info) != 0)
460				return errno;
461
462			status = entry.SetTo(info.device_name);
463			if (status < B_OK)
464				return status;
465
466			entry.GetStat(&stat);
467
468			fBlockSize = info.block_size;
469			if (fBlockSize > 0 && fBlockSize <= 65536)
470				isFileSystem = true;
471		}
472	}
473
474	status = fFile.SetTo(&entry, B_READ_WRITE);
475	if (status < B_OK) {
476		// try to open read only
477		status = fFile.SetTo(&entry, B_READ_ONLY);
478		if (status < B_OK)
479			return status;
480
481		fIsReadOnly = true;
482	} else
483		fIsReadOnly = false;
484
485	entry.GetRef(&fRef);
486	fIsDevice = S_ISBLK(stat.st_mode) || S_ISCHR(stat.st_mode);
487
488	if (IsAttribute()) {
489		BNode node(&fAttributeRef);
490		attr_info info;
491		status = node.GetAttrInfo(fAttribute, &info);
492		if (status != B_OK) {
493			fFile.Unset();
494			return status;
495		}
496
497		fSize = info.size;
498		fType = info.type;
499	} else if (fIsDevice) {
500		device_geometry geometry;
501		int device = fFile.Dup();
502		if (device < 0 || ioctl(device, B_GET_GEOMETRY, &geometry,
503				sizeof(geometry)) < 0) {
504			if (device >= 0)
505				close(device);
506			fFile.Unset();
507			return B_ERROR;
508		}
509		close(device);
510
511		fSize = 1LL * geometry.head_count * geometry.cylinder_count
512			* geometry.sectors_per_track * geometry.bytes_per_sector;
513		if (fSize < 0)
514			fSize = 0;
515		if (!isFileSystem)
516			fBlockSize = geometry.bytes_per_sector;
517	} else if (entry.IsDirectory() || entry.IsSymLink()) {
518		fSize = 0;
519		fIsReadOnly = true;
520	} else {
521		status = fFile.GetSize(&fSize);
522		if (status < B_OK) {
523			fFile.Unset();
524			return status;
525		}
526	}
527
528	if (fBlockSize == 0)
529		fBlockSize = 512;
530
531	fRealViewSize = fViewSize = fBlockSize;
532	fNeedsUpdate = true;
533
534	return B_OK;
535}
536
537
538status_t
539DataEditor::InitCheck()
540{
541	return fFile.InitCheck();
542}
543
544
545void
546DataEditor::AddChange(DataChange *change)
547{
548	if (change == NULL)
549		return;
550
551	StateWatcher watcher(*this);
552		// update state observers
553
554	RemoveRedos();
555	change->Apply(fRealViewOffset, fView, fRealViewSize);
556
557	SendNotices(change);
558		// update observers
559
560	// try to join changes
561	if (fLastChange == NULL || !fLastChange->Merge(change)) {
562		fChanges.AddItem(change);
563		fLastChange = change;
564		fChangesFromSaved++;
565	} else
566		delete change;
567}
568
569
570status_t
571DataEditor::Replace(off_t offset, const uint8 *data, size_t length)
572{
573	if (IsReadOnly())
574		return B_NOT_ALLOWED;
575
576	BAutolock locker(this);
577
578	if (offset >= fSize)
579		return B_BAD_VALUE;
580	if (offset + (off_t)length > fSize)
581		length = fSize - offset;
582
583	if (fNeedsUpdate) {
584		status_t status = Update();
585		if (status < B_OK)
586			return status;
587	}
588
589	ReplaceChange *change = new ReplaceChange(offset, data, length);
590	AddChange(change);
591
592	return B_OK;
593}
594
595
596status_t
597DataEditor::Remove(off_t offset, off_t length)
598{
599	if (IsReadOnly())
600		return B_NOT_ALLOWED;
601
602	BAutolock locker(this);
603
604	// not yet implemented
605	// ToDo: this needs some changes to the whole change mechanism
606
607	return B_ERROR;
608}
609
610
611status_t
612DataEditor::Insert(off_t offset, const uint8 *text, size_t length)
613{
614	if (IsReadOnly())
615		return B_NOT_ALLOWED;
616
617	BAutolock locker(this);
618
619	// not yet implemented
620	// ToDo: this needs some changes to the whole change mechanism
621
622	return B_ERROR;
623}
624
625
626void
627DataEditor::ApplyChanges()
628{
629	if (fLastChange == NULL && fFirstChange == NULL)
630		return;
631
632	int32 firstIndex = fFirstChange != NULL ? fChanges.IndexOf(fFirstChange) + 1
633		: 0;
634	int32 lastIndex = fChanges.IndexOf(fLastChange);
635
636	if (fChangesFromSaved >= 0) {
637		// ascend changes
638		TRACE(("ApplyChanges(): ascend from %ld to %ld\n", firstIndex,
639			lastIndex));
640
641		for (int32 i = firstIndex; i <= lastIndex; i++) {
642			DataChange *change = fChanges.ItemAt(i);
643			change->Apply(fRealViewOffset, fView, fRealViewSize);
644		}
645	} else {
646		// descend changes
647		TRACE(("ApplyChanges(): descend from %ld to %ld\n", firstIndex - 1,
648			lastIndex));
649
650		for (int32 i = firstIndex - 1; i > lastIndex; i--) {
651			DataChange *change = fChanges.ItemAt(i);
652			change->Revert(fRealViewOffset, fView, fRealViewSize);
653		}
654	}
655}
656
657
658status_t
659DataEditor::Save()
660{
661	BAutolock locker(this);
662
663	if (!IsModified())
664		return B_OK;
665
666	StateWatcher watcher(*this);
667
668	// Do we need to ascend or descend the list of changes?
669
670	int32 firstIndex = fFirstChange != NULL ? fChanges.IndexOf(fFirstChange) + 1
671		: 0;
672	int32 lastIndex = fChanges.IndexOf(fLastChange);
673	if (fChangesFromSaved < 0 && firstIndex != lastIndex) {
674		// swap indices
675		ASSERT(firstIndex > lastIndex);
676
677		int32 temp = firstIndex - 1;
678		firstIndex = lastIndex;
679		lastIndex = temp;
680	}
681
682	if (firstIndex < 0)
683		firstIndex = 0;
684	if (lastIndex > fChanges.CountItems() - 1)
685		lastIndex = fChanges.CountItems();
686
687	// Collect ranges of data we need to write.
688
689	// ToDo: This is a very simple implementation and could be drastically
690	// improved by having items that save ranges, not just offsets. If Insert()
691	// and Remove() are going to be implemented, it should be improved that
692	// way to reduce the memory footprint to something acceptable.
693
694	BObjectList<off_t> list;
695	for (int32 i = firstIndex; i <= lastIndex; i++) {
696		DataChange *change = fChanges.ItemAt(i);
697
698		off_t offset, size;
699		change->GetRange(FileSize(), offset, size);
700		offset -= offset % BlockSize();
701
702		while (size > 0) {
703			list.BinaryInsertCopyUnique(offset, CompareOffsets);
704			offset += BlockSize();
705			size -= BlockSize();
706		}
707	}
708
709	// read in data and apply changes, write it back to disk
710
711	off_t oldOffset = fViewOffset;
712
713	for (int32 i = 0; i < list.CountItems(); i++) {
714		off_t offset = *list.ItemAt(i);
715
716		// load the data into our view
717		SetViewOffset(offset, false);
718
719		if (fNeedsUpdate) {
720			status_t status = Update();
721			if (status < B_OK)
722				return status;
723		}
724
725		// save back to disk
726
727		// don't try to change the file size
728		size_t size = fRealViewSize;
729		if (fRealViewOffset + (off_t)fRealViewSize > (off_t)fSize)
730			size = fSize - fRealViewOffset;
731
732		ssize_t bytesWritten;
733		if (IsAttribute()) {
734			bytesWritten = fFile.WriteAttr(fAttribute, fType, fRealViewOffset,
735				fView, size);
736		} else
737			bytesWritten = fFile.WriteAt(fRealViewOffset, fView, size);
738
739		if (bytesWritten < B_OK)
740			return bytesWritten;
741	}
742
743	// update state
744
745	SetViewOffset(oldOffset, false);
746	if (fNeedsUpdate)
747		Update();
748
749	fChangesFromSaved = 0;
750	fFirstChange = fLastChange;
751
752	return B_OK;
753}
754
755
756/** This method will be called by DataEditor::AddChange()
757 *	immediately before a change is applied.
758 *	It removes all pending redo nodes from the list that would
759 *	come after the current change.
760 */
761
762void
763DataEditor::RemoveRedos()
764{
765	int32 start = fChanges.IndexOf(fLastChange) + 1;
766
767	for (int32 i = fChanges.CountItems(); i-- > start; ) {
768		DataChange *change = fChanges.RemoveItemAt(i);
769		delete change;
770	}
771}
772
773
774status_t
775DataEditor::Undo()
776{
777	BAutolock locker(this);
778
779	if (!CanUndo())
780		return B_ERROR;
781
782	StateWatcher watcher(*this);
783		// update state observers
784
785	DataChange *undoChange = fLastChange;
786
787	int32 index = fChanges.IndexOf(undoChange);
788	fChangesFromSaved--;
789	undoChange->Revert(fRealViewOffset, fView, fRealViewSize);
790
791	if (index > 0)
792		fLastChange = fChanges.ItemAt(index - 1);
793	else
794		fLastChange = NULL;
795
796	// update observers
797	SendNotices(undoChange);
798
799	return B_OK;
800}
801
802
803status_t
804DataEditor::Redo()
805{
806	BAutolock locker(this);
807
808	if (!CanRedo())
809		return B_ERROR;
810
811	StateWatcher watcher(*this);
812		// update state observers
813
814	int32 index = fChanges.IndexOf(fLastChange);
815	fLastChange = fChanges.ItemAt(index + 1);
816	fChangesFromSaved++;
817
818	fLastChange->Apply(fRealViewOffset, fView, fRealViewSize);
819
820	// update observers
821	SendNotices(fLastChange);
822
823	return B_OK;
824}
825
826
827bool
828DataEditor::CanUndo() const
829{
830	return fLastChange != NULL;
831}
832
833
834bool
835DataEditor::CanRedo() const
836{
837	return fChanges.IndexOf(fLastChange) < fChanges.CountItems() - 1;
838}
839
840
841status_t
842DataEditor::SetFileSize(off_t size)
843{
844	// ToDo: implement me!
845	//fSize = size;
846	//return B_OK;
847	return B_ERROR;
848}
849
850
851status_t
852DataEditor::SetViewOffset(off_t offset, bool sendNotices)
853{
854	BAutolock locker(this);
855
856	if (fView == NULL) {
857		status_t status = SetViewSize(fViewSize);
858		if (status < B_OK)
859			return status;
860	}
861
862	if (offset < 0 || offset > fSize)
863		return B_BAD_VALUE;
864
865	offset = (offset / fViewSize) * fViewSize;
866	if (offset == fViewOffset)
867		return B_OK;
868
869	fViewOffset = offset;
870	fRealViewOffset = (fViewOffset / fBlockSize) * fBlockSize;
871	fNeedsUpdate = true;
872
873	if (sendNotices) {
874		BMessage update;
875		update.AddInt64("offset", fViewOffset);
876		SendNotices(kMsgDataEditorParameterChange, &update);
877	}
878
879	return B_OK;
880}
881
882
883status_t
884DataEditor::SetViewOffset(off_t offset)
885{
886	return SetViewOffset(offset, true);
887}
888
889
890status_t
891DataEditor::SetViewSize(size_t size, bool sendNotices)
892{
893	BAutolock locker(this);
894
895	size_t realSize = (size + fBlockSize - 1) & ~(fBlockSize - 1);
896		// round to the next multiple of block size
897
898	if (realSize == fRealViewSize && fViewSize == size && fView != NULL)
899		return B_OK;
900
901	if (realSize == 0)
902		return B_BAD_VALUE;
903
904	if (realSize != fRealViewSize || fView == NULL) {
905		uint8 *view = (uint8 *)realloc(fView, realSize);
906		if (view == NULL)
907			return B_NO_MEMORY;
908
909		fView = view;
910		fRealViewSize = realSize;
911	}
912
913	fViewSize = size;
914	fNeedsUpdate = true;
915
916	// let's correct the view offset if necessary
917	if (fViewOffset % size)
918		SetViewOffset(fViewOffset);
919
920	if (sendNotices) {
921		BMessage update;
922		update.AddInt32("view_size", size);
923		SendNotices(kMsgDataEditorParameterChange, &update);
924	}
925
926	return B_OK;
927}
928
929
930status_t
931DataEditor::SetViewSize(size_t size)
932{
933	return SetViewSize(size, true);
934}
935
936
937status_t
938DataEditor::SetBlockSize(size_t size)
939{
940	BAutolock locker(this);
941
942	fBlockSize = size;
943	status_t status = SetViewOffset(fViewOffset, false);
944		// this will trigger an update of the real view offset
945	if (status == B_OK)
946		status = SetViewSize(fViewSize, false);
947
948	BMessage update;
949	update.AddInt32("block_size", size);
950	update.AddInt64("offset", fViewOffset);
951	SendNotices(kMsgDataEditorParameterChange, &update);
952
953	return status;
954}
955
956
957status_t
958DataEditor::Update()
959{
960	ssize_t bytesRead;
961	if (IsAttribute()) {
962		BNode node(&fAttributeRef);
963		bytesRead = node.ReadAttr(fAttribute, fType, fRealViewOffset, fView,
964			fRealViewSize);
965	} else
966		bytesRead = fFile.ReadAt(fRealViewOffset, fView, fRealViewSize);
967
968	if (bytesRead < B_OK)
969		return bytesRead;
970
971	if ((size_t)bytesRead < fRealViewSize) {
972		// make sure the rest of data is cleared
973		memset(fView + bytesRead, 0, fRealViewSize - bytesRead);
974	}
975
976	ApplyChanges();
977	fNeedsUpdate = false;
978
979	return B_OK;
980}
981
982
983status_t
984DataEditor::UpdateIfNeeded(bool *_updated)
985{
986	if (!fNeedsUpdate) {
987		if (_updated)
988			*_updated = false;
989		return B_OK;
990	}
991
992	status_t status = B_OK;
993
994	if (fView == NULL)
995		status = SetViewOffset(fViewOffset);
996
997	if (status == B_OK && fNeedsUpdate) {
998		status = Update();
999		if (status == B_OK && _updated)
1000			*_updated = true;
1001	}
1002
1003	return status;
1004}
1005
1006
1007status_t
1008DataEditor::ForceUpdate()
1009{
1010	BAutolock locker(this);
1011
1012	status_t status = B_OK;
1013
1014	off_t newSize = fSize;
1015	if (IsAttribute()) {
1016		// update attribute size (we ignore the type for now)
1017		attr_info info;
1018		status = fFile.GetAttrInfo(fAttribute, &info);
1019		if (status != B_OK) {
1020			// The attribute may have just been removed before
1021			// it gets rewritten, so we don't do anything
1022			// else here (we just set the file size to 0)
1023			newSize = 0;
1024		} else
1025			newSize = info.size;
1026	} else if (!IsDevice()) {
1027		// update file size
1028
1029		if (fFile.GetSize(&newSize) != B_OK)
1030			return B_ERROR;
1031	}
1032
1033	if (fSize != newSize) {
1034		fSize = newSize;
1035
1036		// update observers
1037		BMessage update;
1038		update.AddInt64("file_size", newSize);
1039		SendNotices(kMsgDataEditorParameterChange, &update);
1040	}
1041
1042	if (fView == NULL)
1043		status = SetViewOffset(fViewOffset);
1044	else {
1045		BMessage update;
1046		update.AddInt64("offset", fViewOffset);
1047		update.AddInt64("size", fViewSize);
1048		SendNotices(kMsgDataEditorUpdate, &update);
1049	}
1050
1051	if (status == B_OK)
1052		status = Update();
1053
1054	return status;
1055}
1056
1057
1058status_t
1059DataEditor::GetViewBuffer(const uint8 **_buffer)
1060{
1061	if (!IsLocked())
1062		debugger("DataEditor: view not locked");
1063
1064	status_t status = UpdateIfNeeded();
1065	if (status != B_OK)
1066		return status;
1067	if (fView == NULL)
1068		return B_NO_INIT;
1069
1070	*_buffer = fView + fViewOffset - fRealViewOffset;
1071	return B_OK;
1072}
1073
1074
1075off_t
1076DataEditor::Find(off_t startPosition, const uint8 *data, size_t dataSize,
1077	bool caseInsensitive, bool cyclic, BMessenger progressMonitor,
1078	volatile bool *stop)
1079{
1080	if (data == NULL || dataSize == 0)
1081		return B_BAD_VALUE;
1082
1083	if (startPosition < 0)
1084		startPosition = 0;
1085
1086	BAutolock locker(this);
1087
1088	typedef int (*compare_func)(const uint8 *a, const uint8 *b, size_t size);
1089	compare_func compareFunc;
1090	if (caseInsensitive)
1091		compareFunc = CompareCaseInsensitive;
1092	else
1093		compareFunc = (compare_func)memcmp;
1094
1095	bool savedIsReadOnly = fIsReadOnly;
1096	fIsReadOnly = true;
1097	off_t savedOffset = fViewOffset;
1098	off_t position = (startPosition / fRealViewSize) * fRealViewSize;
1099	size_t firstByte = startPosition % fRealViewSize;
1100	size_t matchLastOffset = 0;
1101	off_t foundAt = B_ENTRY_NOT_FOUND;
1102	bool noStop = false;
1103	if (stop == NULL)
1104		stop = &noStop;
1105
1106	// start progress monitor
1107	{
1108		BMessage progress(kMsgDataEditorFindProgress);
1109		progress.AddBool("running", true);
1110		progressMonitor.SendMessage(&progress);
1111	}
1112
1113	bigtime_t lastReport = 0;
1114
1115	off_t blocks = fSize;
1116	if (!cyclic)
1117		blocks -= position;
1118	blocks = (blocks + fRealViewSize - 1) / fRealViewSize;
1119
1120	for (; blocks-- > 0 && !*stop; position += fRealViewSize) {
1121		if (position > fSize)
1122			position = 0;
1123
1124		SetViewOffset(position, false);
1125		if (fNeedsUpdate)
1126			Update();
1127
1128		bigtime_t current = system_time();
1129		if (lastReport + 500000LL < current) {
1130			// report the progress two times a second
1131			BMessage progress(kMsgDataEditorFindProgress);
1132			progress.AddInt64("position", position);
1133			progressMonitor.SendMessage(&progress);
1134
1135			lastReport = current;
1136		}
1137
1138		// search for data in current block
1139
1140		if (matchLastOffset != 0) {
1141			// we had a partial match in the previous block, let's
1142			// check if it is a whole match
1143			if (!compareFunc(fView, data + matchLastOffset,
1144					dataSize - matchLastOffset)) {
1145				matchLastOffset = 0;
1146				break;
1147			}
1148
1149			foundAt = B_ENTRY_NOT_FOUND;
1150			matchLastOffset = 0;
1151		}
1152
1153		for (size_t i = firstByte; i < fRealViewSize; i++) {
1154			if (position + (off_t)(i + dataSize) > (off_t)fSize)
1155				break;
1156
1157			if (!compareFunc(fView + i, data, 1)) {
1158				// one byte matches, compare the rest
1159				size_t size = dataSize - 1;
1160				size_t offset = i + 1;
1161
1162				if (offset + size > fRealViewSize)
1163					size = fRealViewSize - offset;
1164
1165				if (size == 0 || !compareFunc(fView + offset, data + 1, size)) {
1166					foundAt = position + i;
1167
1168					if (size != dataSize - 1) {
1169						// only a partial match, we have to check the start
1170						// of the next block
1171						matchLastOffset = size + 1;
1172					}
1173					break;
1174				}
1175			}
1176		}
1177
1178		if (foundAt >= 0 && matchLastOffset == 0)
1179			break;
1180
1181		firstByte = 0;
1182	}
1183
1184	fIsReadOnly = savedIsReadOnly;
1185
1186	if (foundAt >= 0 && matchLastOffset != 0)
1187		foundAt = B_ENTRY_NOT_FOUND;
1188
1189	// stop progress monitor
1190	{
1191		BMessage progress(kMsgDataEditorFindProgress);
1192		progress.AddBool("running", false);
1193		progress.AddInt64("position", foundAt >= 0 ? foundAt : savedOffset);
1194		progressMonitor.SendMessage(&progress);
1195	}
1196
1197	SetViewOffset(savedOffset, false);
1198		// this is to ensure that we're sending an update when
1199		// we're set to the found data offset
1200
1201	if (foundAt < 0 && *stop)
1202		return B_INTERRUPTED;
1203
1204	return foundAt;
1205}
1206
1207
1208void
1209DataEditor::SendNotices(DataChange *change)
1210{
1211	off_t offset, size;
1212	change->GetRange(FileSize(), offset, size);
1213
1214	// update observer
1215	BMessage update;
1216	update.AddInt64("offset", offset);
1217	update.AddInt64("size", size);
1218	SendNotices(kMsgDataEditorUpdate, &update);
1219}
1220
1221
1222void
1223DataEditor::SendNotices(uint32 what, BMessage *message)
1224{
1225	if (fObservers.CountItems() == 0)
1226		return;
1227
1228	BMessage *notice;
1229	if (message) {
1230		notice = new BMessage(*message);
1231		notice->what = what;
1232	} else
1233		notice = new BMessage(what);
1234
1235	for (int32 i = fObservers.CountItems(); i-- > 0;) {
1236		BMessenger *messenger = fObservers.ItemAt(i);
1237		messenger->SendMessage(notice);
1238	}
1239
1240	delete notice;
1241}
1242
1243
1244status_t
1245DataEditor::StartWatching(BMessenger target)
1246{
1247	BAutolock locker(this);
1248
1249	node_ref node;
1250	status_t status = fFile.GetNodeRef(&node);
1251	if (status < B_OK)
1252		return status;
1253
1254	fObservers.AddItem(new BMessenger(target));
1255
1256	return watch_node(&node, B_WATCH_STAT | B_WATCH_ATTR, target);
1257}
1258
1259
1260status_t
1261DataEditor::StartWatching(BHandler *handler, BLooper *looper)
1262{
1263	return StartWatching(BMessenger(handler, looper));
1264}
1265
1266
1267void
1268DataEditor::StopWatching(BMessenger target)
1269{
1270	BAutolock locker(this);
1271
1272	for (int32 i = fObservers.CountItems(); i-- > 0;) {
1273		BMessenger *messenger = fObservers.ItemAt(i);
1274		if (*messenger == target) {
1275			fObservers.RemoveItemAt(i);
1276			delete messenger;
1277			break;
1278		}
1279	}
1280
1281	stop_watching(target);
1282}
1283
1284
1285void
1286DataEditor::StopWatching(BHandler *handler, BLooper *looper)
1287{
1288	StopWatching(BMessenger(handler, looper));
1289}
1290
1291