/*************************************************************************
 *
 *  $RCSfile: dp_package.cxx,v $
 *
 *  $Revision: 1.6 $
 *
 *  last change: $Author: kz $ $Date: 2005-01-21 17:14:13 $
 *
 *  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): _______________________________________
 *
 *
 ************************************************************************/

#include "dp_package.hrc"
#include "dp_backend.h"
#include "dp_ucb.h"
#include "dp_interact.h"
#include "rtl/uri.hxx"
#include "cppuhelper/exc_hlp.hxx"
#include "ucbhelper/content.hxx"
#include "svtools/inettype.hxx"
#include "comphelper/anytostring.hxx"
#include "com/sun/star/lang/WrappedTargetException.hpp"
#include "com/sun/star/beans/UnknownPropertyException.hpp"
#include "com/sun/star/io/XOutputStream.hpp"
#include "com/sun/star/io/XInputStream.hpp"
#include "com/sun/star/task/InteractionClassification.hpp"
#include "com/sun/star/task/XInteractionApprove.hpp"
#include "com/sun/star/ucb/XInteractionReplaceExistingData.hpp"
#include "com/sun/star/ucb/NameClashResolveRequest.hpp"
#include "com/sun/star/ucb/XContentAccess.hpp"
#include "com/sun/star/ucb/NameClash.hpp"
#include "com/sun/star/ucb/UnsupportedCommandException.hpp"
#include "com/sun/star/sdbc/XResultSet.hpp"
#include "com/sun/star/sdbc/XRow.hpp"
#include "com/sun/star/packages/manifest/XManifestReader.hpp"
#include "com/sun/star/packages/manifest/XManifestWriter.hpp"
#include <vector>


using namespace ::dp_misc;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;
using ::rtl::OUString;

namespace dp_registry {
namespace backend {
namespace bundle {
namespace {

//==============================================================================
class BackendImpl : public PackageRegistryBackend
{
    class PackageImpl : public ::dp_registry::backend::Package
    {
        BackendImpl * getMyBackend() const {
            return static_cast<BackendImpl *>(m_myBackend.get());
        }

        OUString m_description;
        OUString m_url_expanded;
        const bool m_legacyBundle;
        Sequence< Reference<deployment::XPackage> > m_bundle;
        Sequence< Reference<deployment::XPackage> > * m_pBundle;

        Reference<deployment::XPackage> bindBundleItem(
            OUString const & url, OUString const & mediaType,
            Reference<XCommandEnvironment> const & xCmdEnv,
            bool notifyDetectionError = true );

        typedef ::std::vector< Reference<deployment::XPackage> > t_packagevec;
        void scanBundle(
            t_packagevec & bundle,
            ::rtl::Reference<AbortChannel> const & abortChannel,
            Reference<XCommandEnvironment> const & xCmdEnv );
        void scanLegacyBundle(
            t_packagevec & bundle,
            OUString const & url,
            ::rtl::Reference<AbortChannel> const & abortChannel,
            Reference<XCommandEnvironment> const & xCmdEnv,
            bool skip_registration = false );

        // Package
        virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
            ::osl::ResettableMutexGuard & guard,
            ::rtl::Reference<AbortChannel> const & abortChannel,
            Reference<XCommandEnvironment> const & xCmdEnv );
        virtual void processPackage_(
            ::osl::ResettableMutexGuard & guard,
            bool registerPackage,
            ::rtl::Reference<AbortChannel> const & abortChannel,
            Reference<XCommandEnvironment> const & xCmdEnv );

        virtual void SAL_CALL disposing();

    public:
        PackageImpl(
            ::rtl::Reference<PackageRegistryBackend> const & myBackend,
            OUString const & url,
            OUString const & name,
            Reference<deployment::XPackageTypeInfo> const & xPackageType,
            bool legacyBundle,
            Reference<XCommandEnvironment> const & xCmdEnv )
            : Package( myBackend, url, name, name /* display-name */,
                       xPackageType ),
              m_url_expanded( expandUnoRcUrl( url ) ),
              m_legacyBundle( legacyBundle ),
              m_pBundle( 0 )
            {}

        // XPackage
        virtual sal_Bool SAL_CALL isBundle() throw (RuntimeException);
        virtual Sequence< Reference<deployment::XPackage> > SAL_CALL getBundle(
            Reference<task::XAbortChannel> const & xAbortChannel,
            Reference<XCommandEnvironment> const & xCmdEnv )
            throw (deployment::DeploymentException,
                   CommandFailedException, CommandAbortedException,
                   lang::IllegalArgumentException, RuntimeException);
        virtual OUString SAL_CALL getDescription() throw (RuntimeException);
        virtual void SAL_CALL exportTo(
            OUString const & destFolderURL, OUString const & newTitle,
            sal_Int32 nameClashAction,
            Reference<XCommandEnvironment> const & xCmdEnv )
            throw (CommandFailedException, CommandAbortedException,
                   RuntimeException);
    };
    friend class PackageImpl;

