blob: 2faea7912859990a6426cd5d9894452f1efc93e7 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "dp_misc.h"
#include "unopkg_main.h"
#include "unopkg_shared.h"
#include "dp_identifier.hxx"
#include "sal/main.h"
#include "tools/extendapplicationenvironment.hxx"
#include "rtl/ustrbuf.hxx"
#include "rtl/uri.hxx"
#include "rtl/bootstrap.hxx"
#include "osl/thread.h"
#include "osl/process.h"
#include "osl/conditn.hxx"
#include "osl/file.hxx"
#include "cppuhelper/implbase1.hxx"
#include "cppuhelper/exc_hlp.hxx"
#include "comphelper/anytostring.hxx"
#include "comphelper/sequence.hxx"
#include "com/sun/star/deployment/ExtensionManager.hpp"
#include "com/sun/star/deployment/ui/PackageManagerDialog.hpp"
#include "com/sun/star/ui/dialogs/XExecutableDialog.hpp"
#include "com/sun/star/lang/DisposedException.hpp"
#include "boost/scoped_array.hpp"
#include "com/sun/star/ui/dialogs/XDialogClosedListener.hpp"
#include "com/sun/star/bridge/XBridgeFactory.hpp"
#include <stdio.h>
#include <vector>
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::unopkg;
using ::rtl::OUString;
namespace css = ::com::sun::star;
namespace {
struct ExtensionName
{
OUString m_str;
ExtensionName( OUString const & str ) : m_str( str ) {}
bool operator () ( Reference<deployment::XPackage> const & e ) const
{
if (m_str.equals(dp_misc::getIdentifier(e))
|| m_str.equals(e->getName()))
return true;
return false;
}
};
//------------------------------------------------------------------------------
const char s_usingText [] =
"\n"
"using: " APP_NAME " add <options> extension-path...\n"
" " APP_NAME " validate <options> extension-identifier...\n"
" " APP_NAME " remove <options> extension-identifier...\n"
" " APP_NAME " list <options> extension-identifier...\n"
" " APP_NAME " reinstall <options>\n"
" " APP_NAME " gui\n"
" " APP_NAME " -V\n"
" " APP_NAME " -h\n"
"\n"
"sub-commands:\n"
" add add extension\n"
" validate checks the prerequisites of an installed extension and"
" registers it if possible\n"
" remove remove extensions by identifier\n"
" reinstall expert feature: reinstall all deployed extensions\n"
" list list information about deployed extensions\n"
" gui raise Extension Manager Graphical User Interface (GUI)\n"
"\n"
"options:\n"
" -h, --help this help\n"
" -V, --version version information\n"
" -v, --verbose verbose output to stdout\n"
" -f, --force force overwriting existing extensions\n"
" -s, --suppress-license prevents showing the license provided that\n"
" the extension allows it\n"
" --log-file <file> custom log file; default: <cache-dir>/log.txt\n"
" --shared expert feature: operate on shared installation\n"
" deployment context;\n"
" run only when no concurrent Office\n"
" process(es) are running!\n"
" --bundled expert feature: operate on bundled extensions. Only\n"
" works with list, validate, reinstall;\n"
" --deployment-context expert feature: explicit deployment context\n"
" <context>\n"
"\n"
"To learn more about the Extension Manager and extensions, see:\n"
"http://wiki.services.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extensions\n\n";
//------------------------------------------------------------------------------
const OptionInfo s_option_infos [] = {
{ RTL_CONSTASCII_STRINGPARAM("help"), 'h', false },
{ RTL_CONSTASCII_STRINGPARAM("version"), 'V', false },
{ RTL_CONSTASCII_STRINGPARAM("verbose"), 'v', false },
{ RTL_CONSTASCII_STRINGPARAM("force"), 'f', false },
{ RTL_CONSTASCII_STRINGPARAM("log-file"), '\0', true },
{ RTL_CONSTASCII_STRINGPARAM("shared"), '\0', false },
{ RTL_CONSTASCII_STRINGPARAM("deployment-context"), '\0', true },
{ RTL_CONSTASCII_STRINGPARAM("bundled"), '\0', false},
{ RTL_CONSTASCII_STRINGPARAM("suppress-license"), 's', false},
{ 0, 0, '\0', false }
};
class DialogClosedListenerImpl :
public ::cppu::WeakImplHelper1< ui::dialogs::XDialogClosedListener >
{
osl::Condition & m_rDialogClosedCondition;
public:
DialogClosedListenerImpl( osl::Condition & rDialogClosedCondition )
: m_rDialogClosedCondition( rDialogClosedCondition ) {}
// XEventListener (base of XDialogClosedListener)
virtual void SAL_CALL disposing( lang::EventObject const & Source )
throw (RuntimeException);
// XDialogClosedListener
virtual void SAL_CALL dialogClosed(
ui::dialogs::DialogClosedEvent const & aEvent )
throw (RuntimeException);
};
// XEventListener (base of XDialogClosedListener)
void DialogClosedListenerImpl::disposing( lang::EventObject const & )
throw (RuntimeException)
{
// nothing to do
}
// XDialogClosedListener
void DialogClosedListenerImpl::dialogClosed(
ui::dialogs::DialogClosedEvent const & )
throw (RuntimeException)
{
m_rDialogClosedCondition.set();
}
// If a package had been installed with a pre OOo 2.2, it could not normally be
// found via its identifier; similarly (and for ease of use), a package
// installed with OOo 2.2 or later could not normally be found via its file
// name.
Reference<deployment::XPackage> findPackage(
OUString const & repository,
Reference<deployment::XExtensionManager> const & manager,
Reference<ucb::XCommandEnvironment > const & environment,
OUString const & idOrFileName )
{
Sequence< Reference<deployment::XPackage> > ps(
manager->getDeployedExtensions(repository,
Reference<task::XAbortChannel>(), environment ) );
for ( sal_Int32 i = 0; i < ps.getLength(); ++i )
if ( dp_misc::getIdentifier( ps[i] ) == idOrFileName )
return ps[i];
for ( sal_Int32 i = 0; i < ps.getLength(); ++i )
if ( ps[i]->getName() == idOrFileName )
return ps[i];
return Reference<deployment::XPackage>();
}
} // anon namespace
//workaround for some reason the bridge threads which communicate with the uno.exe
//process are not releases on time
void disposeBridges(Reference<css::uno::XComponentContext> ctx)
{
if (!ctx.is())
return;
Reference<css::bridge::XBridgeFactory> bridgeFac(
ctx->getServiceManager()->createInstanceWithContext(
OUSTR("com.sun.star.bridge.BridgeFactory"), ctx),
UNO_QUERY);
if (bridgeFac.is())
{
const Sequence< Reference<css::bridge::XBridge> >seqBridges = bridgeFac->getExistingBridges();
for (sal_Int32 i = 0; i < seqBridges.getLength(); i++)
{
Reference<css::lang::XComponent> comp(seqBridges[i], UNO_QUERY);
if (comp.is())
{
try {
comp->dispose();
}
catch (css::lang::DisposedException& )
{
}
}
}
}
}
//##############################################################################
extern "C" int unopkg_main()
{
tools::extendApplicationEnvironment();
DisposeGuard disposeGuard;
bool bNoOtherErrorMsg = false;
OUString subCommand;
bool option_shared = false;
bool option_force = false;
bool option_verbose = false;
bool option_bundled = false;
bool option_suppressLicense = false;
bool subcmd_add = false;
bool subcmd_gui = false;
OUString logFile;
OUString repository;
OUString cmdArg;
::std::vector<OUString> cmdPackages;
OptionInfo const * info_shared = getOptionInfo(
s_option_infos, OUSTR("shared") );
OptionInfo const * info_force = getOptionInfo(
s_option_infos, OUSTR("force") );
OptionInfo const * info_verbose = getOptionInfo(
s_option_infos, OUSTR("verbose") );
OptionInfo const * info_log = getOptionInfo(
s_option_infos, OUSTR("log-file") );
OptionInfo const * info_context = getOptionInfo(
s_option_infos, OUSTR("deployment-context") );
OptionInfo const * info_help = getOptionInfo(
s_option_infos, OUSTR("help") );
OptionInfo const * info_version = getOptionInfo(
s_option_infos, OUSTR("version") );
OptionInfo const * info_bundled = getOptionInfo(
s_option_infos, OUSTR("bundled") );
OptionInfo const * info_suppressLicense = getOptionInfo(
s_option_infos, OUSTR("suppress-license") );
Reference<XComponentContext> xComponentContext;
Reference<XComponentContext> xLocalComponentContext;
try {
sal_uInt32 nPos = 0;
sal_uInt32 nCount = osl_getCommandArgCount();
if (nCount == 0 || isOption( info_help, &nPos ))
{
dp_misc::writeConsole(s_usingText);
return 0;
}
else if (isOption( info_version, &nPos )) {
dp_misc::writeConsole( "\n" APP_NAME " Version 3.3\n");
return 0;
}
//consume all bootstrap variables which may occur before the subcommannd
while(isBootstrapVariable(&nPos));
if(nPos >= nCount)
return 0;
//get the sub command
osl_getCommandArg( nPos, &subCommand.pData );
++nPos;
subCommand = subCommand.trim();
subcmd_add = subCommand.equalsAsciiL(
RTL_CONSTASCII_STRINGPARAM("add") );
subcmd_gui = subCommand.equalsAsciiL(
RTL_CONSTASCII_STRINGPARAM("gui") );
// sun-command options and packages:
while (nPos < nCount)
{
if (readArgument( &cmdArg, info_log, &nPos )) {
logFile = makeAbsoluteFileUrl(
cmdArg.trim(), getProcessWorkingDir() );
}
else if (!readOption( &option_verbose, info_verbose, &nPos ) &&
!readOption( &option_shared, info_shared, &nPos ) &&
!readOption( &option_force, info_force, &nPos ) &&
!readOption( &option_bundled, info_bundled, &nPos ) &&
!readOption( &option_suppressLicense, info_suppressLicense, &nPos ) &&
!readArgument( &repository, info_context, &nPos ) &&
!isBootstrapVariable(&nPos))
{
osl_getCommandArg( nPos, &cmdArg.pData );
++nPos;
cmdArg = cmdArg.trim();
if (cmdArg.getLength() > 0)
{
if (cmdArg[ 0 ] == '-')
{
// is option:
dp_misc::writeConsoleError(
OUSTR("\nERROR: unexpected option ") +
cmdArg +
OUSTR("!\n") +
OUSTR(" Use " APP_NAME " ") +
toString(info_help) +
OUSTR(" to print all options.\n"));
return 1;
}
else
{
// is package:
cmdPackages.push_back(
subcmd_add || subcmd_gui
? makeAbsoluteFileUrl(
cmdArg, getProcessWorkingDir() )
: cmdArg );
}
}
}
}
if (repository.getLength() == 0)
{
if (option_shared)
repository = OUSTR("shared");
else if (option_bundled)
repository = OUSTR("bundled");
else
repository = OUSTR("user");
}
else
{
if (repository.equalsAsciiL(
RTL_CONSTASCII_STRINGPARAM("shared") )) {
option_shared = true;
}
else if (option_shared) {
dp_misc::writeConsoleError(
OUSTR("WARNING: explicit context given! ") +
OUSTR("Ignoring option ") +
toString( info_shared ) +
OUSTR("!\n") );
}
}
if (subCommand.equals(OUSTR("reinstall")))
{
//We must prevent that services and types are loaded by UNO,
//otherwise we cannot delete the registry data folder.
OUString extensionUnorc;
if (repository.equals(OUSTR("user")))
extensionUnorc = OUSTR("$UNO_USER_PACKAGES_CACHE/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc");
else if (repository.equals(OUSTR("shared")))
extensionUnorc = OUSTR("$SHARED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc");
else if (repository.equals(OUSTR("bundled")))
extensionUnorc = OUSTR("$BUNDLED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc");
else
OSL_ASSERT(0);
::rtl::Bootstrap::expandMacros(extensionUnorc);
oslFileError e = osl_removeFile(extensionUnorc.pData);
if (e != osl_File_E_None && e != osl_File_E_NOENT)
throw Exception(OUSTR("Could not delete ") + extensionUnorc, 0);
}
else if (subCommand.equals(OUSTR("sync")))
{
//sync is private!!!! Only to be called from setup!!!
//The UserInstallation is diverted to the prereg folder. But only
//the lock file is written! This requires that
//-env:UNO_JAVA_JFW_INSTALL_DATA is passed to javaldx and unopkg otherwise the
//javasettings file is written to the prereg folder.
//
//For performance reasons unopkg sync is called during the setup and
//creates the registration data for the repository of the bundled
//extensions. It is then copied to the user installation during
//startup of OOo (userdata/extensions/bundled). The registration
//data is in the brand installation and must be removed when
//uninstalling OOo. We do this here, before UNO is
//bootstrapped. Otherwies files could be locked by this process.
//If there is no folder left in
//$OOO_BASE_DIR/share/extensions
//then we can delete the registration data at
//$BUNDLED_EXTENSIONS_USER
if (hasNoFolder(OUSTR("$OOO_BASE_DIR/share/extensions")))
{
removeFolder(OUSTR("$BUNDLED_EXTENSIONS_PREREG"));
//return otherwise we create the registration data again
return 0;
}
//redirect the UserInstallation, so we do not create a
//user installation for the admin and we also do not need
//to call unopkg with -env:UserInstallation
::rtl::Bootstrap::set(OUSTR("UserInstallation"),
OUSTR("$BUNDLED_EXTENSIONS_PREREG/.."));
//Setting UNO_JAVA_JFW_INSTALL_DATA causes the javasettings to be written
//in the office installation. We do not want to create the user data folder
//for the admin. The value must also be set in the unopkg script (Linux, etc.)
//when calling javaldx
::rtl::Bootstrap::set(OUSTR("UNO_JAVA_JFW_INSTALL_DATA"),
OUSTR("$OOO_BASE_DIR/share/config/javasettingsunopkginstall.xml"));
}
xComponentContext = getUNO(
disposeGuard, option_verbose, option_shared, subcmd_gui,
xLocalComponentContext );
Reference<deployment::XExtensionManager> xExtensionManager(
deployment::ExtensionManager::get( xComponentContext ) );
Reference< ::com::sun::star::ucb::XCommandEnvironment > xCmdEnv(
createCmdEnv( xComponentContext, logFile,
option_force, option_verbose) );
//synchronize bundled/shared extensions
//Do not synchronize when command is "reinstall". This could add types and services to UNO and
//prevent the deletion of the registry data folder
//synching is done in XExtensionManager.reinstall
if (!subcmd_gui && ! subCommand.equals(OUSTR("reinstall"))
&& ! subCommand.equals(OUSTR("sync"))
&& ! dp_misc::office_is_running())
dp_misc::syncRepositories(xCmdEnv);
if (subcmd_add ||
subCommand.equalsAsciiL(
RTL_CONSTASCII_STRINGPARAM("remove") ))
{
for ( ::std::size_t pos = 0; pos < cmdPackages.size(); ++pos )
{
OUString const & cmdPackage = cmdPackages[ pos ];
if (subcmd_add)
{
beans::NamedValue nvSuppress(
OUSTR("SUPPRESS_LICENSE"), option_suppressLicense ?
makeAny(OUSTR("1")):makeAny(OUSTR("0")));
xExtensionManager->addExtension(
cmdPackage, Sequence<beans::NamedValue>(&nvSuppress, 1),
repository, Reference<task::XAbortChannel>(), xCmdEnv);
}
else
{
try
{
xExtensionManager->removeExtension(
cmdPackage, cmdPackage, repository,
Reference<task::XAbortChannel>(), xCmdEnv );
}
catch (lang::IllegalArgumentException &)
{
Reference<deployment::XPackage> p(
findPackage(repository,
xExtensionManager, xCmdEnv, cmdPackage ) );
if ( !p.is())
throw;
else if (p.is())
xExtensionManager->removeExtension(
::dp_misc::getIdentifier(p), p->getName(),
repository,
Reference<task::XAbortChannel>(), xCmdEnv );
}
}
}
}
else if (subCommand.equalsAsciiL(
RTL_CONSTASCII_STRINGPARAM("reinstall") ))
{
xExtensionManager->reinstallDeployedExtensions(
repository, Reference<task::XAbortChannel>(), xCmdEnv);
}
else if (subCommand.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("list") ))
{
::std::vector<Reference<deployment::XPackage> > vecExtUnaccepted;
::comphelper::sequenceToContainer(vecExtUnaccepted,
xExtensionManager->getExtensionsWithUnacceptedLicenses(
repository, xCmdEnv));
//This vector tells what XPackage in allExtensions has an
//unaccepted license.
std::vector<bool> vecUnaccepted;
std::vector<Reference<deployment::XPackage> > allExtensions;
if (cmdPackages.empty())
{
Sequence< Reference<deployment::XPackage> >
packages = xExtensionManager->getDeployedExtensions(
repository, Reference<task::XAbortChannel>(), xCmdEnv );
::std::vector<Reference<deployment::XPackage> > vec_packages;
::comphelper::sequenceToContainer(vec_packages, packages);
//First copy the extensions with the unaccepted license
//to vector allExtensions.
allExtensions.resize(vecExtUnaccepted.size() + vec_packages.size());
::std::vector<Reference<deployment::XPackage> >::iterator i_all_ext =
::std::copy(vecExtUnaccepted.begin(), vecExtUnaccepted.end(),
allExtensions.begin());
//Now copy those we got from getDeployedExtensions
::std::copy(vec_packages.begin(), vec_packages.end(), i_all_ext);
//Now prepare the vector which tells what extension has an
//unaccepted license
vecUnaccepted.resize(vecExtUnaccepted.size() + vec_packages.size());
::std::fill_n( vecUnaccepted.begin(), vecExtUnaccepted.size(), true);
std::vector<bool>::iterator i_unaccepted = vecUnaccepted.begin() + vecExtUnaccepted.size();
::std::fill_n(i_unaccepted, vec_packages.size(), false);
dp_misc::writeConsole(
OUSTR("All deployed ") + repository + OUSTR(" extensions:\n\n"));
}
else
{
//The user provided the names (ids or file names) of the extensions
//which shall be listed
for ( ::std::size_t pos = 0; pos < cmdPackages.size(); ++pos )
{
Reference<deployment::XPackage> extension;
try
{
extension = xExtensionManager->getDeployedExtension(
repository, cmdPackages[ pos ], cmdPackages[ pos ], xCmdEnv );
}
catch (lang::IllegalArgumentException &)
{
extension = findPackage(repository,
xExtensionManager, xCmdEnv, cmdPackages[ pos ] );
}
//Now look if the requested extension has an unaccepted license
bool bUnacceptedLic = false;
if (!extension.is())
{
::std::vector<Reference<deployment::XPackage> >::const_iterator
i = ::std::find_if(
vecExtUnaccepted.begin(),
vecExtUnaccepted.end(), ExtensionName(cmdPackages[pos]));
if (i != vecExtUnaccepted.end())
{
extension = *i;
bUnacceptedLic = true;
}
}
if (extension.is())
{
allExtensions.push_back(extension);
vecUnaccepted.push_back(bUnacceptedLic);
}
else
throw lang::IllegalArgumentException(
OUSTR("There is no such extension deployed: ") +
cmdPackages[pos],0,-1);
}
}
printf_packages(allExtensions, vecUnaccepted, xCmdEnv );
}
else if (subCommand.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("validate") ))
{
::std::vector<Reference<deployment::XPackage> > vecExtUnaccepted;
::comphelper::sequenceToContainer(
vecExtUnaccepted, xExtensionManager->getExtensionsWithUnacceptedLicenses(
repository, xCmdEnv));
for ( ::std::size_t pos = 0; pos < cmdPackages.size(); ++pos )
{
Reference<deployment::XPackage> extension;
try
{
extension = xExtensionManager->getDeployedExtension(
repository, cmdPackages[ pos ], cmdPackages[ pos ], xCmdEnv );
}
catch (lang::IllegalArgumentException &)
{
extension = findPackage(
repository, xExtensionManager, xCmdEnv, cmdPackages[ pos ] );
}
if (!extension.is())
{
::std::vector<Reference<deployment::XPackage> >::const_iterator
i = ::std::find_if(
vecExtUnaccepted.begin(),
vecExtUnaccepted.end(), ExtensionName(cmdPackages[pos]));
if (i != vecExtUnaccepted.end())
{
extension = *i;
}
}
if (extension.is())
xExtensionManager->checkPrerequisitesAndEnable(
extension, Reference<task::XAbortChannel>(), xCmdEnv);
}
}
else if (subCommand.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("gui") ))
{
Reference<ui::dialogs::XAsynchronousExecutableDialog> xDialog(
deployment::ui::PackageManagerDialog::createAndInstall(
xComponentContext,
cmdPackages.size() > 0 ? cmdPackages[0] : OUString() ));
osl::Condition dialogEnded;
dialogEnded.reset();
Reference< ui::dialogs::XDialogClosedListener > xListener(
new DialogClosedListenerImpl( dialogEnded ) );
xDialog->startExecuteModal(xListener);
dialogEnded.wait();
}
else if (subCommand.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("sync")))
{
if (! dp_misc::office_is_running())
{
xExtensionManager->synchronizeBundledPrereg(
Reference<task::XAbortChannel>(), xCmdEnv);
}
else
{
dp_misc::writeConsoleError(OUSTR("\nError: office is running"));
}
}
else
{
dp_misc::writeConsoleError(
OUSTR("\nERROR: unknown sub-command ") +
subCommand +
OUSTR("!\n") +
OUSTR(" Use " APP_NAME " ") +
toString(info_help) +
OUSTR(" to print all options.\n"));
return 1;
}
if (option_verbose)
dp_misc::writeConsole( OUSTR( "\n" APP_NAME " done.\n"));
//Force to release all bridges which connect us to the child processes
disposeBridges(xLocalComponentContext);
return 0;
}
catch (ucb::CommandFailedException &e)
{
dp_misc::writeConsoleError(e.Message + OUSTR("\n"));
bNoOtherErrorMsg = true;
}
catch (ucb::CommandAbortedException &)
{
dp_misc::writeConsoleError( "\n" APP_NAME " aborted!\n");
}
catch (deployment::DeploymentException & exc)
{
OUString cause;
if (option_verbose)
{
cause = ::comphelper::anyToString(exc.Cause);
}
else
{
css::uno::Exception e;
if (exc.Cause >>= e)
cause = e.Message;
}
dp_misc::writeConsoleError(
OUSTR("\nERROR: ") + exc.Message + OUSTR("\n"));
if (cause.getLength())
dp_misc::writeConsoleError(
OUSTR(" Cause: ") + cause + OUSTR("\n"));
}
catch (LockFileException & e)
{
if (!subcmd_gui)
dp_misc::writeConsoleError(e.Message);
bNoOtherErrorMsg = true;
}
catch (::com::sun::star::uno::Exception & e ) {
Any exc( ::cppu::getCaughtException() );
dp_misc::writeConsoleError(
OUSTR("\nERROR: ") +
OUString(option_verbose ? e.Message + OUSTR("\nException details: \n") +
::comphelper::anyToString(exc) : e.Message) +
OUSTR("\n"));
}
if (!bNoOtherErrorMsg)
dp_misc::writeConsoleError( "\n" APP_NAME " failed.\n");
disposeBridges(xLocalComponentContext);
return 1;
}