--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mtpfws/mtpfw/datatypes/src/cmtptypefile.cpp Tue Feb 02 01:11:40 2010 +0200
@@ -0,0 +1,620 @@
+// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+/**
+ @file
+ @publishedPartner
+ */
+
+#include <mtp/cmtptypefile.h>
+#include <mtp/mtpdatatypeconstants.h>
+
+// File type constants.
+const TInt KMTPFileChunkSizeForLargeFile(0x00080000); // 512K
+
+//For file size less than 512K, we will use this smaller chunk size to reduce the heap usage.
+const TInt KMTPFileChunkSizeForSmallFile(0x00010000); //64K
+
+//For file size larger than it, we will split one setSize() to several smaller one, each with the following size.
+const TInt64 KMTPFileSetSizeChunk(1<<30); //1G
+
+const TUint KUSBHeaderLen = 12;
+
+/**
+ MTP file object data type factory method.
+ @param aFs The handle of an active file server session.
+ @param aName The name of the file. Any path components (i.e. drive letter
+ or directory), which are not specified, are taken from the session path.
+ @param aMode The mode in which the file is opened (@see TFileMode).
+ @return A pointer to the MTP file object data type. Ownership IS transfered.
+ @leave One of the system wide error codes, if a processing failure occurs.
+ @see TFileMode
+ */
+EXPORT_C CMTPTypeFile* CMTPTypeFile::NewL(RFs& aFs, const TDesC& aName, TFileMode aMode)
+ {
+ CMTPTypeFile* self = NewLC(aFs, aName, aMode);
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+/**
+ MTP file object data type factory method. A pointer to the MTP file object data
+ type is placed on the cleanup stack.
+ @param aFs The handle of an active file server session.
+ @param aName The name of the file. Any path components (i.e. drive letter
+ or directory), which are not specified, are taken from the session path.
+ @param aMode The mode in which the file is opened (@see TFileMode).
+ @return A pointer to the MTP file object data type. Ownership IS transfered.
+ @leave One of the system wide error codes, if a processing failure occurs.
+ @see TFileMode
+ */
+EXPORT_C CMTPTypeFile* CMTPTypeFile::NewLC(RFs& aFs, const TDesC& aName, TFileMode aMode)
+ {
+ CMTPTypeFile* self = new(ELeave) CMTPTypeFile;
+ CleanupStack::PushL(self);
+ self->ConstructL(aFs, aName, aMode);
+ return self;
+ }
+
+EXPORT_C CMTPTypeFile* CMTPTypeFile::NewL(RFs& aFs, const TDesC& aName, TFileMode aMode, TInt64 aRequiredSize, TInt64 aOffSet)
+ {
+ CMTPTypeFile* self = NewLC(aFs, aName, aMode,aRequiredSize,aOffSet);
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+EXPORT_C CMTPTypeFile* CMTPTypeFile::NewLC(RFs& aFs, const TDesC& aName, TFileMode aMode, TInt64 aRequiredSize, TInt64 aOffSet)
+ {
+ CMTPTypeFile* self = new(ELeave) CMTPTypeFile;
+ CleanupStack::PushL(self);
+ self->ConstructL(aFs, aName, aMode, aRequiredSize, aOffSet);
+ return self;
+ }
+
+/**
+ Destructor
+ */
+EXPORT_C CMTPTypeFile::~CMTPTypeFile()
+ {
+ if(iCurrentCommitChunk.Length() != 0)
+ {
+ ToggleRdWrBuffer();
+ }
+
+ iFile.Close();
+
+ iBuffer1.Close();
+ iBuffer2.Close();
+ Cancel();
+ }
+
+/**
+ Sets the size of the file, this function must be called in case of file writting/receiving. related resouce
+ will be allocated in this function to prepare to receive the incoming data.
+ @param aSize The new size of the file (in bytes).
+ @leave One of the system wide error codes, if a processing failure occurs.
+ */
+EXPORT_C void CMTPTypeFile::SetSizeL(TUint64 aSize)
+ {
+ iTargetFileSize = (TInt64)aSize; //keep a record for the target file size
+
+ iRemainingDataSize = (TInt64)aSize;//Current implemenation does not support file size with 2 x64
+
+ if(iRemainingDataSize> KMTPFileChunkSizeForLargeFile) //512K
+ {
+ iBuffer1.CreateMaxL(KMTPFileChunkSizeForLargeFile);
+ iBuffer2.CreateMaxL(KMTPFileChunkSizeForLargeFile);
+ }
+ else
+ {
+ iBuffer1.CreateMaxL(KMTPFileChunkSizeForSmallFile);
+ iBuffer2.CreateMaxL(KMTPFileChunkSizeForSmallFile);
+ }
+ if(iRemainingDataSize> KMTPFileSetSizeChunk)
+ {
+ //split the setSize to multiple calling of 512M
+ User::LeaveIfError(iFile.SetSize(KMTPFileSetSizeChunk));
+ iCurrentFileSetSize = KMTPFileSetSizeChunk;
+ }
+ else
+ {
+ User::LeaveIfError(iFile.SetSize(aSize));
+ iCurrentFileSetSize = aSize;
+ }
+ }
+
+/**
+ Provides a reference to the native file object encapsulate by the MTP file
+ object data type.
+ @return The native file object reference.
+ */
+#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
+EXPORT_C RFile64& CMTPTypeFile::File()
+#else
+EXPORT_C RFile& CMTPTypeFile::File()
+#endif
+ {
+ return iFile;
+ }
+
+EXPORT_C TInt CMTPTypeFile::FirstReadChunk(TPtrC8& aChunk) const
+ {
+ aChunk.Set(NULL, 0);
+ iReadSequenceState = EIdle;
+ iBuffer1.Zero();
+#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
+ TInt64 pos =iOffSet;
+#else
+ TInt pos = static_cast<TInt>(iOffSet);
+#endif
+ TInt err(iFile.Seek(ESeekStart, pos));
+ if (err == KErrNone)
+ {
+ // The USB SIC header is 12 bytes long. If the first chunk is 128K - 12 bytes,
+ // the USB SIC transport will not buffer data, which will improve the transfer rate.
+ err = iFile.Read(iBuffer1, iBuffer1.MaxLength() - KUSBHeaderLen);
+ if (err == KErrNone)
+ {
+ //this chunk is going to be used by USB to read data from it, only CMTPTypefile::RunL() can toggle this flag
+ //When it finishe reading data into Buffer2.
+ iBuffer1AvailForWrite = EFalse;
+
+ aChunk.Set(iBuffer1.Ptr(), iBuffer1.Length());
+
+ //Set the commit chunk to be filled in by CMTPTypeFile::RunL()
+ iCurrentCommitChunk.Set(&iBuffer2[0], 0, iBuffer2.MaxLength());
+
+ iRemainingDataSize -= aChunk.Length();
+
+ if (aChunk.Length() == 0)
+ {
+ // Empty File.
+ iReadSequenceState = EIdle;
+ err = KMTPChunkSequenceCompletion;
+ }
+ else
+ {
+ if (iRemainingDataSize <= 0)
+ {
+ // EOF.
+ iReadSequenceState = EIdle;
+ aChunk.Set(aChunk.Ptr(), aChunk.Length() + iRemainingDataSize); //for partial
+ err = KMTPChunkSequenceCompletion;
+ }
+ else
+ {
+ iReadSequenceState = EInProgress;
+ //This is NOT the last chunk, issue more CMTPTypeFile::RunL()
+ if (!IsActive())
+ {
+ //Since the writting data into file sever will take a long time, will issue a dedicated Active Object to do that.
+ const_cast<CMTPTypeFile*>(this)->SetActive();
+ TRequestStatus* status = (TRequestStatus*)&iStatus;
+ User::RequestComplete(status, KErrNone);
+ }
+ else
+ {
+ //This is a very extreme cases, it only occurs when the following assumption is met
+ //1. USB already took buffer1 and already issue CMTPTypeFileRunL(), therefore, the ActiveObject has completed itself,
+ //2. Somehow, this active object is not scheduled to be running even after the USB already use out the other buffer.
+ //3. USB's active object is scheduled to be running prior to the last File active object(this should not happen if ActiveScheduler follow the priority scheduler).
+ //4. USB call this function again to get the other data buffer.
+ //5. Then it find the previous active is not scheduled to run.
+ //in single-core platform, the code rely on the CActiveScheduler to guarantee the first active call which has higher priority to be running firstly before
+ //the 2nd USB active. but for multi-core platform, this should be re-evaluated .
+ iReadSequenceState = EIdle;
+ err = KMTPChunkSequenceCompletion;
+ }
+ }
+ }
+ }
+ else
+ {
+ iReadSequenceState = EIdle;
+ iFileRdWrError = ETrue;
+ }
+ }
+ iByteSent += aChunk.Length();
+ return err;
+ }
+
+EXPORT_C TInt CMTPTypeFile::NextReadChunk(TPtrC8& aChunk) const
+ {
+ TInt err(KErrNone);
+
+ if((iReadSequenceState != EInProgress) || (iFileRdWrError))
+ {
+ aChunk.Set(NULL, 0);
+ return KErrNotReady;
+ }
+
+ //This is called by USB's RunL(), here, the only possible scenarios is that the CMTPTypleFile::RunL() issued in FirReadChunk or last NextReadChunk must
+ //have already finished. Now take the buffer which is filled in by data in CMTPTypleFile::RunL().
+ aChunk.Set(iCurrentCommitChunk.Ptr(), iCurrentCommitChunk.Length());
+ if(iBuffer1AvailForWrite)
+ {//We have already used buffer_1, now buffer2 contains data read into by CMTPTypeFile::RunL();
+ //Set the commit chunk to be filled in by CMTPTypeFile::RunL()
+ iCurrentCommitChunk.Set(&iBuffer1[0], 0, iBuffer1.MaxLength());
+ }
+ else
+ {
+ //Set the commit chunk to be filled in by CMTPTypeFile::RunL()
+ iCurrentCommitChunk.Set(&iBuffer2[0], 0, iBuffer2.MaxLength());
+ }
+
+ iRemainingDataSize -= aChunk.Length();
+
+ if(aChunk.Length() == 0)
+ {
+ iReadSequenceState = EIdle;
+ err = KMTPChunkSequenceCompletion;
+ }
+ else if(iRemainingDataSize> 0)
+ {
+ //This is NOT the last chunk, issue more CMTPTypeFile::RunL()
+ if (!IsActive())
+ {
+ //Since the writting data into file sever will take a long time, will issue a dedicated Active Object to do that.
+ ((CMTPTypeFile*)this)->SetActive();
+ TRequestStatus* status = (TRequestStatus*)&iStatus;
+ User::RequestComplete(status, KErrNone);
+ }
+ else
+ {
+ //This is a very extreme cases, it only occurs when the following assumption is met
+ //1. USB already took buffer1 and already issue CMTPTypeFileRunL(), therefore, the ActiveObject has completed itself,
+ //2. Somehow, this active object is not scheduled to be running even after the USB already use out the other buffer.
+ //3. USB's active object is scheduled to be running prior to the last File active object(this should not happen if ActiveScheduler follow the priority scheduler).
+ //4. USB call this function again to get the other data buffer.
+ //5. Then it find the previous active is not scheduled to run.
+ //in single-core platform, the code rely on the CActiveScheduler to guarantee the first active call which has higher priority to be running firstly before
+ //the 2nd USB active. but for multi-core platform, this should be re-evaluated .
+ iReadSequenceState = EIdle;
+ err = KMTPChunkSequenceCompletion;
+ }
+ }
+ else
+ {//Last Chunk. Do not issue Active object. and indicate this completion of the chunk
+ iReadSequenceState = EIdle;
+ aChunk.Set(aChunk.Ptr(), aChunk.Length() + iRemainingDataSize); //for partial
+ err = KMTPChunkSequenceCompletion;
+ }
+ iByteSent += aChunk.Length();
+ return err;
+ }
+
+EXPORT_C TInt CMTPTypeFile::FirstWriteChunk(TPtr8& aChunk)
+ {
+ __ASSERT_DEBUG(iBuffer1AvailForWrite, User::Invariant());
+ __ASSERT_DEBUG(!iFileRdWrError, User::Invariant());
+
+ aChunk.Set(NULL, 0, 0);
+ iWriteSequenceState = EIdle;
+
+#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
+ TInt64 pos =0;
+#else
+ TInt pos =0;
+#endif
+ TInt err(iFile.Seek(ESeekStart, pos));
+ if (err == KErrNone)
+ {
+ //Because USB HS's transmission rate is several time faster than the rate of writting data into File System.
+ //If the first packet is a full chunk size packet, then the writting of that data will not start until the full-chunk
+ //sized packet is received. Here we intentionly reduce the first packet size to 1/4 of the full chunk size, therefore,
+ //the start of writting data into File system will start only after 1/4 of the full chunk size data is received.
+ //This can make the writting of data to FS start earlier.
+ aChunk.Set(&iBuffer1[0], 0, iBuffer1.MaxLength());
+ iWriteSequenceState = EInProgress;
+
+ //this chunk is going to be used by Transport to write data into it, and when it is full, transport
+ //will call back CommitChunkL(), at that time, the EFalse means it already contains data in it.
+ //it is ready for reading data from it.
+ //This is a initial value for it to trigger the double-buffering mechanism.
+ iBuffer1AvailForWrite = EFalse;
+ }
+
+ return err;
+ }
+
+EXPORT_C TInt CMTPTypeFile::NextWriteChunk(TPtr8& aChunk)
+ {
+ TInt err(KErrNone);
+ aChunk.Set(NULL, 0, 0);
+
+ if (iWriteSequenceState != EInProgress)
+ {
+ err = KErrNotReady;
+ }
+ else
+ {//toggle between buffer 1 and buffer 2 here.
+ if(iBuffer1AvailForWrite)
+ {
+ aChunk.Set(&iBuffer1[0], 0, iBuffer1.MaxLength());
+ }
+ else
+ {
+ aChunk.Set(&iBuffer2[0], 0, iBuffer2.MaxLength());
+ }
+ }
+
+ return err;
+ }
+
+EXPORT_C TUint64 CMTPTypeFile::Size() const
+ {
+ //The USB transport layer uses USB Container Length to determine the total size of data to be
+ //transfered. In USB protocol, the Container Length is 32 bits long which is up to 4G-1, so
+ //for synchronization of a large file >=4G-12 bytes (the USB header is 12 bytes long), the
+ //Container Length can't be used to determine the total size of data any more. In this kind of
+ //case, our USB transport layer implementation will call this function to get the actual data size.
+
+ //The RFile::SetSize() method may take over 40 seconds if we create a file and set its size
+ //to a very large value, and this will cause timeout in MTP protocol layer. To avoid this
+ //timeout, when creating a large file(over 512MB), instead of setting its size directly to
+ //the target size by one singile RFile::SetSize() call, we'll call RFile::SetSize() multiple
+ //times and set the file size step by step acumulately. For example, for a 2GB file, its
+ //size will be set to 0.5G first, and then 1G, 1.5G and at last 2G.
+
+ //So if a file is transfering to device, the size of the file that returned by RFile::Size() is
+ //just a temporary value and means nothing. In this case, let's return the target file size instead.
+ if(!iFileOpenForRead && iRemainingDataSize)
+ {
+ return iTargetFileSize;
+ }
+
+ //If the initiator get partial of the file, return the requested partial size
+ if (iFileOpenForRead && iTargetFileSize)
+ {
+ return iTargetFileSize;
+ }
+
+#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
+ TInt64 size;
+#else
+ TInt size;
+#endif
+ iFile.Size(size);
+ return size;
+ }
+
+EXPORT_C TUint CMTPTypeFile::Type() const
+ {
+ return EMTPTypeFile;
+ }
+
+EXPORT_C TBool CMTPTypeFile::CommitRequired() const
+ {
+ return ETrue;
+ }
+
+EXPORT_C MMTPType* CMTPTypeFile::CommitChunkL(TPtr8& aChunk)
+ {
+ if(iFileRdWrError)
+ {
+ return NULL;
+ }
+ if(0 == aChunk.Length())
+ {
+ ToggleRdWrBuffer();
+ return NULL;
+ }
+ iCurrentCommitChunk.Set(aChunk);
+
+ if(iRemainingDataSize> iCurrentCommitChunk.Length())
+ {//This is NOT the last chunk, we issue an active object to commit it to File system.
+ iRemainingDataSize -= iCurrentCommitChunk.Length();
+ /*
+ if (!IsActive())
+ {
+ //Since the writting data into file sever will take a long time, will issue a dedicated Active Object to do that.
+ SetActive();
+ TRequestStatus* thisAO = &iStatus;
+ User::RequestComplete(thisAO, KErrNone);
+ }
+ else
+ {
+ //This is a very extreme cases, it only occurs when the following assumption is met
+ //1. USB received buffer1 and already call this CommitChunkL(), therefore, the ActiveObject has completed itself, and USB then use another buffer to
+ //receive the data.
+ //2. Somehow, this active object is not scheduled to be running even after the USB already fill out the other buffer.
+ //3. USB's active object is scheduled to be running prior to the last File active object(this should not happen if ActiveScheduler follow the priority scheduler).
+ //4. USB call this function again to commit the other data buffer.
+ //5. Then it find the previous active is not scheduled to run.
+ //in single-core platform, the code rely on the CActiveScheduler to guarantee the first active call which has higher priority to be running firstly before
+ //the 2nd USB active. but for multi-core platform, this should be re-evaluated .
+ iFileRdWrError = ETrue;//if it really discard the incoming recevied file.
+ //__FLOG(_L8("\nThe program should not arrive here !!!!!\n"));
+ }
+ */
+ }
+ else
+ {//This is the last chunk, we synchronous commit it
+ iRemainingDataSize = 0;
+ ToggleRdWrBuffer();
+ return NULL;
+ }
+ return this;
+ }
+
+//for partial
+EXPORT_C Int64 CMTPTypeFile::GetByteSent()
+ {
+ return iByteSent;
+ }
+
+CMTPTypeFile::CMTPTypeFile() :
+ CActive(EPriorityUserInput), iBuffer1AvailForWrite(ETrue),
+ iFileRdWrError(EFalse), iCurrentCommitChunk(NULL, 0)
+ {
+ CActiveScheduler::Add(this);
+ }
+
+void CMTPTypeFile::ConstructL(RFs& aFs, const TDesC& aName, TFileMode aMode)
+ {
+ if (aMode & EFileWrite)
+ {
+ iFileOpenForRead = EFalse;
+ User::LeaveIfError(iFile.Replace(aFs, aName, aMode|EFileWriteDirectIO));
+ }
+ else
+ {
+ iFileOpenForRead = ETrue;
+ User::LeaveIfError(iFile.Open(aFs, aName, aMode|EFileReadDirectIO|EFileShareReadersOnly));
+#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
+ TInt64 size = 0;
+#else
+ TInt size = 0;
+#endif
+ User::LeaveIfError(iFile.Size(size));
+ iRemainingDataSize = size;
+
+ //For File reading, NO "SetSizeL()" will be called, therefore, create the buffer here.
+ if (iRemainingDataSize > KMTPFileChunkSizeForLargeFile) //512K
+ {
+ iBuffer1.CreateMaxL(KMTPFileChunkSizeForLargeFile);
+ iBuffer2.CreateMaxL(KMTPFileChunkSizeForLargeFile);
+ }
+ else
+ {
+ iBuffer1.CreateMaxL(KMTPFileChunkSizeForSmallFile);
+ iBuffer2.CreateMaxL(KMTPFileChunkSizeForSmallFile);
+ }
+ }
+ }
+
+void CMTPTypeFile::ConstructL(RFs& aFs, const TDesC& aName, TFileMode aMode, TInt64 aRequiredSize, TInt64 aOffSet)
+ {
+ if (aMode & EFileWrite)
+ {
+ iFileOpenForRead = EFalse;
+ User::LeaveIfError(iFile.Replace(aFs, aName, aMode|EFileWriteDirectIO));
+ }
+ else
+ {
+ iFileOpenForRead = ETrue;
+ iOffSet = aOffSet;
+ User::LeaveIfError(iFile.Open(aFs, aName, aMode|EFileReadDirectIO|EFileShareReadersOnly));
+#ifdef SYMBIAN_ENABLE_64_BIT_FILE_SERVER_API
+ TInt64 size = 0;
+#else
+ TInt size = 0;
+#endif
+ User::LeaveIfError(iFile.Size(size));
+
+ if(aRequiredSize < size)
+ {
+ iTargetFileSize = aRequiredSize;
+ }
+ else
+ {
+ iTargetFileSize = size;
+ }
+ iRemainingDataSize = iTargetFileSize;
+
+ //For File reading, NO "SetSizeL()" will be called, therefore, create the buffer here.
+ if (iRemainingDataSize > KMTPFileChunkSizeForLargeFile) //512K
+ {
+ iBuffer1.CreateMaxL(KMTPFileChunkSizeForLargeFile);
+ iBuffer2.CreateMaxL(KMTPFileChunkSizeForLargeFile);
+ }
+ else
+ {
+ iBuffer1.CreateMaxL(KMTPFileChunkSizeForSmallFile);
+ iBuffer2.CreateMaxL(KMTPFileChunkSizeForSmallFile);
+ }
+ }
+ }
+
+void CMTPTypeFile::DoCancel()
+ {
+ // Nothing to cancel here because this Active object does not issue any asynchronous call to others.
+ }
+
+// Catch any leaves - the CActiveScheduler can't handle it.
+TInt CMTPTypeFile::RunError(TInt /* aError*/)
+ {
+ //We did not throw exception in RunL() in reality, therefore, we need not to cope with it.
+ return KErrNone;
+ }
+
+void CMTPTypeFile::RunL()
+ {
+ ToggleRdWrBuffer();
+ }
+
+void CMTPTypeFile::ToggleRdWrBuffer()
+ {
+ //This is triggered by CommitChunkL(), this will write the received data into File system synchronously.
+ //Since someone trigger this RunL(), therefore, there must be one of 2 buffer which is full of data to wait for writing buffer data into File system.
+ //Each RunL(), only need to commit one chunk because transport only prepare one chunk for file system in one RunL().
+
+ TInt err = KErrNone;
+
+ if (!iFileOpenForRead)
+ {
+ if (!iFileRdWrError)
+ {
+ TInt64 temp = iCurrentCommitChunk.Length();
+ iTotalReceivedSize += temp;
+ if (iTotalReceivedSize > iCurrentFileSetSize)
+ {
+ //temp += iRemainingDataSize;//Total uncommitted file size.
+ temp = iTotalReceivedSize-iCurrentFileSetSize+iRemainingDataSize;
+ if (temp >= KMTPFileSetSizeChunk)
+ {
+ iCurrentFileSetSize += KMTPFileSetSizeChunk;
+ }
+ else
+ {
+ iCurrentFileSetSize += temp;
+ }
+ err = iFile.SetSize(iCurrentFileSetSize);
+ }
+
+ if (err != KErrNone)
+ {
+ iFileRdWrError = ETrue;
+ }
+ else
+ {
+ err = iFile.Write(iCurrentCommitChunk);
+ if (err != KErrNone)
+ {// file Write failed, this means we cannot successfully received this file but however, we cannot disrupt a current DIOR phase according to MTP spec.
+ // We should continue to receive the data and discard this data, only after the data phase is finished can we send back an error response
+ //to Initiator. Therefore, we pretend to continue to write this data into file, and let final processor to check the file size and then give back a
+ //corresponding error code to MTP initiator.
+ iFileRdWrError = ETrue;
+ iFile.SetSize(0);
+ }
+ }
+ }
+ iCurrentCommitChunk.Zero();
+ }
+ else
+ {
+ if (!iFileRdWrError)
+ {
+ err = iFile.Read(iCurrentCommitChunk,
+ iCurrentCommitChunk.MaxLength());
+ if (err != KErrNone)
+ {//Error, abort the current file reading.
+ iFileRdWrError = ETrue;
+ }
+ }
+ }
+
+ iBuffer1AvailForWrite = !iBuffer1AvailForWrite;//toggle the flag.
+ }