33 #include "thumbnailpanic.h" |
33 #include "thumbnailpanic.h" |
34 #include "thumbnailmanagerconstants.h" |
34 #include "thumbnailmanagerconstants.h" |
35 #include "thumbnailserver.h" |
35 #include "thumbnailserver.h" |
36 |
36 |
37 |
37 |
38 _LIT8( KThumbnailSqlConfig, "page_size=16384; cache_size=32;" ); |
38 _LIT8( KThumbnailSqlConfig, "page_size=1024; cache_size=32;" ); |
39 |
39 |
40 const TInt KStreamBufferSize = 1024 * 8; |
40 const TInt KStreamBufferSize = 1024 * 8; |
41 const TInt KMajor = 3; |
41 const TInt KMajor = 3; |
42 const TInt KMinor = 2; |
42 const TInt KMinor = 2; |
43 |
43 |
44 const TInt KStoreUnrecoverableErr = KErrCorrupt; |
44 const TInt KStoreUnrecoverableErr = KErrCorrupt; |
45 |
45 |
46 // Database path without drive letter |
46 // Database path without drive letter |
47 _LIT( KThumbnailDatabaseName, ":[102830AB]thumbnail_v3.db" ); |
47 _LIT( KThumbnailDatabaseName, ":[102830AB]thumbnail_v4.db" ); |
48 |
48 |
49 _LIT( KDrv, ":"); |
49 _LIT( KDrv, ":"); |
50 |
50 |
51 // Allow access to database only for the server process |
51 // Allow access to database only for the server process |
52 const TSecurityPolicy KThumbnailDatabaseSecurityPolicy( TSecureId( |
52 const TSecurityPolicy KThumbnailDatabaseSecurityPolicy( TSecureId( |
165 { |
165 { |
166 delete iDiskFullNotifier; |
166 delete iDiskFullNotifier; |
167 iDiskFullNotifier = NULL; |
167 iDiskFullNotifier = NULL; |
168 } |
168 } |
169 |
169 |
170 if(!iServer->IsFormatting()) |
|
171 { |
|
172 FlushCacheTable( ETrue ); |
|
173 } |
|
174 if( iAutoFlushTimer ) |
170 if( iAutoFlushTimer ) |
175 { |
171 { |
176 iAutoFlushTimer->Cancel(); |
172 iAutoFlushTimer->Cancel(); |
177 delete iAutoFlushTimer; |
173 delete iAutoFlushTimer; |
178 iAutoFlushTimer = NULL; |
174 iAutoFlushTimer = NULL; |
179 } |
175 } |
180 |
176 |
|
177 if( iMaintenanceTimer ) |
|
178 { |
|
179 iMaintenanceTimer->Cancel(); |
|
180 delete iMaintenanceTimer; |
|
181 iMaintenanceTimer = NULL; |
|
182 } |
|
183 |
|
184 if(!iServer->IsFormatting()) |
|
185 { |
|
186 FlushCacheTable( ETrue ); |
|
187 } |
|
188 |
181 CloseStatements(); |
189 CloseStatements(); |
182 iDatabase.Close(); |
190 iDatabase.Close(); |
183 |
191 |
184 TN_DEBUG1( "CThumbnailStore::~CThumbnailStore() - database closed" ); |
192 TN_DEBUG1( "CThumbnailStore::~CThumbnailStore() - database closed" ); |
185 } |
193 } |
189 // C++ default constructor can NOT contain any code, that might leave. |
197 // C++ default constructor can NOT contain any code, that might leave. |
190 // --------------------------------------------------------------------------- |
198 // --------------------------------------------------------------------------- |
191 // |
199 // |
192 CThumbnailStore::CThumbnailStore( RFs& aFs, TInt aDrive, TDesC& aImei, CThumbnailServer* aServer ): |
200 CThumbnailStore::CThumbnailStore( RFs& aFs, TInt aDrive, TDesC& aImei, CThumbnailServer* aServer ): |
193 iFs( aFs ), iDrive( aDrive ), iDriveChar( 0 ), iBatchItemCount(0), iImei(aImei), |
201 iFs( aFs ), iDrive( aDrive ), iDriveChar( 0 ), iBatchItemCount(0), iImei(aImei), |
194 iServer(aServer), iDiskFull(EFalse), iUnrecoverable(ETrue) |
202 iServer(aServer), iDiskFull(EFalse), iUnrecoverable(ETrue), iBatchFlushItemCount(KMInBatchItems) |
195 { |
203 { |
196 // no implementation required |
204 // no implementation required |
197 } |
205 } |
198 |
206 |
199 // --------------------------------------------------------------------------- |
207 // --------------------------------------------------------------------------- |
291 if(checkError == KErrNone) |
299 if(checkError == KErrNone) |
292 { |
300 { |
293 checkError = CheckRowIDs(); |
301 checkError = CheckRowIDs(); |
294 } |
302 } |
295 } |
303 } |
296 else |
304 |
297 { |
305 // if db file not found, wrong version, corrupted database or other error opening db |
298 // if db file not found, wrong version, corrupted database or other error opening db |
306 if ( err != KErrNone || checkError == KErrNotSupported ) |
299 if ( checkError == KErrNotSupported || err != KErrNone ) |
307 { |
300 { |
308 CleanupClosePushL(iDatabase); |
301 CleanupClosePushL(iDatabase); |
309 RecreateDatabaseL(ETrue); |
302 RecreateDatabaseL(ETrue); |
310 CleanupStack::Pop(&iDatabase); |
303 CleanupStack::Pop(&iDatabase); |
311 |
304 |
312 aNewDatabase = ETrue; |
305 aNewDatabase = ETrue; |
313 } |
306 } |
|
307 } |
|
308 } |
314 } |
309 |
315 |
310 // opened existing database file |
316 // opened existing database file |
311 if(!aNewDatabase) |
317 if(!aNewDatabase) |
312 { |
318 { |
330 { |
336 { |
331 //Touch blacklisted items |
337 //Touch blacklisted items |
332 TRAP(blistError2, PrepareBlacklistedItemsForRetryL() ); |
338 TRAP(blistError2, PrepareBlacklistedItemsForRetryL() ); |
333 } |
339 } |
334 |
340 |
335 if(imeiError == KSqlErrCorrupt || imeiError == KErrCorrupt || |
341 if(imeiError != KErrNone || blistError != KErrNone || blistError2 != KErrNone ) |
336 blistError == KSqlErrCorrupt || blistError == KErrCorrupt || |
|
337 blistError2 == KSqlErrCorrupt || blistError2 == KErrCorrupt ) |
|
338 { |
342 { |
339 CleanupClosePushL(iDatabase); |
343 CleanupClosePushL(iDatabase); |
340 RecreateDatabaseL(ETrue); |
344 RecreateDatabaseL(ETrue); |
341 CleanupStack::Pop(&iDatabase); |
345 CleanupStack::Pop(&iDatabase); |
342 } |
346 } |
743 |
747 |
744 // ----------------------------------------------------------------------------- |
748 // ----------------------------------------------------------------------------- |
745 // UpdateImeiL() |
749 // UpdateImeiL() |
746 // ----------------------------------------------------------------------------- |
750 // ----------------------------------------------------------------------------- |
747 // |
751 // |
748 TInt CThumbnailStore::UpdateImeiL() |
752 void CThumbnailStore::UpdateImeiL() |
749 { |
753 { |
750 TN_DEBUG1( "CThumbnailStore::UpdateImeiL()" ); |
754 TN_DEBUG1( "CThumbnailStore::UpdateImeiL()" ); |
751 RSqlStatement stmt; |
755 RSqlStatement stmt; |
752 CleanupClosePushL( stmt ); |
756 CleanupClosePushL( stmt ); |
753 |
757 |
754 TInt ret = stmt.Prepare( iDatabase, KThumbnailUpdateIMEI ); |
758 User::LeaveIfError( stmt.Prepare( iDatabase, KThumbnailUpdateIMEI ) ); |
755 |
759 |
756 TInt paramIndex = stmt.ParameterIndex( KThumbnailSqlParamImei ); |
760 TInt paramIndex = stmt.ParameterIndex( KThumbnailSqlParamImei ); |
757 User::LeaveIfError( paramIndex ); |
761 User::LeaveIfError( paramIndex ); |
758 User::LeaveIfError( stmt.BindText( paramIndex, iImei )); |
762 User::LeaveIfError( stmt.BindText( paramIndex, iImei )); |
759 |
763 |
760 TInt err = stmt.Exec(); |
764 TInt err = stmt.Exec(); |
761 |
765 |
762 if(err < 0) |
766 if(err < 0) |
763 { |
767 { |
764 #ifdef _DEBUG |
768 #ifdef _DEBUG |
765 TPtrC errorMsg2 = iDatabase.LastErrorMessage(); |
769 TPtrC errorMsg = iDatabase.LastErrorMessage(); |
766 TN_DEBUG2( "RThumbnailTransaction::ResetThumbnailIDs() lastError %S, ret = %d" , &errorMsg2); |
770 TN_DEBUG2( "RThumbnailTransaction::UpdateImeiL() lastError %S" , &errorMsg); |
767 #endif |
771 #endif |
768 return ret; |
772 User::Leave(err); |
769 } |
773 } |
770 |
774 |
771 CleanupStack::PopAndDestroy( &stmt ); |
775 CleanupStack::PopAndDestroy( &stmt ); |
772 return KErrNone; |
|
773 } |
776 } |
774 |
777 |
775 // --------------------------------------------------------------------------- |
778 // --------------------------------------------------------------------------- |
776 // CThumbnailStore::PrepareStatementsL() |
779 // CThumbnailStore::PrepareStatementsL() |
777 // --------------------------------------------------------------------------- |
780 // --------------------------------------------------------------------------- |
955 |
958 |
956 err = iStmt_KThumbnailSelectAllPaths.Prepare( iDatabase, KThumbnailSelectAllPaths ); |
959 err = iStmt_KThumbnailSelectAllPaths.Prepare( iDatabase, KThumbnailSelectAllPaths ); |
957 #ifdef _DEBUG |
960 #ifdef _DEBUG |
958 msg.Append( iDatabase.LastErrorMessage() ); |
961 msg.Append( iDatabase.LastErrorMessage() ); |
959 TN_DEBUG2( "CThumbnailStore::PrepareStatementsL() KThumbnailSelectAllPaths %S" , &msg ); |
962 TN_DEBUG2( "CThumbnailStore::PrepareStatementsL() KThumbnailSelectAllPaths %S" , &msg ); |
|
963 msg.Zero(); |
|
964 #endif |
|
965 User::LeaveIfError( err ); |
|
966 |
|
967 err = iStmt_KThumbnailRename.Prepare( iDatabase, KThumbnailRename ); |
|
968 #ifdef _DEBUG |
|
969 msg.Append( iDatabase.LastErrorMessage() ); |
|
970 TN_DEBUG2( "CThumbnailStore::PrepareStatementsL() KThumbnailRename %S" , &msg ); |
|
971 msg.Zero(); |
|
972 #endif |
|
973 User::LeaveIfError( err ); |
|
974 |
|
975 err = iStmt_KThumbnailTempRename.Prepare( iDatabase, KThumbnailTempRename ); |
|
976 #ifdef _DEBUG |
|
977 msg.Append( iDatabase.LastErrorMessage() ); |
|
978 TN_DEBUG2( "CThumbnailStore::PrepareStatementsL() KThumbnailTempRename %S" , &msg ); |
960 msg.Zero(); |
979 msg.Zero(); |
961 #endif |
980 #endif |
962 User::LeaveIfError( err ); |
981 User::LeaveIfError( err ); |
963 |
982 |
964 TN_DEBUG1("CThumbnailStore::PrepareStatementsL() end"); |
983 TN_DEBUG1("CThumbnailStore::PrepareStatementsL() end"); |
1003 iStmt_KThumbnailSqlInsertDeleted.Close(); |
1022 iStmt_KThumbnailSqlInsertDeleted.Close(); |
1004 iStmt_KThumbnailSqlSelectMarked.Close(); |
1023 iStmt_KThumbnailSqlSelectMarked.Close(); |
1005 iStmt_KThumbnailSqlDeleteInfoByRowID.Close(); |
1024 iStmt_KThumbnailSqlDeleteInfoByRowID.Close(); |
1006 iStmt_KThumbnailSqlDeleteInfoDataByRowID.Close(); |
1025 iStmt_KThumbnailSqlDeleteInfoDataByRowID.Close(); |
1007 iStmt_KThumbnailSelectAllPaths.Close(); |
1026 iStmt_KThumbnailSelectAllPaths.Close(); |
|
1027 iStmt_KThumbnailRename.Close(); |
|
1028 iStmt_KThumbnailTempRename.Close(); |
1008 |
1029 |
1009 TN_DEBUG1("CThumbnailStore::CloseStatements() end"); |
1030 TN_DEBUG1("CThumbnailStore::CloseStatements() end"); |
1010 } |
1031 } |
1011 |
1032 |
1012 // --------------------------------------------------------------------------- |
1033 // --------------------------------------------------------------------------- |
1345 TN_DEBUG1( "CThumbnailStore::FindDuplicateL() - duplicate in temp table" ); |
1366 TN_DEBUG1( "CThumbnailStore::FindDuplicateL() - duplicate in temp table" ); |
1346 |
1367 |
1347 found = ETrue; |
1368 found = ETrue; |
1348 } |
1369 } |
1349 |
1370 |
|
1371 CleanupStack::PopAndDestroy( stmt ); |
|
1372 |
1350 // check if duplicate in Deleted |
1373 // check if duplicate in Deleted |
1351 if (found) |
1374 if (found) |
1352 { |
1375 { |
1353 CleanupStack::PopAndDestroy( stmt ); |
|
1354 stmt = &iStmt_KThumbnailSqlFindDeleted; |
1376 stmt = &iStmt_KThumbnailSqlFindDeleted; |
1355 CleanupStack::PushL(TCleanupItem(ResetStatement, stmt)); |
1377 CleanupStack::PushL(TCleanupItem(ResetStatement, stmt)); |
1356 |
1378 |
1357 paramIndex = stmt->ParameterIndex( KThumbnailSqlParamPath ); |
1379 paramIndex = stmt->ParameterIndex( KThumbnailSqlParamPath ); |
1358 User::LeaveIfError( paramIndex ); |
1380 User::LeaveIfError( paramIndex ); |
1359 User::LeaveIfError( stmt->BindText( paramIndex, aPath )); |
1381 User::LeaveIfError( stmt->BindText( paramIndex, aPath )); |
1360 |
1382 |
1361 rowStatus = stmt->Next(); |
1383 rowStatus = stmt->Next(); |
|
1384 |
|
1385 CleanupStack::PopAndDestroy( stmt ); |
1362 |
1386 |
1363 if(rowStatus == KSqlAtRow) |
1387 if(rowStatus == KSqlAtRow) |
1364 { |
1388 { |
1365 TN_DEBUG1( "CThumbnailStore::FindDuplicateL() - duplicate marked deleted" ); |
1389 TN_DEBUG1( "CThumbnailStore::FindDuplicateL() - duplicate marked deleted" ); |
1366 |
1390 |
1693 User::LeaveIfError( stmt->BindText( paramIndex, *path )); |
1715 User::LeaveIfError( stmt->BindText( paramIndex, *path )); |
1694 |
1716 |
1695 rowStatus = stmt->Next(); |
1717 rowStatus = stmt->Next(); |
1696 } |
1718 } |
1697 |
1719 |
|
1720 CleanupStack::PopAndDestroy( stmt_infodata ); |
|
1721 CleanupStack::PopAndDestroy( stmt_info ); |
1698 CleanupStack::PopAndDestroy( stmt ); |
1722 CleanupStack::PopAndDestroy( stmt ); |
1699 CleanupStack::PopAndDestroy( stmt_info ); |
1723 |
1700 CleanupStack::PopAndDestroy( stmt_infodata ); |
|
1701 |
|
1702 // if forcing instant delete |
1724 // if forcing instant delete |
1703 if (aForce) |
1725 if (aForce) |
1704 { |
1726 { |
1705 //look from real table |
1727 //look from real table |
1706 TN_DEBUG1( "CThumbnailStore::DeleteThumbnailByPathL() -- MAIN TABLE lookup" ); |
1728 TN_DEBUG1( "CThumbnailStore::DeleteThumbnailByPathL() -- MAIN TABLE lookup" ); |
1781 } |
1806 } |
1782 |
1807 |
1783 CleanupStack::PopAndDestroy( path ); |
1808 CleanupStack::PopAndDestroy( path ); |
1784 } |
1809 } |
1785 |
1810 |
|
1811 // ----------------------------------------------------------------------------- |
|
1812 // Rename thumbnails |
|
1813 // ----------------------------------------------------------------------------- |
|
1814 // |
|
1815 void CThumbnailStore::RenameThumbnailsL( const TDesC& aCurrentPath, const TDesC& aNewPath ) |
|
1816 { |
|
1817 TN_DEBUG2( "CThumbnailStore::RenameThumbnailsL(%S)", &aCurrentPath ); |
|
1818 |
|
1819 #ifdef _DEBUG |
|
1820 TTime aStart, aStop; |
|
1821 aStart.UniversalTime(); |
|
1822 #endif |
|
1823 |
|
1824 User::LeaveIfError( CheckDbState() ); |
|
1825 |
|
1826 TInt paramIndex = 0; |
|
1827 |
|
1828 HBufC* path = aCurrentPath.AllocLC(); |
|
1829 TPtr ptr(path->Des()); |
|
1830 StripDriveLetterL( ptr ); |
|
1831 |
|
1832 HBufC* newPath = aNewPath.AllocLC(); |
|
1833 TPtr ptr2(newPath->Des()); |
|
1834 StripDriveLetterL( ptr2 ); |
|
1835 |
|
1836 RThumbnailTransaction transaction( iDatabase ); |
|
1837 CleanupClosePushL( transaction ); |
|
1838 transaction.BeginL(); |
|
1839 |
|
1840 TN_DEBUG1( "CThumbnailStore::RenameThumbnailsL() -- TEMP TABLE" ); |
|
1841 |
|
1842 RSqlStatement* stmt = NULL; |
|
1843 stmt = &iStmt_KThumbnailTempRename; |
|
1844 CleanupStack::PushL(TCleanupItem(ResetStatement, stmt)); |
|
1845 |
|
1846 paramIndex = stmt->ParameterIndex( KThumbnailSqlParamPath ); |
|
1847 User::LeaveIfError( paramIndex ); |
|
1848 User::LeaveIfError( stmt->BindText( paramIndex, *path )); |
|
1849 |
|
1850 paramIndex = stmt->ParameterIndex( KThumbnailSqlParamNewPath ); |
|
1851 User::LeaveIfError( paramIndex ); |
|
1852 User::LeaveIfError( stmt->BindText( paramIndex, *newPath )); |
|
1853 |
|
1854 User::LeaveIfError( stmt->Exec() ); |
|
1855 |
|
1856 TN_DEBUG1( "CThumbnailStore::RenameThumbnailsL() -- MAIN TABLE" ); |
|
1857 |
|
1858 CleanupStack::PopAndDestroy( stmt ); |
|
1859 stmt = &iStmt_KThumbnailRename; |
|
1860 CleanupStack::PushL(TCleanupItem(ResetStatement, stmt)); |
|
1861 |
|
1862 paramIndex = stmt->ParameterIndex( KThumbnailSqlParamPath ); |
|
1863 User::LeaveIfError( paramIndex ); |
|
1864 User::LeaveIfError( stmt->BindText( paramIndex, *path )); |
|
1865 |
|
1866 paramIndex = stmt->ParameterIndex( KThumbnailSqlParamNewPath ); |
|
1867 User::LeaveIfError( paramIndex ); |
|
1868 User::LeaveIfError( stmt->BindText( paramIndex, *newPath )); |
|
1869 |
|
1870 User::LeaveIfError( stmt->Exec() ); |
|
1871 |
|
1872 CleanupStack::PopAndDestroy( stmt ); |
|
1873 |
|
1874 // if thumb was for some reason already marked deleted, clean from deleted |
|
1875 User::LeaveIfError( iDatabase.Exec( KThumbnailSqlDeleteFromDeleted ) ); |
|
1876 |
|
1877 transaction.CommitL(); |
|
1878 CleanupStack::PopAndDestroy( &transaction ); |
|
1879 |
|
1880 CleanupStack::PopAndDestroy( newPath ); |
|
1881 CleanupStack::PopAndDestroy( path ); |
|
1882 |
|
1883 #ifdef _DEBUG |
|
1884 aStop.UniversalTime(); |
|
1885 TN_DEBUG2( "CThumbnailStore::RenameThumbnailsL() took %d ms", (TInt)aStop.MicroSecondsFrom(aStart).Int64()/1000); |
|
1886 #endif |
|
1887 } |
|
1888 |
1786 // --------------------------------------------------------------------------- |
1889 // --------------------------------------------------------------------------- |
1787 // CThumbnailStore::PersistentSizes() |
1890 // CThumbnailStore::PersistentSizes() |
1788 // --------------------------------------------------------------------------- |
1891 // --------------------------------------------------------------------------- |
1789 // |
1892 // |
1790 void CThumbnailStore::SetPersistentSizes(const RArray < TThumbnailPersistentSize > &aSizes) |
1893 void CThumbnailStore::SetPersistentSizes(const RArray < TThumbnailPersistentSize > &aSizes) |
1806 { |
1909 { |
1807 // cache empty or db unusable |
1910 // cache empty or db unusable |
1808 return; |
1911 return; |
1809 } |
1912 } |
1810 |
1913 |
1811 if(iBatchItemCount < KMaxBatchItems && !aForce) |
1914 if(iBatchItemCount < iBatchFlushItemCount && !aForce) |
1812 { |
1915 { |
1813 //some items in cache |
1916 //some items in cache |
1814 StartAutoFlush(); |
1917 StartAutoFlush(); |
1815 return; |
1918 return; |
1816 } |
1919 } |
1817 |
1920 |
1818 #ifdef _DEBUG |
1921 |
1819 TTime aStart, aStop; |
1922 iStartFlush.UniversalTime(); |
1820 aStart.UniversalTime(); |
|
1821 #endif |
|
1822 |
1923 |
1823 // Move data from temp table to main.... |
1924 // Move data from temp table to main.... |
1824 TInt err_begin = iDatabase.Exec( KThumbnailBeginTransaction ); |
1925 TInt err_begin = iDatabase.Exec( KThumbnailBeginTransaction ); |
1825 TN_DEBUG2("CThumbnailStore::FlushCacheTable() KThumbnailBeginTransaction %d", err_begin); |
1926 TN_DEBUG2("CThumbnailStore::FlushCacheTable() KThumbnailBeginTransaction %d", err_begin); |
1826 |
1927 |
1870 |
1971 |
1871 // open new |
1972 // open new |
1872 TRAP_IGNORE(OpenDatabaseL(ETrue)); |
1973 TRAP_IGNORE(OpenDatabaseL(ETrue)); |
1873 } |
1974 } |
1874 |
1975 |
|
1976 //adjust batch size dynamically between min and max based on read flush speed. |
|
1977 iStopFlush.UniversalTime(); |
|
1978 TInt aFlushDelay = (TInt)iStopFlush.MicroSecondsFrom(iStartFlush).Int64()/1000; |
|
1979 TN_DEBUG2( "CThumbnailStore::FlushCacheTable() took %d ms", aFlushDelay); |
|
1980 |
1875 //cache flushed |
1981 //cache flushed |
1876 iBatchItemCount = 0; |
1982 iBatchItemCount = 0; |
1877 |
1983 |
1878 #ifdef _DEBUG |
1984 //increase batch count if there room for one more item (based on average time per item) |
1879 aStop.UniversalTime(); |
1985 if( aFlushDelay < KMaxFlushDelay && iBatchFlushItemCount < KMaxBatchItems ) |
1880 TN_DEBUG2( "CThumbnailStore::FlushCacheTable() took %d ms", (TInt)aStop.MicroSecondsFrom(aStart).Int64()/1000); |
1986 { |
1881 #endif |
1987 iBatchFlushItemCount++; |
1882 |
1988 } |
1883 TN_DEBUG1("CThumbnailStore::FlushCacheTable() out"); |
1989 //decrease batch count if we exeeced max time allowed in flushing the TEMP table |
|
1990 else if(aFlushDelay > KMaxFlushDelay && iBatchFlushItemCount > KMInBatchItems ) |
|
1991 { |
|
1992 iBatchFlushItemCount--; |
|
1993 } |
|
1994 |
|
1995 TN_DEBUG2("CThumbnailStore::FlushCacheTable() out iBatchFlushItemCount = %d", iBatchFlushItemCount); |
1884 } |
1996 } |
1885 |
1997 |
1886 // ----------------------------------------------------------------------------- |
1998 // ----------------------------------------------------------------------------- |
1887 // StartAutoFlush() |
1999 // StartAutoFlush() |
1888 // ----------------------------------------------------------------------------- |
2000 // ----------------------------------------------------------------------------- |