1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <osl/endian.h>
22 #include <tools/string.hxx>
24 #include "sot/stg.hxx"
25 #include "stgelem.hxx"
26 #include "stgcache.hxx"
27 #include "stgstrms.hxx"
31 //#define CHECK_DIRTY 1
32 //#define READ_AFTER_WRITE 1
34 ////////////////////////////// class StgPage /////////////////////////////
35 // This class implements buffer functionality. The cache will always return
36 // a page buffer, even if a read fails. It is up to the caller to determine
37 // the correctness of the I/O.
39 StgPage::StgPage( short nSize
, sal_Int32 nPage
)
42 , mpData( new sal_uInt8
[ nSize
] )
45 OSL_ENSURE( mnSize
>= 512, "Unexpected page size is provided!" );
46 // We will write this data to a permanent file later
47 // best to clear if first.
48 memset( mpData
, 0, mnSize
);
56 rtl::Reference
< StgPage
> StgPage::Create( short nData
, sal_Int32 nPage
)
58 return rtl::Reference
< StgPage
>( new StgPage( nData
, nPage
) );
61 void StgCache::SetToPage ( const rtl::Reference
< StgPage
> xPage
, short nOff
, sal_Int32 nVal
)
63 if( ( nOff
< (short) ( xPage
->GetSize() / sizeof( sal_Int32
) ) ) && nOff
>= 0 )
66 nVal
= OSL_SWAPDWORD(nVal
);
68 ((sal_Int32
*) xPage
->GetData() )[ nOff
] = nVal
;
73 bool StgPage::IsPageGreater( const StgPage
*pA
, const StgPage
*pB
)
75 return pA
->mnPage
< pB
->mnPage
;
78 //////////////////////////////// class StgCache ////////////////////////////
80 // The disk cache holds the cached sectors. The sector type differ according
83 static sal_Int32
lcl_GetPageCount( sal_uLong nFileSize
, short nPageSize
)
85 // return (nFileSize >= 512) ? (nFileSize - 512) / nPageSize : 0;
86 // #i61980# reallife: last page may be incomplete, return number of *started* pages
87 return (nFileSize
>= 512) ? (nFileSize
- 512 + nPageSize
- 1) / nPageSize
: 0;
91 : nError( SVSTREAM_OK
)
95 , maLRUPages( 8 ) // entries in the LRU lookup
97 , pStorageStream( NULL
)
99 , bMyStream( sal_False
)
104 StgCache::~StgCache()
107 SetStrm( NULL
, sal_False
);
110 void StgCache::SetPhysPageSize( short n
)
112 OSL_ENSURE( n
>= 512, "Unexpecte page size is provided!" );
116 sal_uLong nPos
= pStrm
->Tell();
117 sal_uLong nFileSize
= pStrm
->Seek( STREAM_SEEK_TO_END
);
118 nPages
= lcl_GetPageCount( nFileSize
, nPageSize
);
123 // Create a new cache element
125 rtl::Reference
< StgPage
> StgCache::Create( sal_Int32 nPg
)
127 rtl::Reference
< StgPage
> xElem( StgPage::Create( nPageSize
, nPg
) );
128 maLRUPages
[ nReplaceIdx
++ % maLRUPages
.size() ] = xElem
;
132 // Delete the given element
134 void StgCache::Erase( const rtl::Reference
< StgPage
> &xElem
)
136 OSL_ENSURE( xElem
.is(), "The pointer should not be NULL!" );
138 for ( LRUList::iterator it
= maLRUPages
.begin(); it
!= maLRUPages
.end(); ++it
) {
139 if ( it
->is() && (*it
)->GetPage() == xElem
->GetPage() ) {
147 // remove all cache elements without flushing them
149 void StgCache::Clear()
151 maDirtyPages
.clear();
152 for ( LRUList::iterator it
= maLRUPages
.begin(); it
!= maLRUPages
.end(); ++it
)
156 // Look for a cached page
158 rtl::Reference
< StgPage
> StgCache::Find( sal_Int32 nPage
)
160 for ( LRUList::iterator it
= maLRUPages
.begin(); it
!= maLRUPages
.end(); ++it
)
161 if ( it
->is() && (*it
)->GetPage() == nPage
)
163 IndexToStgPage::iterator it2
= maDirtyPages
.find( nPage
);
164 if ( it2
!= maDirtyPages
.end() )
166 return rtl::Reference
< StgPage
>();
169 // Load a page into the cache
171 rtl::Reference
< StgPage
> StgCache::Get( sal_Int32 nPage
, sal_Bool bForce
)
173 rtl::Reference
< StgPage
> p
= Find( nPage
);
177 if( !Read( nPage
, p
->GetData(), 1 ) && bForce
)
181 SetError( SVSTREAM_READ_ERROR
);
187 // Copy an existing page into a new page. Use this routine
188 // to duplicate an existing stream or to create new entries.
189 // The new page is initially marked dirty. No owner is copied.
191 rtl::Reference
< StgPage
> StgCache::Copy( sal_Int32 nNew
, sal_Int32 nOld
)
193 rtl::Reference
< StgPage
> p
= Find( nNew
);
198 // old page: we must have this data!
199 rtl::Reference
< StgPage
> q
= Get( nOld
, sal_True
);
202 OSL_ENSURE( p
->GetSize() == q
->GetSize(), "Unexpected page size!" );
203 memcpy( p
->GetData(), q
->GetData(), p
->GetSize() );
211 // Historically this wrote pages in a sorted, ascending order;
212 // continue that tradition.
213 sal_Bool
StgCache::Commit()
215 if ( Good() ) // otherwise Write does nothing
217 std::vector
< StgPage
* > aToWrite
;
218 for ( IndexToStgPage::iterator aIt
= maDirtyPages
.begin();
219 aIt
!= maDirtyPages
.end(); ++aIt
)
220 aToWrite
.push_back( aIt
->second
.get() );
222 std::sort( aToWrite
.begin(), aToWrite
.end(), StgPage::IsPageGreater
);
223 for ( std::vector
< StgPage
* >::iterator aWr
= aToWrite
.begin();
224 aWr
!= aToWrite
.end(); ++aWr
)
226 const rtl::Reference
< StgPage
> &pPage
= *aWr
;
227 if ( !Write( pPage
->GetPage(), pPage
->GetData(), 1 ) )
232 maDirtyPages
.clear();
235 SetError( pStrm
->GetError() );
242 void StgCache::SetStrm( SvStream
* p
, sal_Bool bMy
)
246 pStorageStream
->ReleaseRef();
247 pStorageStream
= NULL
;
256 void StgCache::SetStrm( UCBStorageStream
* pStgStream
)
259 pStorageStream
->ReleaseRef();
260 pStorageStream
= pStgStream
;
267 if ( pStorageStream
)
269 pStorageStream
->AddRef();
270 pStrm
= pStorageStream
->GetModifySvStream();
273 bMyStream
= sal_False
;
276 void StgCache::SetDirty( const rtl::Reference
< StgPage
> &xPage
)
278 assert( IsWritable() );
279 maDirtyPages
[ xPage
->GetPage() ] = xPage
;
282 // Open/close the disk file
284 sal_Bool
StgCache::Open( const String
& rName
, StreamMode nMode
)
286 // do not open in exclusive mode!
287 if( nMode
& STREAM_SHARE_DENYALL
)
288 nMode
= ( ( nMode
& ~STREAM_SHARE_DENYALL
) | STREAM_SHARE_DENYWRITE
);
289 SvFileStream
* pFileStrm
= new SvFileStream( rName
, nMode
);
290 // SvStream "Feature" Write Open auch erfolgreich, wenns nicht klappt
291 sal_Bool bAccessDenied
= sal_False
;
292 if( ( nMode
& STREAM_WRITE
) && !pFileStrm
->IsWritable() )
295 bAccessDenied
= sal_True
;
297 SetStrm( pFileStrm
, sal_True
);
298 if( pFileStrm
->IsOpen() )
300 sal_uLong nFileSize
= pStrm
->Seek( STREAM_SEEK_TO_END
);
301 nPages
= lcl_GetPageCount( nFileSize
, nPageSize
);
307 SetError( bAccessDenied
? ERRCODE_IO_ACCESSDENIED
: pStrm
->GetError() );
311 void StgCache::Close()
315 ((SvFileStream
*) pStrm
)->Close();
316 SetError( pStrm
->GetError() );
322 sal_Bool
StgCache::Read( sal_Int32 nPage
, void* pBuf
, sal_Int32 nPg
)
326 /* #i73846# real life: a storage may refer to a page one-behind the
327 last valid page (see document attached to the issue). In that case
328 (if nPage==nPages), just do nothing here and let the caller work on
329 the empty zero-filled buffer. */
330 if ( nPage
> nPages
)
331 SetError( SVSTREAM_READ_ERROR
);
332 else if ( nPage
< nPages
)
334 sal_uLong nPos
= Page2Pos( nPage
);
335 sal_Int32 nPg2
= ( ( nPage
+ nPg
) > nPages
) ? nPages
- nPage
: nPg
;
336 sal_uLong nBytes
= nPg2
* nPageSize
;
337 // fixed address and size for the header
340 nPos
= 0L, nBytes
= 512;
343 if( pStrm
->Tell() != nPos
)
345 if( pStrm
->Seek( nPos
) != nPos
) {
347 ErrorBox( NULL
, WB_OK
, String("SO2: Seek failed") ).Execute();
351 pStrm
->Read( pBuf
, nBytes
);
353 SetError( SVSTREAM_READ_ERROR
);
355 SetError( pStrm
->GetError() );
361 sal_Bool
StgCache::Write( sal_Int32 nPage
, void* pBuf
, sal_Int32 nPg
)
365 sal_uLong nPos
= Page2Pos( nPage
);
366 sal_uLong nBytes
= 0;
367 if ( SAL_MAX_INT32
/ nPg
> nPageSize
)
368 nBytes
= nPg
* nPageSize
;
370 // fixed address and size for the header
371 // nPageSize must be >= 512, otherwise the header can not be written here, we check it on import
373 nPos
= 0L, nBytes
= 512;
374 if( pStrm
->Tell() != nPos
)
376 if( pStrm
->Seek( nPos
) != nPos
) {
378 ErrorBox( NULL
, WB_OK
, String("SO2: Seek failed") ).Execute();
382 sal_uLong nRes
= pStrm
->Write( pBuf
, nBytes
);
384 SetError( SVSTREAM_WRITE_ERROR
);
386 SetError( pStrm
->GetError() );
387 #ifdef READ_AFTER_WRITE
388 sal_uInt8 cBuf
[ 512 ];
391 sal_Bool bRes
= ( pStrm
->Read( cBuf
, 512 ) == 512 );
393 bRes
= !memcmp( cBuf
, pBuf
, 512 );
396 ErrorBox( NULL
, WB_OK
, String("SO2: Read after Write failed") ).Execute();
397 pStrm
->SetError( SVSTREAM_WRITE_ERROR
);
404 // set the file size in pages
406 sal_Bool
StgCache::SetSize( sal_Int32 n
)
408 // Add the file header
409 sal_Int32 nSize
= n
* nPageSize
+ 512;
410 pStrm
->SetStreamSize( nSize
);
411 SetError( pStrm
->GetError() );
417 void StgCache::SetError( sal_uLong n
)
423 void StgCache::ResetError()
425 nError
= SVSTREAM_OK
;
429 void StgCache::MoveError( StorageBase
& r
)
431 if( nError
!= SVSTREAM_OK
)
433 r
.SetError( nError
);
440 sal_Int32
StgCache::Page2Pos( sal_Int32 nPage
)
442 if( nPage
< 0 ) nPage
= 0;
443 return( nPage
* nPageSize
) + nPageSize
;
446 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */