1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <accelerators/presethandler.hxx>
22 #include <classes/fwkresid.hxx>
24 #include <strings.hrc>
26 #include <com/sun/star/configuration/CorruptedUIConfigurationException.hpp>
27 #include <com/sun/star/container/XNameAccess.hpp>
28 #include <com/sun/star/embed/ElementModes.hpp>
29 #include <com/sun/star/embed/FileSystemStorageFactory.hpp>
30 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
31 #include <com/sun/star/util/thePathSettings.hpp>
34 #include <vcl/svapp.hxx>
35 #include <cppuhelper/exc_hlp.hxx>
36 #include <rtl/ustrbuf.hxx>
37 #include <osl/diagnose.h>
38 #include <i18nlangtag/languagetag.hxx>
40 const ::sal_Int32 ID_CORRUPT_UICONFIG_SHARE
= 1;
41 const ::sal_Int32 ID_CORRUPT_UICONFIG_USER
= 2;
42 const ::sal_Int32 ID_CORRUPT_UICONFIG_GENERAL
= 3;
49 /** @short because a concurrent access to the same storage from different implementations
50 isn't supported, we have to share it with others.
52 @descr This struct is allegedly shared and must be used within a
53 synchronized section. But it isn't.
55 struct TSharedStorages final
57 StorageHolder m_lStoragesShare
;
58 StorageHolder m_lStoragesUser
;
64 /** @short provides access to the:
65 a) shared root storages
66 b) shared "inbetween" storages
67 of the share and user layer. */
68 TSharedStorages
& SharedStorages()
70 static TSharedStorages theStorages
;
76 PresetHandler::PresetHandler(css::uno::Reference
< css::uno::XComponentContext
> xContext
)
77 : m_xContext(std::move(xContext
))
78 , m_eConfigType(E_GLOBAL
)
82 PresetHandler::PresetHandler(const PresetHandler
& rCopy
)
84 m_xContext
= rCopy
.m_xContext
;
85 m_eConfigType
= rCopy
.m_eConfigType
;
86 m_xWorkingStorageShare
= rCopy
.m_xWorkingStorageShare
;
87 m_xWorkingStorageNoLang
= rCopy
.m_xWorkingStorageNoLang
;
88 m_xWorkingStorageUser
= rCopy
.m_xWorkingStorageUser
;
89 m_lDocumentStorages
= rCopy
.m_lDocumentStorages
;
90 m_sRelPathShare
= rCopy
.m_sRelPathShare
;
91 m_sRelPathUser
= rCopy
.m_sRelPathUser
;
94 PresetHandler::~PresetHandler()
96 m_xWorkingStorageShare
.clear();
97 m_xWorkingStorageNoLang
.clear();
98 m_xWorkingStorageUser
.clear();
101 Don't call forgetCachedStorages() here for shared storages.
102 Because we opened different sub storages by using openPath().
103 And every already open path was reused and referenced (means it's
104 ref count was increased!)
105 So now we have to release our ref counts to these shared storages
106 only ... and not to free all used storages.
107 Otherwise we will disconnect all other open configuration access
108 objects which base on these storages.
110 auto & sharedStorages
= SharedStorages();
111 sharedStorages
.m_lStoragesShare
.closePath(m_sRelPathShare
);
112 sharedStorages
.m_lStoragesUser
.closePath (m_sRelPathUser
);
114 /* On the other side closePath() is not needed for our special handled
115 document storage. Because it's not shared with others ... so we can
118 m_lDocumentStorages
.forgetCachedStorages();
121 void PresetHandler::forgetCachedStorages()
125 if (m_eConfigType
== E_DOCUMENT
)
127 m_xWorkingStorageShare
.clear();
128 m_xWorkingStorageNoLang
.clear();
129 m_xWorkingStorageUser
.clear();
132 m_lDocumentStorages
.forgetCachedStorages();
137 OUString
lcl_getLocalizedMessage(::sal_Int32 nID
)
139 OUString
sMessage("Unknown error.");
143 case ID_CORRUPT_UICONFIG_SHARE
:
144 sMessage
= FwkResId(STR_CORRUPT_UICFG_SHARE
);
148 case ID_CORRUPT_UICONFIG_USER
:
149 sMessage
= FwkResId(STR_CORRUPT_UICFG_USER
);
152 case ID_CORRUPT_UICONFIG_GENERAL
:
153 sMessage
= FwkResId(STR_CORRUPT_UICFG_GENERAL
);
160 void lcl_throwCorruptedUIConfigurationException(
161 css::uno::Any
const & exception
, sal_Int32 id
)
163 css::uno::Exception e
;
164 bool ok
= (exception
>>= e
);
166 throw css::configuration::CorruptedUIConfigurationException(
167 lcl_getLocalizedMessage(id
),
168 css::uno::Reference
< css::uno::XInterface
>(),
169 exception
.getValueTypeName() + ": \"" + e
.Message
+ "\"");
174 css::uno::Reference
< css::embed::XStorage
> PresetHandler::getOrCreateRootStorageShare()
176 auto & sharedStorages
= SharedStorages();
177 css::uno::Reference
< css::embed::XStorage
> xRoot
= sharedStorages
.m_lStoragesShare
.getRootStorage();
181 css::uno::Reference
< css::uno::XComponentContext
> xContext
;
184 xContext
= m_xContext
;
187 css::uno::Reference
< css::util::XPathSettings
> xPathSettings
=
188 css::util::thePathSettings::get( xContext
);
190 OUString sShareLayer
= xPathSettings
->getBasePathShareLayer();
192 // "UIConfig" is a "multi path" ... use first part only here!
193 sal_Int32 nPos
= sShareLayer
.indexOf(';');
195 sShareLayer
= sShareLayer
.copy(0, nPos
);
197 // Note: May be an user uses URLs without a final slash! Check it ...
198 nPos
= sShareLayer
.lastIndexOf('/');
199 if (nPos
!= sShareLayer
.getLength()-1)
202 sShareLayer
+= "soffice.cfg";
205 // Attention: This is temp. workaround ... We create a temp. storage file
206 // based of a system directory. This must be used so, till the storage implementation
207 // can work on directories too.
209 css::uno::Sequence
< css::uno::Any
> lArgs
{
210 css::uno::Any(sShareLayer
),
211 css::uno::Any(css::embed::ElementModes::READ
| css::embed::ElementModes::NOCREATE
)
214 css::uno::Reference
< css::lang::XSingleServiceFactory
> xStorageFactory
= css::embed::FileSystemStorageFactory::create( xContext
);
215 css::uno::Reference
< css::embed::XStorage
> xStorage
;
219 xStorage
.set(xStorageFactory
->createInstanceWithArguments(lArgs
), css::uno::UNO_QUERY_THROW
);
221 catch(const css::uno::Exception
&)
223 css::uno::Any
ex(cppu::getCaughtException());
224 lcl_throwCorruptedUIConfigurationException(
225 ex
, ID_CORRUPT_UICONFIG_SHARE
);
228 sharedStorages
.m_lStoragesShare
.setRootStorage(xStorage
);
233 css::uno::Reference
< css::embed::XStorage
> PresetHandler::getOrCreateRootStorageUser()
235 auto & sharedStorages
= SharedStorages();
236 css::uno::Reference
< css::embed::XStorage
> xRoot
= sharedStorages
.m_lStoragesUser
.getRootStorage();
240 css::uno::Reference
< css::uno::XComponentContext
> xContext
;
243 xContext
= m_xContext
;
246 css::uno::Reference
< css::util::XPathSettings
> xPathSettings
=
247 css::util::thePathSettings::get( xContext
);
249 OUString sUserLayer
= xPathSettings
->getBasePathUserLayer();
251 // Note: May be an user uses URLs without a final slash! Check it ...
252 sal_Int32 nPos
= sUserLayer
.lastIndexOf('/');
253 if (nPos
!= sUserLayer
.getLength()-1)
256 sUserLayer
+= "soffice.cfg"; // storage file
258 css::uno::Sequence
< css::uno::Any
> lArgs
{ css::uno::Any(sUserLayer
),
259 css::uno::Any(css::embed::ElementModes::READWRITE
) };
261 css::uno::Reference
< css::lang::XSingleServiceFactory
> xStorageFactory
= css::embed::FileSystemStorageFactory::create( xContext
);
262 css::uno::Reference
< css::embed::XStorage
> xStorage
;
266 xStorage
.set(xStorageFactory
->createInstanceWithArguments(lArgs
), css::uno::UNO_QUERY_THROW
);
268 catch(const css::uno::Exception
&)
270 css::uno::Any
ex(cppu::getCaughtException());
271 lcl_throwCorruptedUIConfigurationException(
272 ex
, ID_CORRUPT_UICONFIG_USER
);
275 sharedStorages
.m_lStoragesUser
.setRootStorage(xStorage
);
280 css::uno::Reference
< css::embed::XStorage
> PresetHandler::getWorkingStorageUser() const
283 return m_xWorkingStorageUser
;
286 css::uno::Reference
< css::embed::XStorage
> PresetHandler::getParentStorageShare()
288 css::uno::Reference
< css::embed::XStorage
> xWorking
;
291 xWorking
= m_xWorkingStorageShare
;
294 return SharedStorages().m_lStoragesShare
.getParentStorage(xWorking
);
297 css::uno::Reference
< css::embed::XStorage
> PresetHandler::getParentStorageUser()
299 css::uno::Reference
< css::embed::XStorage
> xWorking
;
302 xWorking
= m_xWorkingStorageUser
;
305 return SharedStorages().m_lStoragesUser
.getParentStorage(xWorking
);
308 void PresetHandler::connectToResource( PresetHandler::EConfigType eConfigType
,
309 std::u16string_view sResource
,
310 std::u16string_view sModule
,
311 const css::uno::Reference
< css::embed::XStorage
>& xDocumentRoot
,
312 const LanguageTag
& rLanguageTag
)
314 // TODO free all current open storages!
318 m_eConfigType
= eConfigType
;
321 css::uno::Reference
< css::embed::XStorage
> xShare
;
322 css::uno::Reference
< css::embed::XStorage
> xNoLang
;
323 css::uno::Reference
< css::embed::XStorage
> xUser
;
325 // special case for documents
326 // use outside root storage, if we run in E_DOCUMENT mode!
327 if (eConfigType
== E_DOCUMENT
)
329 if (!xDocumentRoot
.is())
330 throw css::uno::RuntimeException(
331 "There is valid root storage, where the UI configuration can work on.");
332 m_lDocumentStorages
.setRootStorage(xDocumentRoot
);
333 xShare
= xDocumentRoot
;
334 xUser
= xDocumentRoot
;
338 xShare
= getOrCreateRootStorageShare();
339 xUser
= getOrCreateRootStorageUser();
346 // a) inside share layer we should not create any new structures... We have to use
347 // existing ones only!
348 // b) inside user layer we can (SOFT mode!) but sometimes we should not (HARD mode!)
349 // create new empty structures. We should prefer using of any existing structure.
350 sal_Int32 eShareMode
= (css::embed::ElementModes::READ
| css::embed::ElementModes::NOCREATE
);
351 sal_Int32 eUserMode
= css::embed::ElementModes::READWRITE
;
353 OUStringBuffer
sRelPathBuf(1024);
354 OUString sRelPathShare
;
355 OUString sRelPathUser
;
360 sRelPathShare
= OUString::Concat("global/") + sResource
;
361 sRelPathUser
= sRelPathShare
;
363 xShare
= impl_openPathIgnoringErrors(sRelPathShare
, eShareMode
, true );
364 xUser
= impl_openPathIgnoringErrors(sRelPathUser
, eUserMode
, false);
370 sRelPathShare
= OUString::Concat("modules/") + sModule
+ "/" + sResource
;
371 sRelPathUser
= sRelPathShare
;
373 xShare
= impl_openPathIgnoringErrors(sRelPathShare
, eShareMode
, true );
374 xUser
= impl_openPathIgnoringErrors(sRelPathUser
, eUserMode
, false);
380 // A document does not have a share layer in real.
381 // It has one layer only, and this one should be opened READ_WRITE.
382 // So we open the user layer here only and set the share layer equals to it .-)
384 sRelPathBuf
.append(sResource
);
385 sRelPathUser
= sRelPathBuf
.makeStringAndClear();
386 sRelPathShare
= sRelPathUser
;
390 xUser
= m_lDocumentStorages
.openPath(sRelPathUser
, eUserMode
);
393 catch(const css::uno::RuntimeException
&)
395 catch(const css::uno::Exception
&)
396 { xShare
.clear(); xUser
.clear(); }
401 // Non-localized global share
405 (rLanguageTag
!= LanguageTag(LANGUAGE_USER_PRIV_NOTRANSLATE
)) && // localized level?
406 (eConfigType
!= E_DOCUMENT
) // no localization in document mode!
409 // First try to find the right localized set inside share layer.
410 // Fallbacks are allowed there.
411 OUString
aShareLocale( rLanguageTag
.getBcp47());
412 OUString
sLocalizedSharePath(sRelPathShare
);
413 bool bAllowFallbacks
= true;
414 xShare
= impl_openLocalizedPathIgnoringErrors(sLocalizedSharePath
, eShareMode
, true , aShareLocale
, bAllowFallbacks
);
416 // The try to locate the right sub dir inside user layer ... without using fallbacks!
417 // Normally the corresponding sub dir should be created matching the specified locale.
418 // Because we allow creation of storages inside user layer by default.
419 OUString
aUserLocale( rLanguageTag
.getBcp47());
420 OUString
sLocalizedUserPath(sRelPathUser
);
421 bAllowFallbacks
= false;
422 xUser
= impl_openLocalizedPathIgnoringErrors(sLocalizedUserPath
, eUserMode
, false, aUserLocale
, bAllowFallbacks
);
424 sRelPathShare
= sLocalizedSharePath
;
425 sRelPathUser
= sLocalizedUserPath
;
430 m_xWorkingStorageShare
= xShare
;
431 m_xWorkingStorageNoLang
= xNoLang
;
432 m_xWorkingStorageUser
= xUser
;
433 m_sRelPathShare
= sRelPathShare
;
434 m_sRelPathUser
= sRelPathUser
;
438 catch(const css::uno::Exception
&)
440 css::uno::Any
ex(cppu::getCaughtException());
441 lcl_throwCorruptedUIConfigurationException(
442 ex
, ID_CORRUPT_UICONFIG_GENERAL
);
446 void PresetHandler::copyPresetToTarget(std::u16string_view sPreset
,
447 std::u16string_view sTarget
)
449 // don't check our preset list, if element exists
450 // We try to open it and forward all errors to the user!
452 css::uno::Reference
< css::embed::XStorage
> xWorkingShare
;
453 css::uno::Reference
< css::embed::XStorage
> xWorkingNoLang
;
454 css::uno::Reference
< css::embed::XStorage
> xWorkingUser
;
457 xWorkingShare
= m_xWorkingStorageShare
;
458 xWorkingNoLang
= m_xWorkingStorageNoLang
;
459 xWorkingUser
= m_xWorkingStorageUser
;
462 // e.g. module without any config data ?!
464 (!xWorkingShare
.is()) ||
465 (!xWorkingUser
.is() )
471 OUString sPresetFile
= OUString::Concat(sPreset
) + ".xml";
472 OUString sTargetFile
= OUString::Concat(sTarget
) + ".xml";
474 // remove existing elements before you try to copy the preset to that location ...
475 // Otherwise w will get an ElementExistException inside copyElementTo()!
476 css::uno::Reference
< css::container::XNameAccess
> xCheckingUser(xWorkingUser
, css::uno::UNO_QUERY_THROW
);
477 if (xCheckingUser
->hasByName(sTargetFile
))
478 xWorkingUser
->removeElement(sTargetFile
);
480 xWorkingShare
->copyElementTo(sPresetFile
, xWorkingUser
, sTargetFile
);
482 // If our storages work in transacted mode, we have
483 // to commit all changes from bottom to top!
487 css::uno::Reference
< css::io::XStream
> PresetHandler::openPreset(std::u16string_view sPreset
)
489 css::uno::Reference
< css::embed::XStorage
> xFolder
;
492 xFolder
= m_xWorkingStorageNoLang
;
495 // e.g. module without any config data ?!
497 return css::uno::Reference
< css::io::XStream
>();
499 OUString sFile
= OUString::Concat(sPreset
) + ".xml";
501 // inform user about errors (use original exceptions!)
502 css::uno::Reference
< css::io::XStream
> xStream
= xFolder
->openStreamElement(sFile
, css::embed::ElementModes::READ
);
506 css::uno::Reference
< css::io::XStream
> PresetHandler::openTarget(
507 std::u16string_view sTarget
, sal_Int32
const nMode
)
509 css::uno::Reference
< css::embed::XStorage
> xFolder
;
512 xFolder
= m_xWorkingStorageUser
;
515 // e.g. module without any config data ?!
517 return css::uno::Reference
< css::io::XStream
>();
519 OUString
const sFile(OUString::Concat(sTarget
) + ".xml");
521 return xFolder
->openStreamElement(sFile
, nMode
);
524 void PresetHandler::commitUserChanges()
526 css::uno::Reference
< css::embed::XStorage
> xWorking
;
527 EConfigType eCfgType
;
530 xWorking
= m_xWorkingStorageUser
;
531 eCfgType
= m_eConfigType
;
534 // e.g. module without any config data ?!
545 auto & sharedStorages
= SharedStorages();
546 sPath
= sharedStorages
.m_lStoragesUser
.getPathOfStorage(xWorking
);
547 sharedStorages
.m_lStoragesUser
.commitPath(sPath
);
548 sharedStorages
.m_lStoragesUser
.notifyPath(sPath
);
554 sPath
= m_lDocumentStorages
.getPathOfStorage(xWorking
);
555 m_lDocumentStorages
.commitPath(sPath
);
556 m_lDocumentStorages
.notifyPath(sPath
);
562 void PresetHandler::addStorageListener(XMLBasedAcceleratorConfiguration
* pListener
)
565 EConfigType eCfgType
;
568 sRelPath
= m_sRelPathUser
; // use user path ... because we don't work directly on the share layer!
569 eCfgType
= m_eConfigType
;
572 if (sRelPath
.isEmpty())
580 SharedStorages().m_lStoragesUser
.addStorageListener(pListener
, sRelPath
);
586 m_lDocumentStorages
.addStorageListener(pListener
, sRelPath
);
592 void PresetHandler::removeStorageListener(XMLBasedAcceleratorConfiguration
* pListener
)
595 EConfigType eCfgType
;
598 sRelPath
= m_sRelPathUser
; // use user path ... because we don't work directly on the share layer!
599 eCfgType
= m_eConfigType
;
602 if (sRelPath
.isEmpty())
610 SharedStorages().m_lStoragesUser
.removeStorageListener(pListener
, sRelPath
);
616 m_lDocumentStorages
.removeStorageListener(pListener
, sRelPath
);
622 css::uno::Reference
< css::embed::XStorage
> PresetHandler::impl_openPathIgnoringErrors(const OUString
& sPath
,
626 css::uno::Reference
< css::embed::XStorage
> xPath
;
630 xPath
= SharedStorages().m_lStoragesShare
.openPath(sPath
, eMode
);
632 xPath
= SharedStorages().m_lStoragesUser
.openPath(sPath
, eMode
);
634 catch(const css::uno::RuntimeException
&)
636 catch(const css::uno::Exception
&)
641 ::std::vector
< OUString
>::const_iterator
PresetHandler::impl_findMatchingLocalizedValue(
642 const ::std::vector
< OUString
>& lLocalizedValues
,
643 OUString
& rLanguageTag
,
644 bool bAllowFallbacks
)
646 ::std::vector
< OUString
>::const_iterator pFound
= lLocalizedValues
.end();
649 pFound
= LanguageTag::getFallback(lLocalizedValues
, rLanguageTag
);
650 // if we found a valid locale ... take it over to our in/out parameter
652 if (pFound
!= lLocalizedValues
.end())
654 rLanguageTag
= *pFound
;
659 pFound
= std::find(lLocalizedValues
.begin(), lLocalizedValues
.end(), rLanguageTag
);
665 css::uno::Reference
< css::embed::XStorage
> PresetHandler::impl_openLocalizedPathIgnoringErrors(
669 OUString
& rLanguageTag
,
672 css::uno::Reference
< css::embed::XStorage
> xPath
= impl_openPathIgnoringErrors(sPath
, eMode
, bShare
);
673 ::std::vector
< OUString
> lSubFolders
= impl_getSubFolderNames(xPath
);
674 ::std::vector
< OUString
>::const_iterator pLocaleFolder
= impl_findMatchingLocalizedValue(lSubFolders
, rLanguageTag
, bAllowFallback
);
676 // no fallback ... creation not allowed => no storage
678 (pLocaleFolder
== lSubFolders
.end() ) &&
679 ((eMode
& css::embed::ElementModes::NOCREATE
) == css::embed::ElementModes::NOCREATE
)
681 return css::uno::Reference
< css::embed::XStorage
>();
683 // it doesn't matter, if there is a locale fallback or not
684 // If creation of storages is allowed, we do it anyway.
685 // Otherwise we have no acc config at all, which can make other trouble.
686 OUString sLocalizedPath
= sPath
+ "/";
687 if (pLocaleFolder
!= lSubFolders
.end())
688 sLocalizedPath
+= *pLocaleFolder
;
690 sLocalizedPath
+= rLanguageTag
;
692 css::uno::Reference
< css::embed::XStorage
> xLocalePath
= impl_openPathIgnoringErrors(sLocalizedPath
, eMode
, bShare
);
694 if (xLocalePath
.is())
695 sPath
= sLocalizedPath
;
702 ::std::vector
< OUString
> PresetHandler::impl_getSubFolderNames(const css::uno::Reference
< css::embed::XStorage
>& xFolder
)
705 return ::std::vector
< OUString
>();
707 ::std::vector
< OUString
> lSubFolders
;
708 const css::uno::Sequence
< OUString
> lNames
= xFolder
->getElementNames();
709 const OUString
* pNames
= lNames
.getConstArray();
710 sal_Int32 c
= lNames
.getLength();
717 if (xFolder
->isStorageElement(pNames
[i
]))
718 lSubFolders
.push_back(pNames
[i
]);
720 catch(const css::uno::RuntimeException
&)
722 catch(const css::uno::Exception
&)
729 } // namespace framework
731 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */