|
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 } |