    Reference<deployment::XPackageRegistry> m_xRootRegistry;
    const Reference<deployment::XPackageTypeInfo> m_xBundleTypeInfo;
    const Reference<deployment::XPackageTypeInfo> m_xLegacyBundleTypeInfo;
    Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;

    // PackageRegistryBackend
    virtual Reference<deployment::XPackage> bindPackage_(
        OUString const & url, OUString const & mediaType,
        Reference<XCommandEnvironment> const & xCmdEnv );

    virtual void SAL_CALL disposing();

public:
    BackendImpl(
        Sequence<Any> const & args,
        Reference<XComponentContext> const & xComponentContext,
        OUString const & implName,
        Reference<deployment::XPackageRegistry> const & xRootRegistry );

    // XPackageRegistry
    virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
    getSupportedPackageTypes() throw (RuntimeException);
};

//______________________________________________________________________________
BackendImpl::BackendImpl(
    Sequence<Any> const & args,
    Reference<XComponentContext> const & xComponentContext,
    OUString const & implName,
    Reference<deployment::XPackageRegistry> const & xRootRegistry )
    : PackageRegistryBackend( args, xComponentContext, implName ),
      m_xRootRegistry( xRootRegistry ),
      m_xBundleTypeInfo( new Package::TypeInfo(
                             OUSTR("application/vnd.sun.star.package-bundle"),
                             OUSTR("*.uno.pkg"),
                             getResourceString(RID_STR_PACKAGE_BUNDLE),
                             RID_IMG_DEF_PACKAGE_BUNDLE,
                             RID_IMG_DEF_PACKAGE_BUNDLE_HC ) ),
      m_xLegacyBundleTypeInfo( new Package::TypeInfo(
                                   OUSTR("application/"
                                         "vnd.sun.star.legacy-package-bundle"),
                                   OUSTR("*.zip"),
                                   m_xBundleTypeInfo->getShortDescription(),
                                   RID_IMG_DEF_PACKAGE_BUNDLE,
                                   RID_IMG_DEF_PACKAGE_BUNDLE_HC ) ),
      m_typeInfos( 2 )
{
    m_typeInfos[ 0 ] = m_xBundleTypeInfo;
    m_typeInfos[ 1 ] = m_xLegacyBundleTypeInfo;
}

//______________________________________________________________________________
void BackendImpl::disposing()
{
    m_xRootRegistry.clear();
    PackageRegistryBackend::disposing();
}

// XPackageRegistry
//______________________________________________________________________________
Sequence< Reference<deployment::XPackageTypeInfo> >
BackendImpl::getSupportedPackageTypes() throw (RuntimeException)
{
    return m_typeInfos;
}

// PackageRegistryBackend
//______________________________________________________________________________
Reference<deployment::XPackage> BackendImpl::bindPackage_(
    OUString const & url, OUString const & mediaType_,
    Reference<XCommandEnvironment> const & xCmdEnv )
{
    OUString mediaType( mediaType_ );
    if (mediaType.getLength() == 0)
    {
        // detect media-type:
        ::ucb::Content ucbContent;
        if (create_ucb_content( &ucbContent, url, xCmdEnv ))
        {
            const OUString title( ucbContent.getPropertyValue(
                                      StrTitle::get() ).get<OUString>() );
            if (title.endsWithIgnoreAsciiCaseAsciiL(
                    RTL_CONSTASCII_STRINGPARAM(".uno.pkg") ))
                mediaType = OUSTR("application/vnd.sun.star.package-bundle");
            else if (title.endsWithIgnoreAsciiCaseAsciiL(
                         RTL_CONSTASCII_STRINGPARAM(".zip") ))
                mediaType =
                    OUSTR("application/vnd.sun.star.legacy-package-bundle");
        }
        if (mediaType.getLength() == 0)
            throw lang::IllegalArgumentException(
                StrCannotDetectMediaType::get() + url,
                static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
    }

    String type, subType;
    INetContentTypeParameterList params;
    if (INetContentTypes::parse( mediaType, type, subType, &params ))
    {
        if (type.EqualsIgnoreCaseAscii("application"))
        {
            ::ucb::Content ucbContent( url, xCmdEnv );
            if (subType.EqualsIgnoreCaseAscii("vnd.sun.star.package-bundle")) {
                return new PackageImpl(
                    this, url, ucbContent.getPropertyValue(
                        StrTitle::get() ).get<OUString>(),
                    m_xBundleTypeInfo, false, xCmdEnv );
            }
            else if (subType.EqualsIgnoreCaseAscii(
                         "vnd.sun.star.legacy-package-bundle")) {
                return new PackageImpl(
                    this, url, ucbContent.getPropertyValue(
                        StrTitle::get() ).get<OUString>(),
                    m_xLegacyBundleTypeInfo, true, xCmdEnv );
            }
        }
    }
    throw lang::IllegalArgumentException(
        StrUnsupportedMediaType::get() + mediaType,
        static_cast<OWeakObject *>(this),
        static_cast<sal_Int16>(-1) );
}

//##############################################################################

//______________________________________________________________________________
void BackendImpl::PackageImpl::disposing()
{
    sal_Int32 len = m_bundle.getLength();
    Reference<deployment::XPackage> const * p = m_bundle.getConstArray();
    for ( sal_Int32 pos = 0; pos < len; ++pos )
        try_dispose( p[ pos ] );
    m_bundle.realloc( 0 );

    Package::disposing();
}

// Package
//______________________________________________________________________________
beans::Optional< beans::Ambiguous<sal_Bool> >
BackendImpl::PackageImpl::isRegistered_(
    ::osl::ResettableMutexGuard & guard,
    ::rtl::Reference<AbortChannel> const & abortChannel,
    Reference<XCommandEnvironment> const & xCmdEnv )
{
    const Sequence< Reference<deployment::XPackage> > bundle(
        getBundle( abortChannel.get(), xCmdEnv ) );
    bool reg = false;
    bool present = false;
    bool ambig = false;
    for ( sal_Int32 pos = bundle.getLength(); pos--; )
    {
        Reference<deployment::XPackage> const & xPackage = bundle[ pos ];
        Reference<task::XAbortChannel> xSubAbortChannel(
            xPackage->createAbortChannel() );
        AbortChannel::Chain chain( abortChannel, xSubAbortChannel );
        beans::Optional< beans::Ambiguous<sal_Bool> > option(
            xPackage->isRegistered( xSubAbortChannel, xCmdEnv ) );
        if (option.IsPresent)
        {
            beans::Ambiguous<sal_Bool> const & status = option.Value;
            if (present)
            {
                if (reg != (status.Value != sal_False)) {
                    ambig = true;
                    reg = false;
                    break;
                }
            }
            else
            {
                reg = status.Value;
                present = true;
            }
        }
    }
    return beans::Optional< beans::Ambiguous<sal_Bool> >(
        present, beans::Ambiguous<sal_Bool>(reg, ambig) );
}

//______________________________________________________________________________
void BackendImpl::PackageImpl::processPackage_(
    ::osl::ResettableMutexGuard & guard,
    bool registerPackage,
    ::rtl::Reference<AbortChannel> const & abortChannel,
    Reference<XCommandEnvironment> const & xCmdEnv )
{
    const Sequence< Reference<deployment::XPackage> > bundle(
        getBundle( abortChannel.get(), xCmdEnv ) );

    if (registerPackage)
    {
        const sal_Int32 len = bundle.getLength();
        for ( sal_Int32 pos = 0; pos < len; ++pos )
        {
            Reference<deployment::XPackage> const & xPackage = bundle[ pos ];
            Reference<task::XAbortChannel> xSubAbortChannel(
                xPackage->createAbortChannel() );
            AbortChannel::Chain chain( abortChannel, xSubAbortChannel );
            try {
                xPackage->registerPackage( xSubAbortChannel, xCmdEnv );
            }
            catch (RuntimeException &) {
                throw;
            }
            catch (CommandAbortedException &) {
                throw;
            }
            catch (Exception &) {
                // CommandFailedException, DeploymentException:
                Any exc( ::cppu::getCaughtException() );
                // try to handle exception, notify:
                bool approve = false, abort = false;
                if (! interactContinuation(
                        Any( lang::WrappedTargetException(
                                 OUSTR("bundle item registration error!"),
                                 static_cast<OWeakObject *>(this), exc ) ),
                        task::XInteractionApprove::static_type(), xCmdEnv,
                        &approve, &abort )) {
                    OSL_ASSERT( !approve && !abort );
                    if (m_legacyBundle) // default for legacy packages: ignore
                        continue;
                    // no selection at all, so rethrow;
                    // no C++ rethrow after getCaughtException(),
                    // see cppuhelper/exc_hlp.hxx:
                    ::cppu::throwException(exc);
                }
                if (approve && !abort) // ignore error, just continue
                    continue;

                {
                    ProgressLevel progress(
                        xCmdEnv, OUSTR("rollback...") );
                    // try rollback
                    for ( ; pos--; )
                    {
                        try {
                            bundle[ pos ]->revokePackage(
                                xSubAbortChannel, xCmdEnv );
                        }
                        catch (RuntimeException &) {
                            throw;
                        }
                        catch (CommandAbortedException &) {
                            throw;
                        }
                        catch (Exception &) {
                            // bundle rollback error:
                            OSL_ENSURE( 0, ::rtl::OUStringToOString(
                                            ::comphelper::anyToString(
                                                ::cppu::getCaughtException() ),
                                            RTL_TEXTENCODING_UTF8 ).getStr() );
                            // ignore any errors of rollback
                        }
                    }
                    progress.update( OUSTR("rollback finished.") );
                }

                deployment::DeploymentException dpExc;
                if (exc >>= dpExc) {
                    throw CommandFailedException(
                        dpExc.Message, dpExc.Context, dpExc.Cause );
                }
                else {
                    // rethrow CommandFailedException
                    ::cppu::throwException(exc);
                }
            }
        }
    }
    else
    {
        // revoke in reverse order:
        for ( sal_Int32 pos = bundle.getLength(); pos--; )
        {
            Reference<deployment::XPackage> const & xPackage = bundle[ pos ];
            Reference<task::XAbortChannel> xSubAbortChannel(
                xPackage->createAbortChannel() );
            AbortChannel::Chain chain( abortChannel, xSubAbortChannel );
            try {
                bundle[ pos ]->revokePackage( xSubAbortChannel, xCmdEnv );
            }
            catch (RuntimeException &) {
                throw;
            }
            catch (CommandAbortedException &) {
                throw;
            }
            catch (Exception &) {
                // CommandFailedException, DeploymentException:
                Any exc( ::cppu::getCaughtException() );
                // try to handle exception, notify:
                bool approve = false, abort = false;
                if (! interactContinuation(
                        Any( lang::WrappedTargetException(
                                 OUSTR("bundle item revocation error!"),
                                 static_cast<OWeakObject *>(this), exc ) ),
                        task::XInteractionApprove::static_type(), xCmdEnv,
                        &approve, &abort )) {
                    OSL_ASSERT( !approve && !abort );
                    if (m_legacyBundle) // default for legacy packages: ignore
                        continue;
                    // no selection at all, so rethrow
                    // no C++ rethrow after getCaughtException(),
                    // see cppuhelper/exc_hlp.hxx:
                    ::cppu::throwException(exc);
                }
                // ignore errors when revoking, although abort may have been
                // selected
            }
        }
    }
}

//______________________________________________________________________________
OUString BackendImpl::PackageImpl::getDescription() throw (RuntimeException)
{
    if (m_description.getLength() == 0)
        return Package::getDescription();
    else
        return m_description;
}

//______________________________________________________________________________
void BackendImpl::PackageImpl::exportTo(
    OUString const & destFolderURL, OUString const & newTitle,
    sal_Int32 nameClashAction, Reference<XCommandEnvironment> const & xCmdEnv )
    throw (CommandFailedException, CommandAbortedException, RuntimeException)
{
    ::ucb::Content sourceContent( m_url_expanded, xCmdEnv );
    OUString title(newTitle);
    if (title.getLength() == 0)
        sourceContent.getPropertyValue( StrTitle::get() ) >>= title;
    OUString destURL( makeURL( destFolderURL, ::rtl::Uri::encode(
                                   title, rtl_UriCharClassPchar,
                                   rtl_UriEncodeIgnoreEscapes,
                                   RTL_TEXTENCODING_UTF8 ) ) );

    if (nameClashAction == NameClash::ASK)
    {
        if (create_ucb_content(
                0, destURL, xCmdEnv, false /* no throw */ )) {
            bool replace = false, abort = false;
            if (! interactContinuation(
                    Any( NameClashResolveRequest(
                             OUSTR("file already exists: ") + title,
                             static_cast<OWeakObject *>(this),
                             task::InteractionClassification_QUERY,
                             destFolderURL, title, OUString() ) ),
                    XInteractionReplaceExistingData::static_type(), xCmdEnv,
                    &replace, &abort ) || !replace) {
                return;
            }
        }
    }
    else if (nameClashAction != NameClash::OVERWRITE) {
        throw CommandFailedException(
            OUSTR("unsupported nameClashAction!"),
            static_cast<OWeakObject *>(this), Any() );
    }
    erase_path( destURL, xCmdEnv );

    ::rtl::OUStringBuffer buf;
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("vnd.sun.star.zip://") );
    buf.append( ::rtl::Uri::encode( destURL,
                                    rtl_UriCharClassRegName,
                                    rtl_UriEncodeIgnoreEscapes,
                                    RTL_TEXTENCODING_UTF8 ) );
    buf.append( static_cast<sal_Unicode>('/') );
    OUString destFolder( buf.makeStringAndClear() );

    ::ucb::Content destFolderContent( destFolder, xCmdEnv );
    {
        // transfer every item of folder into zip:
        Reference<sdbc::XResultSet> xResultSet(
            sourceContent.createCursor(
                Sequence<OUString>(),
                ::ucb::INCLUDE_FOLDERS_AND_DOCUMENTS ) );
        ProgressLevel progress( xCmdEnv, OUString() );
        while (xResultSet->next())
        {
            ::ucb::Content subContent(
                Reference<XContentAccess>(
                    xResultSet, UNO_QUERY_THROW )->queryContent(), xCmdEnv );
            if (! destFolderContent.transferContent(
                    subContent, ::ucb::InsertOperation_COPY,
                    OUString(), NameClash::OVERWRITE ))
                throw RuntimeException( OUSTR("UCB transferContent() failed!"),
                                        static_cast<OWeakObject *>(this) );
            progress.update( Any() ); // animating progress bar
        }
    }

    // assure META-INF folder:
    ::ucb::Content metainfFolderContent;
    create_folder( &metainfFolderContent,
                   makeURL( destFolderContent.getURL(), OUSTR("META-INF") ),
                   xCmdEnv );

    if (m_legacyBundle)
    {
        // easy to migrate legacy bundles to new format:
        // just export them once using a .uno.pkg name!
        // set detected media-types of any bundle item:

        // collect all manifest entries:
        Sequence< Reference<deployment::XPackage> > bundle;
        try {
            bundle = getBundle( Reference<task::XAbortChannel>(), xCmdEnv );
        }
        // xxx todo: think about exception specs:
        catch (deployment::DeploymentException &) {
            OSL_ENSURE( 0, ::rtl::OUStringToOString(
                            ::comphelper::anyToString(
                                ::cppu::getCaughtException() ),
                            RTL_TEXTENCODING_UTF8 ).getStr() );
        }
        catch (lang::IllegalArgumentException & exc) {
            (void) exc;
            OSL_ENSURE( 0, ::rtl::OUStringToOString(
                            exc.Message, RTL_TEXTENCODING_UTF8 ).getStr() );
        }

        ::std::vector< Sequence<beans::PropertyValue> > manifest;
        manifest.reserve( bundle.getLength() );
        sal_Int32 baseURLlen = m_url_expanded.getLength();
        Reference<deployment::XPackage> const *pbundle = bundle.getConstArray();
        const OUString strMediaType = OUSTR("MediaType");
        const OUString strFullPath = OUSTR("FullPath");
        const OUString strIsFolder = OUSTR("IsFolder");
        for ( sal_Int32 pos = bundle.getLength(); pos--; )
        {
            Reference<deployment::XPackage> const & xPackage = pbundle[ pos ];
            OUString url_( expandUnoRcUrl( xPackage->getURL() ) );
            OSL_ASSERT( url_.getLength() >= baseURLlen );
            OUString fullPath;
            if (url_.getLength() > baseURLlen)
                fullPath = url_.copy( baseURLlen + 1 );
            ::ucb::Content ucbContent( url_, xCmdEnv );
            if (ucbContent.getPropertyValue(strIsFolder).get<bool>())
                fullPath += OUSTR("/");
            Sequence<beans::PropertyValue> attribs( 2 );
            beans::PropertyValue * pattribs = attribs.getArray();
            pattribs[ 0 ].Name = strFullPath;
            pattribs[ 0 ].Value <<= fullPath;
            pattribs[ 1 ].Name = strMediaType;
            const Reference<deployment::XPackageTypeInfo> xPackageType(
                xPackage->getPackageType() );
            OUString mediaType;
            OSL_ASSERT( xPackageType.is() );
            if (xPackageType.is())
                mediaType = xPackageType->getMediaType();
            else
                mediaType = OUSTR("unknown");
            pattribs[ 1 ].Value <<= mediaType;
            manifest.push_back( attribs );
        }

        // write into pipe:
        Reference<XComponentContext> xContext(
            getMyBackend()->getComponentContext() );
        Reference<packages::manifest::XManifestWriter> xManifestWriter(
            xContext->getServiceManager()->createInstanceWithContext(
                OUSTR("com.sun.star.packages.manifest.ManifestWriter"),
                xContext ), UNO_QUERY_THROW );
        Reference<io::XOutputStream> xPipe(
            xContext->getServiceManager()->createInstanceWithContext(
                OUSTR("com.sun.star.io.Pipe"), xContext ), UNO_QUERY_THROW );
        xManifestWriter->writeManifestSequence(
            xPipe, Sequence< Sequence<beans::PropertyValue> >(
                &manifest[ 0 ], manifest.size() ) );

        // write buffered pipe data to content:
        ::ucb::Content manifestContent(
            makeURL( metainfFolderContent.getURL(), OUSTR("manifest.xml") ),
            xCmdEnv );
        manifestContent.writeStream(
            Reference<io::XInputStream>( xPipe, UNO_QUERY_THROW ),
            true /* replace existing */ );
    }
    else
    {
        // overwrite manifest.xml:
        ::ucb::Content manifestContent(
            makeURL( m_url_expanded, OUSTR("META-INF/manifest.xml") ),
            xCmdEnv );
        if (! metainfFolderContent.transferContent(
                manifestContent, ::ucb::InsertOperation_COPY,
                OUString(), NameClash::OVERWRITE ))
            throw RuntimeException( OUSTR("UCB transferContent() failed!"),
                                    static_cast<OWeakObject *>(this) );
    }

    // xxx todo: maybe obsolete in the future
    try {
        destFolderContent.executeCommand( OUSTR("flush"), Any() );
    }
    catch (UnsupportedCommandException &) {
    }
}

//______________________________________________________________________________
sal_Bool BackendImpl::PackageImpl::isBundle() throw (RuntimeException)
{
    return true;
}

//______________________________________________________________________________
Sequence< Reference<deployment::XPackage> > BackendImpl::PackageImpl::getBundle(
    Reference<task::XAbortChannel> const & xAbortChannel,
    Reference<XCommandEnvironment> const & xCmdEnv )
    throw (deployment::DeploymentException,
           CommandFailedException, CommandAbortedException,
           lang::IllegalArgumentException, RuntimeException)
{
    Sequence< Reference<deployment::XPackage> > * pBundle = m_pBundle;
    if (pBundle == 0)
    {
        t_packagevec bundle;
        try {
            if (m_legacyBundle)
            {
                // .zip legacy packages allow script.xlb, dialog.xlb in bundle
                // root folder:
                OUString mediaType;
                // probe for script.xlb:
                if (create_ucb_content(
                        0, makeURL( m_url_expanded, OUSTR("script.xlb") ),
                        xCmdEnv, false /* no throw */ )) {
                    mediaType = OUSTR("application/vnd.sun.star.basic-library");
                }
                // probe for dialog.xlb:
                else if (create_ucb_content(
                             0, makeURL( m_url_expanded, OUSTR("dialog.xlb") ),
                             xCmdEnv, false /* no throw */ ))
                    mediaType = OUSTR("application/vnd.sun.star."
                                      "dialog-library");

                if (mediaType.getLength() > 0) {
                    const Reference<deployment::XPackage> xPackage(
                        bindBundleItem( getURL(), mediaType, xCmdEnv ) );
                    if (xPackage.is())
                        bundle.push_back( xPackage );
                    // continue scanning:
                }
                scanLegacyBundle( bundle, getURL(),
                                  AbortChannel::get(xAbortChannel), xCmdEnv );
            }
            else
            {
                // .uno.pkg:
                scanBundle( bundle, AbortChannel::get(xAbortChannel), xCmdEnv );
            }
        }
        catch (RuntimeException &) {
            throw;
        }
        catch (CommandFailedException &) {
            throw;
        }
        catch (CommandAbortedException &) {
            throw;
        }
        catch (deployment::DeploymentException &) {
            throw;
        }
        catch (Exception &) {
            Any exc( ::cppu::getCaughtException() );
            throw deployment::DeploymentException(
                OUSTR("error scanning bundle: ") + getURL(),
                static_cast<OWeakObject *>(this), exc );
        }

        // sort: schema before config data, typelibs before components:
        Sequence< Reference<deployment::XPackage> > ret( bundle.size() );
        Reference<deployment::XPackage> * pret = ret.getArray();
        sal_Int32 lower_end = 0;
        sal_Int32 upper_end = ret.getLength();
        t_packagevec::const_iterator iPos( bundle.begin() );
        t_packagevec::const_iterator const iEnd( bundle.end() );
        for ( ; iPos != iEnd; ++iPos )
        {
            const Reference<deployment::XPackageTypeInfo> xPackageType(
                (*iPos)->getPackageType() );
            OSL_ASSERT( xPackageType.is() );
            if (xPackageType.is()) {
                const OUString mediaType( xPackageType->getMediaType() );
                String type, subType;
                INetContentTypeParameterList params;
                if (INetContentTypes::parse(
                        mediaType, type, subType, &params ) &&
                    type.EqualsIgnoreCaseAscii("application") &&
                    (subType.EqualsIgnoreCaseAscii(
                        "vnd.sun.star.uno-component") ||
                     subType.EqualsIgnoreCaseAscii(
                         "vnd.sun.star.configuration-data")))
                {
                    --upper_end;
                    pret[ upper_end ] = *iPos;
                    continue;
                }
            }
            pret[ lower_end ] = *iPos;
            ++lower_end;
        }
        OSL_ASSERT( lower_end == upper_end );

        const ::osl::MutexGuard guard( getMutex() );
        pBundle = m_pBundle;
        if (pBundle == 0) {
            m_bundle = ret;
            pBundle = &m_bundle;
            OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
            m_pBundle = pBundle;
        }
    }
    else
        OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
    return *pBundle;
}

inline bool isBundle_( OUString const & mediaType )
{
    // xxx todo: additional parsing?
    return mediaType.getLength() > 0 &&
        (mediaType.matchIgnoreAsciiCaseAsciiL(
            RTL_CONSTASCII_STRINGPARAM(
                "application/vnd.sun.star.package-bundle") ) ||
         mediaType.matchIgnoreAsciiCaseAsciiL(
             RTL_CONSTASCII_STRINGPARAM(
                 "application/vnd.sun.star.legacy-package-bundle") ));
}

//______________________________________________________________________________
Reference<deployment::XPackage> BackendImpl::PackageImpl::bindBundleItem(
    OUString const & url, OUString const & mediaType,
    Reference<XCommandEnvironment> const & xCmdEnv,
    bool notifyDetectionError )
{
    // ignore any nested bundles:
    if (isBundle_(mediaType))
        return Reference<deployment::XPackage>();

    Reference<deployment::XPackage>xPackage;
    try {
        xPackage.set( getMyBackend()->m_xRootRegistry->bindPackage(
                          url, mediaType, xCmdEnv ) );
        OSL_ASSERT( xPackage.is() );
    }
    catch (RuntimeException &) {
        throw;
    }
    catch (CommandFailedException &) {
        // ignore already handled error
    }
    catch (Exception &) {
        const Any exc( ::cppu::getCaughtException() );
        if (notifyDetectionError ||
            !exc.isExtractableTo(
                ::getCppuType( reinterpret_cast<
                               lang::IllegalArgumentException const *>(0) ) ))
        {
            interactContinuation(
                Any( lang::WrappedTargetException(
                         OUSTR("bundle item error!"),
                         static_cast<OWeakObject *>(this), exc ) ),
                task::XInteractionApprove::static_type(), xCmdEnv, 0, 0 );
        }
    }

    if (xPackage.is()) {
        const Reference<deployment::XPackageTypeInfo> xPackageType(
            xPackage->getPackageType() );
        OSL_ASSERT( xPackageType.is() );
        // ignore any nested bundles:
        if (xPackageType.is() && isBundle_( xPackageType->getMediaType() ))
            xPackage.clear();
    }
    return xPackage;
}

//______________________________________________________________________________
void BackendImpl::PackageImpl::scanBundle(
    t_packagevec & bundle,
    ::rtl::Reference<AbortChannel> const & abortChannel,
    Reference<XCommandEnvironment> const & xCmdEnv )
{
    OSL_ASSERT( !m_legacyBundle );

    ::ucb::Content manifestContent;
    if (! create_ucb_content(
            &manifestContent,
            makeURL( m_url_expanded, OUSTR("META-INF/manifest.xml") ),
            xCmdEnv, false /* no throw */ )) {
        OSL_ENSURE( 0, "### missing META-INF/manifest.xml file!" );
        return;
    }

    lang::Locale const & officeLocale = getOfficeLocale();
    OUString descrFile;
    lang::Locale descrFileLocale;

    const Reference<XComponentContext> xContext(
        getMyBackend()->getComponentContext() );
    Reference<packages::manifest::XManifestReader> xManifestReader(
        xContext->getServiceManager()->createInstanceWithContext(
            OUSTR("com.sun.star.packages.manifest.ManifestReader"),
            xContext ), UNO_QUERY_THROW );
    const Sequence< Sequence<beans::PropertyValue> > manifestSeq(
        xManifestReader->readManifestSequence( manifestContent.openStream() ) );
    const OUString packageRootURL( getURL() );
    for ( sal_Int32 pos = manifestSeq.getLength(); pos--; )
    {
        OUString fullPath, mediaType;
        Sequence<beans::PropertyValue> const & attribs = manifestSeq[ pos ];
        for ( sal_Int32 i = attribs.getLength(); i--; )
        {
            if (fullPath.getLength() > 0 && mediaType.getLength() > 0)
                break;
            if (attribs[i].Name.equalsAsciiL(
                    RTL_CONSTASCII_STRINGPARAM("FullPath") ))
                attribs[i].Value >>= fullPath;
            else if (attribs[i].Name.equalsAsciiL(
                         RTL_CONSTASCII_STRINGPARAM("MediaType") ))
                attribs[i].Value >>= mediaType;
        }

        if (fullPath.getLength() == 0 || mediaType.getLength() == 0 ||
            mediaType.equalsAsciiL( // opt: exclude common text/xml
                RTL_CONSTASCII_STRINGPARAM("text/xml") ))
            continue;

        String type, subType;
        INetContentTypeParameterList params;
        if (! INetContentTypes::parse( mediaType, type, subType, &params ))
            continue;

        INetContentTypeParameter const * param = params.find(
            ByteString("platform") );
        if (param != 0 && !platform_fits( param->m_sValue ))
            continue;
        const OUString url( makeURL( packageRootURL, fullPath ) );

        // check for bundle description:
        if (type.EqualsIgnoreCaseAscii("application") &&
            subType.EqualsIgnoreCaseAscii(
                "vnd.sun.star.package-bundle-description"))
        {
            // check locale:
            INetContentTypeParameter const * param =
                params.find( ByteString("locale") );
            if (param == 0) {
                if (descrFile.getLength() == 0)
                    descrFile = url;
            }
            else {
                // match best locale:
                lang::Locale locale( toLocale(param->m_sValue) );
                if (locale.Language == officeLocale.Language)
                {
                    if (descrFileLocale.Country == officeLocale.Country
                        && locale.Country != officeLocale.Country)
                        continue;
                    if (descrFileLocale.Variant == officeLocale.Variant
                        && locale.Variant != officeLocale.Variant)
                        continue;
                    descrFile = url;
                    descrFileLocale = locale;
                }
            }
            continue;
        }

        checkAborted( abortChannel );

        const Reference<deployment::XPackage> xPackage(
            bindBundleItem( url, mediaType, xCmdEnv ) );
        if (xPackage.is())
            bundle.push_back( xPackage );
    }

    if (descrFile.getLength() > 0)
    {
        ::ucb::Content descrFileContent;
        if (create_ucb_content( &descrFileContent, descrFile,
                                xCmdEnv, false /* no throw */ ))
        {
            // patch description:
            ::rtl::ByteSequence bytes( readFile( descrFileContent ) );
            ::rtl::OUStringBuffer buf;
            buf.append( OUString( reinterpret_cast<sal_Char const *>(
                                      bytes.getConstArray() ),
                                  bytes.getLength(), RTL_TEXTENCODING_UTF8 ) );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\n") );
            buf.append( Package::getDescription() );
            m_description = buf.makeStringAndClear();
        }
    }
}

//______________________________________________________________________________
void BackendImpl::PackageImpl::scanLegacyBundle(
    t_packagevec & bundle,
    OUString const & url,
    ::rtl::Reference<AbortChannel> const & abortChannel,
    Reference<XCommandEnvironment> const & xCmdEnv,
    bool skip_registration )
{
    ::ucb::Content ucbContent( url, xCmdEnv );

    // check for platform pathes:
    const OUString title( ucbContent.getPropertyValue(
                              StrTitle::get() ).get<OUString>() );
    if (title.endsWithIgnoreAsciiCaseAsciiL(
            RTL_CONSTASCII_STRINGPARAM(".plt") ) &&
        !platform_fits( title.copy( 0, title.getLength() - 4 ) )) {
        return;
    }
    if (title.endsWithIgnoreAsciiCaseAsciiL(
            RTL_CONSTASCII_STRINGPARAM("skip_registration") ))
        skip_registration = true;

    OUString ar [] = { StrTitle::get(), OUSTR("IsFolder") };
    Reference<sdbc::XResultSet> xResultSet(
        ucbContent.createCursor( Sequence<OUString>( ar, ARLEN(ar) ),
                                 ::ucb::INCLUDE_FOLDERS_AND_DOCUMENTS ) );
    while (xResultSet->next())
    {
        checkAborted( abortChannel );

        const Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW );
        const OUString title( xRow->getString( 1 /* Title */ ) );
        const OUString title_enc( ::rtl::Uri::encode(
                                      title, rtl_UriCharClassPchar,
                                      rtl_UriEncodeIgnoreEscapes,
                                      RTL_TEXTENCODING_UTF8 ) );
        const OUString path( makeURL( url, title_enc ) );

        OUString mediaType;
        const Reference<deployment::XPackage> xPackage(
            bindBundleItem( path, OUString() /* detect */, xCmdEnv,
                            false /* ignore detection errors */ ) );
        if (xPackage.is()) {
            const Reference<deployment::XPackageTypeInfo> xPackageType(
                xPackage->getPackageType() );
            OSL_ASSERT( xPackageType.is() );
            if (xPackageType.is())
                mediaType = xPackageType->getMediaType();

            if (skip_registration &&
                // xxx todo: additional parsing?
                mediaType.matchIgnoreAsciiCaseAsciiL(
                    RTL_CONSTASCII_STRINGPARAM(
                        "application/vnd.sun.star.uno-component") ))
                continue;

            bundle.push_back( xPackage );
        }

        if (mediaType.getLength() == 0 ||
            // script.xlb, dialog.xlb can be met everywhere:
            mediaType.matchIgnoreAsciiCaseAsciiL(
                RTL_CONSTASCII_STRINGPARAM(
                    "application/vnd.sun.star.basic-library") ) ||
            mediaType.matchIgnoreAsciiCaseAsciiL(
                RTL_CONSTASCII_STRINGPARAM(
                    "application/vnd.sun.star.dialog-library") ))
        {
            if (xRow->getBoolean( 2 /* IsFolder */ )) { // recurse into folder:
                scanLegacyBundle(
                    bundle, path, abortChannel, xCmdEnv, skip_registration );
            }
        }
    }
}

} // anon namespace

//==============================================================================
Reference<deployment::XPackageRegistry> create(
    Reference<deployment::XPackageRegistry> const & xRootRegistry,
    OUString const & context, OUString const & cachePath, bool readOnly,
    Reference<XComponentContext> const & xComponentContext )
{
    Sequence<Any> args(
        cachePath.getLength() == 0 ? 1 : 3 );
    args[ 0 ] <<= context;
    if (cachePath.getLength() > 0) {
        args[ 1 ] <<= cachePath;
        args[ 2 ] <<= readOnly;
    }
    return new BackendImpl(
        args, xComponentContext,
        OUSTR("com.sun.star.comp.deployment.bundle.PackageRegistryBackend"),
        xRootRegistry );
}

} // namespace bundle
} // namespace backend
} // namespace dp_registry

