Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / ucb / source / core / ucb.cxx
blob6a2796a2282d71ef11ab803772d14472895d7b6c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
21 /**************************************************************************
22 TODO
23 **************************************************************************
25 *************************************************************************/
26 #include <osl/diagnose.h>
27 #include <sal/log.hxx>
28 #include <rtl/ustrbuf.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <comphelper/interfacecontainer2.hxx>
31 #include <comphelper/propertysequence.hxx>
32 #include <com/sun/star/lang/IllegalArgumentException.hpp>
33 #include <com/sun/star/ucb/DuplicateProviderException.hpp>
34 #include <com/sun/star/ucb/GlobalTransferCommandArgument2.hpp>
35 #include <com/sun/star/ucb/UnsupportedCommandException.hpp>
36 #include <com/sun/star/ucb/XCommandInfo.hpp>
37 #include <com/sun/star/ucb/XContentProviderSupplier.hpp>
38 #include <com/sun/star/configuration/theDefaultProvider.hpp>
39 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
40 #include <com/sun/star/container/XNameAccess.hpp>
41 #include <com/sun/star/uno/Any.hxx>
42 #include <ucbhelper/cancelcommandexecution.hxx>
43 #include <ucbhelper/getcomponentcontext.hxx>
44 #include <ucbhelper/macros.hxx>
45 #include "identify.hxx"
46 #include "ucbcmds.hxx"
48 #include "ucb.hxx"
50 using namespace comphelper;
51 using namespace com::sun::star::uno;
52 using namespace com::sun::star::lang;
53 using namespace com::sun::star::ucb;
54 using namespace ucb_impl;
55 using namespace com::sun::star;
56 using namespace ucbhelper;
59 namespace {
61 bool fillPlaceholders(OUString const & rInput,
62 uno::Sequence< uno::Any > const & rReplacements,
63 OUString * pOutput)
65 sal_Unicode const * p = rInput.getStr();
66 sal_Unicode const * pEnd = p + rInput.getLength();
67 sal_Unicode const * pCopy = p;
68 OUStringBuffer aBuffer;
69 while (p != pEnd)
70 switch (*p++)
72 case '&':
73 if (pEnd - p >= 4
74 && p[0] == 'a' && p[1] == 'm' && p[2] == 'p'
75 && p[3] == ';')
77 aBuffer.append(pCopy, p - 1 - pCopy);
78 aBuffer.append('&');
79 p += 4;
80 pCopy = p;
82 else if (pEnd - p >= 3
83 && p[0] == 'l' && p[1] == 't' && p[2] == ';')
85 aBuffer.append(pCopy, p - 1 - pCopy);
86 aBuffer.append('<');
87 p += 3;
88 pCopy = p;
90 else if (pEnd - p >= 3
91 && p[0] == 'g' && p[1] == 't' && p[2] == ';')
93 aBuffer.append(pCopy, p - 1 - pCopy);
94 aBuffer.append('>');
95 p += 3;
96 pCopy = p;
98 break;
100 case '<':
101 sal_Unicode const * q = p;
102 while (q != pEnd && *q != '>')
103 ++q;
104 if (q == pEnd)
105 break;
106 OUString aKey(p, q - p);
107 OUString aValue;
108 bool bFound = false;
109 for (sal_Int32 i = 2; i + 1 < rReplacements.getLength();
110 i += 2)
112 OUString aReplaceKey;
113 if ((rReplacements[i] >>= aReplaceKey)
114 && aReplaceKey == aKey
115 && (rReplacements[i + 1] >>= aValue))
117 bFound = true;
118 break;
121 if (!bFound)
122 return false;
123 aBuffer.append(pCopy, p - 1 - pCopy);
124 aBuffer.append(aValue);
125 p = q + 1;
126 pCopy = p;
127 break;
129 aBuffer.append(pCopy, pEnd - pCopy);
130 *pOutput = aBuffer.makeStringAndClear();
131 return true;
134 void makeAndAppendXMLName(
135 OUStringBuffer & rBuffer, const OUString & rIn )
137 sal_Int32 nCount = rIn.getLength();
138 for ( sal_Int32 n = 0; n < nCount; ++n )
140 const sal_Unicode c = rIn[ n ];
141 switch ( c )
143 case '&':
144 rBuffer.append( "&amp;" );
145 break;
147 case '"':
148 rBuffer.append( "&quot;" );
149 break;
151 case '\'':
152 rBuffer.append( "&apos;" );
153 break;
155 case '<':
156 rBuffer.append( "&lt;" );
157 break;
159 case '>':
160 rBuffer.append( "&gt;" );
161 break;
163 default:
164 rBuffer.append( c );
165 break;
170 bool createContentProviderData(
171 const OUString & rProvider,
172 const uno::Reference< container::XHierarchicalNameAccess >& rxHierNameAccess,
173 ContentProviderData & rInfo)
175 // Obtain service name.
177 OUString aValue;
180 if ( !( rxHierNameAccess->getByHierarchicalName(
181 rProvider + "/ServiceName" ) >>= aValue ) )
183 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
184 "Error getting item value!" );
187 catch (const container::NoSuchElementException&)
189 return false;
192 rInfo.ServiceName = aValue;
194 // Obtain URL Template.
196 if ( !( rxHierNameAccess->getByHierarchicalName(
197 rProvider + "/URLTemplate" ) >>= aValue ) )
199 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
200 "Error getting item value!" );
203 rInfo.URLTemplate = aValue;
205 // Obtain Arguments.
207 if ( !( rxHierNameAccess->getByHierarchicalName(
208 rProvider + "/Arguments" ) >>= aValue ) )
210 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
211 "Error getting item value!" );
214 rInfo.Arguments = aValue;
215 return true;
221 // UniversalContentBroker Implementation.
224 UniversalContentBroker::UniversalContentBroker(
225 const Reference< css::uno::XComponentContext >& xContext )
226 : m_xContext( xContext ),
227 m_nCommandId( 0 )
229 OSL_ENSURE( m_xContext.is(),
230 "UniversalContentBroker ctor: No service manager" );
234 // virtual
235 UniversalContentBroker::~UniversalContentBroker()
240 // XComponent methods.
243 // virtual
244 void SAL_CALL UniversalContentBroker::dispose()
246 if ( m_pDisposeEventListeners && m_pDisposeEventListeners->getLength() )
248 EventObject aEvt;
249 aEvt.Source = static_cast< XComponent* >(this);
250 m_pDisposeEventListeners->disposeAndClear( aEvt );
253 if ( m_xNotifier.is() )
254 m_xNotifier->removeChangesListener( this );
258 // virtual
259 void SAL_CALL UniversalContentBroker::addEventListener(
260 const Reference< XEventListener >& Listener )
262 if ( !m_pDisposeEventListeners )
263 m_pDisposeEventListeners.reset( new OInterfaceContainerHelper2( m_aMutex ) );
265 m_pDisposeEventListeners->addInterface( Listener );
269 // virtual
270 void SAL_CALL UniversalContentBroker::removeEventListener(
271 const Reference< XEventListener >& Listener )
273 if ( m_pDisposeEventListeners )
274 m_pDisposeEventListeners->removeInterface( Listener );
276 // Note: Don't want to delete empty container here -> performance.
280 // XServiceInfo methods.
282 XSERVICEINFO_COMMOM_IMPL( UniversalContentBroker,
283 "com.sun.star.comp.ucb.UniversalContentBroker" )
284 /// @throws css::uno::Exception
285 static css::uno::Reference< css::uno::XInterface >
286 UniversalContentBroker_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr )
288 css::lang::XServiceInfo* pX = new UniversalContentBroker( ucbhelper::getComponentContext(rSMgr) );
289 return css::uno::Reference< css::uno::XInterface >::query( pX );
292 css::uno::Sequence< OUString >
293 UniversalContentBroker::getSupportedServiceNames_Static()
295 css::uno::Sequence< OUString > aSNS { UCB_SERVICE_NAME };
296 return aSNS;
299 // Service factory implementation.
302 ONE_INSTANCE_SERVICE_FACTORY_IMPL( UniversalContentBroker );
305 // XInitialization methods.
308 // virtual
309 void SAL_CALL UniversalContentBroker::initialize( const css::uno::Sequence< Any >& aArguments )
312 osl::MutexGuard aGuard(m_aMutex);
313 if (m_aArguments.hasElements())
315 if (aArguments.hasElements()
316 && !(m_aArguments.getLength() == 2
317 && aArguments.getLength() == 2
318 && m_aArguments[0] == aArguments[0]
319 && m_aArguments[1] == aArguments[1]))
321 throw IllegalArgumentException(
322 "UCB reinitialized with different arguments",
323 static_cast< cppu::OWeakObject * >(this), 0);
325 return;
327 if (!aArguments.hasElements())
329 m_aArguments.realloc(2);
330 m_aArguments[0] <<= OUString("Local");
331 m_aArguments[1] <<= OUString("Office");
333 else
335 m_aArguments = aArguments;
338 configureUcb();
342 // XContentProviderManager methods.
345 // virtual
346 Reference< XContentProvider > SAL_CALL
347 UniversalContentBroker::registerContentProvider(
348 const Reference< XContentProvider >& Provider,
349 const OUString& Scheme,
350 sal_Bool ReplaceExisting )
352 osl::MutexGuard aGuard(m_aMutex);
354 ProviderMap_Impl::iterator aIt;
357 aIt = m_aProviders.find(Scheme);
359 catch (const IllegalArgumentException&)
361 return nullptr; //@@@
364 Reference< XContentProvider > xPrevious;
365 if (aIt == m_aProviders.end())
367 ProviderList_Impl aList;
368 aList.push_front( ProviderListEntry_Impl(Provider) );
371 m_aProviders.add(Scheme, aList);
373 catch (const IllegalArgumentException&)
375 return nullptr; //@@@
378 else
380 if (!ReplaceExisting)
381 throw DuplicateProviderException();
383 ProviderList_Impl & rList = aIt->getValue();
384 xPrevious = rList.front().getProvider();
385 rList.push_front( ProviderListEntry_Impl(Provider) );
388 return xPrevious;
392 // virtual
393 void SAL_CALL UniversalContentBroker::deregisterContentProvider(
394 const Reference< XContentProvider >& Provider,
395 const OUString& Scheme )
397 osl::MutexGuard aGuard(m_aMutex);
399 ProviderMap_Impl::iterator aMapIt;
402 aMapIt = m_aProviders.find(Scheme);
404 catch (const IllegalArgumentException&)
406 return; //@@@
409 if (aMapIt != m_aProviders.end())
411 ProviderList_Impl & rList = aMapIt->getValue();
413 auto aListIt = std::find_if(rList.begin(), rList.end(),
414 [&Provider](const ProviderListEntry_Impl& rEntry) { return rEntry.getProvider() == Provider; });
415 if (aListIt != rList.end())
416 rList.erase(aListIt);
418 if (rList.empty())
419 m_aProviders.erase(aMapIt);
424 // virtual
425 css::uno::Sequence< ContentProviderInfo > SAL_CALL
426 UniversalContentBroker::queryContentProviders()
428 // Return a list with information about active(!) content providers.
430 osl::MutexGuard aGuard(m_aMutex);
432 css::uno::Sequence< ContentProviderInfo > aSeq( m_aProviders.size() );
433 ContentProviderInfo* pInfo = aSeq.getArray();
435 ProviderMap_Impl::const_iterator end = m_aProviders.end();
436 for (ProviderMap_Impl::const_iterator it(m_aProviders.begin()); it != end;
437 ++it)
439 // Note: Active provider is always the first list element.
440 pInfo->ContentProvider = it->getValue().front().getProvider();
441 pInfo->Scheme = it->getRegexp();
442 ++pInfo;
445 return aSeq;
449 // virtual
450 Reference< XContentProvider > SAL_CALL
451 UniversalContentBroker::queryContentProvider( const OUString&
452 Identifier )
454 return queryContentProvider( Identifier, false );
458 // XContentProvider methods.
461 // virtual
462 Reference< XContent > SAL_CALL UniversalContentBroker::queryContent(
463 const Reference< XContentIdentifier >& Identifier )
466 // Let the content provider for the scheme given with the content
467 // identifier create the XContent instance.
470 if ( !Identifier.is() )
471 return Reference< XContent >();
473 Reference< XContentProvider > xProv =
474 queryContentProvider( Identifier->getContentIdentifier(), true );
475 if ( xProv.is() )
476 return xProv->queryContent( Identifier );
478 return Reference< XContent >();
482 // virtual
483 sal_Int32 SAL_CALL UniversalContentBroker::compareContentIds(
484 const Reference< XContentIdentifier >& Id1,
485 const Reference< XContentIdentifier >& Id2 )
487 OUString aURI1( Id1->getContentIdentifier() );
488 OUString aURI2( Id2->getContentIdentifier() );
490 Reference< XContentProvider > xProv1
491 = queryContentProvider( aURI1, true );
492 Reference< XContentProvider > xProv2
493 = queryContentProvider( aURI2, true );
495 // When both identifiers belong to the same provider, let that provider
496 // compare them; otherwise, simply compare the URI strings (which must
497 // be different):
498 if ( xProv1.is() && ( xProv1 == xProv2 ) )
499 return xProv1->compareContentIds( Id1, Id2 );
500 else
501 return aURI1.compareTo( aURI2 );
505 // XContentIdentifierFactory methods.
508 // virtual
509 Reference< XContentIdentifier > SAL_CALL
510 UniversalContentBroker::createContentIdentifier(
511 const OUString& ContentId )
514 // Let the content provider for the scheme given with content
515 // identifier create the XContentIdentifier instance, if he supports
516 // the XContentIdentifierFactory interface. Otherwise create standard
517 // implementation object for XContentIdentifier.
520 Reference< XContentIdentifier > xIdentifier;
522 Reference< XContentProvider > xProv
523 = queryContentProvider( ContentId, true );
524 if ( xProv.is() )
526 Reference< XContentIdentifierFactory > xFac( xProv, UNO_QUERY );
527 if ( xFac.is() )
528 xIdentifier = xFac->createContentIdentifier( ContentId );
531 if ( !xIdentifier.is() )
532 xIdentifier = new ContentIdentifier( ContentId );
534 return xIdentifier;
538 // XCommandProcessor methods.
541 // virtual
542 sal_Int32 SAL_CALL UniversalContentBroker::createCommandIdentifier()
544 osl::MutexGuard aGuard( m_aMutex );
546 // Just increase counter on every call to generate an identifier.
547 return ++m_nCommandId;
551 // virtual
552 Any SAL_CALL UniversalContentBroker::execute(
553 const Command& aCommand,
554 sal_Int32,
555 const Reference< XCommandEnvironment >& Environment )
557 Any aRet;
560 // Note: Don't forget to adapt ucb_commands::CommandProcessorInfo
561 // ctor in ucbcmds.cxx when adding new commands!
564 if ( ( aCommand.Handle == GETCOMMANDINFO_HANDLE ) || aCommand.Name == GETCOMMANDINFO_NAME )
567 // getCommandInfo
570 aRet <<= getCommandInfo();
572 else if ( ( aCommand.Handle == GLOBALTRANSFER_HANDLE ) || aCommand.Name == GLOBALTRANSFER_NAME )
575 // globalTransfer
578 GlobalTransferCommandArgument2 aTransferArg;
579 if ( !( aCommand.Argument >>= aTransferArg ) )
581 GlobalTransferCommandArgument aArg;
582 if ( !( aCommand.Argument >>= aArg ) )
584 ucbhelper::cancelCommandExecution(
585 makeAny( IllegalArgumentException(
586 "Wrong argument type!",
587 static_cast< cppu::OWeakObject * >( this ),
588 -1 ) ),
589 Environment );
590 // Unreachable
593 // Copy infos into the new structure
594 aTransferArg.Operation = aArg.Operation;
595 aTransferArg.SourceURL = aArg.SourceURL;
596 aTransferArg.TargetURL = aArg.TargetURL;
597 aTransferArg.NewTitle = aArg.NewTitle;
598 aTransferArg.NameClash = aArg.NameClash;
601 globalTransfer( aTransferArg, Environment );
603 else if ( ( aCommand.Handle == CHECKIN_HANDLE ) || aCommand.Name == CHECKIN_NAME )
605 ucb::CheckinArgument aCheckinArg;
606 if ( !( aCommand.Argument >>= aCheckinArg ) )
608 ucbhelper::cancelCommandExecution(
609 makeAny( IllegalArgumentException(
610 "Wrong argument type!",
611 static_cast< cppu::OWeakObject * >( this ),
612 -1 ) ),
613 Environment );
614 // Unreachable
616 aRet = checkIn( aCheckinArg, Environment );
618 else
621 // Unknown command
624 ucbhelper::cancelCommandExecution(
625 makeAny( UnsupportedCommandException(
626 OUString(),
627 static_cast< cppu::OWeakObject * >( this ) ) ),
628 Environment );
629 // Unreachable
632 return aRet;
636 // XCommandProcessor2 methods.
639 // virtual
640 void SAL_CALL UniversalContentBroker::releaseCommandIdentifier(sal_Int32 /*aCommandId*/)
642 // @@@ Not implemented ( yet).
646 // virtual
647 void SAL_CALL UniversalContentBroker::abort( sal_Int32 )
649 // @@@ Not implemented ( yet).
653 // XChangesListener methods
656 // virtual
657 void SAL_CALL UniversalContentBroker::changesOccurred( const util::ChangesEvent& Event )
659 if ( Event.Changes.hasElements() )
661 uno::Reference< container::XHierarchicalNameAccess > xHierNameAccess;
662 Event.Base >>= xHierNameAccess;
664 OSL_ASSERT( xHierNameAccess.is() );
666 ContentProviderDataList aData;
667 for ( const util::ElementChange& rElem : Event.Changes )
669 OUString aKey;
670 rElem.Accessor >>= aKey;
672 ContentProviderData aInfo;
674 // Removal of UCPs from the configuration leads to changesOccurred
675 // notifications, too, but it is hard to tell for a given
676 // ElementChange whether it is an addition or a removal, so as a
677 // heuristic consider as removals those that cause a
678 // NoSuchElementException in createContentProviderData.
680 // For now, removal of UCPs from the configuration is simply ignored
681 // (and not reflected in the UCB's data structures):
682 if (createContentProviderData(aKey, xHierNameAccess, aInfo))
684 aData.push_back(aInfo);
688 prepareAndRegister(aData);
693 // XEventListener methods
696 // virtual
697 void SAL_CALL UniversalContentBroker::disposing(const lang::EventObject&)
699 if ( m_xNotifier.is() )
701 osl::Guard< osl::Mutex > aGuard( m_aMutex );
703 if ( m_xNotifier.is() )
704 m_xNotifier.clear();
709 // Non-interface methods
712 Reference< XContentProvider > UniversalContentBroker::queryContentProvider(
713 const OUString& Identifier,
714 bool bResolved )
716 osl::MutexGuard aGuard( m_aMutex );
718 ProviderList_Impl const * pList = m_aProviders.map( Identifier );
719 return pList ? bResolved ? pList->front().getResolvedProvider()
720 : pList->front().getProvider()
721 : Reference< XContentProvider >();
724 void UniversalContentBroker::configureUcb()
726 OUString aKey1;
727 OUString aKey2;
728 if (m_aArguments.getLength() < 2
729 || !(m_aArguments[0] >>= aKey1) || !(m_aArguments[1] >>= aKey2))
731 OSL_FAIL("UniversalContentBroker::configureUcb(): Bad arguments");
732 return;
735 ContentProviderDataList aData;
736 if (!getContentProviderData(aKey1, aKey2, aData))
738 SAL_WARN( "ucb", "No configuration");
739 return;
742 prepareAndRegister(aData);
745 void UniversalContentBroker::prepareAndRegister(
746 const ContentProviderDataList& rData)
748 for (const auto& rContentProviderData : rData)
750 OUString aProviderArguments;
751 if (fillPlaceholders(rContentProviderData.Arguments,
752 m_aArguments,
753 &aProviderArguments))
755 registerAtUcb(this,
756 m_xContext,
757 rContentProviderData.ServiceName,
758 aProviderArguments,
759 rContentProviderData.URLTemplate);
762 else
763 OSL_FAIL("UniversalContentBroker::prepareAndRegister(): Bad argument placeholders");
768 bool UniversalContentBroker::getContentProviderData(
769 const OUString & rKey1,
770 const OUString & rKey2,
771 ContentProviderDataList & rListToFill )
773 if ( !m_xContext.is() || rKey1.isEmpty() || rKey2.isEmpty() )
775 OSL_FAIL( "UniversalContentBroker::getContentProviderData - Invalid argument!" );
776 return false;
781 uno::Reference< lang::XMultiServiceFactory > xConfigProv =
782 configuration::theDefaultProvider::get( m_xContext );
784 OUStringBuffer aFullPath(128);
785 aFullPath.append(
786 "/org.openoffice.ucb.Configuration/ContentProviders"
787 "/['" );
788 makeAndAppendXMLName( aFullPath, rKey1 );
789 aFullPath.append( "']/SecondaryKeys/['" );
790 makeAndAppendXMLName( aFullPath, rKey2 );
791 aFullPath.append( "']/ProviderData" );
793 uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence(
795 {"nodepath", uno::Any(aFullPath.makeStringAndClear())}
796 }));
798 uno::Reference< uno::XInterface > xInterface(
799 xConfigProv->createInstanceWithArguments(
800 "com.sun.star.configuration.ConfigurationAccess",
801 aArguments ) );
803 if ( !m_xNotifier.is() )
805 m_xNotifier.set( xInterface, uno::UNO_QUERY_THROW );
807 m_xNotifier->addChangesListener( this );
810 uno::Reference< container::XNameAccess > xNameAccess(
811 xInterface, uno::UNO_QUERY_THROW );
813 const uno::Sequence< OUString > aElems = xNameAccess->getElementNames();
815 if ( aElems.hasElements() )
817 uno::Reference< container::XHierarchicalNameAccess >
818 xHierNameAccess( xInterface, uno::UNO_QUERY_THROW );
820 // Iterate over children.
821 for ( const auto& rElem : aElems )
827 ContentProviderData aInfo;
829 OUStringBuffer aElemBuffer;
830 aElemBuffer.append( "['" );
831 makeAndAppendXMLName( aElemBuffer, rElem );
832 aElemBuffer.append( "']" );
834 OSL_VERIFY(
835 createContentProviderData(
836 aElemBuffer.makeStringAndClear(), xHierNameAccess,
837 aInfo));
839 rListToFill.push_back( aInfo );
841 catch (const container::NoSuchElementException&)
843 // getByHierarchicalName
844 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
845 "caught NoSuchElementException!" );
850 catch (const uno::RuntimeException&)
852 SAL_WARN( "ucb", "caught RuntimeException!" );
853 return false;
855 catch (const uno::Exception&)
857 // createInstance, createInstanceWithArguments
859 SAL_WARN( "ucb", "caught Exception!" );
860 return false;
863 return true;
867 // ProviderListEntry_Impl implementation.
870 Reference< XContentProvider > const & ProviderListEntry_Impl::resolveProvider() const
872 if ( !m_xResolvedProvider.is() )
874 Reference< XContentProviderSupplier > xSupplier(
875 m_xProvider, UNO_QUERY );
876 if ( xSupplier.is() )
877 m_xResolvedProvider = xSupplier->getContentProvider();
879 if ( !m_xResolvedProvider.is() )
880 m_xResolvedProvider = m_xProvider;
883 return m_xResolvedProvider;
886 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */