mmserv/thumbnailengine/TneAPISrc/HXTneserver.cpp
branchRCL_3
changeset 30 ab526b8cacfb
parent 17 3570217d8c21
equal deleted inserted replaced
24:bea5e7625e42 30:ab526b8cacfb
    92 	  // get clean-up stack
    92 	  // get clean-up stack
    93 	if (pServerName)
    93 	if (pServerName)
    94 	{
    94 	{
    95     		cleanup =CTrapCleanup::New(); 
    95     		cleanup =CTrapCleanup::New(); 
    96     	 	pA = new CActiveScheduler;    
    96     	 	pA = new CActiveScheduler;    
    97     		pS = new CTneServer(EPriorityStandard);
    97     		pS = new CTneServer(EPriorityHigh);
    98     		
    98     		
    99     	 }
    99     	 }
   100      
   100      
   101     
   101     
   102     if (cleanup && pA && pS )
   102     if (cleanup && pA && pS )
   160 
   160 
   161 	
   161 	
   162 	////////////////////////ServerSession//////////////////////////////
   162 	////////////////////////ServerSession//////////////////////////////
   163 	
   163 	
   164 	
   164 	
   165 CTneSession::CTneSession(): 
   165 CTneSession::CTneSession():CActive(EPriorityStandard),
   166         iWidth(0),                 
   166         iWidth(0),                 
   167         iHeight(0) ,                                            
   167         iHeight(0) ,                                            
   168         iDuration(0)  ,                                        
   168         iDuration(0)  ,                                        
   169         iFrameCount(0)    ,                                     
   169         iFrameCount(0)    ,                                     
   170         m_LastError(KErrNone),                                          
   170         m_LastError(KErrNone),                                          
   171         m_uPacketsReceived(0)    ,                                   
   171         m_uPacketsReceived(0)    ,                                   
   172         m_bDone(EFalse),                                                      
   172         m_bDone(EFalse),                                                      
   173         iFs(NULL),                                                    
   173         iFs(NULL),                                                    
   174         iYUVBuffer(NULL),
   174         iYUVBuffer(NULL),
   175         iClientYUVBufferPtrPtr(NULL),                         
   175         iClientYUVBufferPtrPtr(NULL),                         
   176         m_bOpenFileLPending(EFalse),                          
   176         iReOpenFileLPending(EFalse),                          
   177         iGetThumbPending(EFalse),                        
   177         iGetThumbPending(EFalse),                        
   178         iCloseHandle(EFalse),        	                        
   178         iCloseHandle(EFalse),        	                        
   179         ibOpenFilePending(EFalse),  
   179         ibOpenFilePending(EFalse),  
   180         iThumbIndex(0),
   180         iThumbIndex(0),
   181         iUtil(NULL),                                                
   181         iUtil(NULL),                                                
   182         m_State( ENotReady),
   182         m_State( ENotReady),
   183         m_bMetaDataReady(EFalse)                                   
   183         m_bMetaDataReady(EFalse),
   184         {
   184         iPosition(0)
   185      FLOG(_L("CTneSession::CTneSession()in this=%x"), this);                      
   185 {
   186      FLOG(_L("CTneSession::CTneSession()out this=%x"), this); 
   186     CActiveScheduler::Add(this);
   187           }                           
   187     FLOG(_L("CTneSession::CTneSession()in this=%x"), this);                      
       
   188     FLOG(_L("CTneSession::CTneSession()out this=%x"), this); 
       
   189 }                           
   188                          
   190                          
   189                          
   191                          
   190                                      
   192                                      
   191    CTneSession::~CTneSession() 
   193    CTneSession::~CTneSession() 
   192 {                          
   194 {                          
   193 	 FLOG(_L("CTneSession::~CTneSession()in this=%x"), this); 	
   195 	 FLOG(_L("CTneSession::~CTneSession()in this=%x"), this); 
       
   196 	 
       
   197 	 Cancel();
       
   198 	 
   194      if(iYUVBuffer)        
   199      if(iYUVBuffer)        
   195      {    	               
   200      {    	               
   196         User::Free(iYUVBuffer);
   201         User::Free(iYUVBuffer);
   197         iYUVBuffer = NULL; 
   202         iYUVBuffer = NULL; 
   198 	 }                     
   203 	 }                     
   210     {                      
   215     {                      
   211 	    iFs->Close();      
   216 	    iFs->Close();      
   212 	    delete iFs;
   217 	    delete iFs;
   213 	    iFs = NULL;
   218 	    iFs = NULL;
   214     }
   219     }
   215 	 CompleteRequest(KErrCancel);
   220 	 CompleteRequest(iClientRequest,KErrCancel);
       
   221 	 CompleteRequest(iMetaDataRequest,KErrCancel); 
       
   222 	 CompleteRequest(iThumbnailRequest,KErrCancel); 
   216 	 CompleteCancelRequest();	 
   223 	 CompleteCancelRequest();	 
   217 	 CActiveScheduler::Stop();
   224 	 CActiveScheduler::Stop();
   218    
   225    
   219     FLOG(_L("CTneSession::~CTneSession()out this=%x"), this);                	
   226     FLOG(_L("CTneSession::~CTneSession()out this=%x"), this);                	
   220 }
   227 }
   235    	TBool bCompleteRequest = EFalse;
   242    	TBool bCompleteRequest = EFalse;
   236    	TInt lError = KErrNone;
   243    	TInt lError = KErrNone;
   237     RFile* pFileHandle;	
   244     RFile* pFileHandle;	
   238     TFileName *pFileName;
   245     TFileName *pFileName;
   239     TInt aPosition;
   246     TInt aPosition;
   240     TNEMetaData* pMetaData;
       
   241     TNEThumbRequest *pThumbRequestData;
   247     TNEThumbRequest *pThumbRequestData;
   242     RFile64 aFilehandle;
   248     RFile64 aFilehandle;
   243     
   249     
   244     FLOG(_L("CTneSession::DispatchMessageL in type=%d"), aMessage.Function());
   250     FLOG(_L("CTneSession::DispatchMessageL in type=%d"), aMessage.Function());
   245     
   251     
   246     switch (aMessage.Function())
   252     switch (aMessage.Function())
   247         {        	        
   253         {        	        
   248     case  EOpenFileRFmsg:
   254     case  EOpenFileRFmsg:
   249         m_State = EStartGettingMetadata;
   255         m_State = EStartGettingMetadata;
   250         bCompleteRequest = ETrue;
   256         bCompleteRequest = EFalse;
       
   257         CompleteRequest(iClientRequest,KErrNotReady); // Any previous pending request
   251         iClientRequest = aMessage;
   258         iClientRequest = aMessage;
   252      	pFileHandle = ( RFile* ) aMessage.Ptr0(); // Handle to read Message data     
   259      	pFileHandle = ( RFile* ) aMessage.Ptr0(); // Handle to read Message data     
   253      	aPosition  = (TInt  ) aMessage.Ptr1();
   260      	aPosition  = (TInt  ) aMessage.Ptr1();
   254      	
   261      	
   255      	lError = iFileHandle.Duplicate(*pFileHandle);
   262      	lError = iFileHandle.Duplicate(*pFileHandle);
       
   263      	iPosition = aPosition;
       
   264      	     	
   256      	if (lError == KErrNone)
   265      	if (lError == KErrNone)
   257      	{
   266      	{
   258             iCloseHandle = ETrue;
   267             iCloseHandle = ETrue;
   259             lError = DoOpenFile(iFileHandle, aPosition);            
   268             iStatus = KRequestPending;
   260         }
   269             SetActive();
   261        
   270             TRequestStatus* pStatus = &iStatus;
   262         if( m_State == EStartGettingThumbNailWithIndex)
   271             User::RequestComplete(pStatus, KErrNone);          
   263         {
   272         }
   264             lError = ReOpenFile(iFileHandle);            
       
   265         }
       
   266                 
       
   267         CompleteCancelRequest(); // it will check also if cancel needs to be done.
       
   268         
   273         
   269         break;
   274         break;
   270         
   275         
   271 //fix me - Avoid duplicate code -common func (Future Work)
   276 //fix me - Avoid duplicate code -common func (Future Work)
   272     case  EOpenFIleNamemsg:
   277     case  EOpenFIleNamemsg:
   273         iFs = NULL;
   278         iFs = NULL;
   274         m_State = EStartGettingMetadata;
   279         m_State = EStartGettingMetadata;
       
   280         CompleteRequest(iClientRequest,KErrNotReady); // Any previous pending request
   275         iClientRequest = aMessage;
   281         iClientRequest = aMessage;
   276      	pFileName = (TFileName* ) aMessage.Ptr0();    	      
   282      	pFileName = (TFileName* ) aMessage.Ptr0();    	      
   277      	aPosition  = (TInt  ) aMessage.Ptr1();
   283      	aPosition  = (TInt  ) aMessage.Ptr1();
   278      
   284      	
   279      iFs = new RFs;
   285      	iFs = new RFs;
   280      if(iFs == NULL)
   286      	if(iFs == NULL)
   281 	 {
   287      	{
   282 	 	lError = KErrNoMemory;
   288      	    lError = KErrNoMemory;
   283 	 	    	bCompleteRequest = ETrue;
   289      	    bCompleteRequest = ETrue;
   284  	 }
   290      	}
   285     else if ( (lError  = iFs->Connect())!= KErrNone)
   291      	else if ( (lError  = iFs->Connect())!= KErrNone)
   286     {
   292      	{
   287     	    	bCompleteRequest = ETrue;
   293      	    bCompleteRequest = ETrue;
   288      }
   294      	}
   289      else if ((lError = aFilehandle.Open(*iFs,*pFileName, EFileShareReadersOnly | EFileStream | EFileRead))!= KErrNone)
   295      	else if ((lError = aFilehandle.Open(*iFs,*pFileName, EFileShareReadersOnly | EFileStream | EFileRead))!= KErrNone)
   290     {
   296      	{
   291     	    	bCompleteRequest = ETrue;
   297      	    bCompleteRequest = ETrue;
   292       }
   298      	}
   293 	else if ((lError = iFileHandle.Duplicate(aFilehandle))!= KErrNone)
   299      	else if ((lError = iFileHandle.Duplicate(aFilehandle))!= KErrNone)
   294 	{
   300      	{
   295     	    	bCompleteRequest = ETrue;
   301      	    bCompleteRequest = ETrue;
   296     	    	   aFilehandle.Close();
   302      	    aFilehandle.Close();
   297 
   303      	}
   298       }
   304      	else
   299      else if ( (lError = DoOpenFile(iFileHandle, aPosition)) != KErrNone )
   305      	{
   300         {
   306      	    bCompleteRequest = EFalse;
   301             bCompleteRequest = ETrue;
   307      	    aFilehandle.Close();
   302             aFilehandle.Close();      
   308      	    iStatus = KRequestPending;
   303             iCloseHandle = ETrue;
   309      	    iCloseHandle = ETrue;
   304         }
   310      	    SetActive();
   305         else 
   311      	    TRequestStatus* pStatus = &iStatus;
   306         {
   312      	    User::RequestComplete(pStatus, KErrNone);
   307             bCompleteRequest = ETrue;
   313      	}
   308             aFilehandle.Close();
   314 
   309             iCloseHandle = ETrue;
       
   310         }
       
   311 
       
   312         
       
   313         if( m_State == EStartGettingThumbNailWithIndex)
       
   314         {
       
   315             lError = ReOpenFile(iFileHandle);            
       
   316         }
       
   317                 
       
   318         CompleteCancelRequest(); // it will check also if cancel needs to be done.
       
   319         
       
   320         if (lError  !=  KErrNone)
   315         if (lError  !=  KErrNone)
   321         {
   316         {
   322             bCompleteRequest = ETrue;
   317             bCompleteRequest = ETrue;
   323         }                        
   318         }                        
   324         
   319         
   325         break;
   320         break;
   326       case EGetMetaDatamsg:
   321       case EGetMetaDatamsg:
   327         iClientRequest = aMessage;
   322         
   328       	pMetaData = ( TNEMetaData* ) aMessage.Ptr0(); 
   323         CompleteRequest(iMetaDataRequest,KErrNotReady); // Any previous pending request
   329         pMetaData->iWidth = iWidth;
   324         iMetaDataRequest = aMessage;
   330         pMetaData->iHeight = iHeight;
   325         NotifyIfGetMetaDataPending(m_LastError);       
   331         pMetaData->iFrameCount = iFrameCount;
       
   332         bCompleteRequest = ETrue;
       
   333        
       
   334         break;
   326         break;
   335         
   327         
   336       case EGetThumbmsg:
   328       case EGetThumbmsg:
   337         
   329         
   338         iClientRequest = aMessage;
   330         CompleteRequest(iThumbnailRequest,KErrNotReady); // Any previous pending request
       
   331         iThumbnailRequest = aMessage;
   339         pThumbRequestData  = ( TNEThumbRequest * ) aMessage.Ptr0();
   332         pThumbRequestData  = ( TNEThumbRequest * ) aMessage.Ptr0();
   340         // store thumb request parameters       	    
   333         // store thumb request parameters       	    
   341         iClientYUVBufferPtrPtr  = &(pThumbRequestData->iYUVBuffer);        
   334         iClientYUVBufferPtrPtr  = &(pThumbRequestData->iYUVBuffer);        
   342         iThumbIndex = pThumbRequestData->iIndex;
   335         iThumbIndex = pThumbRequestData->iIndex;
   343         if (iThumbIndex == KBestThumbIndex || iThumbIndex <= 1)
   336         if (iThumbIndex == KBestThumbIndex || iThumbIndex <= 1)
   348         else
   341         else
   349         {
   342         {
   350             m_State = EStartGettingThumbNailWithIndex;
   343             m_State = EStartGettingThumbNailWithIndex;
   351 			if (!ibOpenFilePending)
   344 			if (!ibOpenFilePending)
   352 			{
   345 			{
   353 			lError = ReOpenFile(iFileHandle); 
   346 			    lError = ReOpenFile(iFileHandle);
   354 			}                
   347 			    if(lError)
       
   348 			    {
       
   349 			        m_LastError = lError;
       
   350 			        CompleteRequest(iThumbnailRequest,m_LastError);
       
   351 			    }
       
   352 			}
       
   353 			else
       
   354 			{ // Previous thumbnail generation is in progress so cancel it	
       
   355 			    if(iUtil)
       
   356 			    {
       
   357 			        iUtil->CancelThumb();
       
   358 			    }
       
   359 			    // Once control returns and if m_State is EStartGettingThumbNailWithIndex call ReOpenFile
       
   360 			    iReOpenFileLPending = ETrue;
       
   361 			}
   355 		}
   362 		}
   356         break;
   363         break;
   357     case ECancelThumbmsg:
   364     case ECancelThumbmsg:
       
   365         FLOG(_L("CTneSession::DispatchMessageL cancellation ++"));
   358         iGetThumbPending = EFalse;
   366         iGetThumbPending = EFalse;
   359         iCancelRequest = aMessage;        
   367         iCancelRequest = aMessage;        
   360         m_State = ECancelling;               
   368         m_State = ECancelling;               
   361         if(iUtil)
   369         if(iUtil)
   362         {
   370         {
   363         	iUtil->CancelThumb();
   371         	iUtil->CancelThumb();
   364         }
   372         }
   365         // cancel any pending getthumb or openfile request.
   373         // cancel any pending getthumb or openfile request.
   366         lError = KErrCancel;
   374         lError = KErrCancel;
   367         CompleteRequest(lError); 
   375         CompleteRequest(iClientRequest,lError); 
       
   376         CompleteRequest(iThumbnailRequest,lError); 
   368         
   377         
   369         if (!ibOpenFilePending)
   378         if (!ibOpenFilePending)
   370         {
   379         {
   371            CompleteCancelRequest(); 
   380            CompleteCancelRequest(); 
   372         }
   381         }
       
   382         FLOG(_L("CTneSession::DispatchMessageL cancellation --"));
       
   383 
   373         break;    
   384         break;    
   374     
   385     
   375    default:
   386    default:
   376       //  PanicClient(aMessage,EBadRequest);
   387       //  PanicClient(aMessage,EBadRequest);
   377         return;
   388         return;
   378         
   389         
   379       }
   390       }
   380 
   391     
   381  	if (bCompleteRequest)
   392     // If any error occured during file opening
   382  	{
   393     if (bCompleteRequest)
   383  	    CompleteRequest(lError);
   394     {
   384 	}
   395         CompleteRequest(iClientRequest,lError);
   385 	
   396     }
   386 	FLOG(_L("CTneSession::DispatchMessageL out type=%d"), aMessage.Function());
   397 	FLOG(_L("CTneSession::DispatchMessageL out type=%d"), aMessage.Function());
   387 }
   398 }
   388 
   399 
   389 
   400 
   390 
   401 
   423     	{	
   434     	{	
   424     	    iUtil->CancelThumb();
   435     	    iUtil->CancelThumb();
   425     		delete iUtil;
   436     		delete iUtil;
   426     		iUtil = NULL;    						    
   437     		iUtil = NULL;    						    
   427     	}
   438     	}
   428     	    		   
   439     	
   429     	lError = DoOpenFile(aFileHandle, uStartingTime);    	    	    	
   440     	iPosition = uStartingTime;
   430     }   
   441     	
   431     
   442     	if(!IsActive())   		   
       
   443     	{
       
   444             iStatus = KRequestPending;
       
   445             SetActive();
       
   446             TRequestStatus* pStatus = &iStatus;
       
   447             User::RequestComplete(pStatus, KErrNone);
       
   448     	}
       
   449     }
       
   450    
   432     return lError; 	
   451     return lError; 	
   433 }
   452 }
   434 
   453 
   435 
   454 
   436 
   455 
   559 	    
   578 	    
   560 	    // only complete the request if there is any error. If the request is not completed here then 
   579 	    // only complete the request if there is any error. If the request is not completed here then 
   561 	    // it will be completed after the DoOpenFile() returns.	 
   580 	    // it will be completed after the DoOpenFile() returns.	 
   562 	    if (aError != KErrNone)
   581 	    if (aError != KErrNone)
   563 	    {
   582 	    {
   564 	     	CompleteRequest(aError);
   583 	     	CompleteRequest(iClientRequest,aError);
   565 	    }
   584 	    }
   566 	   
   585 	   
   567     }
   586     }
   568     FLOG(_L("CTneSession::MetaDataReady out aError=%d"), aError);      
   587     FLOG(_L("CTneSession::MetaDataReady out aError=%d"), aError);      
   569     m_bMetaDataReady = ETrue;          	
   588     m_bMetaDataReady = ETrue;    
   570 
   589     
       
   590     NotifyIfGetMetaDataPending(m_LastError);       
   571 }
   591 }
   572 
   592 
   573 
   593 
   574 void CTneSession::PacketReady(TInt aError, 
   594 void CTneSession::PacketReady(TInt aError, 
   575                 void *pData, TUint32 aDataSize)
   595                 void *pData, TUint32 aDataSize)
   581     	
   601     	
   582       }
   602       }
   583     if (m_State == ECancelling)
   603     if (m_State == ECancelling)
   584     {
   604     {
   585         FLOG(_L("CTneSession::PacketReady no op"));
   605         FLOG(_L("CTneSession::PacketReady no op"));
       
   606         // Calling cancel thumbnail
       
   607         if(iUtil)
       
   608         {
       
   609             iUtil->CancelThumb();
       
   610         }
       
   611         
   586         return;
   612         return;
   587     }
   613     }
   588     if(aDataSize < (iWidth*iHeight*3/2 ))  // check to avoid getting very low size
   614     if(aDataSize < (iWidth*iHeight*3/2 ))  // check to avoid getting very low size
   589     {
   615     {
   590     	if(m_uPacketsReceived == 0)
   616     	if(m_uPacketsReceived == 0)
   836 	return err;
   862 	return err;
   837 }
   863 }
   838 
   864 
   839 
   865 
   840 
   866 
   841 void CTneSession::CompleteRequest(TInt aError)
   867 void CTneSession::CompleteRequest(const RMessage2& aMessage, TInt aError)
   842 {
   868 {
   843      if(!iClientRequest.IsNull())
   869      if(!aMessage.IsNull())
   844      {
   870      {
   845         iClientRequest.Complete(aError);
   871          aMessage.Complete(aError);
   846      }
   872      }
   847 }
   873 }
   848 
   874 
   849 
   875 
   850 
   876 
   851 
   877 
   852 // ownership of pBitMap will be passed to Observer
   878 // ownership of pBitMap will be passed to Observer
   853 void CTneSession::NotifyIfGetThumbPending(TInt aError, TUint8 *&pYUVBuffer) 
   879 void CTneSession::NotifyIfGetThumbPending(TInt aError, TUint8 *&pYUVBuffer) 
   854 {
   880 {
   855 	if (iGetThumbPending && !iClientRequest.IsNull())
   881 	if (iGetThumbPending && !iThumbnailRequest.IsNull())
   856 	{		
   882 	{		
   857 		iGetThumbPending = EFalse;        
   883 		iGetThumbPending = EFalse;        
   858         *iClientYUVBufferPtrPtr = pYUVBuffer;
   884         *iClientYUVBufferPtrPtr = pYUVBuffer;
   859         iClientRequest.Complete(aError);
   885         iThumbnailRequest.Complete(aError);
   860     
   886     
   861 	}	
   887 	}	
   862 }
   888 }
   863 
   889 
       
   890 void CTneSession::NotifyIfGetMetaDataPending(TInt aError)
       
   891 {
       
   892     if (m_bMetaDataReady && !iMetaDataRequest.IsNull())
       
   893     {   
       
   894         TNEMetaData* pMetaData;
       
   895         pMetaData = ( TNEMetaData* ) iMetaDataRequest.Ptr0();
       
   896         pMetaData->iWidth = iWidth;
       
   897         pMetaData->iHeight = iHeight;
       
   898         pMetaData->iFrameCount = iFrameCount;
       
   899         iMetaDataRequest.Complete(aError);        
       
   900     }
       
   901     
       
   902 }
       
   903 
   864 void CTneSession::CompleteCancelRequest()
   904 void CTneSession::CompleteCancelRequest()
   865 {
   905 {
   866     
   906     FLOG(_L("CTneSession::CompleteCancelRequest ++"));
       
   907 
   867      if (!iCancelRequest.IsNull())
   908      if (!iCancelRequest.IsNull())
   868      {
   909      {
   869       
   910       
   870         if (iUtil)
   911         if (iUtil)
   871         {
   912         {
   872             delete iUtil;
   913             delete iUtil;
   873             iUtil = NULL;
   914             iUtil = NULL;
   874           }
   915         }
   875             iCancelRequest.Complete(KErrNone);
   916         iCancelRequest.Complete(KErrNone);
   876      }    
   917         FLOG(_L("CTneSession::CompleteCancelRequest message sent "));
   877 }
   918      }   
       
   919      FLOG(_L("CTneSession::CompleteCancelRequest --"));
       
   920 
       
   921 }
       
   922 // Open the file
       
   923 void CTneSession::RunL()
       
   924 {
       
   925     TInt lError = KErrNone;
       
   926     m_bDone = EFalse;
       
   927     m_uPacketsReceived = 0;
       
   928     FLOG(_L("CTneSession::RunL ++"));
       
   929 
       
   930     lError = DoOpenFile(iFileHandle, iPosition);
       
   931     
       
   932     if(lError)
       
   933     {
       
   934         if(iUtil)
       
   935         {
       
   936             iUtil->CancelThumb();
       
   937             delete iUtil;
       
   938             iUtil = NULL;
       
   939         }
       
   940     }
       
   941     
       
   942     CompleteRequest(iClientRequest,lError); // Complete client request 
       
   943     ibOpenFilePending = EFalse;
       
   944     // If any cancel call is pending
       
   945     CompleteCancelRequest();
       
   946     
       
   947     // This mean App has requested thumbnail based on index when last file opening was still in progress
       
   948     if(iReOpenFileLPending)
       
   949     {
       
   950       lError = ReOpenFile(iFileHandle);
       
   951       // If some error sent notification to client
       
   952       if(lError)
       
   953       {
       
   954           m_LastError = lError;
       
   955           CompleteRequest(iThumbnailRequest,m_LastError);
       
   956       }
       
   957       iReOpenFileLPending = EFalse;
       
   958     }
       
   959     FLOG(_L("CTneSession::RunL -- "));
       
   960 }
       
   961 
       
   962 // Cancel file open and pending thumbnail 
       
   963 void CTneSession::DoCancel()
       
   964 {
       
   965     CompleteRequest(iClientRequest,KErrCancel); // Complete any request
       
   966     CompleteRequest(iMetaDataRequest,KErrCancel); // Complete any request 
       
   967     CompleteRequest(iThumbnailRequest,KErrCancel); // Complete any request 
       
   968     ibOpenFilePending = EFalse;
       
   969     iReOpenFileLPending = EFalse;
       
   970     iGetThumbPending = EFalse;
       
   971     // If any cancel call is pending
       
   972     CompleteCancelRequest();
       
   973 
       
   974 }