/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* 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 .
*/
#include <sal/config.h>
#include <algorithm>
#include <iterator>
#include <map>
#include <new>
#include <set>
#include <migration.hxx>
#include "migration_impl.hxx"
#include <sal/log.hxx>
#include <unotools/textsearch.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <unotools/bootstrap.hxx>
#include <rtl/bootstrap.hxx>
#include <rtl/uri.hxx>
#include <i18nlangtag/lang.h>
#include <tools/urlobj.hxx>
#include <osl/file.hxx>
#include <osl/security.hxx>
#include <unotools/configmgr.hxx>
#include <com/sun/star/configuration/Update.hpp>
#include <com/sun/star/configuration/theDefaultProvider.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/task/XJob.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/util/XRefreshable.hpp>
#include <com/sun/star/util/XChangesBatch.hpp>
#include <com/sun/star/util/XStringSubstitution.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/embed/FileSystemStorageFactory.hpp>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
#include <com/sun/star/frame/theUICommandDescription.hpp>
#include <com/sun/star/ui/UIConfigurationManager.hpp>
#include <com/sun/star/ui/XUIConfiguration.hpp>
#include <com/sun/star/ui/XUIConfigurationStorage.hpp>
#include <com/sun/star/ui/XUIConfigurationPersistence.hpp>
using namespace osl;
using namespace std;
using namespace com::sun::star::task;
using namespace com::sun::star::lang;
using namespace com::sun::star::beans;
using namespace com::sun::star::util;
using namespace com::sun::star::container;
using com::sun::star::uno::Exception;
using namespace com::sun::star;
namespace desktop
{
static const char ITEM_DESCRIPTOR_COMMANDURL[] = "CommandURL";
static const char ITEM_DESCRIPTOR_CONTAINER[] = "ItemDescriptorContainer";
static const char ITEM_DESCRIPTOR_LABEL[] = "Label";
OUString retrieveLabelFromCommand(const OUString& sCommand, const OUString& sModuleIdentifier)
{
OUString sLabel;
uno::Reference< container::XNameAccess > xUICommands;
uno::Reference< container::XNameAccess > const xNameAccess(
frame::theUICommandDescription::get(
::comphelper::getProcessComponentContext()) );
xNameAccess->getByName( sModuleIdentifier ) >>= xUICommands;
if (xUICommands.is()) {
if ( !sCommand.isEmpty() ) {
OUString aStr;
::uno::Sequence< beans::PropertyValue > aPropSeq;
try {
uno::Any a( xUICommands->getByName( sCommand ));
if ( a >>= aPropSeq ) {
for ( sal_Int32 i = 0; i < aPropSeq.getLength(); i++ ) {
if ( aPropSeq[i].Name == "Label" ) {
aPropSeq[i].Value >>= aStr;
break;
}
}
}
sLabel = aStr;
} catch (const container::NoSuchElementException&) {
sLabel = sCommand;
sal_Int32 nIndex = sLabel.indexOf(':');
if (nIndex>=0 && nIndex <= sLabel.getLength()-1)
sLabel = sLabel.copy(nIndex+1);
}
}
}
return sLabel;
}
OUString mapModuleShortNameToIdentifier(const OUString& sShortName)
{
OUString sIdentifier;
if ( sShortName == "StartModule" )
sIdentifier = "com.sun.star.frame.StartModule";
else if ( sShortName == "swriter" )
sIdentifier = "com.sun.star.text.TextDocument";
else if ( sShortName == "scalc" )
sIdentifier = "com.sun.star.sheet.SpreadsheetDocument";
else if ( sShortName == "sdraw" )
sIdentifier = "com.sun.star.drawing.DrawingDocument";
else if ( sShortName == "simpress" )
sIdentifier = "com.sun.star.presentation.PresentationDocument";
else if ( sShortName == "smath" )
sIdentifier = "com.sun.star.formula.FormulaProperties";
else if ( sShortName == "schart" )
sIdentifier = "com.sun.star.chart2.ChartDocument";
else if ( sShortName == "BasicIDE" )
sIdentifier = "com.sun.star.script.BasicIDE";
else if ( sShortName == "dbapp" )
sIdentifier = "com.sun.star.sdb.OfficeDatabaseDocument";
else if ( sShortName == "sglobal" )
sIdentifier = "com.sun.star.text.GlobalDocument";
else if ( sShortName == "sweb" )
sIdentifier = "com.sun.star.text.WebDocument";
else if ( sShortName == "swxform" )
sIdentifier = "com.sun.star.xforms.XMLFormDocument";
else if ( sShortName == "sbibliography" )
sIdentifier = "com.sun.star.frame.Bibliography";
return sIdentifier;
}
bool MigrationImpl::alreadyMigrated()
{
OUString const MIGRATION_STAMP_NAME("/MIGRATED4");
OUString aStr = m_aInfo.userdata + MIGRATION_STAMP_NAME;
File aFile(aStr);
// create migration stamp, and/or check its existence
bool bRet = aFile.open (osl_File_OpenFlag_Write | osl_File_OpenFlag_Create | osl_File_OpenFlag_NoLock) == FileBase::E_EXIST;
SAL_INFO( "desktop.migration", "File '" << aStr << "' exists? " << bRet );
return bRet;
}
bool MigrationImpl::initializeMigration()
{
bool bRet = false;
if (!checkMigrationCompleted()) {
readAvailableMigrations(m_vMigrationsAvailable);
sal_Int32 nIndex = findPreferredMigrationProcess(m_vMigrationsAvailable);
// m_aInfo is now set to the preferred migration source
if ( nIndex >= 0 ) {
if (alreadyMigrated())
return false;
m_vrMigrations = readMigrationSteps(m_vMigrationsAvailable[nIndex].name);
}
bRet = !m_aInfo.userdata.isEmpty();
}
SAL_INFO( "desktop.migration", "Migration " << ( bRet ? "needed" : "not required" ) );
return bRet;
}
void Migration::migrateSettingsIfNecessary()
{
MigrationImpl aImpl;
if (! aImpl.initializeMigration() )
return;
bool bResult = false;
try {
bResult = aImpl.doMigration();
} catch (const Exception& e) {
SAL_WARN( "desktop", "doMigration(): " << e);
}
OSL_ENSURE(bResult, "Migration has not been successful");
}
MigrationImpl::MigrationImpl()
{
}
MigrationImpl::~MigrationImpl()
{
}
// The main entry point for migrating settings
bool MigrationImpl::doMigration()
{
// compile file list for migration
m_vrFileList = compileFileList();
bool result = false;
try {
NewVersionUIInfo aNewVersionUIInfo;
std::vector< MigrationModuleInfo > vModulesInfo = dectectUIChangesForAllModules();
aNewVersionUIInfo.init(vModulesInfo);
copyFiles();
const OUString sMenubarResourceURL("private:resource/menubar/menubar");
const OUString sToolbarResourcePre("private:resource/toolbar/");
for (MigrationModuleInfo & i : vModulesInfo) {
OUString sModuleIdentifier = mapModuleShortNameToIdentifier(i.sModuleShortName);
if (sModuleIdentifier.isEmpty())
continue;
OUString aOldCfgDataPath = m_aInfo.userdata + "/user/config/soffice.cfg/modules/" + i.sModuleShortName;
uno::Sequence< uno::Any > lArgs {uno::makeAny(aOldCfgDataPath), uno::makeAny(embed::ElementModes::READ)};
uno::Reference< uno::XComponentContext > xContext(comphelper::getProcessComponentContext());
uno::Reference< lang::XSingleServiceFactory > xStorageFactory(embed::FileSystemStorageFactory::create(xContext));
uno::Reference< embed::XStorage > xModules(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY);
uno::Reference< ui::XUIConfigurationManager2 > xOldCfgManager = ui::UIConfigurationManager::create(xContext);
if ( xModules.is() ) {
xOldCfgManager->setStorage( xModules );
xOldCfgManager->reload();
}
uno::Reference< ui::XUIConfigurationManager > xCfgManager = aNewVersionUIInfo.getConfigManager(i.sModuleShortName);
if (i.bHasMenubar) {
uno::Reference< container::XIndexContainer > xOldVersionMenuSettings(xOldCfgManager->getSettings(sMenubarResourceURL, true), uno::UNO_QUERY);
uno::Reference< container::XIndexContainer > xNewVersionMenuSettings = aNewVersionUIInfo.getNewMenubarSettings(i.sModuleShortName);
compareOldAndNewConfig(OUString(), xOldVersionMenuSettings, xNewVersionMenuSettings, sMenubarResourceURL);
mergeOldToNewVersion(xCfgManager, xNewVersionMenuSettings, sModuleIdentifier, sMenubarResourceURL);
}
sal_Int32 nToolbars = i.m_vToolbars.size();
if (nToolbars >0) {
for (sal_Int32 j=0; j<nToolbars; ++j) {
OUString sToolbarName = i.m_vToolbars[j];
OUString sToolbarResourceURL = sToolbarResourcePre + sToolbarName;
uno::Reference< container::XIndexContainer > xOldVersionToolbarSettings(xOldCfgManager->getSettings(sToolbarResourceURL, true), uno::UNO_QUERY);
uno::Reference< container::XIndexContainer > xNewVersionToolbarSettings = aNewVersionUIInfo.getNewToolbarSettings(i.sModuleShortName, sToolbarName);
compareOldAndNewConfig(OUString(), xOldVersionToolbarSettings, xNewVersionToolbarSettings, sToolbarResourceURL);
mergeOldToNewVersion(xCfgManager, xNewVersionToolbarSettings, sModuleIdentifier, sToolbarResourceURL);
}
}
m_aOldVersionItemsHashMap.clear();
}
// execute the migration items from Setup.xcu
copyConfig();
// execute custom migration services from Setup.xcu
// and refresh the cache
runServices();
uno::Reference< XRefreshable >(
configuration::theDefaultProvider::get(comphelper::getProcessComponentContext()),
uno::UNO_QUERY_THROW)->refresh();
result = true;
} catch (css::uno::Exception & e) {
SAL_WARN(
"desktop.migration",
"ignored Exception \"" << e
<< "\" while migrating from version \"" << m_aInfo.productname
<< "\" data \"" << m_aInfo.userdata << "\"");
}
// prevent running the migration multiple times
setMigrationCompleted();
return result;
}
void MigrationImpl::setMigrationCompleted()
{
try {
uno::Reference< XPropertySet > aPropertySet(getConfigAccess("org.openoffice.Setup/Office", true), uno::UNO_QUERY_THROW);
aPropertySet->setPropertyValue("MigrationCompleted", uno::makeAny(true));
uno::Reference< XChangesBatch >(aPropertySet, uno::UNO_QUERY_THROW)->commitChanges();
} catch (...) {
// fail silently
}
}
bool MigrationImpl::checkMigrationCompleted()
{
bool bMigrationCompleted = false;
try {
uno::Reference< XPropertySet > aPropertySet(
getConfigAccess("org.openoffice.Setup/Office"), uno::UNO_QUERY_THROW);
aPropertySet->getPropertyValue("MigrationCompleted") >>= bMigrationCompleted;
if( !bMigrationCompleted && getenv("SAL_DISABLE_USERMIGRATION" ) ) {
// migration prevented - fake its success
setMigrationCompleted();
bMigrationCompleted = true;
}
} catch (const Exception&) {
// just return false...
}
SAL_INFO( "desktop.migration", "Migration " << ( bMigrationCompleted ? "already completed" : "not done" ) );
return bMigrationCompleted;
}
static void insertSorted(migrations_available& rAvailableMigrations, supported_migration const & aSupportedMigration)
{
bool bInserted( false );
migrations_available::iterator pIter = rAvailableMigrations.begin();
while ( !bInserted && pIter != rAvailableMigrations.end()) {
if ( pIter->nPriority < aSupportedMigration.nPriority ) {
rAvailableMigrations.insert(pIter, aSupportedMigration );
bInserted = true;
break; // i111193: insert invalidates iterator!
}
++pIter;
}
if ( !bInserted )
rAvailableMigrations.push_back( aSupportedMigration );
}
void MigrationImpl::readAvailableMigrations(migrations_available& rAvailableMigrations)
{
// get supported version names
uno::Reference< XNameAccess > aMigrationAccess(getConfigAccess("org.openoffice.Setup/Migration/SupportedVersions"), uno::UNO_QUERY_THROW);
uno::Sequence< OUString > seqSupportedVersions = aMigrationAccess->getElementNames();
const OUString aVersionIdentifiers( "VersionIdentifiers" );
const OUString aPriorityIdentifier( "Priority" );
for (sal_Int32 i=0; i<seqSupportedVersions.getLength(); i++) {
sal_Int32 nPriority( 0 );
uno::Sequence< OUString > seqVersions;
uno::Reference< XNameAccess > xMigrationData( aMigrationAccess->getByName(seqSupportedVersions[i]), uno::UNO_QUERY_THROW );
xMigrationData->getByName( aVersionIdentifiers ) >>= seqVersions;
xMigrationData->getByName( aPriorityIdentifier ) >>= nPriority;
supported_migration aSupportedMigration;
aSupportedMigration.name = seqSupportedVersions[i];
aSupportedMigration.nPriority = nPriority;
for (sal_Int32 j=0; j<seqVersions.getLength(); j++)
aSupportedMigration.supported_versions.push_back(seqVersions[j].trim());
insertSorted( rAvailableMigrations, aSupportedMigration );
SAL_INFO( "desktop.migration", " available migration '" << aSupportedMigration.name << "'" );
}
}
migrations_vr MigrationImpl::readMigrationSteps(const OUString& rMigrationName)
{
// get migration access
uno::Reference< XNameAccess > aMigrationAccess(getConfigAccess("org.openoffice.Setup/Migration/SupportedVersions"), uno::UNO_QUERY_THROW);
uno::Reference< XNameAccess > xMigrationData( aMigrationAccess->getByName(rMigrationName), uno::UNO_QUERY_THROW );
// get migration description from org.openoffice.Setup/Migration
// and build vector of migration steps
uno::Reference< XNameAccess > theNameAccess(xMigrationData->getByName("MigrationSteps"), uno::UNO_QUERY_THROW);
uno::Reference< XNameAccess > tmpAccess;
uno::Sequence< OUString > tmpSeq;
migrations_vr vrMigrations(new migrations_v);
for (const OUString& rMigrationStep : theNameAccess->getElementNames()) {
// get current migration step
theNameAccess->getByName(rMigrationStep) >>= tmpAccess;
migration_step tmpStep;
tmpStep.name = rMigrationStep;
// read included files from current step description
if (tmpAccess->getByName("IncludedFiles") >>= tmpSeq) {
for (const OUString& rSeqEntry : tmpSeq)
tmpStep.includeFiles.push_back(rSeqEntry);
}
// excluded files...
if (tmpAccess->getByName("ExcludedFiles") >>= tmpSeq) {
for (const OUString& rSeqEntry : tmpSeq)
tmpStep.excludeFiles.push_back(rSeqEntry);
}
// included nodes...
if (tmpAccess->getByName("IncludedNodes") >>= tmpSeq) {
for (const OUString& rSeqEntry : tmpSeq)
tmpStep.includeConfig.push_back(rSeqEntry);
}
// excluded nodes...
if (tmpAccess->getByName("ExcludedNodes") >>= tmpSeq) {
for (const OUString& rSeqEntry : tmpSeq)
tmpStep.excludeConfig.push_back(rSeqEntry);
}
// included extensions...
if (tmpAccess->getByName("IncludedExtensions") >>= tmpSeq) {
for (const OUString& rSeqEntry : tmpSeq)
tmpStep.includeExtensions.push_back(rSeqEntry);
}
// excluded extensions...
if (tmpAccess->getByName("ExcludedExtensions") >>= tmpSeq) {
for (const OUString& rSeqEntry : tmpSeq)
tmpStep.excludeExtensions.push_back(rSeqEntry);
}
// generic service
tmpAccess->getByName("MigrationService") >>= tmpStep.service;
vrMigrations->push_back(tmpStep);
}
return vrMigrations;
}
static FileBase::RC _checkAndCreateDirectory(INetURLObject const & dirURL)
{
FileBase::RC result = Directory::create(dirURL.GetMainURL(INetURLObject::DecodeMechanism::ToIUri));
if (result == FileBase::E_NOENT) {
INetURLObject baseURL(dirURL);
baseURL.removeSegment();
_checkAndCreateDirectory(baseURL);
return Directory::create(dirURL.GetMainURL(INetURLObject::DecodeMechanism::ToIUri));
} else
return result;
}
#if defined UNX && ! defined MACOSX
static const char XDG_CONFIG_PART[] = "/.config/";
OUString MigrationImpl::preXDGConfigDir(const OUString& rConfigDir)
{
OUString aPreXDGConfigPath;
const char* pXDGCfgHome = getenv("XDG_CONFIG_HOME");
// cater for XDG_CONFIG_HOME change
// If XDG_CONFIG_HOME is set then we;
// assume the user knows what they are doing ( room for improvement here, we could
// of course search the default config dir etc. also - but this is more complex,
// we would need to weigh results from the current config dir against matches in
// the 'old' config dir etc. ) - currently we just use the returned config dir.
// If XDG_CONFIG_HOME is NOT set;
// assume then we should now using the default $HOME/.config config location for
// our user profiles, however *all* previous libreoffice and openoffice.org
// configurations will be in the 'old' config directory and that's where we need
// to search - we convert the returned config dir to the 'old' dir
if ( !pXDGCfgHome && rConfigDir.endsWith( XDG_CONFIG_PART ) )
// remove trailing '.config/' but leave the terminating '/'
aPreXDGConfigPath = rConfigDir.copy( 0, rConfigDir.getLength() - sizeof( XDG_CONFIG_PART ) + 2 );
else
aPreXDGConfigPath = rConfigDir;
// the application-specific config dir is no longer prefixed by '.' because it is hidden under ".config"
// we have to add the '.' for the pre-XDG directory names
aPreXDGConfigPath += ".";
return aPreXDGConfigPath;
}
#endif
void MigrationImpl::setInstallInfoIfExist(
install_info& aInfo,
const OUString& rConfigDir,
const OUString& rVersion)
{
OUString url(INetURLObject(rConfigDir).GetMainURL(INetURLObject::DecodeMechanism::NONE));
osl::DirectoryItem item;
osl::FileStatus stat(osl_FileStatus_Mask_Type);
if (osl::DirectoryItem::get(url, item) == osl::FileBase::E_None
&& item.getFileStatus(stat) == osl::FileBase::E_None
&& stat.getFileType() == osl::FileStatus::Directory) {
aInfo.userdata = url;
aInfo.productname = rVersion;
}
}
install_info MigrationImpl::findInstallation(const strings_v& rVersions)
{
OUString aTopConfigDir;
osl::Security().getConfigDir( aTopConfigDir );
if ( !aTopConfigDir.isEmpty() && aTopConfigDir[ aTopConfigDir.getLength()-1 ] != '/' )
aTopConfigDir += "/";
#if defined UNX && ! defined MACOSX
OUString aPreXDGTopConfigDir = preXDGConfigDir(aTopConfigDir);
#endif
install_info aInfo;
for (auto const& elem : rVersions)
{
OUString aVersion, aProfileName;
sal_Int32 nSeparatorIndex = elem.indexOf('=');
if ( nSeparatorIndex != -1 ) {
aVersion = elem.copy( 0, nSeparatorIndex );
aProfileName = elem.copy( nSeparatorIndex+1 );
}
if ( !aVersion.isEmpty() && !aProfileName.isEmpty() &&
( aInfo.userdata.isEmpty() ||
aProfileName.equalsIgnoreAsciiCase(
utl::ConfigManager::getProductName() ) ) ) {
setInstallInfoIfExist(aInfo, aTopConfigDir + aProfileName, aVersion);
#if defined UNX && ! defined MACOSX
//try preXDG path if the new one does not exist
if ( aInfo.userdata.isEmpty())
setInstallInfoIfExist(aInfo, aPreXDGTopConfigDir + aProfileName, aVersion);
#endif
}
}
return aInfo;
}
sal_Int32 MigrationImpl::findPreferredMigrationProcess(const migrations_available& rAvailableMigrations)
{
sal_Int32 nIndex( -1 );
sal_Int32 i( 0 );
for (auto const& availableMigration : rAvailableMigrations)
{
install_info aInstallInfo = findInstallation(availableMigration.supported_versions);
if (!aInstallInfo.productname.isEmpty() ) {
m_aInfo = aInstallInfo;
nIndex = i;
break;
}
++i;
}
SAL_INFO( "desktop.migration", " preferred migration is from product '" << m_aInfo.productname << "'");
SAL_INFO( "desktop.migration", " and settings directory '" << m_aInfo.userdata << "'");
return nIndex;
}
strings_vr MigrationImpl::applyPatterns(const strings_v& vSet, const strings_v& vPatterns)
{
using namespace utl;
strings_vr vrResult(new strings_v);
for (auto const& pattern : vPatterns)
{
// find matches for this pattern in input set
// and copy them to the result
SearchParam param(pattern, SearchParam::SearchType::Regexp);
TextSearch ts(param, LANGUAGE_DONTKNOW);
sal_Int32 start = 0;
sal_Int32 end = 0;
for (auto const& elem : vSet)
{
end = elem.getLength();
if (ts.SearchForward(elem, &start, &end))
vrResult->push_back(elem);
}
}
return vrResult;
}
strings_vr MigrationImpl::getAllFiles(const OUString& baseURL) const
{
strings_vr vrResult(new strings_v);
// get sub dirs
Directory dir(baseURL);
if (dir.open() == FileBase::E_None) {
strings_v vSubDirs;
strings_vr vrSubResult;
// work through directory contents...
DirectoryItem item;
FileStatus fs(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL);
while (dir.getNextItem(item) == FileBase::E_None) {
if (item.getFileStatus(fs) == FileBase::E_None) {
if (fs.getFileType() == FileStatus::Directory)
vSubDirs.push_back(fs.getFileURL());
else
vrResult->push_back(fs.getFileURL());
}
}
// recurse subfolders
for (auto const& subDir : vSubDirs)
{
vrSubResult = getAllFiles(subDir);
vrResult->insert(vrResult->end(), vrSubResult->begin(), vrSubResult->end());
}
}
return vrResult;
}
namespace
{
// removes elements of vector 2 in vector 1
strings_v subtract(strings_v const & va, strings_v const & vb)
{
strings_v a(va);
std::sort(a.begin(), a.end());
strings_v::iterator ae(std::unique(a.begin(), a.end()));
strings_v b(vb);
std::sort(b.begin(), b.end());
strings_v::iterator be(std::unique(b.begin(), b.end()));
strings_v c;
std::set_difference(a.begin(), ae, b.begin(), be, std::back_inserter(c));
return c;
}
}
strings_vr MigrationImpl::compileFileList()
{
strings_vr vrResult(new strings_v);
strings_vr vrInclude;
strings_vr vrExclude;
// get a list of all files:
strings_vr vrFiles = getAllFiles(m_aInfo.userdata);
// get a file list result for each migration step
for (auto const& rMigration : *m_vrMigrations)
{
vrInclude = applyPatterns(*vrFiles, rMigration.includeFiles);
vrExclude = applyPatterns(*vrFiles, rMigration.excludeFiles);
strings_v sub(subtract(*vrInclude, *vrExclude));
vrResult->insert(vrResult->end(), sub.begin(), sub.end());
}
return vrResult;
}
namespace
{
struct componentParts {
std::set< OUString > includedPaths;
std::set< OUString > excludedPaths;
};
typedef std::map< OUString, componentParts > Components;
bool getComponent(OUString const & path, OUString * component)
{
OSL_ASSERT(component != nullptr);
if (path.isEmpty() || path[0] != '/') {
SAL_INFO( "desktop.migration", "configuration migration in/exclude path " << path << " ignored (does not start with slash)" );
return false;
}
sal_Int32 i = path.indexOf('/', 1);
*component = i < 0 ? path.copy(1) : path.copy(1, i - 1);
return true;
}
}
void MigrationImpl::copyConfig()
{
Components comps;
for (auto const& rMigrationStep : *m_vrMigrations) {
for (const OUString& rIncludePath : rMigrationStep.includeConfig) {
OUString comp;
if (getComponent(rIncludePath, &comp)) {
comps[comp].includedPaths.insert(rIncludePath);
}
}
for (const OUString& rExcludePath : rMigrationStep.excludeConfig) {
OUString comp;
if (getComponent(rExcludePath, &comp)) {
comps[comp].excludedPaths.insert(rExcludePath);
}
}
}
// check if the shared registrymodifications.xcu file exists
bool bRegistryModificationsXcuExists = false;
OUString regFilePath(m_aInfo.userdata);
regFilePath += "/user/registrymodifications.xcu";
File regFile(regFilePath);
::osl::FileBase::RC nError = regFile.open(osl_File_OpenFlag_Read);
if ( nError == ::osl::FileBase::E_None ) {
bRegistryModificationsXcuExists = true;
regFile.close();
}
for (auto const& comp : comps)
{
if (!comp.second.includedPaths.empty()) {
if (!bRegistryModificationsXcuExists) {
// shared registrymodifications.xcu does not exists
// the configuration is split in many registry files
// determine the file names from the first element in included paths
OUStringBuffer buf(m_aInfo.userdata);
buf.append("/user/registry/data");
sal_Int32 n = 0;
do {
OUString seg(comp.first.getToken(0, '.', n));
OUString enc(
rtl::Uri::encode(
seg, rtl_UriCharClassPchar, rtl_UriEncodeStrict,
RTL_TEXTENCODING_UTF8));
if (enc.isEmpty() && !seg.isEmpty()) {
SAL_INFO( "desktop.migration", "configuration migration component " << comp.first << " ignored (cannot be encoded as file path)" );
goto next;
}
buf.append('/');
buf.append(enc);
} while (n >= 0);
buf.append(".xcu");
regFilePath = buf.toString();
}
configuration::Update::get(
comphelper::getProcessComponentContext())->
insertModificationXcuFile(
regFilePath,
comphelper::containerToSequence(comp.second.includedPaths),
comphelper::containerToSequence(comp.second.excludedPaths));
} else {
SAL_INFO( "desktop.migration", "configuration migration component " << comp.first << " ignored (only excludes, no includes)" );
}
next:
;
}
}
uno::Reference< XNameAccess > MigrationImpl::getConfigAccess(const sal_Char* pPath, bool bUpdate)
{
uno::Reference< XNameAccess > xNameAccess;
try {
OUString sAccessSrvc;
if (bUpdate)
sAccessSrvc = "com.sun.star.configuration.ConfigurationUpdateAccess";
else
sAccessSrvc = "com.sun.star.configuration.ConfigurationAccess";
OUString sConfigURL = OUString::createFromAscii(pPath);
uno::Reference< XMultiServiceFactory > theConfigProvider(
configuration::theDefaultProvider::get(
comphelper::getProcessComponentContext()));
// access the provider
uno::Sequence< uno::Any > theArgs {uno::makeAny(sConfigURL)};
xNameAccess.set(
theConfigProvider->createInstanceWithArguments(
sAccessSrvc, theArgs ), uno::UNO_QUERY_THROW );
} catch (const css::uno::Exception& e) {
SAL_WARN(
"desktop.migration", "ignoring Exception \"" << e << "\"");
}
return xNameAccess;
}
void MigrationImpl::copyFiles()
{
OUString localName;
OUString destName;
OUString userInstall;
utl::Bootstrap::PathStatus aStatus;
aStatus = utl::Bootstrap::locateUserInstallation(userInstall);
if (aStatus == utl::Bootstrap::PATH_EXISTS) {
for (auto const& rFile : *m_vrFileList)
{
// remove installation prefix from file
localName = rFile.copy(m_aInfo.userdata.getLength());
if (localName.endsWith( "/autocorr/acor_.dat")) {
// Previous versions used an empty language tag for
// LANGUAGE_DONTKNOW with the "[All]" autocorrection entry.
// As of LibreOffice 4.0 it is 'und' for LANGUAGE_UNDETERMINED
// so the file name is "acor_und.dat".
localName = localName.copy( 0, localName.getLength() - 4) + "und.dat";
}
destName = userInstall + localName;
INetURLObject aURL(destName);
// check whether destination directory exists
aURL.removeSegment();
_checkAndCreateDirectory(aURL);
FileBase::RC copyResult = File::copy(rFile, destName);
if (copyResult != FileBase::E_None) {
SAL_WARN( "desktop", "Cannot copy " << rFile << " to " << destName);
}
}
} else {
OSL_FAIL("copyFiles: UserInstall does not exist");
}
}
void MigrationImpl::runServices()
{
// Build argument array
uno::Sequence< uno::Any > seqArguments(3);
seqArguments[0] <<= NamedValue("Productname",
uno::makeAny(m_aInfo.productname));
seqArguments[1] <<= NamedValue("UserData",
uno::makeAny(m_aInfo.userdata));
// create an instance of every migration service
// and execute the migration job
uno::Reference< XJob > xMigrationJob;
uno::Reference< uno::XComponentContext > xContext(comphelper::getProcessComponentContext());
for (auto const& rMigration : *m_vrMigrations)
{
if( !rMigration.service.isEmpty()) {
try {
// set black list for extension migration
uno::Sequence< OUString > seqExtBlackList;
sal_uInt32 nSize = rMigration.excludeExtensions.size();
if ( nSize > 0 )
seqExtBlackList = comphelper::arrayToSequence< OUString >(
&rMigration.excludeExtensions[0], nSize );
seqArguments[2] <<= NamedValue("ExtensionBlackList",
uno::makeAny( seqExtBlackList ));
xMigrationJob.set(
xContext->getServiceManager()->createInstanceWithArgumentsAndContext(rMigration.service, seqArguments, xContext),
uno::UNO_QUERY_THROW);
xMigrationJob->execute(uno::Sequence< NamedValue >());
} catch (const Exception& e) {
SAL_WARN( "desktop", "Execution of migration service failed (Exception caught).\nService: "
<< rMigration.service
<< "\nMessage: " << e);
} catch (...) {
SAL_WARN( "desktop", "Execution of migration service failed (Exception caught).\nService: "
<< rMigration.service << "\nNo message available");
}
}
}
}
std::vector< MigrationModuleInfo > MigrationImpl::dectectUIChangesForAllModules() const
{
std::vector< MigrationModuleInfo > vModulesInfo;
const OUString MENUBAR("menubar");
const OUString TOOLBAR("toolbar");
uno::Sequence< uno::Any > lArgs {uno::makeAny(m_aInfo.userdata + "/user/config/soffice.cfg/modules"),
uno::makeAny(embed::ElementModes::READ)};
uno::Reference< lang::XSingleServiceFactory > xStorageFactory(
embed::FileSystemStorageFactory::create(comphelper::getProcessComponentContext()));
uno::Reference< embed::XStorage > xModules;
xModules.set(xStorageFactory->createInstanceWithArguments(lArgs), uno::UNO_QUERY);
if (!xModules.is())
return vModulesInfo;
uno::Reference< container::XNameAccess > xAccess(xModules, uno::UNO_QUERY);
uno::Sequence< OUString > lNames = xAccess->getElementNames();
sal_Int32 nLength = lNames.getLength();
for (sal_Int32 i=0; i<nLength; ++i) {
OUString sModuleShortName = lNames[i];
uno::Reference< embed::XStorage > xModule = xModules->openStorageElement(sModuleShortName, embed::ElementModes::READ);
if (xModule.is()) {
MigrationModuleInfo aModuleInfo;
uno::Reference< embed::XStorage > xMenubar = xModule->openStorageElement(MENUBAR, embed::ElementModes::READ);
if (xMenubar.is()) {
uno::Reference< container::XNameAccess > xNameAccess(xMenubar, uno::UNO_QUERY);
if (xNameAccess->getElementNames().getLength() > 0) {
aModuleInfo.sModuleShortName = sModuleShortName;
aModuleInfo.bHasMenubar = true;
}
}
uno::Reference< embed::XStorage > xToolbar = xModule->openStorageElement(TOOLBAR, embed::ElementModes::READ);
if (xToolbar.is()) {
const OUString RESOURCEURL_CUSTOM_ELEMENT("custom_");
sal_Int32 nCustomLen = 7;
uno::Reference< container::XNameAccess > xNameAccess(xToolbar, uno::UNO_QUERY);
::uno::Sequence< OUString > lToolbars = xNameAccess->getElementNames();
for (sal_Int32 j=0; j<lToolbars.getLength(); ++j) {
OUString sToolbarName = lToolbars[j];
if (sToolbarName.getLength()>=nCustomLen &&
sToolbarName.copy(0, nCustomLen) == RESOURCEURL_CUSTOM_ELEMENT)
continue;
aModuleInfo.sModuleShortName = sModuleShortName;
sal_Int32 nIndex = sToolbarName.lastIndexOf('.');
if (nIndex > 0) {
OUString sExtension(sToolbarName.copy(nIndex));
OUString sToolbarResourceName(sToolbarName.copy(0, nIndex));
if (!sToolbarResourceName.isEmpty() && sExtension == ".xml")
aModuleInfo.m_vToolbars.push_back(sToolbarResourceName);
}
}
}
if (!aModuleInfo.sModuleShortName.isEmpty())
vModulesInfo.push_back(aModuleInfo);
}
}
return vModulesInfo;
}
void MigrationImpl::compareOldAndNewConfig(const OUString& sParent,
const uno::Reference< container::XIndexContainer >& xIndexOld,
const uno::Reference< container::XIndexContainer >& xIndexNew,
const OUString& sResourceURL)
{
const OUString MENU_SEPARATOR(" | ");
std::vector< MigrationItem > vOldItems;
std::vector< MigrationItem > vNewItems;
uno::Sequence< beans::PropertyValue > aProp;
sal_Int32 nOldCount = xIndexOld->getCount();
sal_Int32 nNewCount = xIndexNew->getCount();
for (int n=0; n<nOldCount; ++n) {
MigrationItem aMigrationItem;
if (xIndexOld->getByIndex(n) >>= aProp) {
for(int i=0; i<aProp.getLength(); ++i) {
if ( aProp[i].Name == ITEM_DESCRIPTOR_COMMANDURL )
aProp[i].Value >>= aMigrationItem.m_sCommandURL;
else if ( aProp[i].Name == ITEM_DESCRIPTOR_CONTAINER )
aProp[i].Value >>= aMigrationItem.m_xPopupMenu;
}
if (!aMigrationItem.m_sCommandURL.isEmpty())
vOldItems.push_back(aMigrationItem);
}
}
for (int n=0; n<nNewCount; ++n) {
MigrationItem aMigrationItem;
if (xIndexNew->getByIndex(n) >>= aProp) {
for(int i=0; i<aProp.getLength(); ++i) {
if ( aProp[i].Name == ITEM_DESCRIPTOR_COMMANDURL )
aProp[i].Value >>= aMigrationItem.m_sCommandURL;
else if ( aProp[i].Name == ITEM_DESCRIPTOR_CONTAINER )
aProp[i].Value >>= aMigrationItem.m_xPopupMenu;
}
if (!aMigrationItem.m_sCommandURL.isEmpty())
vNewItems.push_back(aMigrationItem);
}
}
OUString sSibling;
for (auto const& oldItem : vOldItems)
{
std::vector< MigrationItem >::iterator pFound = std::find(vNewItems.begin(), vNewItems.end(), oldItem);
if (pFound != vNewItems.end() && oldItem.m_xPopupMenu.is()) {
OUString sName;
if (!sParent.isEmpty())
sName = sParent + MENU_SEPARATOR + oldItem.m_sCommandURL;
else
sName = oldItem.m_sCommandURL;
compareOldAndNewConfig(sName, oldItem.m_xPopupMenu, pFound->m_xPopupMenu, sResourceURL);
} else if (pFound == vNewItems.end()) {
MigrationItem aMigrationItem(sParent, sSibling, oldItem.m_sCommandURL, oldItem.m_xPopupMenu);
if (m_aOldVersionItemsHashMap.find(sResourceURL)==m_aOldVersionItemsHashMap.end()) {
std::vector< MigrationItem > vMigrationItems;
m_aOldVersionItemsHashMap.emplace(sResourceURL, vMigrationItems);
m_aOldVersionItemsHashMap[sResourceURL].push_back(aMigrationItem);
} else {
if (std::find(m_aOldVersionItemsHashMap[sResourceURL].begin(), m_aOldVersionItemsHashMap[sResourceURL].end(), aMigrationItem)==m_aOldVersionItemsHashMap[sResourceURL].end())
m_aOldVersionItemsHashMap[sResourceURL].push_back(aMigrationItem);
}
}
sSibling = oldItem.m_sCommandURL;
}
}
void MigrationImpl::mergeOldToNewVersion(const uno::Reference< ui::XUIConfigurationManager >& xCfgManager,
const uno::Reference< container::XIndexContainer>& xIndexContainer,
const OUString& sModuleIdentifier,
const OUString& sResourceURL)
{
MigrationHashMap::iterator pFound = m_aOldVersionItemsHashMap.find(sResourceURL);
if (pFound==m_aOldVersionItemsHashMap.end())
return;
for (auto const& elem : pFound->second)
{
uno::Reference< container::XIndexContainer > xTemp = xIndexContainer;
OUString sParentNodeName = elem.m_sParentNodeName;
sal_Int32 nIndex = 0;
do {
OUString sToken = sParentNodeName.getToken(0, '|', nIndex).trim();
if (sToken.isEmpty())
break;
sal_Int32 nCount = xTemp->getCount();
for (sal_Int32 i=0; i<nCount; ++i) {
OUString sCommandURL;
OUString sLabel;
uno::Reference< container::XIndexContainer > xChild;
uno::Sequence< beans::PropertyValue > aPropSeq;
xTemp->getByIndex(i) >>= aPropSeq;
for (sal_Int32 j=0; j<aPropSeq.getLength(); ++j) {
OUString sPropName = aPropSeq[j].Name;
if ( sPropName == ITEM_DESCRIPTOR_COMMANDURL )
aPropSeq[j].Value >>= sCommandURL;
else if ( sPropName == ITEM_DESCRIPTOR_LABEL )
aPropSeq[j].Value >>= sLabel;
else if ( sPropName == ITEM_DESCRIPTOR_CONTAINER )
aPropSeq[j].Value >>= xChild;
}
if (sCommandURL == sToken) {
xTemp = xChild;
break;
}
}
} while (nIndex >= 0);
if (nIndex == -1) {
uno::Sequence< beans::PropertyValue > aPropSeq {
beans::PropertyValue(ITEM_DESCRIPTOR_COMMANDURL, 0, uno::makeAny(elem.m_sCommandURL), beans::PropertyState_DIRECT_VALUE),
beans::PropertyValue(ITEM_DESCRIPTOR_LABEL, 0, uno::makeAny(retrieveLabelFromCommand(elem.m_sCommandURL, sModuleIdentifier)), beans::PropertyState_DIRECT_VALUE),
beans::PropertyValue(ITEM_DESCRIPTOR_CONTAINER, 0, uno::makeAny(elem.m_xPopupMenu), beans::PropertyState_DIRECT_VALUE)
};
if (elem.m_sPrevSibling.isEmpty())
xTemp->insertByIndex(0, uno::makeAny(aPropSeq));
else {
sal_Int32 nCount = xTemp->getCount();
sal_Int32 i = 0;
for (; i<nCount; ++i) {
OUString sCmd;
uno::Sequence< beans::PropertyValue > aTempPropSeq;
xTemp->getByIndex(i) >>= aTempPropSeq;
for (sal_Int32 j=0; j<aTempPropSeq.getLength(); ++j) {
if ( aTempPropSeq[j].Name == ITEM_DESCRIPTOR_COMMANDURL ) {
aTempPropSeq[j].Value >>= sCmd;
break;
}
}
if (sCmd == elem.m_sPrevSibling)
break;
}
xTemp->insertByIndex(i+1, uno::makeAny(aPropSeq));
}
}
}
uno::Reference< container::XIndexAccess > xIndexAccess(xIndexContainer, uno::UNO_QUERY);
if (xIndexAccess.is())
xCfgManager->replaceSettings(sResourceURL, xIndexAccess);
uno::Reference< ui::XUIConfigurationPersistence > xUIConfigurationPersistence(xCfgManager, uno::UNO_QUERY);
if (xUIConfigurationPersistence.is())
xUIConfigurationPersistence->store();
}
uno::Reference< ui::XUIConfigurationManager > NewVersionUIInfo::getConfigManager(const OUString& sModuleShortName) const
{
uno::Reference< ui::XUIConfigurationManager > xCfgManager;
for ( const css::beans::PropertyValue& rProp : m_lCfgManagerSeq) {
if (rProp.Name == sModuleShortName) {
rProp.Value >>= xCfgManager;
break;
}
}
return xCfgManager;
}
uno::Reference< container::XIndexContainer > NewVersionUIInfo::getNewMenubarSettings(const OUString& sModuleShortName) const
{
uno::Reference< container::XIndexContainer > xNewMenuSettings;
for (sal_Int32 i=0; i<m_lNewVersionMenubarSettingsSeq.getLength(); ++i) {
if (m_lNewVersionMenubarSettingsSeq[i].Name == sModuleShortName) {
m_lNewVersionMenubarSettingsSeq[i].Value >>= xNewMenuSettings;
break;
}
}
return xNewMenuSettings;
}
uno::Reference< container::XIndexContainer > NewVersionUIInfo::getNewToolbarSettings(const OUString& sModuleShortName, const OUString& sToolbarName) const
{
uno::Reference< container::XIndexContainer > xNewToolbarSettings;
for (sal_Int32 i=0; i<m_lNewVersionToolbarSettingsSeq.getLength(); ++i) {
if (m_lNewVersionToolbarSettingsSeq[i].Name == sModuleShortName) {
uno::Sequence< beans::PropertyValue > lToolbarSettingsSeq;
m_lNewVersionToolbarSettingsSeq[i].Value >>= lToolbarSettingsSeq;
for (sal_Int32 j=0; j<lToolbarSettingsSeq.getLength(); ++j) {
if (lToolbarSettingsSeq[j].Name == sToolbarName) {
lToolbarSettingsSeq[j].Value >>= xNewToolbarSettings;
break;
}
}
break;
}
}
return xNewToolbarSettings;
}
void NewVersionUIInfo::init(const std::vector< MigrationModuleInfo >& vModulesInfo)
{
m_lCfgManagerSeq.resize(vModulesInfo.size());
m_lNewVersionMenubarSettingsSeq.realloc(vModulesInfo.size());
m_lNewVersionToolbarSettingsSeq.realloc(vModulesInfo.size());
const OUString sMenubarResourceURL("private:resource/menubar/menubar");
const OUString sToolbarResourcePre("private:resource/toolbar/");
uno::Reference< ui::XModuleUIConfigurationManagerSupplier > xModuleCfgSupplier = ui::theModuleUIConfigurationManagerSupplier::get( ::comphelper::getProcessComponentContext() );
for (size_t i=0; i<vModulesInfo.size(); ++i) {
OUString sModuleIdentifier = mapModuleShortNameToIdentifier(vModulesInfo[i].sModuleShortName);
if (!sModuleIdentifier.isEmpty()) {
uno::Reference< ui::XUIConfigurationManager > xCfgManager = xModuleCfgSupplier->getUIConfigurationManager(sModuleIdentifier);
m_lCfgManagerSeq[i].Name = vModulesInfo[i].sModuleShortName;
m_lCfgManagerSeq[i].Value <<= xCfgManager;
if (vModulesInfo[i].bHasMenubar) {
m_lNewVersionMenubarSettingsSeq[i].Name = vModulesInfo[i].sModuleShortName;
m_lNewVersionMenubarSettingsSeq[i].Value <<= xCfgManager->getSettings(sMenubarResourceURL, true);
}
sal_Int32 nToolbars = vModulesInfo[i].m_vToolbars.size();
if (nToolbars > 0) {
uno::Sequence< beans::PropertyValue > lPropSeq(nToolbars);
for (sal_Int32 j=0; j<nToolbars; ++j) {
OUString sToolbarName = vModulesInfo[i].m_vToolbars[j];
OUString sToolbarResourceURL = sToolbarResourcePre + sToolbarName;
lPropSeq[j].Name = sToolbarName;
lPropSeq[j].Value <<= xCfgManager->getSettings(sToolbarResourceURL, true);
}
m_lNewVersionToolbarSettingsSeq[i].Name = vModulesInfo[i].sModuleShortName;
m_lNewVersionToolbarSettingsSeq[i].Value <<= lPropSeq;
}
}
}
}
} // namespace desktop
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V560 A part of conditional expression is always true: !bInserted.