commands/fed/src/filebuffer.cpp
changeset 0 7f656887cf89
child 81 72ffa331d78d
equal deleted inserted replaced
-1:000000000000 0:7f656887cf89
       
     1 // filebuffer.cpp
       
     2 // 
       
     3 // Copyright (c) 2009 - 2010 Accenture. All rights reserved.
       
     4 // This component and the accompanying materials are made available
       
     5 // under the terms of the "Eclipse Public License v1.0"
       
     6 // which accompanies this distribution, and is available
       
     7 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 // 
       
     9 // Initial Contributors:
       
    10 // Accenture - Initial contribution
       
    11 //
       
    12 
       
    13 #include <e32base.h>
       
    14 #include <e32math.h>
       
    15 #include <utf.h>
       
    16 #include <charconv.h>
       
    17 
       
    18 #include "filebuffer.h"
       
    19 
       
    20 const TInt KMinBlockSize = 2048; // Not least because an 80x24 console is about 2k
       
    21 const TInt KMaxBlockSize = 28*1024; // 28 is good compromise, at least up to about 50MB files
       
    22 const TInt KTargetNumBlocks = 100; // Grow block size linearly to try and keep the number of blocks at around 100
       
    23 
       
    24 /////DEBUG
       
    25 //const TInt KMaxBlockSize = 64*1024-1;
       
    26 //const TInt KTargetNumBlocks = 2;
       
    27 //////END DEBUG
       
    28 
       
    29 __ASSERT_COMPILE(KMaxBlockSize <= (TInt)KMaxTUint16);
       
    30 
       
    31 const TInt KUnicodeLineBreak = 0x2028;
       
    32 //const TInt KUnicodeParagraphBreak = 0x2029; // We treat this the same as a unicode line break, oh well.
       
    33 const TUint KFilePermissions = EFileShareReadersOnly | EFileRead;
       
    34 void ByteSwitch(TUint16* data, TInt aLen);
       
    35 
       
    36 CFileBuffer* CFileBuffer::NewL(RFs& aFs, CCnvCharacterSetConverter* aCharconv, const TDesC& aName, TBool aAllowNonexistantName)
       
    37 	{
       
    38 	CFileBuffer* fb = new (ELeave) CFileBuffer(aFs, aCharconv);
       
    39 	fb->PushL();
       
    40 	if (aName.Length())
       
    41 		{
       
    42 		fb->ConstructL(aName, aAllowNonexistantName);
       
    43 		}
       
    44 	else
       
    45 		{
       
    46 		fb->ConstructL();
       
    47 		}
       
    48 	CleanupStack::Pop(fb);
       
    49 	return fb;
       
    50 	}
       
    51 
       
    52 CFileBuffer::~CFileBuffer()
       
    53 	{
       
    54 	iFile.Close();
       
    55 
       
    56 	CFileBlock* block = iFirstBlock;
       
    57 	while (block)
       
    58 		{
       
    59 		CFileBlock* next = block->Next();
       
    60 		delete block;
       
    61 		block = next;
       
    62 		}
       
    63 	iNarrowBuf.Close();
       
    64 	}
       
    65 
       
    66 CFileBuffer::CFileBuffer(RFs& aFs, CCnvCharacterSetConverter* aCharconv)
       
    67 	: iDelimType(EDelimNotYetKnown), iEncoding(EEncodingUnknown), iFs(aFs), iCharconv(aCharconv)
       
    68 	{
       
    69 	CActiveScheduler::Add(this);
       
    70 	}
       
    71 
       
    72 void CFileBuffer::ConstructL(const TDesC& aName, TBool aAllowNonexistantName)
       
    73 	{
       
    74 	TInt err = iFile.Open(iFs, aName, KFilePermissions);
       
    75 	if ((err == KErrNotFound || err == KErrPathNotFound) && aAllowNonexistantName)
       
    76 		{
       
    77 		// Then treat it the same as an new untitled file (except that it has a title)
       
    78 		ConstructL();
       
    79 		iFinalName = aName;
       
    80 		return;
       
    81 		}
       
    82 
       
    83 	User::LeaveIfError(err); // view will take care of displaying appropriate message
       
    84 	CommonConstructL();
       
    85 	User::LeaveIfError(iFile.FullName(iFinalName));
       
    86 	TInt size = 0;
       
    87 	User::LeaveIfError(iFile.Size(size));
       
    88 	iCacheBufSize = CalculateCacheBufSize(size);
       
    89 	User::LeaveIfError(iCacheBufSize);
       
    90 	iNarrowBuf.CreateL(iCacheBufSize);
       
    91 	// And set up our initial file blocks, all of which start out with size iCacheBufSize (with the exception of the last one)
       
    92 	CFileBlock* endBlock = NULL;
       
    93 	TInt blockStart = 0;
       
    94 	while (blockStart < size)
       
    95 		{
       
    96 		TInt blockSize = Min(iCacheBufSize, size - blockStart);
       
    97 		CFileBlock* block = new(ELeave) CFileBlock(blockStart, blockSize);
       
    98 		if (endBlock)
       
    99 			{
       
   100 			block->InsertAfterBlock(endBlock);
       
   101 			}
       
   102 		else
       
   103 			{
       
   104 			iFirstBlock = block;
       
   105 			}
       
   106 		endBlock = block;
       
   107 		blockStart += blockSize;
       
   108 		}
       
   109 	if (!iFirstBlock)
       
   110 		{
       
   111 		// File must be empty, in which case we need to do the same as in ConstructL() and create a placeholder empty block
       
   112 		iFirstBlock = new(ELeave) CFileBlock(0, 0);
       
   113 		// Do I need to call this?
       
   114 		User::LeaveIfError(iFirstBlock->BlockDidLoad(iEncoding, iDelimType, KNullDesC8, iCharconv));
       
   115 		}
       
   116 	}
       
   117 
       
   118 void CFileBuffer::ConstructL() // Overload for no file - ie use new, untitled file
       
   119 	{
       
   120 	CommonConstructL();
       
   121 	iCacheBufSize = KMinBlockSize;
       
   122 	iNarrowBuf.CreateL(iCacheBufSize);
       
   123 	iFirstBlock = new(ELeave) CFileBlock(0, 0);
       
   124 	User::LeaveIfError(iFirstBlock->BlockDidLoad(iEncoding, iDelimType, KNullDesC8, iCharconv));
       
   125 	}
       
   126 
       
   127 void CFileBuffer::CommonConstructL()
       
   128 	{
       
   129 	// Used to be something here, not any more
       
   130 	}
       
   131 
       
   132 TInt CFileBuffer::CalculateCacheBufSize(TInt aFileSize)
       
   133 	{
       
   134 	TInt blockSize = aFileSize / KTargetNumBlocks;
       
   135 	blockSize = blockSize & 0xFFFFFF0; // Round down to nearest 16-byte boundary
       
   136 	if (blockSize < KMinBlockSize)
       
   137 		{
       
   138 		return KMinBlockSize;
       
   139 		}
       
   140 	else if (blockSize > KMaxBlockSize)
       
   141 		{
       
   142 		return KMaxBlockSize;
       
   143 		}
       
   144 	else
       
   145 		{
       
   146 		return blockSize;
       
   147 		}
       
   148 	}
       
   149 
       
   150 /*
       
   151  This function is responsible for unloading unnecessary CFileBlocks [and calling MSharedCacheClient::InvalidateBuffer
       
   152  on any client accessing data from buffers being deleted]. The function decides who is using which blocks based on
       
   153  TClientRequest::iRequestedBlock to see what the most-recently requested block was.
       
   154  */
       
   155 //For now the function is kept as simple as possible. For each client it just keeps three read-only buffers: the current buffer,
       
   156 //the previous buffer, and the next buffer. It also keeps all writable buffers (containing data written by the user) because
       
   157 //they have to be written to the disk before deleting.
       
   158 void CFileBuffer::TidyCache()
       
   159 	{
       
   160 	for (CFileBlock* block = iFirstBlock; block != NULL; block = block->Next())
       
   161 		{
       
   162 		UnloadBlockIfPossible(block);
       
   163 		}
       
   164 	}
       
   165 
       
   166 void CFileBuffer::UnloadBlockIfPossible(CFileBlock* aBlock)
       
   167 	{
       
   168 	if (aBlock->IsDirty() || !aBlock->IsLoaded() || aBlock == iCurrentBlock) return;
       
   169 	// Check if any client is using it
       
   170 	TBool inUse = EFalse;
       
   171 	for (TInt i = 0; i < iClientRequests.Count(); i++)
       
   172 		{
       
   173 		CFileBlock* clientBlock = iClientRequests[i].iLastRequestedBlock;
       
   174 		if (!clientBlock) continue;
       
   175 		else if (clientBlock == aBlock || clientBlock->Prev() == aBlock || clientBlock->Next() == aBlock)
       
   176 			{
       
   177 			inUse = ETrue;
       
   178 			break;
       
   179 			}
       
   180 		}
       
   181 	if (!inUse)
       
   182 		{
       
   183 		aBlock->Unload();
       
   184 		}
       
   185 	}
       
   186 
       
   187 CFileBlock* CFileBuffer::FindBlockForDocumentPosition(TInt aDocPosition, TInt* aOffset, TInt* aLineCountToDocPos)
       
   188 	{
       
   189 	ASSERT(aDocPosition >= 0);
       
   190 
       
   191 	TInt blockStart = 0;
       
   192 	TInt lineCount = 0;
       
   193 	CFileBlock* block = iFirstBlock;
       
   194 	while (block)
       
   195 		{
       
   196 		if (!block->HasBeenLoaded())
       
   197 			{
       
   198 			// Need the block to have been loaded to know the char count (and the line count)
       
   199 			TInt err = LoadBlock(block);
       
   200 			if (err) return NULL;
       
   201 			}
       
   202 
       
   203 		TInt charCount = block->CharacterCount();
       
   204 
       
   205 		if (aDocPosition < blockStart + charCount || block->Next() == NULL && aDocPosition == blockStart + charCount)
       
   206 			{
       
   207 			// Found it (note that the last block is returned if the document position DocumentSize() is requested)
       
   208 			TInt offset = aDocPosition - blockStart;
       
   209 			if (aOffset) *aOffset = offset;
       
   210 			if (aLineCountToDocPos)
       
   211 				{
       
   212 				TInt lineCountToOffset = 0;
       
   213 				if (offset != 0)
       
   214 					{
       
   215 					// We have to load the block if the caller requested line count info and offset is non-zero
       
   216 					TInt err = LoadBlock(block);
       
   217 					if (err) return NULL;
       
   218 					lineCountToOffset = block->CountNewLinesUpToOffset(offset);
       
   219 					}
       
   220 				*aLineCountToDocPos = lineCount + lineCountToOffset;
       
   221 				}
       
   222 			return block;
       
   223 			}
       
   224 		else
       
   225 			{
       
   226 			UnloadBlockIfPossible(block);
       
   227 			}
       
   228 		blockStart += charCount;
       
   229 		lineCount += block->NewlineCount();
       
   230 		block = block->Next();
       
   231 		}
       
   232 	//ASSERT(EFalse); // If we get here then we don't have a block for this offset - maybe offset was >= file length?
       
   233 	return NULL;
       
   234 	}
       
   235 
       
   236 TInt CFileBuffer::LoadBlock(CFileBlock* aBlock)
       
   237 	{
       
   238 	TInt err = KErrNone;
       
   239 	if (aBlock->IsLoaded())
       
   240 		{
       
   241 		return KErrNone;
       
   242 		}
       
   243 
       
   244 	if (!iFile.SubSessionHandle()) return KErrNotReady;
       
   245 
       
   246 	// Then load it from disk
       
   247 	iNarrowBuf.Zero();
       
   248 	TInt blockSize = aBlock->BlockSize();
       
   249 	if (blockSize > iNarrowBuf.MaxSize())
       
   250 		{
       
   251 		err = iNarrowBuf.ReAlloc(blockSize);
       
   252 		}
       
   253 
       
   254 	if (!err)
       
   255 		{
       
   256 		err = iFile.Read(aBlock->FilePosition(), iNarrowBuf, blockSize);
       
   257 		if (err)
       
   258 			{
       
   259 			aBlock->Unload();
       
   260 			}
       
   261 		}
       
   262 
       
   263 	if (err == KErrNone)
       
   264 		{
       
   265 		TEncodingType blockEncoding = iEncoding;
       
   266 		TDelimiterType blockDelim = iDelimType;
       
   267 		err = aBlock->BlockDidLoad(blockEncoding, blockDelim, iNarrowBuf, iCharconv);
       
   268 		if (err)
       
   269 			{
       
   270 			// Don't do anything with encodings
       
   271 			return err;
       
   272 			}
       
   273 
       
   274 		if (iEncoding == EEncodingUtf8 && blockEncoding == EEncodingNarrow)
       
   275 			{
       
   276 			// Conceivably previous blocks could have been narrow that was also UTF-8 conformant but this block makes it clear the file definitely isn't UTF-8 - therefore allow the change of encoding
       
   277 			iEncoding = EEncodingNarrow;
       
   278 			}
       
   279 		else if (iEncoding == EEncodingUnknown)
       
   280 			{
       
   281 			iEncoding = blockEncoding;
       
   282 			}
       
   283 		else if (blockEncoding != iEncoding)
       
   284 			{
       
   285 			__DEBUGGER();
       
   286 			err = KErrCorrupt;
       
   287 			}
       
   288 
       
   289 		if (iDelimType == EDelimNotYetKnown && blockDelim)
       
   290 			{
       
   291 			iDelimType = blockDelim;
       
   292 			}
       
   293 		// Inconsistant line ending info is ignored here, as most of it has already been thrown away by CountNewLines
       
   294 		}
       
   295 
       
   296 	return err;
       
   297 	}
       
   298 
       
   299 TInt CFileBuffer::GetData(MSharedCacheClient& aClient, TInt aDocumentPosition)
       
   300 	{
       
   301 	TInt idx = FindClientRequest(aClient);
       
   302 	if (idx < 0)
       
   303 		{
       
   304 		return idx;
       
   305 		}
       
   306 
       
   307 	TClientRequest& client = iClientRequests[idx];
       
   308 	ASSERT(aDocumentPosition >= 0);
       
   309 
       
   310 	TInt offset;
       
   311 	TInt lineCount;
       
   312 	CFileBlock* block = FindBlockForDocumentPosition(aDocumentPosition, &offset, &lineCount);
       
   313 	client.iRange = TRange(aDocumentPosition, 0); // Length will be filled in once block is loaded (as could change due to TransferDataFromPreviousBlock)
       
   314 	client.iRangeStartLineNumber = lineCount;
       
   315 
       
   316 	TInt err = LoadBlock(block);
       
   317 	if (err) return err;
       
   318 
       
   319 	const TDesC& text = block->Text();
       
   320 	client.iDes.Set(text.Mid(offset));
       
   321 	client.iRange.iLength = text.Length() - offset;
       
   322 	client.iLastRequestedBlock = block;
       
   323 	TidyCache();
       
   324 	return KErrNone;
       
   325 	}
       
   326 
       
   327 TInt CFileBuffer::SeekFromOffset(MSharedCacheClient& aClient, TInt aDocumentPosition, TInt aNumLinesFromOffset, TInt aLineLength)
       
   328 	{
       
   329 	TInt idx = FindClientRequest(aClient);
       
   330 	if (idx < 0)
       
   331 		{
       
   332 		return idx;
       
   333 		}
       
   334 
       
   335 	TClientRequest& client = iClientRequests[idx];
       
   336 	//ASSERT(client.iClientStatus == NULL); // Client can't make more than 1 request at once
       
   337 	ASSERT(aDocumentPosition >= 0);
       
   338 	//ASSERT(aDocumentPosition < DocumentSize());
       
   339 	ASSERT(aNumLinesFromOffset != 0);
       
   340 
       
   341 	TInt offset;
       
   342 	CFileBlock* block = FindBlockForDocumentPosition(aDocumentPosition, &offset);
       
   343 
       
   344 	TInt& lineDelta = aNumLinesFromOffset;
       
   345 	TInt& maxLineLength = aLineLength;
       
   346 
       
   347 	TInt err = KErrNone;
       
   348 	while (ETrue)
       
   349 		{
       
   350 		ASSERT(block);
       
   351 		err = LoadBlock(block);
       
   352 		if (err) return err;
       
   353 
       
   354 		TInt lines = lineDelta;
       
   355 		TInt bufPos;
       
   356 		if (lines > 0)
       
   357 			{
       
   358 			bufPos = CFileBlock::CountNewLines(block->Text().Mid(offset), lines, maxLineLength);
       
   359 			if (bufPos == KErrNotFound)
       
   360 				{
       
   361 				CFileBlock* nextBlock = block->Next();
       
   362 				if (!nextBlock)
       
   363 					{
       
   364 					// If no more blocks, change the request to be "find the first newline searching backwards from the end of the last block" (phrasing it like this means it'll work even if there aren't any newlines in this block at all)
       
   365 					lineDelta = -1;
       
   366 					offset = block->CharacterCount();
       
   367 					continue;
       
   368 					}
       
   369 				offset = 0;
       
   370 				lineDelta -= lines;
       
   371 				UnloadBlockIfPossible(block);
       
   372 				block = nextBlock;
       
   373 				continue;
       
   374 				}
       
   375 			else
       
   376 				{
       
   377 				bufPos += offset; // Since the bufPos returned was relative to the Mid we did
       
   378 				}
       
   379 			}
       
   380 		else
       
   381 			{
       
   382 			lineDelta--; // Client will want the line *before* the first new line we encounter (the first new line we encounter will be for the line they're actually on)
       
   383 			lines = lineDelta;
       
   384 			bufPos = CFileBlock::CountNewLines(block->Text().Left(offset), lines, maxLineLength);
       
   385 			if (bufPos == KErrNotFound)
       
   386 				{
       
   387 				CFileBlock* prevBlock = block->Prev();
       
   388 				if (!prevBlock)
       
   389 					{
       
   390 					// If no more blocks, then the start of the first block is closest
       
   391 					offset = 0;
       
   392 					break;
       
   393 					}
       
   394 				UnloadBlockIfPossible(block);
       
   395 				block = prevBlock;
       
   396 				offset = block->CharacterCount();
       
   397 				lineDelta += lines; // since lineDelta is negative
       
   398 				continue;
       
   399 				}
       
   400 			}
       
   401 		ASSERT(bufPos != KErrNotFound); // Must be, to have got here
       
   402 		offset = bufPos;
       
   403 		break;
       
   404 		}
       
   405 
       
   406 	client.iDes.Set(block->Text().Mid(offset));
       
   407 	TInt docPos;
       
   408 	TInt lineCount;
       
   409 	block->CalculateBlockPositions(docPos, lineCount);
       
   410 	client.iRange = TRange(docPos + offset, block->CharacterCount() - offset);
       
   411 	client.iLastRequestedBlock = block;
       
   412 	ASSERT(client.iRange.iLength == client.iDes.Length());
       
   413 	client.iRangeStartLineNumber = lineCount + block->CountNewLinesUpToOffset(offset);
       
   414 	TidyCache();
       
   415 	return KErrNone;
       
   416 	}
       
   417 
       
   418 TBool CFileBuffer::DocumentPositionIsEof(TInt aDocumentPosition) const
       
   419 	{
       
   420 	// Like FindBlockForDocumentPosition but doesn't try and load blocks
       
   421 	TInt blockStart = 0;
       
   422 	for (CFileBlock* block = iFirstBlock; block != NULL; block = block->Next())
       
   423 		{
       
   424 		if (!block->HasBeenLoaded())
       
   425 			{
       
   426 			return EFalse;
       
   427 			}
       
   428 		TInt charCount = block->CharacterCount();
       
   429 		TInt endOfBlock = blockStart + charCount;
       
   430 		if (aDocumentPosition < endOfBlock) return EFalse; // We have more characters than this doc pos so it can't be the end
       
   431 		else if (charCount == 0 && block->Next() != NULL)
       
   432 			{
       
   433 			continue; // we're an empty block followed by another block, so defer the decision to the next block
       
   434 			}
       
   435 		else if (aDocumentPosition == endOfBlock)
       
   436 			{
       
   437 			// Then this position is the eof if there aren't any more blocks
       
   438 			return (block->Next() == NULL);
       
   439 			}
       
   440 		blockStart += charCount;
       
   441 		}
       
   442 	ASSERT(EFalse);
       
   443 	return EFalse;
       
   444 	}
       
   445 
       
   446 void CFileBuffer::InsertTextL(TInt aDocumentPosition, const TDesC& aText)
       
   447 	{
       
   448 	TInt offset;
       
   449 	CFileBlock* block = FindBlockForDocumentPosition(aDocumentPosition, &offset);
       
   450 
       
   451 	TInt err = LoadBlock(block);
       
   452 	User::LeaveIfError(err);
       
   453 
       
   454 	block->InsertTextL(offset, aText);
       
   455 	iUnsaved = ETrue;
       
   456 	}
       
   457 
       
   458 void CFileBuffer::DeleteTextL(TRange aRange)
       
   459 	{
       
   460 	while (aRange.iLength > 0)
       
   461 		{
       
   462 		TInt offset;
       
   463 		CFileBlock* block = FindBlockForDocumentPosition(aRange.iLocation, &offset);
       
   464 		if (!block) return; // We will tolerate it if the range is beyond the document position
       
   465 		TInt err = LoadBlock(block);
       
   466 		User::LeaveIfError(err);
       
   467 
       
   468 		TInt blockCharCount = block->CharacterCount();
       
   469 		TInt blockLen = Min(blockCharCount - offset, aRange.iLength);
       
   470 		if (blockLen) // Again, tolerating if the range is completely outside the document length
       
   471 			{
       
   472 			block->DeleteText(offset, blockLen);
       
   473 			// The range location stays the same, since everything will have shuffled up
       
   474 			aRange.iLength -= blockLen;
       
   475 			iUnsaved = ETrue;
       
   476 			}
       
   477 		else
       
   478 			{
       
   479 			break;
       
   480 			}
       
   481 		}	
       
   482 	}
       
   483 
       
   484 void CFileBuffer::DeleteTempFile(TAny* aSelf)
       
   485 	{
       
   486 	CFileBuffer* self = static_cast<CFileBuffer*>(aSelf);
       
   487 	if (self->iTempName->Length())
       
   488 		{
       
   489 		self->iFs.Delete(*self->iTempName);
       
   490 		}
       
   491 	delete self->iTempName;
       
   492 	self->iTempName = NULL;
       
   493 	}
       
   494 
       
   495 void CFileBuffer::SetFinalNameToTemporary(TAny* aSelf)
       
   496 	{
       
   497 	CFileBuffer* self = static_cast<CFileBuffer*>(aSelf);
       
   498 	self->iFinalName = *self->iTempName;
       
   499 	}
       
   500 
       
   501 void CFileBuffer::SaveL(const TDesC& aName, TBool aReplace)
       
   502 	{
       
   503 	// Try opening aName as given - if it suceeds use it, if it fails with alreadyExists use a temp file and rename at the end
       
   504 	RBuf origRenamed;
       
   505 	CleanupClosePushL(origRenamed);
       
   506 	iTempName = new(ELeave) TFileName;
       
   507 	origRenamed.CreateL(KMaxFileName);
       
   508 
       
   509 	RFile outFile;
       
   510 	CleanupStack::PushL(TCleanupItem(&DeleteTempFile, this));
       
   511 	CleanupClosePushL(outFile);
       
   512 	TBool needToRename = EFalse;
       
   513 
       
   514 	TInt err = iFs.MkDirAll(aName); // Avoid path not found errors in the case where the file has not yet been saved
       
   515 	if (err == KErrAlreadyExists) err = KErrNone;
       
   516 	User::LeaveIfError(err);
       
   517 
       
   518 	const TUint fileMode = EFileWrite | EFileShareExclusive | EFileStream;
       
   519 	err = outFile.Create(iFs, aName, fileMode);
       
   520 	if ((err == KErrAlreadyExists || err == KErrInUse) && aReplace)
       
   521 		{
       
   522 		needToRename = ETrue;
       
   523 		TParsePtrC parse(aName);
       
   524 		TPtrC path = parse.DriveAndPath();
       
   525 		err = outFile.Temp(iFs, path, *iTempName, fileMode);
       
   526 		}
       
   527 	User::LeaveIfError(err);
       
   528 
       
   529 	if (iDelimType == EDelimNotYetKnown) iDelimType = EDelimCRLF; // It's conceivable that we still might not have a newline type - default to Symbian default, CRLF.
       
   530 	if (iEncoding == EEncodingUnknown) iEncoding = EEncodingUtf8;
       
   531 
       
   532 	CFileBlock* block = iFirstBlock;
       
   533 	while (block)
       
   534 		{
       
   535 		err = LoadBlock(block);
       
   536 		User::LeaveIfError(err);
       
   537 		err = block->ConvertToEightBit(iEncoding, iDelimType, iNarrowBuf);
       
   538 		User::LeaveIfError(err); // Conversion could fail, or oom trying to realloc iNarrowBuf
       
   539 		err = outFile.Write(iNarrowBuf);
       
   540 		User::LeaveIfError(err); // File writing failed
       
   541 		UnloadBlockIfPossible(block);
       
   542 		block = block->Next();
       
   543 		}
       
   544 	CleanupStack::PopAndDestroy(&outFile); // closes outFile
       
   545 	TBool fileWasOpen = IsOpen();
       
   546 	if (fileWasOpen)
       
   547 		{
       
   548 		// If the file wasn't open, there's no need for us to preserve the temporary (because the whole file must be by definition in memory)
       
   549 		CleanupStack::Pop(); // Remove DeleteTempFile - the temp file is important once we start tinkering with the original
       
   550 		CleanupStack::PushL(iTempName); // But the descriptor isn't
       
   551 		iUnsaved = EFalse; // We've succeeded in saving it *somewhere*
       
   552 		}
       
   553 	// From here on in, we bail on any failure that leaves the original file moved and/or closed. We do not try to recover from any of these, instead just bail and let the user tidy up the temp files
       
   554 
       
   555 	if (needToRename)
       
   556 		{
       
   557 		CleanupStack::PushL(TCleanupItem(&SetFinalNameToTemporary, this)); // If we bail out and leave, this indicates where we managed to save the file to
       
   558 		iFile.Close(); // Is necessary if we're trying to overwrite the original
       
   559 		TBool done = EFalse;
       
   560 		TInt nonce = 1;
       
   561 
       
   562 		// First, rename the original out of the way
       
   563 		while (!done)
       
   564 			{
       
   565 			_LIT(KFmt, "%S.orig%d");
       
   566 			origRenamed.Format(KFmt, &aName, nonce);
       
   567 			nonce++;
       
   568 			err = iFs.Rename(aName, origRenamed);
       
   569 			if (err != KErrAlreadyExists) done = ETrue;
       
   570 			}
       
   571 		User::LeaveIfError(err);
       
   572 
       
   573 		// If we reach here, rename has succeeded - now rename temp file to orig
       
   574 		err = iFs.Rename(*iTempName, aName);
       
   575 		User::LeaveIfError(err);
       
   576 
       
   577 		// Now just need to bin the original file
       
   578 		iFs.Delete(origRenamed); // Don't care if this succeeds or not
       
   579 
       
   580 		// Finally, reopen the file
       
   581 		err = iFile.Open(iFs, aName, KFilePermissions);
       
   582 		User::LeaveIfError(err);
       
   583 		// If this all succeeded, we're sorted
       
   584 		CleanupStack::Pop(); // SetFinalNameToTemporary
       
   585 		}
       
   586 	else
       
   587 		{
       
   588 		// If we didn't use a temporary, we just have to reopen the file
       
   589 		iFile.Close(); // Don't imagine it could be open here...
       
   590 		err = iFile.Open(iFs, aName, KFilePermissions);
       
   591 		User::LeaveIfError(err);
       
   592 		}
       
   593 
       
   594 	CleanupStack::PopAndDestroy(2, &origRenamed); // iTempName OR DeleteTempFile, origNamed
       
   595 	iTempName = NULL;
       
   596 	iUnsaved = EFalse;
       
   597 	iFile.FullName(iFinalName); // We don't just do "iFinalName = aName", in case the caller managed to sneak a relative path past the file system
       
   598 	// Once everything has completed ok, go through and set every block as not dirty. This also updates block file positions
       
   599 	iFirstBlock->FileHasBeenSaved();
       
   600 	}
       
   601 
       
   602 TBool CFileBuffer::Modified() const
       
   603 	{
       
   604 	return iUnsaved;
       
   605 	}
       
   606 
       
   607 TDelimiterType CFileBuffer::DelimiterType() const
       
   608 	{
       
   609 	return iDelimType;
       
   610 	}
       
   611 
       
   612 TEncodingType CFileBuffer::Encoding() const
       
   613 	{
       
   614 	return iEncoding;
       
   615 	}
       
   616 
       
   617 const TDesC& CFileBuffer::Title() const
       
   618 	{
       
   619 	return iFinalName;
       
   620 	}
       
   621 
       
   622 TBool CFileBuffer::Editable() const
       
   623 	{
       
   624 	return ETrue;
       
   625 	}
       
   626 
       
   627 TBool CFileBuffer::IsOpen() const
       
   628 	{
       
   629 	return iFile.SubSessionHandle() != 0;
       
   630 	}
       
   631 
       
   632 TInt CFileBuffer::Find(TInt aStartingPosition, const TDesC& aSearchString, TBool aBackwards)
       
   633 	{
       
   634 	TInt res = 0;
       
   635 	TRAPD(err, res = DoFindL(aStartingPosition, aSearchString, aBackwards));
       
   636 	if (err) res = err;
       
   637 	return res;
       
   638 	}
       
   639 
       
   640 #define EnsureCapacity(buf, len) if (buf.MaxLength() < len) { TInt err = buf.ReAlloc(Max(len, buf.Length()*2)); if (err) return err; }
       
   641 #define EnsureCapacityL(buf, len) if (buf.MaxLength() < len) { buf.ReAllocL(Max(len, buf.Length()*2)); }
       
   642 #define EnsureHBufCapacity(hbuf, len) if (hbuf->Des().MaxLength() < len) { HBufC* newBuf = hbuf->ReAllocL(Max(len, hbuf->Length()*2)); if (newBuf) { hbuf = newBuf; } else { return KErrNoMemory; } }
       
   643 #define EnsureHBufCapacityL(hbuf, len) if (hbuf->Des().MaxLength() < len) { hbuf = hbuf->ReAllocL(Max(len, hbuf->Length()*2)); }
       
   644 
       
   645 TInt CFileBuffer::DoFindL(TInt aStartingPosition, const TDesC& aSearchString, TBool aBackwards)
       
   646 	{
       
   647 	ASSERT(!aBackwards); // TODO not supported yet
       
   648 	// We use a separate buffer because searching directly through the block's text wouldn't handle the case
       
   649 	// where the search string is split over 2 blocks. We use a rolling buffer containing the current buffer and
       
   650 	// the last one, and seach in that, so 
       
   651 	RBuf searchBuf;
       
   652 	CleanupClosePushL(searchBuf);
       
   653 	const TInt searchLen = aSearchString.Length();
       
   654 	searchBuf.CreateL(iCacheBufSize + searchLen); // A match could only span buffers by a max of searchLen
       
   655 	
       
   656 	TInt offset;
       
   657 	CFileBlock* block = FindBlockForDocumentPosition(aStartingPosition, &offset);
       
   658 	TInt bufStartPos = aStartingPosition;
       
   659 	while (block)
       
   660 		{
       
   661 		// Block must be loaded to search through its data
       
   662 		TInt err = LoadBlock(block);
       
   663 		User::LeaveIfError(err);
       
   664 		// Get block data into the search buf, leaving up to searchLen's worth of the previous block, so we can handle the search term spanning 2 buffers
       
   665 		TPtrC blockData = block->Text().Mid(offset);
       
   666 		TInt newBlockLen = blockData.Length();
       
   667 
       
   668 		EnsureCapacityL(searchBuf, newBlockLen + searchLen);
       
   669 		TPtrC toKeep = searchBuf.Right(searchLen);
       
   670 		TInt amountToDeleteFromBuf = searchBuf.Length() - toKeep.Length();
       
   671 		searchBuf.Delete(0, amountToDeleteFromBuf);
       
   672 		bufStartPos += amountToDeleteFromBuf;
       
   673 		searchBuf.Append(blockData);
       
   674 
       
   675 		TInt found = searchBuf.Find(aSearchString);
       
   676 		if (found != KErrNotFound)
       
   677 			{
       
   678 			TInt result = bufStartPos + found;
       
   679 			CleanupStack::PopAndDestroy(&searchBuf);
       
   680 			return result;
       
   681 			}
       
   682 		
       
   683 		UnloadBlockIfPossible(block);
       
   684 		block = block->Next();
       
   685 		offset = 0; // Non-zero offset only relevant for the first block searced, the one returned by FindBlockForDocumentPosition
       
   686 		}
       
   687 	User::Leave(KErrNotFound);
       
   688 	return 0;
       
   689 	}
       
   690 
       
   691 //////
       
   692 
       
   693 CFileBlock::CFileBlock(TInt aFilePosition, TInt aBlockSize)
       
   694 	: iFilePosition(aFilePosition), iBlockSize(aBlockSize)
       
   695 	{
       
   696 	if (aBlockSize == 0)
       
   697 		{
       
   698 		ASSERT(aFilePosition == 0); // A zero blocksize is only valid if we're the first (and only) block of an empty, untitled, unsaved file
       
   699 		iFlags.Set(EHasBeenLoaded); // Ie, it doesn't need loading
       
   700 		}
       
   701 	}
       
   702 
       
   703 TInt CFileBlock::BlockSize() const
       
   704 	{
       
   705 	ASSERT(!iFlags.IsSet(EDirty)); // We can't know the blocksize for a dirty block (it's not known until the block is saved, ie converted back to 8-bit data)
       
   706 	return iBlockSize;
       
   707 	}
       
   708 
       
   709 TBool CFileBlock::IsLoaded() const
       
   710 	{
       
   711 	return iData != NULL;
       
   712 	}
       
   713 
       
   714 TBool CFileBlock::HasBeenLoaded() const
       
   715 	{
       
   716 	return iFlags.IsSet(EHasBeenLoaded);
       
   717 	}
       
   718 
       
   719 void CFileBlock::CalculateBlockPositions(TInt& aCharacterPos, TInt& aLineCount) const
       
   720 	{
       
   721 	// Have to figure out our document position from the previous blocks. We don't cache this information because that would make inserting text into the file much harder
       
   722 	aCharacterPos = 0;
       
   723 	aLineCount = 0;
       
   724 	
       
   725 	CFileBlock* prev = iPrev;
       
   726 	while (prev)
       
   727 		{
       
   728 		aCharacterPos += prev->CharacterCount();
       
   729 		aLineCount += prev->NewlineCount();
       
   730 		prev = prev->iPrev;
       
   731 		}
       
   732 	}
       
   733 
       
   734 TInt CFileBlock::FilePosition() const
       
   735 	{
       
   736 	return iFilePosition;
       
   737 	}
       
   738 
       
   739 CFileBlock::~CFileBlock()
       
   740 	{
       
   741 	delete iData;
       
   742 	}
       
   743 
       
   744 TBool CFileBlock::IsDirty() const
       
   745 	{
       
   746 	return iFlags.IsSet(EDirty);
       
   747 	}
       
   748 
       
   749 void CFileBlock::Unload()
       
   750 	{
       
   751 	ASSERT(!IsDirty());
       
   752 	ASSERT(iData->Length() <= (TInt)KMaxTUint16); // Otherwise we can't safely update our character count
       
   753 	iNumCharacters = iData->Length();
       
   754 	delete iData;
       
   755 	iData = NULL;
       
   756 	}
       
   757 
       
   758 void CFileBlock::InsertAfterBlock(CFileBlock* aBlock)
       
   759 	{
       
   760 	ASSERT(iPrev == NULL);
       
   761 	ASSERT(iNext == NULL);
       
   762 	CFileBlock* next = aBlock->iNext;
       
   763 	aBlock->iNext = this;
       
   764 	iPrev = aBlock;
       
   765 	iNext = next;
       
   766 	if (next) next->iPrev = this;
       
   767 	}
       
   768 
       
   769 const TDesC16& CFileBlock::Text() const
       
   770 	{
       
   771 	ASSERT(IsLoaded()); // Otherwise shouldn't be calling this function
       
   772 	return *iData;
       
   773 	}
       
   774 
       
   775 CFileBlock* CFileBlock::Next() const
       
   776 	{
       
   777 	return iNext;
       
   778 	}
       
   779 
       
   780 CFileBlock* CFileBlock::Prev() const
       
   781 	{
       
   782 	return iPrev;
       
   783 	}
       
   784 
       
   785 TInt CFileBlock::BlockDidLoad(TEncodingType& aEncoding, TDelimiterType& aDelimType, const TDesC8& aData, CCnvCharacterSetConverter* aCharconvForUtf8Conversion)
       
   786 	{
       
   787 	ASSERT(!IsLoaded());
       
   788 	iData = HBufC::New(HasBeenLoaded() ? iNumCharacters : iBlockSize);
       
   789 	if (!iData) return KErrNoMemory;
       
   790 	
       
   791 	// For the duration of this function, iData is now defined to be this->iData->Des(). This saves me having to rewrite the rest of the function that is expecting iData to be an RBuf
       
   792 	TPtr iData = this->iData->Des();
       
   793 
       
   794 	iFlags.Set(EHasBeenLoaded);
       
   795 
       
   796 	TEncodingType& blockEncoding = aEncoding;
       
   797 
       
   798 	TBool bom = EFalse;
       
   799 	if (FilePosition() == 0)
       
   800 		{
       
   801 		// First block - check for BOM
       
   802 		TPtrC8 bomData = aData.Left(2);
       
   803 		if (bomData == KLittleEndianBom)
       
   804 			{
       
   805 			blockEncoding = EEncodingUtf16LE;
       
   806 			bom = ETrue;
       
   807 			}
       
   808 		else if (bomData == KBigEndianBom)
       
   809 			{
       
   810 			blockEncoding = EEncodingUtf16BE;
       
   811 			bom = ETrue;
       
   812 			}
       
   813 		else if (aData.Left(3) == KUtf8Bom)
       
   814 			{
       
   815 			blockEncoding = EEncodingUtf8;
       
   816 			bom = ETrue;
       
   817 			}
       
   818 		}
       
   819 	
       
   820 	switch (blockEncoding)
       
   821 		{
       
   822 		case EEncodingUtf16Native:
       
   823 			{
       
   824 			TPtrC16 wdata = TPtrC16((TUint16*)aData.Ptr(), aData.Size()/2);
       
   825 			if (bom) wdata.Set(wdata.Mid(1));
       
   826 			iData.Copy(wdata);
       
   827 			break;
       
   828 			}
       
   829 		case EEncodingUtf16Switched:
       
   830 			{
       
   831 			// There's a way to do this using charconv, but it's more effort to look it up than to byte-switch it myself
       
   832 			TPtrC16 wdata = TPtrC16((TUint16*)aData.Ptr(), aData.Size()/2);
       
   833 			if (bom) wdata.Set(wdata.Mid(1));
       
   834 			iData.Copy(wdata);
       
   835 			ByteSwitch((TUint16*)iData.Ptr(), iData.Length());
       
   836 			break;
       
   837 			}
       
   838 		case EEncodingNarrow:
       
   839 			// No attempt at conversion in this case
       
   840 			iData.Copy(aData);
       
   841 			break;
       
   842 		case EEncodingUtf8:
       
   843 		case EEncodingUnknown:
       
   844 			{
       
   845 			TPtrC8 data(aData);
       
   846 			if (bom) data.Set(data.Mid(3));
       
   847 			//TInt bytesLeft = CnvUtfConverter::ConvertToUnicodeFromUtf8(iData, data); <--- DOESN'T WORK!
       
   848 			TInt state = CCnvCharacterSetConverter::KStateDefault;
       
   849 			TInt numUnconverted = 0, firstUnconverted = -1;
       
   850 			TInt bytesLeft = -1;
       
   851 			if (aCharconvForUtf8Conversion)
       
   852 				{
       
   853 				// Can be null if charconv is busted
       
   854 				bytesLeft = aCharconvForUtf8Conversion->ConvertToUnicode(iData, data, state, numUnconverted, firstUnconverted);
       
   855 				ASSERT(bytesLeft <= 0); // Otherwise we didn't make our buffer big enough
       
   856 				// bytesLeft less than zero means an invalid sequence at the start of the buffer. Since our block size will never be so small there's no risk it's the start of a truncated sequence
       
   857 				}
       
   858 
       
   859 			if (bytesLeft < 0)
       
   860 				{
       
   861 				// It's just not UTF-8
       
   862 				iData.Copy(aData);
       
   863 				blockEncoding = EEncodingNarrow;
       
   864 				}
       
   865 			else if (firstUnconverted >= 0)
       
   866 				{
       
   867 				// Got some bad data
       
   868 				if (iNext && data.Length() - firstUnconverted < 4)
       
   869 					{
       
   870 					// Possibly a UTF-8 sequence spread over a block boundary
       
   871 					blockEncoding = EEncodingUtf8;
       
   872 					TInt err = iNext->TransferDataFromPreviousBlock(this, data.Mid(firstUnconverted));
       
   873 					if (!err)
       
   874 						{
       
   875 						iBlockSize = firstUnconverted;
       
   876 						}
       
   877 					iData.SetLength(iData.Locate(0xFFFD)); // No better way of figuring out where charconv barfed than to scan for the first instance of the substitution character
       
   878 					}
       
   879 				else
       
   880 					{
       
   881 					// It's just not UTF-8
       
   882 					iData.Copy(aData);
       
   883 					blockEncoding = EEncodingNarrow;
       
   884 					}
       
   885 				}
       
   886 			else
       
   887 				{
       
   888 				// it is conformant UTF-8, go with that for now. A future block could downgrade to narrow, if so no harm done (unless we had characters that appeared valid UTF-8 even though they weren't)
       
   889 				blockEncoding = EEncodingUtf8;
       
   890 				}
       
   891 			break;
       
   892 			}
       
   893 		default:
       
   894 			ASSERT(EFalse);
       
   895 		}
       
   896 
       
   897 	iNumCharacters = iData.Length();
       
   898 	if (!iFlags.IsSet(EHaveCalculatedLineEndings))
       
   899 		{
       
   900 		// Need to calculate num line endings
       
   901 		TInt numLineEndings = KMaxTInt;
       
   902 		CountNewLines(iData, numLineEndings, KMaxTInt, &aDelimType);
       
   903 		ASSERT(numLineEndings <= (TInt)KMaxTUint16); // Otherwise I've got some overflow-related badness
       
   904 		iNumLineEndings = numLineEndings;
       
   905 		iFlags.Set(EHaveCalculatedLineEndings);
       
   906 		}
       
   907 
       
   908 	if (iData.Length() && iData[iData.Length()-1] == '\r')
       
   909 		{
       
   910 		// uh oh, possible new line split over a block. To simplify matters we'll ensure this never happens, by moving the newline to the next block
       
   911 		TInt charLen = (blockEncoding == EEncodingUtf16LE || blockEncoding == EEncodingUtf16BE) ? 2 : 1;
       
   912 		if (iNext)
       
   913 			{
       
   914 			TInt err = iNext->TransferDataFromPreviousBlock(this, aData.Right(charLen));
       
   915 			if (err)
       
   916 				{
       
   917 				// The hell with it, we tried... (line counts will not be precise but otherwise shouldn't be the end of the world
       
   918 				}
       
   919 			else
       
   920 				{
       
   921 				iBlockSize -= charLen;
       
   922 				iNumCharacters--;
       
   923 				iNumLineEndings--;
       
   924 				iData.SetLength(iNumCharacters);
       
   925 				}
       
   926 			}
       
   927 		}
       
   928 
       
   929 	return KErrNone;
       
   930 	}
       
   931 
       
   932 TInt CFileBlock::CountNewLines(const TDesC& aDes, TInt& aTarget, TInt aSoftWrapLineLength, TDelimiterType* aDelimType)
       
   933 	{
       
   934 	ASSERT(aTarget != 0);
       
   935 	ASSERT(aSoftWrapLineLength > 0);
       
   936 	TBool countUp = aTarget > 0;
       
   937 	if (!countUp) aTarget = -aTarget;
       
   938 	TInt numFound = 0;
       
   939 	TInt lineLen = 0;
       
   940 	TBool foundDelim = EFalse;
       
   941 	if (aDelimType) *aDelimType = EDelimNotYetKnown;
       
   942 
       
   943 	for (TInt i = 0; i < aDes.Length(); i++)
       
   944 		{
       
   945 		TInt idx = (countUp ? i : aDes.Length()-1 - i);
       
   946 		TUint16 ch = aDes[idx];
       
   947 		TDelimiterType delim = EDelimNotYetKnown;
       
   948 
       
   949 		if (ch == '\r')
       
   950 			{
       
   951 			numFound++;
       
   952 			lineLen = 0;
       
   953 			if (countUp && i+1 < aDes.Length() && aDes[i+1] == '\n')
       
   954 				{
       
   955 				delim = EDelimCRLF;
       
   956 				i++; // Skip over the LF of a CRLF pair
       
   957 				idx++; // Idx needs to point to the END of the line ending
       
   958 				}
       
   959 			else
       
   960 				{
       
   961 				delim = EDelimCR;
       
   962 				}
       
   963 			}
       
   964 		else if (ch == '\n')
       
   965 			{
       
   966 			numFound++;
       
   967 			lineLen = 0;
       
   968 			if (!countUp && idx > 0 && aDes[idx-1] == '\r')
       
   969 				{
       
   970 				delim = EDelimCRLF;
       
   971 				i++; // Skip over the CR of a CRLF pair
       
   972 				// Don't modify idx, it is already pointing at the end character of the line ending
       
   973 				}
       
   974 			else
       
   975 				{
       
   976 				delim = EDelimLF;
       
   977 				}
       
   978 			}
       
   979 		else if (ch == KUnicodeLineBreak /*|| ch == KUnicodeParagraphBreak*/)
       
   980 			{
       
   981 			numFound++;
       
   982 			lineLen = 0;
       
   983 			delim = EDelimUnicode;
       
   984 			}
       
   985 		else
       
   986 			{
       
   987 			lineLen++;
       
   988 			}
       
   989 
       
   990 		if (lineLen == aSoftWrapLineLength)
       
   991 			{
       
   992 			//TODO fix this so that soft wraps are put in the correct place when iterating backwards
       
   993 			numFound++;
       
   994 			lineLen = 0;
       
   995 			}
       
   996 
       
   997 		if (!foundDelim && delim && aDelimType)
       
   998 			{
       
   999 			foundDelim = ETrue;
       
  1000 			*aDelimType = delim;
       
  1001 			}
       
  1002 		if (numFound == aTarget)
       
  1003 			{
       
  1004 			return idx+1; // We return the index of the first character after the newline
       
  1005 			}
       
  1006 		}
       
  1007 	aTarget = numFound;
       
  1008 	return KErrNotFound;
       
  1009 	}
       
  1010 
       
  1011 TInt CFileBlock::TransferDataFromPreviousBlock(CFileBlock* aPrev, const TDesC8& aData)
       
  1012 	{
       
  1013 	ASSERT(aPrev == iPrev);
       
  1014 	ASSERT(!IsLoaded()); // We should only ever be doing a transfer the first time through the file. It's too complicated to update everything (char count, etc) if the block is already loaded
       
  1015 
       
  1016 	iBlockSize += aData.Length();
       
  1017 	iFilePosition -= aData.Length();
       
  1018 	return KErrNone;
       
  1019 	}
       
  1020 
       
  1021 TInt CFileBlock::NewlineCount() const
       
  1022 	{
       
  1023 	ASSERT(HasBeenLoaded());
       
  1024 	if (!iFlags.IsSet(EHaveCalculatedLineEndings))
       
  1025 		{
       
  1026 		// We don't know the line count
       
  1027 		ASSERT(IsLoaded());
       
  1028 		TInt numLineEndings = KMaxTInt;
       
  1029 		CountNewLines(*iData, numLineEndings, KMaxTInt);
       
  1030 		ASSERT(numLineEndings <= (TInt)KMaxTUint16); // Otherwise I've got some overflow-related badness
       
  1031 		iNumLineEndings = numLineEndings;
       
  1032 		iFlags.Set(EHaveCalculatedLineEndings);
       
  1033 		}
       
  1034 
       
  1035 	return iNumLineEndings;
       
  1036 	}
       
  1037 
       
  1038 TInt CFileBlock::CountNewLinesUpToOffset(TInt aOffset) const
       
  1039 	{
       
  1040 	ASSERT(IsLoaded());
       
  1041 	// Don't try and be smart (yet)
       
  1042 	TInt res = KMaxTInt;
       
  1043 	CountNewLines(iData->Left(aOffset), res, KMaxTInt);
       
  1044 	return res;
       
  1045 	}
       
  1046 
       
  1047 void CFileBlock::InsertTextL(TInt aBlockOffset, const TDesC16& aText)
       
  1048 	{
       
  1049 	ASSERT(IsLoaded());
       
  1050 	ASSERT(aBlockOffset <= iData->Length());
       
  1051 	EnsureHBufCapacityL(iData, iData->Length() + aText.Length());
       
  1052 	TPtr data = iData->Des();
       
  1053 
       
  1054 	// Ugh check we're not splitting a CRLF
       
  1055 	if (aBlockOffset > 0 && aBlockOffset < data.Length() && data[aBlockOffset-1] == '\r' && data[aBlockOffset] == '\n')
       
  1056 		{
       
  1057 		aBlockOffset++;
       
  1058 		}
       
  1059 
       
  1060 	data.Insert(aBlockOffset, aText);
       
  1061 	iFlags.Clear(EHaveCalculatedLineEndings); // Don't recalculate until asked for
       
  1062 	// iBlockSize is not updated until we save the file (as we don't know how much space it will take up until then)
       
  1063 	// iNumCharacters is not updated because it's not needed while the block is loaded (plus, in case our data has exceeded KMaxTUint16)
       
  1064 	iFlags.Set(EDirty);
       
  1065 	}
       
  1066 
       
  1067 void CFileBlock::DeleteText(TInt aPos, TInt aLen)
       
  1068 	{
       
  1069 	ASSERT(IsLoaded());
       
  1070 	TPtr data = iData->Des();
       
  1071 	ASSERT(aPos >= 0);
       
  1072 	ASSERT(aLen > 0);
       
  1073 	ASSERT(aPos < data.Length());
       
  1074 	ASSERT(aPos + aLen <= data.Length());
       
  1075 
       
  1076 	// Ugh check neither end of the range splits a CRLF
       
  1077 	if (data[aPos] == '\n' && aPos > 0 && data[aPos-1] == '\r') { aPos--; aLen++; }
       
  1078 	if (data[aPos+aLen-1] == '\r' && aPos + aLen < data.Length() && data[aPos+aLen] == '\n') aLen++; // Be greedy in both directions? Is this right?
       
  1079 
       
  1080 	data.Delete(aPos, aLen);
       
  1081 	iFlags.Clear(EHaveCalculatedLineEndings); // Don't recalculate until asked for
       
  1082 	iFlags.Set(EDirty);
       
  1083 	}
       
  1084 
       
  1085 TInt CFileBlock::CharacterCount() const
       
  1086 	{
       
  1087 	ASSERT(HasBeenLoaded());
       
  1088 	if (IsLoaded()) return Text().Length();
       
  1089 	else return iNumCharacters;
       
  1090 	}
       
  1091 
       
  1092 TInt CFileBlock::ReplaceAll(const TDesC& aFrom, const TDesC& aTo)
       
  1093 	{
       
  1094 	// Replace all occurences of aFrom with aTo, in iData
       
  1095 	TInt pos = 0;
       
  1096 	TInt lenDelta = -aFrom.Length() + aTo.Length();
       
  1097 	while (ETrue)
       
  1098 		{
       
  1099 		TPtrC des(iData->Mid(pos));
       
  1100 		TInt found = des.Find(aFrom);
       
  1101 		if (found == KErrNotFound) break;
       
  1102 		TInt idx = pos + found;
       
  1103 		EnsureHBufCapacity(iData, iData->Length() + lenDelta);
       
  1104 		iData->Des().Replace(idx, aFrom.Length(), aTo);
       
  1105 		pos = idx + aTo.Length();
       
  1106 		}
       
  1107 	return KErrNone;
       
  1108 	}
       
  1109 
       
  1110 TInt CFileBlock::ConvertToEightBit(TEncodingType aEncoding, TDelimiterType aDelimeter, RBuf8& aResultBuf)
       
  1111 	{
       
  1112 	ASSERT(IsLoaded());
       
  1113 	aResultBuf.Zero();
       
  1114 	// First, make sure the line endings are consistant
       
  1115 	
       
  1116 	_LIT(KCr, "\r");
       
  1117 	_LIT(KCrLf, "\r\n");
       
  1118 	_LIT(KLf, "\n");
       
  1119 	_LIT(KUniNl, "\x2028");
       
  1120 	//_LIT(KUniPara, "\x2029");
       
  1121 	// This could benefit from some optimisation, it is at least easy to understand
       
  1122 	TInt err = KErrNone;
       
  1123 	switch(aDelimeter)
       
  1124 		{
       
  1125 		case EDelimCR:
       
  1126 			ReplaceAll(KCrLf, KCr);
       
  1127 			ReplaceAll(KLf, KCr);
       
  1128 			ReplaceAll(KUniNl, KCr);
       
  1129 			break;
       
  1130 		case EDelimLF:
       
  1131 			ReplaceAll(KCrLf, KLf);
       
  1132 			ReplaceAll(KCr, KLf);
       
  1133 			ReplaceAll(KUniNl, KLf);
       
  1134 			break;
       
  1135 		case EDelimUnicode:
       
  1136 			ReplaceAll(KCrLf, KUniNl);
       
  1137 			ReplaceAll(KCr, KUniNl);
       
  1138 			ReplaceAll(KLf, KUniNl);
       
  1139 			break;
       
  1140 		case EDelimCRLF:
       
  1141 			// Easiest to replace everything (including CRLFs) with a single-character sequence, then replace that to CRLF at the end. This avoids having to figure out if a CR is part of a CRLF, etc
       
  1142 			ReplaceAll(KCrLf, KUniNl);
       
  1143 			ReplaceAll(KCr, KUniNl);
       
  1144 			ReplaceAll(KLf, KUniNl);
       
  1145 			err = ReplaceAll(KUniNl, KCrLf); // This one could actually need an alloc as the string could be bigger
       
  1146 			if (err) return err;
       
  1147 			break;
       
  1148 		default:
       
  1149 			__DEBUGGER();
       
  1150 			break;
       
  1151 		}
       
  1152 
       
  1153 	switch (aEncoding)
       
  1154 		{
       
  1155 		case EEncodingUtf16Native:
       
  1156 		case EEncodingUtf16Switched:
       
  1157 			{
       
  1158 			TBool needBom = (FilePosition() == 0);
       
  1159 			TPtrC8 narrowData = TPtrC8((TUint8*)iData->Ptr(), iData->Size());
       
  1160 			TInt requiredLen = narrowData.Length();
       
  1161 			if (needBom) requiredLen += 2;
       
  1162 			EnsureCapacity(aResultBuf, requiredLen);
       
  1163 			if (needBom) aResultBuf.Append(KNativeBom);
       
  1164 			aResultBuf.Append(narrowData);
       
  1165 			if (aEncoding == EEncodingUtf16Switched)
       
  1166 				{
       
  1167 				ByteSwitch((TUint16*)aResultBuf.Ptr(), requiredLen);
       
  1168 				}
       
  1169 			break;
       
  1170 			}
       
  1171 		case EEncodingNarrow:
       
  1172 			// No attempt at conversion in this case
       
  1173 			EnsureCapacity(aResultBuf, iData->Length());
       
  1174 			aResultBuf.Copy(*iData);
       
  1175 			break;
       
  1176 		case EEncodingUtf8:
       
  1177 			{
       
  1178 			EnsureCapacity(aResultBuf, iData->Length() * 4); // Worst case for UTF-8 is 4 bytes per character
       
  1179 			TInt result = CnvUtfConverter::ConvertFromUnicodeToUtf8(aResultBuf, *iData);
       
  1180 			if (result > 0) return KErrCorrupt;
       
  1181 			if (result < 0) return result;
       
  1182 			break;
       
  1183 			}
       
  1184 		default:
       
  1185 			ASSERT(EFalse);
       
  1186 		}
       
  1187 
       
  1188 	if (iData->Length() > (TInt)KMaxTUint16 || aResultBuf.Length() > (TInt)KMaxTUint16)
       
  1189 		{
       
  1190 		// Oh dear we've blown our (self-imposed) limit on how big iNumCharacters and iBlockSize can get
       
  1191 		// Split the block and try again
       
  1192 		TInt err = SplitBufferAt(iData->Length()/2);
       
  1193 		if (err) return err;
       
  1194 		else return ConvertToEightBit(aEncoding, aDelimeter, aResultBuf);
       
  1195 		}
       
  1196 	iBlockSize = aResultBuf.Length(); // Now we know how big the block will be when it gets written back to disk
       
  1197 	return KErrNone;
       
  1198 	}
       
  1199 
       
  1200 void CFileBlock::FileHasBeenSaved()
       
  1201 	{
       
  1202 	ASSERT(iPrev == NULL); // Should only call on the first block
       
  1203 	TInt filePos = 0;
       
  1204 	CFileBlock* block = this;
       
  1205 	while (block)
       
  1206 		{
       
  1207 		block->iFilePosition = filePos;
       
  1208 		block->iFlags.Clear(EDirty);
       
  1209 		filePos = filePos + block->BlockSize(); // File positions need updating in light of how block sizes may have changed (the blocksize is set to the correct value at the end of ConvertToEightBit)
       
  1210 		block = block->Next();
       
  1211 		}
       
  1212 	}
       
  1213 
       
  1214 TInt CFileBlock::SplitBufferAt(TInt aCharacterPosition)
       
  1215 	{
       
  1216 	ASSERT(IsLoaded());
       
  1217 	ASSERT(iData);
       
  1218 
       
  1219 	if (aCharacterPosition > iData->Length()) aCharacterPosition = iData->Length();
       
  1220 	// Ugh check we're not splitting a CRLF
       
  1221 	if (aCharacterPosition > 0 && (*iData)[aCharacterPosition-1] == '\r' && (*iData)[aCharacterPosition] == '\n')
       
  1222 		{
       
  1223 		aCharacterPosition++;
       
  1224 		}
       
  1225 
       
  1226 	CFileBlock* newBlock = new CFileBlock(-1, 1); // Neither file position nor blocksize is important for a block that is dirty, and this block will be created dirty
       
  1227 	if (!newBlock) return KErrNoMemory;
       
  1228 	newBlock->iFlags.Set(EDirty);
       
  1229 	newBlock->iFlags.Set(EHasBeenLoaded);
       
  1230 
       
  1231 	TPtrC dataToMove = iData->Mid(aCharacterPosition);
       
  1232 	newBlock->iData = HBufC::New(dataToMove.Length());
       
  1233 	if (!newBlock->iData)
       
  1234 		{
       
  1235 		delete newBlock;
       
  1236 		return KErrNoMemory;
       
  1237 		}
       
  1238 	newBlock->iData->Des().Copy(dataToMove);
       
  1239 	// That's it bar the shouting
       
  1240 	iData->Des().SetLength(aCharacterPosition);
       
  1241 	iFlags.Set(EDirty); // We have to do this because we've invalidated iBlockSize (we can't just update iBlockSize because we don't know how many bytes aCharacterPosition corresponds to).
       
  1242 	newBlock->iPrev = this;
       
  1243 	newBlock->iNext = iNext;
       
  1244 	iNext->iPrev = newBlock;
       
  1245 	iNext = newBlock;
       
  1246 	return KErrNone;
       
  1247 	}
       
  1248 
       
  1249 void ByteSwitch(TUint16* data, TInt aLen)
       
  1250 	{
       
  1251 	const TUint16* endp = data + aLen;
       
  1252 	while (data < endp)
       
  1253 		{
       
  1254 		TUint16 ch = *data;
       
  1255 		*data = (ch>>8) | ((ch & 0xFF) << 8);
       
  1256 		data++;
       
  1257 		}
       
  1258 	}