1572 lines
57 KiB
C++
1572 lines
57 KiB
C++
/*************************************************************************
|
|
*
|
|
* $RCSfile: ZipPackage.cxx,v $
|
|
*
|
|
* $Revision: 1.60 $
|
|
*
|
|
* last change: $Author: mtg $ $Date: 2001-09-03 13:58:24 $
|
|
*
|
|
* The Contents of this file are made available subject to the terms of
|
|
* either of the following licenses
|
|
*
|
|
* - GNU Lesser General Public License Version 2.1
|
|
* - Sun Industry Standards Source License Version 1.1
|
|
*
|
|
* Sun Microsystems Inc., October, 2000
|
|
*
|
|
* GNU Lesser General Public License Version 2.1
|
|
* =============================================
|
|
* Copyright 2000 by Sun Microsystems, Inc.
|
|
* 901 San Antonio Road, Palo Alto, CA 94303, USA
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License version 2.1, as published by the Free Software Foundation.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
|
* MA 02111-1307 USA
|
|
*
|
|
*
|
|
* Sun Industry Standards Source License Version 1.1
|
|
* =================================================
|
|
* The contents of this file are subject to the Sun Industry Standards
|
|
* Source License Version 1.1 (the "License"); You may not use this file
|
|
* except in compliance with the License. You may obtain a copy of the
|
|
* License at http://www.openoffice.org/license.html.
|
|
*
|
|
* Software provided under this License is provided on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
|
* WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
|
|
* MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
|
|
* See the License for the specific provisions governing your rights and
|
|
* obligations concerning the Software.
|
|
*
|
|
* The Initial Developer of the Original Code is: Sun Microsystems, Inc.
|
|
*
|
|
* Copyright: 2000 by Sun Microsystems, Inc.
|
|
*
|
|
* All Rights Reserved.
|
|
*
|
|
* Contributor(s): Martin Gallwey (gallwey@sun.com)
|
|
*
|
|
*
|
|
************************************************************************/
|
|
#ifndef _ZIP_PACKAGE_HXX
|
|
#include <ZipPackage.hxx>
|
|
#endif
|
|
#ifndef _ZIP_PACKAGE_SINK_HXX
|
|
#include <ZipPackageSink.hxx>
|
|
#endif
|
|
#ifndef _ZIP_ENUMERATION_HXX
|
|
#include <ZipEnumeration.hxx>
|
|
#endif
|
|
#ifndef _ZIP_PACKAGE_STREAM_HXX
|
|
#include <ZipPackageStream.hxx>
|
|
#endif
|
|
#ifndef _ZIP_PACKAGE_FOLDER_HXX
|
|
#include <ZipPackageFolder.hxx>
|
|
#endif
|
|
#ifndef _ZIP_OUTPUT_STREAM_HXX
|
|
#include <ZipOutputStream.hxx>
|
|
#endif
|
|
#ifndef _ZIP_FILE_HXX
|
|
#include <ZipFile.hxx>
|
|
#endif
|
|
#ifndef _THREADED_BUFFER_HXX
|
|
#include <ThreadedBuffer.hxx>
|
|
#endif
|
|
#ifndef _PACKAGE_CONSTANTS_HXX_
|
|
#include <PackageConstants.hxx>
|
|
#endif
|
|
#ifndef _COM_SUN_STAR_BEANS_PROPERTYVALUE_HPP_
|
|
#include <com/sun/star/beans/PropertyValue.hpp>
|
|
#endif
|
|
#ifndef _COM_SUN_STAR_PACKAGES_ZIP_ZIPCONSTANTS_HPP_
|
|
#include <com/sun/star/packages/zip/ZipConstants.hpp>
|
|
#endif
|
|
#ifndef _COM_SUN_STAR_PACKAGES_MANIFEST_XMANIFESTREADER_HPP_
|
|
#include <com/sun/star/packages/manifest/XManifestReader.hpp>
|
|
#endif
|
|
#ifndef _COM_SUN_STAR_UCB_INTERACTIVEAUGMENTEDIOEXCEPTION_HPP_
|
|
#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
|
|
#endif
|
|
#ifndef _COM_SUN_STAR_UCB_INTERACTIVEWRONGMEDIUMEXCEPTION_HPP_
|
|
#include <com/sun/star/ucb/InteractiveWrongMediumException.hpp>
|
|
#endif
|
|
#ifndef _COM_SUN_STAR_UCB_IOERRORCODE_HPP
|
|
#include <com/sun/star/ucb/IOErrorCode.hpp>
|
|
#endif
|
|
#ifndef _RTL_USTRBUF_HXX_
|
|
#include <rtl/ustrbuf.hxx>
|
|
#endif
|
|
#ifndef _INTERACTION_REQUEST_HXX_
|
|
#include <InteractionRequest.hxx>
|
|
#endif
|
|
#include <memory>
|
|
|
|
using namespace rtl;
|
|
using namespace ucb;
|
|
using namespace std;
|
|
using namespace osl;
|
|
using namespace cppu;
|
|
using namespace com::sun::star::io;
|
|
using namespace com::sun::star::util;
|
|
using namespace com::sun::star::uno;
|
|
using namespace com::sun::star::ucb;
|
|
using namespace com::sun::star::util;
|
|
using namespace com::sun::star::lang;
|
|
using namespace com::sun::star::task;
|
|
using namespace com::sun::star::beans;
|
|
using namespace com::sun::star::packages;
|
|
using namespace com::sun::star::registry;
|
|
using namespace com::sun::star::container;
|
|
using namespace com::sun::star::registry;
|
|
using namespace com::sun::star::container;
|
|
using namespace com::sun::star::packages::zip;
|
|
using namespace com::sun::star::packages::manifest;
|
|
using namespace com::sun::star::packages::zip::ZipConstants;
|
|
|
|
struct SuffixGenerator
|
|
{
|
|
sal_Char mpSuffix[30], *mpSuffixMid, *mpSuffixEnd;
|
|
sal_uInt16 mnDigits;
|
|
SuffixGenerator(): mnDigits (2), mpSuffixMid ( mpSuffix), mpSuffixEnd ( mpSuffix + 2)
|
|
{
|
|
memset ( mpSuffix, 0, 30 );
|
|
mpSuffixMid[0] = 'a';
|
|
mpSuffixMid[1] = 'a'; // first generated suffix will be .ab
|
|
}
|
|
void generateFileName( OUString &rFileName, const OUString &rPrefix );
|
|
void generateFileName( OUString &rFileName, const OUString &rPrefix, sal_uInt16 nDiskNum);
|
|
};
|
|
|
|
void SuffixGenerator::generateFileName( OUString &rFileName, const OUString &rPrefix )
|
|
{
|
|
// same file prefix, different extension
|
|
OUStringBuffer aStringBuf;
|
|
sal_Char *p;
|
|
for ( p = mpSuffixEnd-1; *p == 'z'; p--)
|
|
*p = 'a';
|
|
++(*p);
|
|
if (*p == 'z' && p == mpSuffixMid )
|
|
{
|
|
++mnDigits;
|
|
++mpSuffixMid;
|
|
*mpSuffixEnd++ = 'a';
|
|
*mpSuffixEnd++ = 'a';
|
|
}
|
|
aStringBuf.append ( rPrefix );
|
|
aStringBuf.appendAscii ( mpSuffix );
|
|
rFileName = aStringBuf.makeStringAndClear();
|
|
}
|
|
void SuffixGenerator::generateFileName( OUString &rFileName, const OUString &rPrefix, sal_uInt16 nDiskNum)
|
|
{
|
|
// same file prefix, different extension
|
|
OUStringBuffer aStringBuf;
|
|
aStringBuf.append ( rPrefix );
|
|
aStringBuf.appendAscii ( nDiskNum < 10 ? "00" : nDiskNum < 100 ? "0" : "" );
|
|
aStringBuf.append ( static_cast < sal_Int32 > ( nDiskNum-1) );
|
|
|
|
rFileName = aStringBuf.makeStringAndClear();
|
|
}
|
|
|
|
char * ImplGetChars( const OUString & rString )
|
|
{
|
|
// Memory leak ? oh yeah! Who cares, this function lives until this feature works
|
|
// and no longer
|
|
sal_Int32 nLength = rString.getLength();
|
|
const sal_Unicode *pString = rString.getStr();
|
|
char * pChar = new char [nLength+1];
|
|
for ( sal_Int16 i = 0; i < nLength; i++ )
|
|
{
|
|
pChar[i] = static_cast < char > (pString[i]);
|
|
}
|
|
pChar[nLength] = '\0';
|
|
return pChar;
|
|
}
|
|
|
|
ZipPackage::ZipPackage (const Reference < XMultiServiceFactory > &xNewFactory)
|
|
: pContent(NULL)
|
|
, pZipFile(NULL)
|
|
, pRootFolder(NULL)
|
|
, xContentStream (NULL)
|
|
, xContentSeek (NULL)
|
|
, xRootFolder (NULL)
|
|
, xFactory(xNewFactory)
|
|
, bHasEncryptedEntries ( sal_False )
|
|
, bSpanned( sal_False )
|
|
, nSegmentSize ( 0 )
|
|
{
|
|
pRootFolder = new ZipPackageFolder();
|
|
xRootFolder = Reference < XNameContainer > (pRootFolder );
|
|
}
|
|
|
|
ZipPackage::~ZipPackage( void )
|
|
{
|
|
delete pContent;
|
|
delete pZipFile;
|
|
// As all folders and streams contain references to their parents,
|
|
// we must remove these references so that they will be deleted when
|
|
// the hash_map of the root folder is cleared, releasing all subfolders
|
|
// and substreams which in turn release theirs, etc. When xRootFolder is
|
|
// released when this destructor completes, the folder tree should be
|
|
// deleted fully (and automagically).
|
|
|
|
pRootFolder->releaseUpwardRef();
|
|
}
|
|
|
|
void ZipPackage::getZipFileContents()
|
|
{
|
|
auto_ptr < ZipEnumeration > pEnum ( pZipFile->entries() );
|
|
Reference< XNameContainer > xCurrent;
|
|
ZipPackageStream *pPkgStream;
|
|
ZipPackageFolder *pPkgFolder;
|
|
Any aAny;
|
|
|
|
while (pEnum->hasMoreElements())
|
|
{
|
|
xCurrent = xRootFolder;
|
|
sal_Int32 nOldIndex = 0,nIndex = 0;
|
|
const ZipEntry & rEntry = *pEnum->nextElement();
|
|
const OUString &rName = rEntry.sName;
|
|
|
|
if (rName.lastIndexOf('/') == rName.getLength()-1)
|
|
{
|
|
while ((nIndex = rName.indexOf('/', nOldIndex)) != -1)
|
|
{
|
|
OUString sTemp = rName.copy (nOldIndex, nIndex - nOldIndex);
|
|
if (nIndex == nOldIndex)
|
|
break;
|
|
if (!xCurrent->hasByName(sTemp))
|
|
{
|
|
pPkgFolder = new ZipPackageFolder();
|
|
pPkgFolder->setName(sTemp);
|
|
try
|
|
{
|
|
pPkgFolder->setParent( Reference < XInterface > (xCurrent, UNO_QUERY) );
|
|
}
|
|
catch ( NoSupportException& )
|
|
{
|
|
VOS_ENSURE(0, "setParent threw an exception: attempted to set Parent to non-existing interface!");
|
|
}
|
|
xCurrent = Reference < XNameContainer > (pPkgFolder);
|
|
}
|
|
else
|
|
{
|
|
Reference < XUnoTunnel> xRef;
|
|
aAny = xCurrent->getByName(sTemp);
|
|
aAny >>= xRef;
|
|
xCurrent = Reference < XNameContainer > (xRef, UNO_QUERY);
|
|
}
|
|
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ((nIndex = rName.indexOf('/', nOldIndex)) != -1)
|
|
{
|
|
OUString sTemp = rName.copy (nOldIndex, nIndex - nOldIndex);
|
|
if (nIndex == nOldIndex)
|
|
break;
|
|
if (!xCurrent->hasByName(sTemp))
|
|
{
|
|
pPkgFolder = new ZipPackageFolder();
|
|
pPkgFolder->setName(sTemp);
|
|
try
|
|
{
|
|
pPkgFolder->setParent( Reference < XInterface >(xCurrent, UNO_QUERY));
|
|
}
|
|
catch ( NoSupportException& )
|
|
{
|
|
VOS_ENSURE( 0, "setParent threw an exception: attempted to set Parent to non-existing interface!");
|
|
}
|
|
xCurrent = Reference < XNameContainer > (pPkgFolder);
|
|
}
|
|
else
|
|
{
|
|
Reference < XUnoTunnel> xRef;
|
|
aAny = xCurrent->getByName(sTemp);
|
|
aAny >>= xRef;
|
|
xCurrent = Reference < XNameContainer > (xRef, UNO_QUERY);
|
|
}
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
OUString sStreamName = rName.copy( nOldIndex, rName.getLength() - nOldIndex);
|
|
pPkgStream = new ZipPackageStream( *this );
|
|
pPkgStream->SetPackageMember( sal_True );
|
|
pPkgStream->setZipEntry( rEntry );
|
|
pPkgStream->setName( sStreamName );
|
|
try
|
|
{
|
|
pPkgStream->setParent( Reference < XInterface > (xCurrent, UNO_QUERY));
|
|
}
|
|
catch ( NoSupportException& )
|
|
{
|
|
VOS_ENSURE( 0, "setParent threw an exception: attempted to set Parent to non-existing interface!");
|
|
}
|
|
}
|
|
}
|
|
const OUString sManifest (RTL_CONSTASCII_USTRINGPARAM( "META-INF/manifest.xml") );
|
|
if (hasByHierarchicalName( sManifest ) )
|
|
{
|
|
Reference < XUnoTunnel > xTunnel;
|
|
aAny = getByHierarchicalName( sManifest );
|
|
aAny >>= xTunnel;
|
|
Reference < XActiveDataSink > xSink (xTunnel, UNO_QUERY);
|
|
if (xSink.is())
|
|
{
|
|
OUString sManifestReader ( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.packages.manifest.ManifestReader" ) );
|
|
Reference < XManifestReader > xReader (xFactory->createInstance( sManifestReader ), UNO_QUERY );
|
|
if ( xReader.is() )
|
|
{
|
|
const OUString sPropFullPath ( RTL_CONSTASCII_USTRINGPARAM ( "FullPath" ) );
|
|
const OUString sPropMediaType ( RTL_CONSTASCII_USTRINGPARAM ( "MediaType" ) );
|
|
const OUString sPropInitialisationVector ( RTL_CONSTASCII_USTRINGPARAM ( "InitialisationVector" ) );
|
|
const OUString sPropSalt ( RTL_CONSTASCII_USTRINGPARAM ( "Salt" ) );
|
|
const OUString sPropIterationCount ( RTL_CONSTASCII_USTRINGPARAM ( "IterationCount" ) );
|
|
const OUString sPropSize ( RTL_CONSTASCII_USTRINGPARAM ( "Size" ) );
|
|
|
|
Sequence < Sequence < PropertyValue > > aManifestSequence = xReader->readManifestSequence ( xSink->getInputStream() );
|
|
sal_Int32 nLength = aManifestSequence.getLength();
|
|
const Sequence < PropertyValue > *pSequence = aManifestSequence.getConstArray();
|
|
ZipPackageStream *pStream = NULL;
|
|
ZipPackageFolder *pFolder = NULL;
|
|
|
|
for (sal_Int32 i = 0; i < nLength ; i++, pSequence++)
|
|
{
|
|
OUString sPath, sMediaType;
|
|
const PropertyValue *pValue = pSequence->getConstArray();
|
|
const Any *pSalt = NULL, *pVector = NULL, *pCount = NULL, *pSize = NULL;
|
|
for (sal_Int32 j = 0, nNum = pSequence->getLength(); j < nNum; j++ )
|
|
{
|
|
if (pValue[j].Name.equals( sPropFullPath ) )
|
|
pValue[j].Value >>= sPath;
|
|
else if (pValue[j].Name.equals( sPropMediaType ) )
|
|
pValue[j].Value >>= sMediaType;
|
|
else if (pValue[j].Name.equals( sPropSalt ) )
|
|
pSalt = &(pValue[j].Value);
|
|
else if (pValue[j].Name.equals( sPropInitialisationVector ) )
|
|
pVector = &(pValue[j].Value);
|
|
else if (pValue[j].Name.equals( sPropIterationCount ) )
|
|
pCount = &(pValue[j].Value);
|
|
else if (pValue[j].Name.equals( sPropSize ) )
|
|
pSize = &(pValue[j].Value);
|
|
}
|
|
if (sPath.getLength() && hasByHierarchicalName ( sPath ) )
|
|
{
|
|
Any aAny = getByHierarchicalName( sPath );
|
|
Reference < XUnoTunnel > xTunnel;
|
|
aAny >>= xTunnel;
|
|
sal_Int64 nTest=0;
|
|
if ((nTest = xTunnel->getSomething(ZipPackageFolder::getUnoTunnelImplementationId())) != 0)
|
|
{
|
|
pFolder = reinterpret_cast < ZipPackageFolder* > ( nTest );
|
|
pFolder->SetMediaType ( sMediaType );
|
|
}
|
|
else
|
|
{
|
|
pStream = reinterpret_cast < ZipPackageStream* > ( xTunnel->getSomething(ZipPackageStream::getUnoTunnelImplementationId()));
|
|
pStream->SetMediaType ( sMediaType );
|
|
|
|
if (pSalt && pVector && pCount && pSize)
|
|
{
|
|
Sequence < sal_uInt8 > aSequence;
|
|
sal_Int32 nCount, nSize;
|
|
|
|
*pSalt >>= aSequence;
|
|
pStream->setSalt ( aSequence );
|
|
|
|
*pVector >>= aSequence;
|
|
pStream->setInitialisationVector ( aSequence );
|
|
|
|
*pCount >>= nCount;
|
|
pStream->setIterationCount ( nCount );
|
|
|
|
*pSize >>= nSize;
|
|
pStream->setSize ( nSize );
|
|
|
|
pStream->SetToBeEncrypted ( sal_True );
|
|
bHasEncryptedEntries = sal_True;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const OUString sMeta ( RTL_CONSTASCII_USTRINGPARAM ( "META-INF" ) );
|
|
if (xRootFolder->hasByName( sMeta ) )
|
|
xRootFolder->removeByName( sMeta );
|
|
}
|
|
// XInitialization
|
|
void SAL_CALL ZipPackage::initialize( const Sequence< Any >& aArguments )
|
|
throw(Exception, RuntimeException)
|
|
{
|
|
if (! (aArguments[0] >>= sURL))
|
|
throw com::sun::star::uno::Exception ( OUString::createFromAscii ( "Bad URL." ),
|
|
static_cast < ::cppu::OWeakObject * > ( this ) );
|
|
pContent = new Content(sURL, Reference < XCommandEnvironment >() );
|
|
sal_Bool bBadZipFile = sal_False, bHaveZipFile = sal_True;
|
|
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: initialise called on %s\n", ImplGetChars ( sURL ) );
|
|
#endif
|
|
|
|
try
|
|
{
|
|
Reference < XActiveDataSink > xSink = new ZipPackageSink;
|
|
if (pContent->openStream ( xSink ) )
|
|
xContentStream = xSink->getInputStream();
|
|
if (xContentStream.is())
|
|
{
|
|
xContentSeek = Reference < XSeekable > (xContentStream, UNO_QUERY);
|
|
Sequence < sal_Int8 > aSequence ( 4 );
|
|
xContentStream->readBytes( aSequence, 4 );
|
|
xContentSeek->seek ( 0 );
|
|
const sal_Int8 *pSeq = aSequence.getConstArray();
|
|
if (pSeq[0] == 'P' &&
|
|
pSeq[1] == 'K' &&
|
|
pSeq[2] == 7 &&
|
|
pSeq[3] == 8 )
|
|
bSpanned = sal_True;
|
|
}
|
|
else
|
|
bHaveZipFile = sal_False;
|
|
}
|
|
catch (com::sun::star::uno::Exception&)
|
|
{
|
|
// Exception derived from uno::Exception thrown. This probably
|
|
// means the file doesn't exist...we'll create it at
|
|
// commitChanges time
|
|
bHaveZipFile = sal_False;
|
|
}
|
|
if ( bHaveZipFile )
|
|
{
|
|
try
|
|
{
|
|
if ( bSpanned)
|
|
{
|
|
// after unSpanFile, xContentStream will refer to the tempfile containing
|
|
// the unspanned file
|
|
unSpanFile ();
|
|
pZipFile = new ZipFile ( xContentStream, sURL );
|
|
}
|
|
else
|
|
pZipFile = new ZipFile ( xContentStream, sal_True);
|
|
getZipFileContents();
|
|
}
|
|
catch ( IOException & )
|
|
{
|
|
bBadZipFile = sal_True;
|
|
}
|
|
catch ( ZipException & )
|
|
{
|
|
bBadZipFile = sal_True;
|
|
}
|
|
if ( bBadZipFile )
|
|
{
|
|
// clean up the memory, and tell the UCB about the error
|
|
delete pZipFile; pZipFile = NULL;
|
|
delete pContent; pContent = NULL;
|
|
throw com::sun::star::uno::Exception ( OUString::createFromAscii ( "Bad Zip File." ),
|
|
static_cast < ::cppu::OWeakObject * > ( this ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
Any SAL_CALL ZipPackage::getByHierarchicalName( const OUString& aName )
|
|
throw(NoSuchElementException, RuntimeException)
|
|
{
|
|
OUString sTemp, sRoot( RTL_CONSTASCII_USTRINGPARAM ( "/" ) );
|
|
sal_Int32 nOldIndex =0, nIndex;
|
|
Any aAny;
|
|
Reference < XNameContainer > xCurrent (xRootFolder);
|
|
Reference < XNameContainer > xPrevious (NULL);
|
|
|
|
if (aName[nOldIndex] == '/')
|
|
nOldIndex++;
|
|
|
|
if (aName == sRoot)
|
|
aAny <<= Reference < XUnoTunnel > (pRootFolder);
|
|
else if (aName.lastIndexOf('/') == (nIndex = aName.getLength()-1))
|
|
{
|
|
if ( aRecent.count(aName) && (nOldIndex = aName.lastIndexOf('/', nIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy(++nOldIndex, nIndex-nOldIndex);
|
|
if (aRecent[aName]->hasByName(sTemp) )
|
|
return aRecent[aName]->getByName(sTemp);
|
|
else
|
|
aRecent.erase(aName);
|
|
}
|
|
nOldIndex=0;
|
|
while ((nIndex = aName.indexOf('/', nOldIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy (nOldIndex, nIndex - nOldIndex);
|
|
Reference < XUnoTunnel > xRef;
|
|
|
|
if (nIndex == nOldIndex)
|
|
break;
|
|
if (xCurrent->hasByName(sTemp))
|
|
{
|
|
aAny = xCurrent->getByName(sTemp);
|
|
aAny >>= xRef;
|
|
xCurrent = Reference < XNameContainer > (xRef, UNO_QUERY);
|
|
}
|
|
else
|
|
throw NoSuchElementException();
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
aRecent[aName] = xPrevious;
|
|
}
|
|
else
|
|
{
|
|
if ( aRecent.count(aName) && (nOldIndex = aName.lastIndexOf('/', nIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy(++nOldIndex, nIndex-nOldIndex);
|
|
if (aRecent[aName]->hasByName(sTemp) )
|
|
return aRecent[aName]->getByName(sTemp);
|
|
else
|
|
aRecent.erase(aName);
|
|
}
|
|
nOldIndex=0;
|
|
while ((nIndex = aName.indexOf('/', nOldIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy (nOldIndex, nIndex - nOldIndex);
|
|
Reference < XUnoTunnel > xChildRef;
|
|
|
|
if (nIndex == nOldIndex)
|
|
break;
|
|
if (xCurrent->hasByName(sTemp))
|
|
{
|
|
aAny = xCurrent->getByName(sTemp);
|
|
aAny >>= xChildRef;
|
|
xCurrent = Reference < XNameContainer > (xChildRef, UNO_QUERY);
|
|
}
|
|
else
|
|
throw NoSuchElementException();
|
|
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
OUString sStreamName = aName.copy( nOldIndex, aName.getLength() - nOldIndex);
|
|
if (xCurrent->hasByName(sStreamName))
|
|
{
|
|
aRecent[aName] = xCurrent;
|
|
return xCurrent->getByName(sStreamName);
|
|
}
|
|
else
|
|
throw NoSuchElementException();
|
|
}
|
|
return aAny;
|
|
}
|
|
|
|
sal_Bool SAL_CALL ZipPackage::hasByHierarchicalName( const OUString& aName )
|
|
throw(RuntimeException)
|
|
{
|
|
OUString sTemp, sRoot( RTL_CONSTASCII_USTRINGPARAM ( "/" ) );
|
|
sal_Int32 nOldIndex = 0, nIndex;
|
|
Any aAny;
|
|
Reference < XNameContainer > xCurrent (xRootFolder);
|
|
Reference < XNameContainer > xPrevious (NULL);
|
|
|
|
if (aName[nOldIndex] == '/')
|
|
nOldIndex++;
|
|
|
|
if (aName == sRoot)
|
|
return sal_True;
|
|
else if (aName.lastIndexOf('/') == (nIndex = aName.getLength()-1))
|
|
{
|
|
if ( aRecent.count(aName) && (nOldIndex = aName.lastIndexOf('/', nIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy(++nOldIndex, nIndex - nOldIndex);
|
|
if (aRecent[aName]->hasByName(sTemp) )
|
|
return sal_True;
|
|
else
|
|
aRecent.erase(aName);
|
|
}
|
|
nOldIndex=0;
|
|
while ((nIndex = aName.indexOf('/', nOldIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy (nOldIndex, nIndex - nOldIndex);
|
|
Reference < XUnoTunnel > xRef;
|
|
if (nIndex == nOldIndex)
|
|
break;
|
|
if (xCurrent->hasByName(sTemp))
|
|
{
|
|
aAny = xCurrent->getByName(sTemp);
|
|
aAny >>= xRef;
|
|
xPrevious = xCurrent;
|
|
xCurrent = Reference < XNameContainer > (xRef, UNO_QUERY);
|
|
}
|
|
else
|
|
return sal_False;
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
aRecent[aName] = xPrevious;
|
|
return sal_True;
|
|
}
|
|
else
|
|
{
|
|
if ( aRecent.count(aName) && (nOldIndex = aName.lastIndexOf('/', nIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy(++nOldIndex, nIndex - nOldIndex);
|
|
if (aRecent[aName]->hasByName(sTemp) )
|
|
return sal_True;
|
|
else
|
|
aRecent.erase(aName);
|
|
}
|
|
nOldIndex=0;
|
|
while ((nIndex = aName.indexOf('/', nOldIndex)) != -1)
|
|
{
|
|
sTemp = aName.copy (nOldIndex, nIndex - nOldIndex);
|
|
Reference < XUnoTunnel > xChildRef;
|
|
if (nIndex == nOldIndex)
|
|
break;
|
|
if (xCurrent->hasByName(sTemp))
|
|
{
|
|
aAny = xCurrent->getByName(sTemp);
|
|
aAny >>= xChildRef;
|
|
xCurrent = Reference < XNameContainer > (xChildRef, UNO_QUERY);
|
|
}
|
|
else
|
|
return sal_False;
|
|
nOldIndex = nIndex+1;
|
|
}
|
|
OUString sStreamName = aName.copy( nOldIndex, aName.getLength() - nOldIndex);
|
|
|
|
if (xCurrent->hasByName(sStreamName))
|
|
{
|
|
aRecent[aName] = xCurrent;
|
|
return sal_True;
|
|
}
|
|
return sal_False;
|
|
}
|
|
}
|
|
|
|
// XSingleServiceFactory
|
|
Reference< XInterface > SAL_CALL ZipPackage::createInstance( )
|
|
throw(Exception, RuntimeException)
|
|
{
|
|
Reference < XInterface > xRef = *(new ZipPackageStream ( *this ));
|
|
return xRef;
|
|
}
|
|
Reference< XInterface > SAL_CALL ZipPackage::createInstanceWithArguments( const Sequence< Any >& aArguments )
|
|
throw(Exception, RuntimeException)
|
|
{
|
|
sal_Bool bArg;
|
|
Reference < XInterface > xRef;
|
|
aArguments[0] >>= bArg;
|
|
if (bArg)
|
|
xRef = *new ZipPackageFolder ( );
|
|
else
|
|
xRef = *new ZipPackageStream ( *this );
|
|
|
|
return xRef;
|
|
}
|
|
|
|
// XChangesBatch
|
|
void SAL_CALL ZipPackage::commitChanges( )
|
|
throw(WrappedTargetException, RuntimeException)
|
|
{
|
|
ThreadedBuffer *pBuffer;
|
|
Reference < XOutputStream > xOutBuffer = (pBuffer = new ThreadedBuffer ( n_ConstBufferSize, *this ));
|
|
Reference < XInputStream > xInBuffer ( pBuffer );
|
|
ZipOutputStream aZipOut ( xOutBuffer, nSegmentSize != 0);
|
|
pBuffer->setZipOutputStream ( aZipOut );
|
|
|
|
aZipOut.setMethod(DEFLATED);
|
|
aZipOut.setLevel(DEFAULT_COMPRESSION);
|
|
|
|
// Remove the old META-INF directory as this will be re-generated below.
|
|
// Pass save-contents a vector which will be used to store information
|
|
// that should be stored in the manifest.
|
|
|
|
const OUString sMeta ( RTL_CONSTASCII_USTRINGPARAM ( "META-INF" ) );
|
|
if (xRootFolder->hasByName( sMeta ) )
|
|
xRootFolder->removeByName( sMeta );
|
|
/// >>>>>>>>
|
|
// Then create a tempfile...
|
|
OUString sServiceName ( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.io.TempFile" ) );
|
|
|
|
Reference < XOutputStream > xTempOut = Reference < XOutputStream > ( xFactory->createInstance ( sServiceName ), UNO_QUERY );
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "We have a %s tempfile!\n", xTempOut.is() ? "good" : "bad" );
|
|
#endif
|
|
Reference < XInputStream > xTempIn = Reference < XInputStream > ( xTempOut, UNO_QUERY );
|
|
Reference < XSeekable > xTempSeek = Reference < XSeekable > ( xTempOut, UNO_QUERY );
|
|
// Then write the full package to the temp file...
|
|
Sequence < sal_Int8 > aBuffer;
|
|
sal_Int64 nRead;
|
|
|
|
do
|
|
{
|
|
nRead = xInBuffer->readBytes ( aBuffer, n_ConstBufferSize );
|
|
xTempOut->writeBytes( aBuffer );
|
|
}
|
|
while ( nRead == n_ConstBufferSize );
|
|
|
|
// seek back to the beginning of the temp file so we can read segments from it
|
|
xTempSeek->seek ( 0 );
|
|
xContentStream = xTempIn;
|
|
xContentSeek = xTempSeek;
|
|
pZipFile->setInputStream ( xTempIn );
|
|
// <<<<<<<<<<
|
|
|
|
|
|
if (!nSegmentSize )
|
|
{
|
|
try
|
|
{
|
|
if ( !pContent )
|
|
pContent = new Content(sURL, Reference < XCommandEnvironment >() );
|
|
pContent->writeStream ( xTempIn, sal_True );
|
|
}
|
|
catch (::com::sun::star::uno::Exception& r)
|
|
{
|
|
throw WrappedTargetException( OUString::createFromAscii( "Unable to write Zip File to disk!" ),
|
|
static_cast < OWeakObject * > ( this ), makeAny( r ) );
|
|
}
|
|
/*
|
|
Reference < XActiveDataSink > xSink = new ZipPackageSink;
|
|
* We want to reference the temp file, not the one we just wrote
|
|
try
|
|
{
|
|
// Update our references to point to the new file
|
|
if (pContent->openStream ( xSink ) )
|
|
xContentStream = xSink->getInputStream();
|
|
xContentSeek = Reference < XSeekable > (xContentStream, UNO_QUERY);
|
|
pZipFile->setInputStream ( xContentStream );
|
|
}
|
|
catch (com::sun::star::uno::Exception& r)
|
|
{
|
|
throw WrappedTargetException( OUString::createFromAscii( "Unable to read Zip File content!" ),
|
|
static_cast < OWeakObject * > ( this ), makeAny( r ) );
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
// We want to span...first, make sure we have an interaction handler...
|
|
getInteractionHandler();
|
|
|
|
sal_Int16 nDiskNum = 0;
|
|
VolumeInfo aInfo ( osl_VolumeInfo_Mask_FreeSpace | osl_VolumeInfo_Mask_DeviceHandle | osl_VolumeInfo_Mask_Attributes );
|
|
FileBase::RC aRC = Directory::getVolumeInfo ( sURL, aInfo );
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: url is %s getVolumeInfo returned %d\n", ImplGetChars ( sURL), aRC);
|
|
fprintf(stderr, "MTG: isRemovable returned %d\n", aInfo.getRemoveableFlag() );
|
|
fprintf(stderr, "MTG: isValid osl_VolumeInfo_Mask_FreeSpace returned %d\n", aInfo.isValid(osl_VolumeInfo_Mask_FreeSpace) );
|
|
fprintf(stderr, "MTG: isValid osl_VolumeInfo_Mask_DeviceHandle returned %d\n", aInfo.isValid(osl_VolumeInfo_Mask_DeviceHandle) );
|
|
fprintf(stderr, "MTG: isValid osl_VolumeInfo_Mask_Attributes returned %d\n", aInfo.isValid(osl_VolumeInfo_Mask_Attributes) );
|
|
fprintf(stderr, "MTG: getFreeSpace returned %ld\n", static_cast < sal_Int32 > ( aInfo.getFreeSpace() ) );
|
|
fprintf(stderr, "MTG: getUsedSpace returned %ld\n", static_cast < sal_Int32 > ( aInfo.getUsedSpace() ) );
|
|
fprintf(stderr, "MTG: getTotalSpace returned %ld\n", static_cast < sal_Int32 > ( aInfo.getTotalSpace() ) );
|
|
#endif
|
|
VolumeDevice aDevice = aInfo.getDeviceHandle();
|
|
OUString sFileName, sMountPath = aDevice.getMountPath();
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: mount path is %s %d\n", ImplGetChars ( sMountPath ), aRC);
|
|
#endif
|
|
sal_Int32 nLastSlash = sURL.lastIndexOf ( '/' );
|
|
OUString sFilePrefix = sURL.copy ( 1 + nLastSlash, sURL.lastIndexOf ( '.' ) - nLastSlash );
|
|
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: url is %s first getVolumeInfo on %s returned %d\n", ImplGetChars ( sURL), ImplGetChars ( sMountPath), aRC);
|
|
#endif
|
|
sal_Bool bIsRemovable = aInfo.getRemoveableFlag();
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: Removable flag is %d\n", bIsRemovable);
|
|
#endif
|
|
SegmentEnum eRet = e_Finished;
|
|
SuffixGenerator aGenerator;
|
|
do
|
|
{
|
|
//pBuffer->nextSegment( ++nDiskNum);
|
|
nDiskNum++;
|
|
sal_Int64 nCurrentPos = xTempSeek->getPosition();
|
|
if ( nDiskNum == 1 )
|
|
sFileName = sURL.copy ( 1 + nLastSlash );
|
|
else
|
|
aGenerator.generateFileName ( sFileName, sFilePrefix, nDiskNum );
|
|
if ( bIsRemovable )
|
|
{
|
|
if ( nDiskNum > 1 && RequestDisk( sMountPath, nDiskNum ) < 0 )
|
|
return;
|
|
|
|
do
|
|
{
|
|
eRet = writeSegment ( sFileName, sMountPath, xTempIn, nDiskNum );
|
|
if (eRet == e_Aborted)
|
|
return;
|
|
else if ( eRet == e_Retry )
|
|
xTempSeek->seek ( nCurrentPos );
|
|
}
|
|
while ( eRet == e_Retry );
|
|
}
|
|
else
|
|
{
|
|
OUString sFullPath = sURL.copy ( 0, nLastSlash + 1 ) + sFileName;
|
|
if ( xTempSeek->getLength() > static_cast < sal_Int64 > ( aInfo.getFreeSpace() ) )
|
|
{
|
|
// no room on the hard drive, display a message and return
|
|
HandleError ( osl_File_E_NOSPC, EC_YES, sFullPath );
|
|
return;
|
|
}
|
|
else if ( nSegmentSize < 0 )
|
|
{
|
|
try
|
|
{
|
|
if ( !pContent )
|
|
pContent = new Content(sURL, Reference < XCommandEnvironment >() );
|
|
pContent->writeStream ( xTempIn, sal_True );
|
|
}
|
|
catch (::com::sun::star::uno::Exception& r)
|
|
{
|
|
throw WrappedTargetException( OUString::createFromAscii( "Unable to write Zip File to disk!" ),
|
|
static_cast < OWeakObject * > ( this ), makeAny( r ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
eRet = writeSegment ( sFullPath, xTempIn );
|
|
if (eRet == e_Aborted)
|
|
return;
|
|
else if ( eRet == e_Retry )
|
|
xTempSeek->seek ( nCurrentPos );
|
|
}
|
|
while ( eRet == e_Retry );
|
|
}
|
|
}
|
|
}
|
|
while ( eRet != e_Finished );
|
|
}
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: ZipPackage Commit finished\n");
|
|
#endif
|
|
}
|
|
|
|
sal_Int32 ZipPackage::RequestDisk ( OUString &rMountPath, sal_Int16 nDiskNum)
|
|
{
|
|
VolumeInfo aInfo ( osl_VolumeInfo_Mask_FreeSpace | osl_VolumeInfo_Mask_DeviceHandle | osl_VolumeInfo_Mask_Attributes );
|
|
VolumeDevice aDevice;
|
|
FileBase::RC aRC;
|
|
|
|
do
|
|
{
|
|
aRC = Directory::getVolumeInfo ( rMountPath, aInfo );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: Requesting disk for %s, result is %d\n", ImplGetChars ( rMountPath ), aRC );
|
|
#endif
|
|
if ( aRC == FileBase::E_None )
|
|
aDevice = aInfo.getDeviceHandle();
|
|
else
|
|
{
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: Requesting disk calling HandleError with osl_File_E_INVAL\n" );
|
|
#endif
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, rMountPath) )
|
|
return -1;
|
|
}
|
|
}
|
|
while ( aRC != FileBase::E_None );
|
|
#ifdef UNX
|
|
do
|
|
{
|
|
aRC = aDevice.unmount();
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: unmount returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None )
|
|
{
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, rMountPath) )
|
|
return -1;
|
|
}
|
|
}
|
|
while ( aRC != FileBase::E_None );
|
|
#endif
|
|
|
|
Any aExceptionAny, aMediumException;
|
|
InteractiveWrongMediumException aException;
|
|
aMediumException <<= static_cast < sal_Int16 > (nDiskNum-1);
|
|
aException.Medium = aMediumException;
|
|
aExceptionAny <<= aException;
|
|
if ( !HandleError ( aExceptionAny, EC_YES|EC_ABORT ) )
|
|
return -1;
|
|
#ifdef UNX
|
|
do
|
|
{
|
|
aRC = aDevice.automount();
|
|
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: automount returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None )
|
|
{
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, rMountPath) )
|
|
return -1;
|
|
}
|
|
}
|
|
while ( aRC != FileBase::E_None );
|
|
OUString aNewMountPath ( aDevice.getMountPath() );
|
|
|
|
if (aNewMountPath != rMountPath)
|
|
rMountPath = aNewMountPath;
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: returning, new mountpath is %s\n", ImplGetChars ( rMountPath ) );
|
|
#endif
|
|
#endif
|
|
return FileBase::E_None;
|
|
}
|
|
SegmentEnum ZipPackage::writeSegment ( const OUString &rFileName, OUString &rMountPath, Reference < XInputStream > &xInBuffer, const sal_Int16 nDiskNum )
|
|
{
|
|
File *pFile = NULL;
|
|
FileBase::RC aRC;
|
|
sal_Bool bDynamicSpan = nSegmentSize < 0;
|
|
Sequence < sal_Int8 > aBuffer;
|
|
|
|
sal_Int32 nRead = n_ConstBufferSize;
|
|
sal_uInt64 nLeft, nWritten;
|
|
VolumeInfo aInfo ( osl_VolumeInfo_Mask_FreeSpace | osl_VolumeInfo_Mask_DeviceHandle | osl_VolumeInfo_Mask_Attributes );
|
|
|
|
#ifdef MTG_DEBUG
|
|
fprintf (stderr, "MTG: In writeSegment, disk num is %d, file is %s, dir is %s\n",
|
|
nDiskNum, ImplGetChars(rFileName), ImplGetChars(rMountPath));
|
|
#endif
|
|
do
|
|
{
|
|
aRC = Directory::getVolumeInfo ( rMountPath, aInfo );
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: getVolumeInfo returned %d\n", aRC );
|
|
#endif
|
|
// return value is not useful, so we check if we have valid attributes instead
|
|
if ( aInfo.isValid ( osl_VolumeInfo_Mask_FreeSpace) )
|
|
{
|
|
sal_Bool bReCheck;
|
|
OUStringBuffer aBuffer;
|
|
aBuffer.append ( rMountPath );
|
|
if ( rMountPath.lastIndexOf ( '/' ) != rMountPath.getLength()-1 )
|
|
aBuffer.appendAscii ( "/" );
|
|
aBuffer.append ( rFileName );
|
|
OUString sFullPath ( aBuffer.makeStringAndClear() );
|
|
do
|
|
{
|
|
bReCheck = sal_False;
|
|
sal_uInt64 nFree = aInfo.getFreeSpace();
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: free is %d\n", static_cast < sal_Int32 > ( nFree ) );
|
|
#endif
|
|
if ( ( bDynamicSpan && nFree < 1000 ) ||
|
|
( !bDynamicSpan && nFree < nSegmentSize ) )
|
|
{
|
|
if ( !HandleError ( osl_File_E_NOSPC, EC_RETRY|EC_ABORT, sFullPath ) )
|
|
{
|
|
if ( pFile )
|
|
delete pFile;
|
|
return e_Aborted;
|
|
}
|
|
else
|
|
{
|
|
aRC = Directory::getVolumeInfo ( rMountPath, aInfo );
|
|
bReCheck = sal_True;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nLeft = bDynamicSpan ? nFree : nSegmentSize;
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: left is %ld\n", static_cast < sal_Int32 > ( nLeft ) );
|
|
#endif
|
|
}
|
|
}
|
|
while ( bReCheck );
|
|
#ifdef MTG_DEBUG
|
|
fprintf( stderr, "MTG: sDirectoryName is %s sFileName is %s FullPath is %s\n",
|
|
ImplGetChars ( rMountPath ), ImplGetChars ( rFileName ), ImplGetChars ( sFullPath ) );
|
|
#endif
|
|
pFile = new File ( sFullPath );
|
|
sal_Bool bExists = sal_False;
|
|
aRC = pFile->open ( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write );
|
|
if ( aRC == FileBase::E_EXIST )
|
|
{
|
|
aRC = pFile->open ( osl_File_OpenFlag_Write );
|
|
bExists = sal_True;
|
|
}
|
|
if ( aRC != FileBase::E_None )
|
|
{
|
|
#ifdef MTG_DEBUG
|
|
fprintf(stderr, "MTG: file open returned %d\n", aRC );
|
|
#endif
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, sFullPath ) )
|
|
{
|
|
delete pFile;
|
|
return e_Aborted;
|
|
}
|
|
}
|
|
else if ( bExists )
|
|
{
|
|
// Truncate to 0 if necessary
|
|
aRC = pFile->setSize ( 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aRC = FileBase::E_IO;
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, rMountPath ) )
|
|
return e_Aborted;
|
|
}
|
|
}
|
|
while (aRC != FileBase::E_None );
|
|
|
|
// Now! We should have an open file on a disk which has at least nSegmentSize if not
|
|
// dynamic spanning and 1000 bytes if dynamic spanning.
|
|
|
|
|
|
// Let's read it all into the buffer in case something goes wrong and also
|
|
// so that the spannable checks in ByteChucker and ZipOutputStream work
|
|
|
|
nRead = xInBuffer->readBytes ( aBuffer, static_cast < sal_Int32 > ( nLeft ) );
|
|
|
|
aRC = pFile->write ( aBuffer.getConstArray(), nRead, nWritten );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: write returned %d\n", aRC );
|
|
#endif
|
|
|
|
sal_Bool bRetry = sal_False;
|
|
|
|
if ( nWritten != nRead || aRC != FileBase::E_None )
|
|
bRetry = sal_True;
|
|
else
|
|
{
|
|
aRC = pFile->close ();
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: close returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None )
|
|
bRetry = sal_True;
|
|
}
|
|
delete pFile;
|
|
return nRead < nLeft ? e_Finished : bRetry ? e_Retry : e_Success;
|
|
}
|
|
|
|
SegmentEnum ZipPackage::writeSegment ( const OUString &rFileName, Reference < XInputStream > &xInBuffer )
|
|
{
|
|
FileBase::RC aRC;
|
|
Sequence < sal_Int8 > aBuffer ( nSegmentSize );
|
|
|
|
sal_uInt64 nWritten;
|
|
|
|
sal_Int32 nRead = xInBuffer->readBytes ( aBuffer, static_cast < sal_Int32 > ( nSegmentSize ) );
|
|
|
|
File aFile ( rFileName );
|
|
aRC = aFile.open ( osl_File_OpenFlag_Create | osl_File_OpenFlag_Write );
|
|
if ( aRC == FileBase::E_EXIST )
|
|
aRC = aFile.open ( osl_File_OpenFlag_Write );
|
|
if ( aRC != FileBase::E_None )
|
|
{
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, rFileName ) )
|
|
return e_Aborted;
|
|
}
|
|
|
|
aRC = aFile.write ( aBuffer.getConstArray(), nRead, nWritten );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: write returned %d\n", aRC );
|
|
#endif
|
|
|
|
sal_Bool bRetry = sal_False;
|
|
if ( nWritten != nRead || aRC != FileBase::E_None )
|
|
bRetry = sal_True;
|
|
else
|
|
{
|
|
aRC = aFile.close ();
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: close returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None )
|
|
bRetry = sal_True;
|
|
}
|
|
return nRead < nSegmentSize ? e_Finished : bRetry ? e_Retry : e_Success;
|
|
}
|
|
|
|
SegmentEnum ZipPackage::readSegment ( const OUString &rFileName, OUString &rMountPath, Reference < XOutputStream > &xTempOut, const sal_Int16 nDiskNum )
|
|
{
|
|
File *pFile = NULL;
|
|
FileBase::RC aRC;
|
|
SegmentEnum eRet;
|
|
|
|
OUStringBuffer aStringBuffer;
|
|
aStringBuffer.append ( rMountPath );
|
|
if ( rMountPath.lastIndexOf ( '/' ) != rMountPath.getLength()-1 )
|
|
aStringBuffer.appendAscii ( "/" );
|
|
aStringBuffer.append ( rFileName );
|
|
OUString sFullPath ( aStringBuffer.makeStringAndClear() );
|
|
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: trying to read %s\n", ImplGetChars ( sFullPath ) );
|
|
#endif
|
|
DirectoryItem aItem;
|
|
do
|
|
{
|
|
aRC = DirectoryItem::get ( sFullPath, aItem );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: DirectoryItem::get returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None && !HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, sFullPath ) )
|
|
return e_Aborted;
|
|
}
|
|
while (aRC != FileBase::E_None );
|
|
do
|
|
{
|
|
pFile = new File ( sFullPath );
|
|
aRC = pFile->open ( osl_File_OpenFlag_Read );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG:file open returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None )
|
|
{
|
|
delete pFile;
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, sFullPath ) )
|
|
return e_Aborted;
|
|
}
|
|
}
|
|
while (aRC != FileBase::E_None );
|
|
|
|
FileStatus aStatus ( FileStatusMask_FileSize );
|
|
aItem.getFileStatus ( aStatus );
|
|
sal_uInt64 nRead, nLeft = aStatus.getFileSize(), nToRead;
|
|
|
|
Sequence < sal_Int8 > aBuffer ( n_ConstBufferSize );
|
|
do
|
|
{
|
|
nToRead = nLeft < n_ConstBufferSize ? nLeft : n_ConstBufferSize;
|
|
aRC = pFile->read ( static_cast < void* > ( aBuffer.getArray() ), nToRead, nRead );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG:file read returned %d\n", aRC );
|
|
#endif
|
|
if ( aRC != FileBase::E_None )
|
|
{
|
|
if ( ! HandleError ( (oslFileError) aRC, EC_RETRY|EC_ABORT, sFullPath ) )
|
|
return e_Aborted;
|
|
}
|
|
else
|
|
{
|
|
Sequence < sal_Int8 > aReadBuf ( aBuffer.getConstArray(), static_cast < sal_Int32 > ( nRead ) );
|
|
nLeft -= nRead;
|
|
xTempOut->writeBytes ( aReadBuf );
|
|
if (nLeft == 0)
|
|
eRet = checkEnd ( aReadBuf ) ? e_Finished : e_Success;
|
|
}
|
|
}
|
|
while (nLeft > 0 );
|
|
|
|
aRC = pFile->close ();
|
|
delete pFile;
|
|
return eRet;
|
|
}
|
|
|
|
sal_Bool SAL_CALL ZipPackage::hasPendingChanges( )
|
|
throw(RuntimeException)
|
|
{
|
|
return sal_False;
|
|
}
|
|
Sequence< ElementChange > SAL_CALL ZipPackage::getPendingChanges( )
|
|
throw(RuntimeException)
|
|
{
|
|
return Sequence < ElementChange > ( NULL, 0 );
|
|
}
|
|
|
|
/**
|
|
* Function to create a new component instance; is needed by factory helper implementation.
|
|
* @param xMgr service manager to if the components needs other component instances
|
|
*/
|
|
Reference < XInterface >SAL_CALL ZipPackage_createInstance(
|
|
const Reference< XMultiServiceFactory > & xMgr )
|
|
{
|
|
return Reference< XInterface >( *new ZipPackage(xMgr) );
|
|
}
|
|
|
|
OUString ZipPackage::getImplementationName()
|
|
{
|
|
#if SUPD>625
|
|
return OUString( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.packages.comp.ZipPackage" ) );
|
|
#else
|
|
return OUString( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.package.Package" ) );
|
|
#endif
|
|
}
|
|
|
|
Sequence< OUString > ZipPackage::getSupportedServiceNames()
|
|
{
|
|
Sequence< OUString > aNames(1);
|
|
#if SUPD>625
|
|
aNames[0] = OUString( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.packages.comp.ZipPackage" ) );
|
|
#else
|
|
aNames[0] = OUString( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.package.Package" ) );
|
|
#endif
|
|
return aNames;
|
|
}
|
|
sal_Bool SAL_CALL ZipPackage::supportsService( OUString const & rServiceName )
|
|
throw (RuntimeException)
|
|
{
|
|
return rServiceName == getSupportedServiceNames()[0];
|
|
}
|
|
Reference < XSingleServiceFactory > ZipPackage::createServiceFactory( Reference < XMultiServiceFactory > const & rServiceFactory )
|
|
{
|
|
return cppu::createSingleFactory (rServiceFactory,
|
|
getImplementationName(),
|
|
ZipPackage_createInstance,
|
|
getSupportedServiceNames());
|
|
}
|
|
|
|
//XInterface
|
|
Any SAL_CALL ZipPackage::queryInterface( const Type& rType )
|
|
throw(RuntimeException)
|
|
{
|
|
return ::cppu::queryInterface ( rType ,
|
|
// OWeakObject interfaces
|
|
reinterpret_cast< XInterface* > ( this ) ,
|
|
static_cast< XWeak* > ( this ) ,
|
|
// my own interfaces
|
|
static_cast< XInitialization* > ( this ) ,
|
|
static_cast< XSingleServiceFactory* > ( this ) ,
|
|
static_cast< XUnoTunnel* > ( this ) ,
|
|
static_cast< XHierarchicalNameAccess* > ( this ) ,
|
|
static_cast< XPropertySet* > ( this ) ,
|
|
static_cast< XChangesBatch* > ( this ) );
|
|
}
|
|
|
|
void SAL_CALL ZipPackage::acquire( )
|
|
throw()
|
|
{
|
|
OWeakObject::acquire();
|
|
}
|
|
void SAL_CALL ZipPackage::release( )
|
|
throw()
|
|
{
|
|
OWeakObject::release();
|
|
}
|
|
|
|
// XUnoTunnel
|
|
Sequence< sal_Int8 > ZipPackage::getUnoTunnelImplementationId( void )
|
|
throw (RuntimeException)
|
|
{
|
|
static ::cppu::OImplementationId * pId = 0;
|
|
if (! pId)
|
|
{
|
|
::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
|
|
if (! pId)
|
|
{
|
|
static ::cppu::OImplementationId aId;
|
|
pId = &aId;
|
|
}
|
|
}
|
|
return pId->getImplementationId();
|
|
}
|
|
|
|
sal_Int64 SAL_CALL ZipPackage::getSomething( const Sequence< sal_Int8 >& aIdentifier )
|
|
throw(RuntimeException)
|
|
{
|
|
if (aIdentifier.getLength() == 16 && 0 == rtl_compareMemory(getUnoTunnelImplementationId().getConstArray(), aIdentifier.getConstArray(), 16 ) )
|
|
return reinterpret_cast < sal_Int64 > ( this );
|
|
return 0;
|
|
}
|
|
|
|
Reference< XPropertySetInfo > SAL_CALL ZipPackage::getPropertySetInfo( )
|
|
throw(RuntimeException)
|
|
{
|
|
return Reference < XPropertySetInfo > (NULL);
|
|
}
|
|
void SAL_CALL ZipPackage::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
|
|
throw(UnknownPropertyException, PropertyVetoException, IllegalArgumentException, WrappedTargetException, RuntimeException)
|
|
{
|
|
if (aPropertyName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("HasEncryptedEntries") ) )
|
|
throw IllegalArgumentException (); // This property is read-only
|
|
else if (aPropertyName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("EncryptionKey") ) )
|
|
{
|
|
if (!( aValue >>= aEncryptionKey ) )
|
|
throw IllegalArgumentException();
|
|
}
|
|
else if (aPropertyName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("SegmentSize") ) )
|
|
{
|
|
if (!( aValue >>= nSegmentSize ) )
|
|
throw IllegalArgumentException();
|
|
}
|
|
else
|
|
throw UnknownPropertyException();
|
|
}
|
|
Any SAL_CALL ZipPackage::getPropertyValue( const OUString& PropertyName )
|
|
throw(UnknownPropertyException, WrappedTargetException, RuntimeException)
|
|
{
|
|
Any aAny;
|
|
if (PropertyName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( "EncryptionKey" ) ) )
|
|
{
|
|
aAny <<= aEncryptionKey;
|
|
return aAny;
|
|
}
|
|
else if (PropertyName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( "SegmentSize" ) ) )
|
|
{
|
|
aAny <<= nSegmentSize;
|
|
return aAny;
|
|
}
|
|
if (PropertyName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( "HasEncryptedEntries" ) ) )
|
|
{
|
|
aAny <<= bHasEncryptedEntries;
|
|
return aAny;
|
|
}
|
|
throw UnknownPropertyException();
|
|
}
|
|
void SAL_CALL ZipPackage::addPropertyChangeListener( const OUString& aPropertyName, const Reference< XPropertyChangeListener >& xListener )
|
|
throw(UnknownPropertyException, WrappedTargetException, RuntimeException)
|
|
{
|
|
}
|
|
void SAL_CALL ZipPackage::removePropertyChangeListener( const OUString& aPropertyName, const Reference< XPropertyChangeListener >& aListener )
|
|
throw(UnknownPropertyException, WrappedTargetException, RuntimeException)
|
|
{
|
|
}
|
|
void SAL_CALL ZipPackage::addVetoableChangeListener( const OUString& PropertyName, const Reference< XVetoableChangeListener >& aListener )
|
|
throw(UnknownPropertyException, WrappedTargetException, RuntimeException)
|
|
{
|
|
}
|
|
void SAL_CALL ZipPackage::removeVetoableChangeListener( const OUString& PropertyName, const Reference< XVetoableChangeListener >& aListener )
|
|
throw(UnknownPropertyException, WrappedTargetException, RuntimeException)
|
|
{
|
|
}
|
|
void ZipPackage::getInteractionHandler()
|
|
{
|
|
if ( ! xInteractionHandler.is() )
|
|
{
|
|
OUString sServiceName ( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.task.InteractionHandler" ) );
|
|
xInteractionHandler = Reference < XInteractionHandler > ( xFactory->createInstance ( sServiceName ), UNO_QUERY );
|
|
}
|
|
}
|
|
|
|
IOErrorCode Impl_OSLFileErrorToUCBIoErrorCode ( oslFileError aRC )
|
|
{
|
|
IOErrorCode eReturn = IOErrorCode_UNKNOWN;
|
|
switch ( aRC )
|
|
{
|
|
// open
|
|
case osl_File_E_NOMEM:// not enough memory for allocating structures <br>
|
|
eReturn = IOErrorCode_OUT_OF_MEMORY;
|
|
break;
|
|
case osl_File_E_NAMETOOLONG:// pathname was too long<br>
|
|
eReturn = IOErrorCode_NAME_TOO_LONG;
|
|
break;
|
|
case osl_File_E_NOENT:// No such file or directory<br>
|
|
eReturn = IOErrorCode_NOT_EXISTING;
|
|
break;
|
|
case osl_File_E_ACCES:// permission denied<P>
|
|
eReturn = IOErrorCode_ACCESS_DENIED;
|
|
break;
|
|
case osl_File_E_ISDIR:// Is a directory<p>
|
|
eReturn = IOErrorCode_INVALID_ACCESS;
|
|
break;
|
|
case osl_File_E_NOTDIR:// Not a directory<br>
|
|
eReturn = IOErrorCode_NO_DIRECTORY;
|
|
break;
|
|
case osl_File_E_NXIO:// No such device or address<br>
|
|
eReturn = IOErrorCode_INVALID_DEVICE;
|
|
break;
|
|
case osl_File_E_NODEV:// No such device<br>
|
|
eReturn = IOErrorCode_INVALID_DEVICE;
|
|
break;
|
|
case osl_File_E_ROFS:// Read-only file system<br>
|
|
eReturn = IOErrorCode_ACCESS_DENIED;
|
|
break;
|
|
case osl_File_E_FAULT:// Bad address<br>
|
|
eReturn = IOErrorCode_INVALID_DEVICE;
|
|
break;
|
|
case osl_File_E_LOOP:// Too many symbolic links encountered<br>
|
|
break;
|
|
case osl_File_E_MFILE:// too many open files used by the process<br>
|
|
break;
|
|
case osl_File_E_NFILE:// too many open files in the system<br>
|
|
break;
|
|
case osl_File_E_EXIST:// File exists<br>
|
|
eReturn = IOErrorCode_CANT_CREATE;
|
|
break;
|
|
case osl_File_E_MULTIHOP:// Multihop attempted<br>
|
|
break;
|
|
case osl_File_E_FBIG:// File too large<br>
|
|
eReturn = IOErrorCode_INVALID_LENGTH;
|
|
break;
|
|
|
|
// write
|
|
case osl_File_E_AGAIN:// Operation would block<br>
|
|
break;
|
|
case osl_File_E_NOLCK:// No record locks available<br>
|
|
break;
|
|
case osl_File_E_NOSPC:// No space left on device<br>
|
|
eReturn = IOErrorCode_OUT_OF_DISK_SPACE;
|
|
break;
|
|
case osl_File_E_INVAL:// the format of the parameters was not valid<p>
|
|
eReturn = IOErrorCode_INVALID_PARAMETER;
|
|
break;
|
|
|
|
// close
|
|
case osl_File_E_BADF:// Bad file<br>
|
|
eReturn = IOErrorCode_NO_FILE;
|
|
break;
|
|
case osl_File_E_INTR:// function call was interrupted<br>
|
|
eReturn = IOErrorCode_ABORT;
|
|
break;
|
|
case osl_File_E_NOLINK:// Link has been severed<br>
|
|
break;
|
|
case osl_File_E_IO:// I/O error<p>
|
|
eReturn = IOErrorCode_GENERAL;
|
|
break;
|
|
}
|
|
return eReturn;
|
|
}
|
|
|
|
sal_Bool ZipPackage::HandleError ( Any &rAny, sal_uInt16 eContinuations )
|
|
{
|
|
InteractionRequest* pRequest;
|
|
Reference < XInteractionRequest > xRequest ( pRequest = new InteractionRequest ( rAny, eContinuations ));
|
|
xInteractionHandler->handle ( xRequest );
|
|
const sal_uInt16 nSelection = pRequest->getSelection();
|
|
return nSelection == EC_YES || nSelection == EC_RETRY;
|
|
}
|
|
|
|
sal_Bool ZipPackage::HandleError ( oslFileError aRC, sal_uInt16 eContinuations, const OUString &rFileName )
|
|
{
|
|
Any aAny;
|
|
InteractiveAugmentedIOException aException;
|
|
aException.Code = Impl_OSLFileErrorToUCBIoErrorCode ( aRC );
|
|
aException.Arguments.realloc ( 1 );
|
|
PropertyValue aProperty;
|
|
aProperty.Name = OUString ( RTL_CONSTASCII_USTRINGPARAM ( "Uri" ) );
|
|
aProperty.Handle = static_cast < sal_Int32 > ( -1 );
|
|
aProperty.Value <<= rFileName;
|
|
aException.Arguments[0] <<= aProperty;
|
|
aAny <<= aException;
|
|
return HandleError (aAny, eContinuations );
|
|
}
|
|
|
|
void ZipPackage::unSpanFile ( )
|
|
{
|
|
const OUString sServiceName ( RTL_CONSTASCII_USTRINGPARAM ( "com.sun.star.io.TempFile" ) );
|
|
Reference < XInputStream > xTempIn = Reference < XInputStream > ( xFactory->createInstance ( sServiceName ), UNO_QUERY );
|
|
Reference < XOutputStream > xTempOut = Reference < XOutputStream > ( xTempIn, UNO_QUERY );
|
|
Reference < XSeekable > xTempSeek = Reference < XSeekable > ( xTempIn, UNO_QUERY );
|
|
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "MTG: unspanning %s\n", ImplGetChars ( sURL ) );
|
|
#endif
|
|
Sequence < sal_Int8 > aBuffer;
|
|
sal_Int64 nRead;
|
|
xContentSeek->seek ( 0 );
|
|
do
|
|
{
|
|
nRead = xContentStream->readBytes ( aBuffer, n_ConstBufferSize );
|
|
xTempOut->writeBytes ( aBuffer );
|
|
}
|
|
while ( nRead == n_ConstBufferSize );
|
|
|
|
// Clear the references to the first segment so that we can unmount the disk
|
|
xContentStream = xTempIn;
|
|
xContentSeek = xTempSeek;
|
|
delete pContent; pContent = NULL;
|
|
|
|
// Check if the buffer just read is the last one
|
|
if ( checkEnd ( aBuffer ) )
|
|
return;
|
|
|
|
sal_Int16 nDiskNum = 1;
|
|
VolumeInfo aInfo ( osl_VolumeInfo_Mask_FreeSpace | osl_VolumeInfo_Mask_DeviceHandle | osl_VolumeInfo_Mask_Attributes );
|
|
FileBase::RC aRC = Directory::getVolumeInfo ( sURL, aInfo );
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "GetVolumeInfo returned %d\n", aRC );
|
|
#endif
|
|
VolumeDevice aDevice = aInfo.getDeviceHandle();
|
|
sal_Bool bIsRemovable = aInfo.getRemoveableFlag();
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "isRemovable is %d\n", bIsRemovable );
|
|
#endif
|
|
|
|
sal_Int32 nLastSlash = sURL.lastIndexOf ( '/' );
|
|
OUString sFileName, sMountPath = aDevice.getMountPath();
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "mountpath is %s\n", ImplGetChars ( sMountPath ) );
|
|
#endif
|
|
const OUString sFilePrefix = sURL.copy ( 1 + nLastSlash, sURL.lastIndexOf ( '.' ) - nLastSlash );
|
|
SegmentEnum eRet = e_Finished;
|
|
SuffixGenerator aGenerator;
|
|
do
|
|
{
|
|
//pBuffer->nextSegment( ++nDiskNum);
|
|
nDiskNum++;
|
|
aGenerator.generateFileName ( sFileName, sFilePrefix, nDiskNum );
|
|
if ( bIsRemovable )
|
|
{
|
|
// We need an interaction handler to request disks
|
|
getInteractionHandler();
|
|
if ( RequestDisk( sMountPath, nDiskNum ) < 0 )
|
|
return;
|
|
#ifdef MTG_DEBUG
|
|
fprintf ( stderr, "unSpanFile calling readSegment on disk number %d (mount path is %s )\n", nDiskNum, ImplGetChars ( sMountPath ) );
|
|
#endif
|
|
eRet = readSegment ( sFileName, sMountPath, xTempOut, nDiskNum );
|
|
if (eRet == e_Aborted)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
OUString sFullPath = sURL.copy ( 0, nLastSlash + 1 ) + sFileName;
|
|
Reference < XInputStream > xStream;
|
|
Content aContent (sFullPath, Reference < XCommandEnvironment >() );
|
|
Reference < XActiveDataSink > xSink = new ZipPackageSink;
|
|
try
|
|
{
|
|
if (aContent.openStream ( xSink ) )
|
|
xStream = xSink->getInputStream();
|
|
if ( xStream.is() )
|
|
{
|
|
do
|
|
{
|
|
nRead = xStream->readBytes ( aBuffer, n_ConstBufferSize );
|
|
xTempOut->writeBytes ( aBuffer );
|
|
}
|
|
while ( nRead == n_ConstBufferSize );
|
|
eRet = checkEnd ( aBuffer ) ? e_Finished : e_Success;
|
|
}
|
|
}
|
|
catch (com::sun::star::uno::Exception&)
|
|
{
|
|
// bad juju
|
|
//
|
|
}
|
|
}
|
|
}
|
|
while ( eRet != e_Finished );
|
|
}
|
|
|
|
sal_Bool ZipPackage::checkEnd ( Sequence < sal_Int8 > &rSequence )
|
|
{
|
|
sal_Int32 nLength, nPos, nEnd;
|
|
|
|
nLength = static_cast <sal_Int32 > (rSequence.getLength());
|
|
// Start at what should be the beginning of the end header
|
|
if ( nLength == 0 || nLength < ENDHDR )
|
|
return sal_False;
|
|
nPos = nLength - ENDHDR - ZIP_MAXNAMELEN;
|
|
// We'll only look for the END signature within a sane range (ZIP_MAXNAMELEN)
|
|
nEnd = nPos >= 0 ? nPos : 0;
|
|
|
|
const sal_Int8 *pBuffer = rSequence.getConstArray();
|
|
nPos = nLength - ENDHDR;
|
|
while ( nPos >= nEnd )
|
|
{
|
|
if (pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 5 && pBuffer[nPos+3] == 6 )
|
|
return sal_True;
|
|
nPos--;
|
|
}
|
|
return sal_False;
|
|
}
|