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 .
21 #include <rtl/ref.hxx>
22 #include <comphelper/diagnose_ex.hxx>
23 #include <sal/log.hxx>
24 #include <tools/datetime.hxx>
26 #include <comphelper/documentinfo.hxx>
27 #include <comphelper/namedvaluecollection.hxx>
28 #include <comphelper/sequence.hxx>
30 #include <com/sun/star/awt/XTopWindow.hpp>
31 #include <com/sun/star/beans/XPropertySet.hpp>
32 #include <com/sun/star/document/XDocumentEventBroadcaster.hpp>
33 #include <com/sun/star/document/XStorageBasedDocument.hpp>
34 #include <com/sun/star/frame/UnknownModuleException.hpp>
35 #include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
36 #include <com/sun/star/frame/ModuleManager.hpp>
37 #include <com/sun/star/lang/DisposedException.hpp>
38 #include <com/sun/star/lang/NotInitializedException.hpp>
39 #include <com/sun/star/util/XCloseBroadcaster.hpp>
41 #include "tdoc_docmgr.hxx"
42 #include "tdoc_provider.hxx"
44 using namespace com::sun::star
;
45 using namespace tdoc_ucp
;
47 // OfficeDocumentsCloseListener Implementation.
50 // util::XCloseListener
54 void SAL_CALL
OfficeDocumentsManager::OfficeDocumentsCloseListener::queryClosing(
55 const lang::EventObject
& /*Source*/, sal_Bool
/*GetsOwnership*/ )
60 void SAL_CALL
OfficeDocumentsManager::OfficeDocumentsCloseListener::notifyClosing(
61 const lang::EventObject
& Source
)
63 if (!m_pManager
) return; // disposed?
65 document::DocumentEvent aDocEvent
;
66 aDocEvent
.Source
= Source
.Source
;
67 aDocEvent
.EventName
= "OfficeDocumentsListener::notifyClosing";
68 m_pManager
->documentEventOccured( aDocEvent
);
72 // lang::XDocumentEventListener (base of util::XCloseListener)
76 void SAL_CALL
OfficeDocumentsManager::OfficeDocumentsCloseListener::disposing(
77 const lang::EventObject
& /*Source*/ )
82 // OfficeDocumentsManager Implementation.
85 OfficeDocumentsManager::OfficeDocumentsManager(
86 const uno::Reference
< uno::XComponentContext
> & rxContext
,
87 ContentProvider
* pDocEventListener
)
88 : m_xContext( rxContext
),
89 m_xDocEvtNotifier( frame::theGlobalEventBroadcaster::get( rxContext
) ),
90 m_pDocEventListener( pDocEventListener
),
91 m_xDocCloseListener( new OfficeDocumentsCloseListener( this ) )
93 // Order is important (multithreaded environment)
94 uno::Reference
< document::XDocumentEventBroadcaster
>(
95 m_xDocEvtNotifier
, uno::UNO_QUERY_THROW
)->addDocumentEventListener( this );
101 OfficeDocumentsManager::~OfficeDocumentsManager()
103 //OSL_ENSURE( m_aDocs.empty(), "document list not empty!" );
104 // no need to assert this: Normal shutdown of LibreOffice could already trigger it, since the order
105 // in which objects are actually released/destroyed upon shutdown is not defined. And when we
106 // arrive *here*, LibreOffice *is* shutting down currently, since we're held by the TDOC provider,
107 // which is disposed upon shutdown.
108 m_xDocCloseListener
->Dispose();
112 void OfficeDocumentsManager::destroy()
114 uno::Reference
< document::XDocumentEventBroadcaster
>(
115 m_xDocEvtNotifier
, uno::UNO_QUERY_THROW
)->removeDocumentEventListener( this );
120 getDocumentId( const uno::Reference
< uno::XInterface
> & xDoc
)
124 // Try to get the UID directly from the document.
125 uno::Reference
< beans::XPropertySet
> xPropSet( xDoc
, uno::UNO_QUERY
);
130 uno::Any aValue
= xPropSet
->getPropertyValue("RuntimeUID");
133 catch ( beans::UnknownPropertyException
const & )
135 // Not actually an error. Property is optional.
137 catch ( lang::WrappedTargetException
const & )
139 TOOLS_WARN_EXCEPTION("ucb.ucp", "Caught WrappedTargetException!");
145 // fallback: generate UID from document's this pointer.
146 // normalize the interface pointer first. Else, calls with different
147 // interfaces to the same object (say, XFoo and XBar) will produce
149 uno::Reference
< uno::XInterface
> xNormalizedIFace( xDoc
, uno::UNO_QUERY
);
150 sal_Int64 nId
= reinterpret_cast< sal_Int64
>( xNormalizedIFace
.get() );
151 aId
= OUString::number( nId
);
154 OSL_ENSURE( !aId
.isEmpty(), "getDocumentId - Empty id!" );
159 // document::XDocumentEventListener
163 void SAL_CALL
OfficeDocumentsManager::documentEventOccured(
164 const document::DocumentEvent
& Event
)
167 Events documentation: OOo Developer's Guide / Writing UNO Components /
168 Integrating Components into OpenOffice.org / Jobs
171 if ( Event
.EventName
== "OnLoadFinished" // document loaded
172 || Event
.EventName
== "OnCreate" ) // document created
174 if ( isOfficeDocument( Event
.Source
) )
176 uno::Reference
<frame::XModel
> const xModel(
177 Event
.Source
, uno::UNO_QUERY
);
178 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
183 std::scoped_lock
aGuard( m_aMtx
);
185 found
= std::any_of(m_aDocs
.begin(), m_aDocs
.end(),
186 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
191 // no mutex to avoid deadlocks!
192 // need no lock to access const members, ContentProvider is safe
196 uno::Reference
< document::XStorageBasedDocument
>
197 xDoc( Event
.Source
, uno::UNO_QUERY
);
198 OSL_ENSURE( xDoc
.is(), "Got no document::XStorageBasedDocument!" );
200 uno::Reference
< embed::XStorage
> xStorage
201 = xDoc
->getDocumentStorage();
202 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
204 rtl:: OUString aDocId
= getDocumentId( Event
.Source
);
205 rtl:: OUString aTitle
= comphelper::DocumentInfo::getDocumentTitle(
206 uno::Reference
< frame::XModel
>( Event
.Source
, uno::UNO_QUERY
) );
209 std::scoped_lock
g(m_aMtx
);
210 m_aDocs
[ aDocId
] = StorageInfo( aTitle
, xStorage
, xModel
);
213 uno::Reference
< util::XCloseBroadcaster
> xCloseBroadcaster(
214 Event
.Source
, uno::UNO_QUERY
);
215 OSL_ENSURE( xCloseBroadcaster
.is(),
216 "OnLoadFinished/OnCreate event: got no close broadcaster!" );
218 if ( xCloseBroadcaster
.is() )
219 xCloseBroadcaster
->addCloseListener(m_xDocCloseListener
);
221 // Propagate document closure.
222 OSL_ENSURE( m_pDocEventListener
,
223 "OnLoadFinished/OnCreate event: no owner for insert event propagation!" );
225 if ( m_pDocEventListener
)
226 m_pDocEventListener
->notifyDocumentOpened( aDocId
);
230 else if ( Event
.EventName
== "OfficeDocumentsListener::notifyClosing" )
232 if ( isOfficeDocument( Event
.Source
) )
234 // Document has been closed (unloaded)
236 // Official event "OnUnload" does not work here. Event
237 // gets fired too early. Other OnUnload listeners called after this
238 // listener may still need TDOC access to the document. Remove the
239 // document from TDOC docs list on XCloseListener::notifyClosing.
240 // See OfficeDocumentsManager::OfficeDocumentsListener::notifyClosing.
242 uno::Reference
< frame::XModel
>
243 xModel( Event
.Source
, uno::UNO_QUERY
);
244 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
250 std::scoped_lock
aGuard( m_aMtx
);
252 auto it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
253 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
254 if ( it
!= m_aDocs
.end() )
256 aDocId
= (*it
).first
;
263 "OnUnload event notified for unknown document!" );
267 // Propagate document closure.
268 OSL_ENSURE( m_pDocEventListener
,
269 "OnUnload event: no owner for close event propagation!" );
270 if (m_pDocEventListener
)
272 m_pDocEventListener
->notifyDocumentClosed(aDocId
);
274 uno::Reference
< util::XCloseBroadcaster
> xCloseBroadcaster(
275 Event
.Source
, uno::UNO_QUERY
);
276 OSL_ENSURE( xCloseBroadcaster
.is(),
277 "OnUnload event: got no XCloseBroadcaster from XModel" );
278 if ( xCloseBroadcaster
.is() )
279 xCloseBroadcaster
->removeCloseListener(m_xDocCloseListener
);
283 else if ( Event
.EventName
== "OnSaveDone" )
285 if ( isOfficeDocument( Event
.Source
) )
287 // Storage gets exchanged while saving.
288 uno::Reference
<document::XStorageBasedDocument
> const xDoc(
289 Event
.Source
, uno::UNO_QUERY
);
290 OSL_ENSURE( xDoc
.is(),
291 "Got no document::XStorageBasedDocument!" );
292 uno::Reference
<embed::XStorage
> const xStorage(
293 xDoc
->getDocumentStorage());
294 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
296 uno::Reference
< frame::XModel
>
297 xModel( Event
.Source
, uno::UNO_QUERY
);
298 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
300 std::scoped_lock
aGuard( m_aMtx
);
302 DocumentList::iterator it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
303 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
305 OSL_ENSURE( it
!= m_aDocs
.end(),
306 "OnSaveDone event notified for unknown document!" );
307 if ( it
!= m_aDocs
.end() )
309 (*it
).second
.xStorage
= xStorage
;
313 else if ( Event
.EventName
== "OnSaveAsDone" )
315 if ( isOfficeDocument( Event
.Source
) )
317 // Storage gets exchanged while saving.
318 uno::Reference
<document::XStorageBasedDocument
> const xDoc(
319 Event
.Source
, uno::UNO_QUERY
);
320 OSL_ENSURE( xDoc
.is(),
321 "Got no document::XStorageBasedDocument!" );
322 uno::Reference
<embed::XStorage
> const xStorage(
323 xDoc
->getDocumentStorage());
324 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
326 uno::Reference
< frame::XModel
>
327 xModel( Event
.Source
, uno::UNO_QUERY
);
328 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
330 OUString
const title(comphelper::DocumentInfo::getDocumentTitle(xModel
));
332 std::scoped_lock
aGuard( m_aMtx
);
334 DocumentList::iterator it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
335 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
337 OSL_ENSURE( it
!= m_aDocs
.end(),
338 "OnSaveAsDone event notified for unknown document!" );
339 if ( it
!= m_aDocs
.end() )
341 (*it
).second
.xStorage
= xStorage
;
344 (*it
).second
.aTitle
= title
;
348 else if ( Event
.EventName
== "OnTitleChanged"
349 || Event
.EventName
== "OnStorageChanged" )
351 if ( isOfficeDocument( Event
.Source
) )
353 // Storage gets exchanged while saving.
354 uno::Reference
<document::XStorageBasedDocument
> const xDoc(
355 Event
.Source
, uno::UNO_QUERY
);
356 OSL_ENSURE( xDoc
.is(),
357 "Got no document::XStorageBasedDocument!" );
358 uno::Reference
<embed::XStorage
> const xStorage(
359 xDoc
->getDocumentStorage());
360 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
362 uno::Reference
< frame::XModel
>
363 xModel( Event
.Source
, uno::UNO_QUERY
);
364 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
366 OUString
const aTitle(comphelper::DocumentInfo::getDocumentTitle(xModel
));
368 OUString
const aDocId(getDocumentId(Event
.Source
));
370 std::scoped_lock
aGuard( m_aMtx
);
372 DocumentList::iterator it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
373 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
374 if ( it
!= m_aDocs
.end() )
377 (*it
).second
.aTitle
= aTitle
;
379 m_aDocs
[ aDocId
] = StorageInfo( aTitle
, xStorage
, xModel
);
382 // OSL_ENSURE( it != m_aDocs.end(),
383 // "TitleChanged event notified for unknown document!" );
384 // TODO: re-enable this assertion. It has been disabled for now, since it breaks the assertion-free smoketest,
385 // and the fix is more difficult than what can be done now.
386 // The problem is that at the moment, when you close a SFX-based document via API, it will first
387 // fire the notifyClosing event, which will make the OfficeDocumentsManager remove the doc from its list.
388 // Then, it will notify an OnTitleChanged, then an OnUnload. Documents closed via call the notifyClosing
389 // *after* OnUnload and all other On* events.
390 // In agreement with MBA, the implementation for SfxBaseModel::Close should be changed to also send notifyClosing
391 // as last event. When this happens, the assertion here must be enabled, again.
396 // lang::XDocumentEventListener (base of document::XDocumentEventListener)
399 void SAL_CALL
OfficeDocumentsManager::disposing(
400 const lang::EventObject
& /*Source*/ )
406 void OfficeDocumentsManager::buildDocumentsList()
408 uno::Reference
< container::XEnumeration
> xEnum
409 = m_xDocEvtNotifier
->createEnumeration();
411 while ( xEnum
->hasMoreElements() )
413 uno::Any aValue
= xEnum
->nextElement();
414 // container::NoSuchElementException
415 // lang::WrappedTargetException
419 uno::Reference
< frame::XModel
> xModel
;
424 if ( isOfficeDocument( xModel
) )
429 std::scoped_lock
aGuard( m_aMtx
);
431 found
= std::any_of(m_aDocs
.begin(), m_aDocs
.end(),
432 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
438 OUString aDocId
= getDocumentId( xModel
);
439 OUString aTitle
= comphelper::DocumentInfo::getDocumentTitle( xModel
);
441 uno::Reference
< document::XStorageBasedDocument
>
442 xDoc( xModel
, uno::UNO_QUERY
);
443 OSL_ENSURE( xDoc
.is(),
444 "Got no document::XStorageBasedDocument!" );
446 uno::Reference
< embed::XStorage
> xStorage
447 = xDoc
->getDocumentStorage();
448 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
451 std::scoped_lock
aGuard( m_aMtx
);
453 = StorageInfo( aTitle
, xStorage
, xModel
);
456 uno::Reference
< util::XCloseBroadcaster
> xCloseBroadcaster(
457 xModel
, uno::UNO_QUERY
);
458 OSL_ENSURE( xCloseBroadcaster
.is(),
459 "buildDocumentsList: got no close broadcaster!" );
461 if ( xCloseBroadcaster
.is() )
462 xCloseBroadcaster
->addCloseListener(m_xDocCloseListener
);
467 catch ( lang::DisposedException
const & )
469 // Note: Due to race conditions the XEnumeration can
470 // contain docs that have already been closed
472 catch ( lang::NotInitializedException
const & )
474 // Note: Due to race conditions the XEnumeration can
475 // contain docs that are still uninitialized
480 uno::Reference
< embed::XStorage
>
481 OfficeDocumentsManager::queryStorage( const OUString
& rDocId
)
483 std::scoped_lock
aGuard( m_aMtx
);
485 DocumentList::const_iterator it
= m_aDocs
.find( rDocId
);
486 if ( it
== m_aDocs
.end() )
487 return uno::Reference
< embed::XStorage
>();
489 return (*it
).second
.xStorage
;
493 OUString
OfficeDocumentsManager::queryDocumentId(
494 const uno::Reference
< frame::XModel
> & xModel
)
496 return getDocumentId( xModel
);
500 uno::Reference
< frame::XModel
>
501 OfficeDocumentsManager::queryDocumentModel( const OUString
& rDocId
)
503 std::scoped_lock
aGuard( m_aMtx
);
505 DocumentList::const_iterator it
= m_aDocs
.find( rDocId
);
506 if ( it
== m_aDocs
.end() )
507 return uno::Reference
< frame::XModel
>();
509 return (*it
).second
.xModel
;
513 uno::Sequence
< OUString
> OfficeDocumentsManager::queryDocuments()
515 std::scoped_lock
aGuard( m_aMtx
);
517 return comphelper::mapKeysToSequence( m_aDocs
);
522 OfficeDocumentsManager::queryStorageTitle( const OUString
& rDocId
)
524 std::scoped_lock
aGuard( m_aMtx
);
526 DocumentList::const_iterator it
= m_aDocs
.find( rDocId
);
527 if ( it
== m_aDocs
.end() )
530 return (*it
).second
.aTitle
;
534 css::util::DateTime
OfficeDocumentsManager::queryStreamDateModified(OUString
const & uri
) {
535 std::scoped_lock
g(m_aMtx
);
536 auto const i1
= m_aDocs
.find(Uri(uri
).getDocumentId());
537 if (i1
!= m_aDocs
.end()) {
538 auto const i2
= i1
->second
.streamDateModified
.find(uri
);
539 if (i2
!= i1
->second
.streamDateModified
.end()) {
547 void OfficeDocumentsManager::updateStreamDateModified(OUString
const & uri
) {
548 std::scoped_lock
g(m_aMtx
);
549 auto const i
= m_aDocs
.find(Uri(uri
).getDocumentId());
550 if (i
== m_aDocs
.end()) {
551 SAL_WARN("ucb.ucp.tdoc", "No document info for <" << uri
<< ">");
554 i
->second
.streamDateModified
[uri
] = DateTime(DateTime::SYSTEM
).GetUNODateTime();
558 bool OfficeDocumentsManager::isDocumentPreview(
559 const uno::Reference
< frame::XModel
> & xModel
)
564 bool bIsPreview
= ::comphelper::NamedValueCollection::getOrDefault( xModel
->getArgs(), u
"Preview", false );
569 bool OfficeDocumentsManager::isHelpDocument(
570 const uno::Reference
< frame::XModel
> & xModel
)
575 OUString
sURL( xModel
->getURL() );
576 return sURL
.match( "vnd.sun.star.help://" );
580 bool OfficeDocumentsManager::isWithoutOrInTopLevelFrame(
581 const uno::Reference
< frame::XModel
> & xModel
)
586 uno::Reference
< frame::XController
> xController
587 = xModel
->getCurrentController();
588 if ( xController
.is() )
590 uno::Reference
< frame::XFrame
> xFrame
591 = xController
->getFrame();
594 // don't use XFrame::isTop here. This nowadays excludes
595 // "sub documents" such as forms embedded in database documents
596 uno::Reference
< awt::XTopWindow
> xFrameContainer(
597 xFrame
->getContainerWindow(), uno::UNO_QUERY
);
598 if ( !xFrameContainer
.is() )
607 bool OfficeDocumentsManager::isBasicIDE(
608 const uno::Reference
< frame::XModel
> & xModel
)
610 if ( !m_xModuleMgr
.is() )
612 std::scoped_lock
aGuard( m_aMtx
);
613 if ( !m_xModuleMgr
.is() )
617 m_xModuleMgr
= frame::ModuleManager::create( m_xContext
);
619 catch ( uno::Exception
const & )
624 OSL_ENSURE( m_xModuleMgr
.is(),
625 "Could not instantiate ModuleManager service!" );
629 if ( m_xModuleMgr
.is() )
634 aModule
= m_xModuleMgr
->identify( xModel
);
636 catch ( lang::IllegalArgumentException
const & )
638 TOOLS_WARN_EXCEPTION("ucb.ucp", "");
640 catch ( frame::UnknownModuleException
const & )
642 TOOLS_WARN_EXCEPTION("ucb.ucp", "");
645 if ( !aModule
.isEmpty() )
647 // Filter unwanted items, that are no real documents.
648 if ( aModule
== "com.sun.star.script.BasicIDE" )
659 bool OfficeDocumentsManager::isOfficeDocument(
660 const uno::Reference
< uno::XInterface
> & xDoc
)
662 uno::Reference
< frame::XModel
> xModel( xDoc
, uno::UNO_QUERY
);
663 uno::Reference
< document::XStorageBasedDocument
>
664 xStorageBasedDoc( xModel
, uno::UNO_QUERY
);
665 if ( !xStorageBasedDoc
.is() )
668 if ( !isWithoutOrInTopLevelFrame( xModel
) )
671 if ( isDocumentPreview( xModel
) )
674 if ( isHelpDocument( xModel
) )
677 if ( isBasicIDE( xModel
) )
683 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */