Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  autorecovery.cxx   Sprache: C

 
/* -*- 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 <config_features.h>

#include <loadenv/loadenv.hxx>

#include <strings.hrc>
#include <classes/fwkresid.hxx>
#include <properties.h>
#include <targets.h>

#include <helper/mischelper.hxx>

#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/configuration/theDefaultProvider.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
#include <com/sun/star/frame/XLoadable.hpp>
#include <com/sun/star/frame/XModel3.hpp>
#include <com/sun/star/frame/ModuleManager.hpp>
#include <com/sun/star/frame/XTitle.hpp>
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/util/XModifiable.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <com/sun/star/util/XURLTransformer.hpp>
#include <com/sun/star/frame/XDesktop.hpp>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/util/XChangesNotifier.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/document/XDocumentRecovery2.hpp>
#include <com/sun/star/document/XExtendedFilterDetection.hpp>
#include <com/sun/star/util/XCloseable.hpp>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/task/XStatusIndicatorFactory.hpp>
#include <com/sun/star/task/ErrorCodeIOException.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/XTypeProvider.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/frame/XDispatch.hpp>
#include <com/sun/star/document/XDocumentEventListener.hpp>
#include <com/sun/star/document/XDocumentEventBroadcaster.hpp>
#include <com/sun/star/util/XChangesListener.hpp>
#include <com/sun/star/task/XStatusIndicator.hpp>
#include <com/sun/star/util/XModifyListener.hpp>

#include <comphelper/configuration.hxx>
#include <cppuhelper/basemutex.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/propshlp.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/typed_flags_set.hxx>
#include <o3tl/string_view.hxx>
#include <unotools/fcm.hxx>
#include <unotools/mediadescriptor.hxx>
#include <comphelper/multiinterfacecontainer3.hxx>
#include <comphelper/namedvaluecollection.hxx>
#include <comphelper/sequence.hxx>
#include <utility>
#include <vcl/evntpost.hxx>
#include <vcl/svapp.hxx>
#include <vcl/timer.hxx>
#include <unotools/pathoptions.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <unotools/tempfile.hxx>
#include <unotools/ucbhelper.hxx>
#include <ucbhelper/content.hxx>
#include <svtools/sfxecode.hxx>

#include <vcl/weld.hxx>
#include <osl/file.hxx>
#include <sal/log.hxx>
#include <unotools/bootstrap.hxx>
#include <unotools/configmgr.hxx>
#include <svl/documentlockfile.hxx>
#include <tools/urlobj.hxx>
#include <officecfg/Office/Common.hxx>
#include <officecfg/Office/Recovery.hxx>
#include <officecfg/Setup.hxx>

using namespace css::uno;
using namespace css::document;
using namespace css::frame;
using namespace css::lang;
using namespace framework;

/** After the fact documentation - hopefully it is correct.
 *
 * NOTE TO DEVELOPERS: instsetoo_native/ooenv sets the environment variable OOO_DISABLE_RECOVERY
 * which means that your non-gdb execution of the compiled code will not display the recovery dialog
 * and if officecfg::Office::Recovery::RecoveryList has some autosave files to recover,
 * then autorecovery itself will not be started, and none of the code in this file will be executed.
 * THEREFORE you probably want to COMMENT OUT that environment variable when working in this file.
 *
 * AutoRecovery handles 3 types of recovery, as well as periodic document saving
 *   1a) timed, automatic saving of the documents (aka UserAutoSave)
 *   or more commonly:
 *   1b) timed, ODF, temporary recovery files created in the backup folder (default setting)
 *      -temporary: deleted when the document itself is saved
 *      -handles the situation where LO immediately exits (power outage, program crash, pkill -9 soffice)
 *          -not restored immediately (user needs to restart LibreOffice)
 *          -no guarantee of availability of recovery file (since deleted on document save)
 *           or original document (perhaps /tmp, removeable disk, disconnected server).
 *          -TODO tdf#57414: if SessionSave not desired, don't recover unmodified files.
 *   2) emergency save-and-restart immediately triggers creation of temporary, ODF, recovery files
 *      -handles the situation where LO is partially functioning (pkill -6 soffice)
 *          -restore attempted immediately, so try to restore entire session - all open files
 *          -always create recovery file for every open document in emergency situation
 *      -works without requiring AutoRecovery to be enabled
 *   3) session save on exit requested by OS or user
 *      -triggered by OS's shutdown/logout
 *          -appears to be purely theoretical: no known working OS at the moment.
 *          -also no known way for user to initiate within LO (tdf#146769)
 *      -same as emergency save, except maybe more time critical - OS kill timeout
 *          -not restored until much later - the user has stopped doing computer work
 *          -always create recovery file for every open document: needed for /tmp, disconnected docs
 *
 * All of these use the same recovery dialog - re-opening all the files listed in the RecoveryList
 * of the user's officecfg settings.
 *
 * Since these 3 have very different expectations, and yet share the same code, keep all of them
 * in mind when making code changes.
 *
 * Note: often, entries in m_lDocCache are copied. So realize that changes to aInfo/rInfo might not
 * apply to async events like mark-document-as-saved-and-delete-TMP-URLs or set-modified-status,
 * or ignoreClosing, or ListenForModify. For example, DocState::Modified should be considered only
 * a good hint, and not as definitively accurate.
 */


namespace {

/** @short  hold all needed information for an asynchronous dispatch alive.

    @descr  Because some operations are forced to be executed asynchronously
            (e.g. requested by our CrashSave/Recovery dialog) ... we must make sure
            that this information won't be set as "normal" members of our AutoRecovery
            instance. Otherwise they can disturb our normal AutoSave-timer handling.
            e.g. it can be unclear then, which progress has to be used for storing documents...
 */

struct DispatchParams
{
public:
     DispatchParams();
     DispatchParams(const ::comphelper::SequenceAsHashMap&             lArgs ,
                    const css::uno::Reference< css::uno::XInterface >& xOwner);

     void forget();

public:

    /** @short  can be set from outside and is provided to
                our internal started operations.

        @descr  Normally we use the normal status indicator
                of the document windows to show a progress.
                But in case we are used by any special UI,
                it can provide its own status indicator object
                to us - so we use it instead of the normal one.
     */

    css::uno::Reference< css::task::XStatusIndicator > m_xProgress;

    /** TODO document me */
    OUString m_sSavePath;

    /** @short  define the current cache entry, which should be used for current
                backup or cleanUp operation ... which is may be done asynchronous */

    sal_Int32 m_nWorkingEntryID;

    /** @short  used for async operations, to prevent us from dying.

        @descr  If our dispatch() method was forced to start the
                internal operation asynchronous... we send an event
                to start and return immediately. But we must be sure that
                our instance live if the event callback reach us.
                So we hold a uno reference to ourself.
     */

    css::uno::Reference< css::uno::XInterface > m_xHoldRefForAsyncOpAlive;
};

/** These values are used as flags and represent the current state of a document.
    Every state of the life time of a document has to be recognized here.

    @attention  Do not change (means reorganize) already used numbers.
                There exists some code inside SVX, which uses the same numbers,
                to analyze such document states.
                Not the best design ... but may be it will be changed later .-)
*/

enum class DocState: sal_Int32
{
    /* TEMP STATES */

    /// default state, if a document was new created or loaded
    Unknown = 0,
    /// modified against the original file
    Modified = 1,
    /// an active document can be postponed to be saved later.
    Postponed = 2,
    /// was already handled during one AutoSave/Recovery session.
    Handled = 4,
    /** an action was started (saving/loading) ... Can be interesting later if the process may be was interrupted by an exception. */
    TrySave = 8,
    TryLoadBackup = 16,
    TryLoadOriginal = 32,

    /* FINAL STATES */

    /// the Auto/Emergency saved document is not usable any longer
    Damaged = 64,
    /// the Auto/Emergency saved document is not really up-to-date (some changes can be missing)
    Incomplete = 128,
    /// the Auto/Emergency saved document was processed successfully
    Succeeded = 512
};

}

template<> struct o3tl::typed_flags<DocState>: o3tl::is_typed_flags<DocState, 0x2FF> {};

namespace {

// TODO document me ... flag field
// Emergency_Save and Recovery overwrites Auto_Save!
enum class Job
{
    NoJob                     =   0,
    AutoSave                  =   1,
    EmergencySave             =   2,
    Recovery                  =   4,
    EntryBackup               =   8,
    EntryCleanup              =  16,
    PrepareEmergencySave      =  32,
    SessionSave               =  64,
    SessionRestore            = 128,
    DisableAutorecovery       = 256,
    SetAutosaveState          = 512,
    SessionQuietQuit          = 1024,
    UserAutoSave              = 2048
};

}

template<> struct o3tl::typed_flags<Job>: o3tl::is_typed_flags<Job, 0xFFF> {};

namespace {

/**
    implements the functionality of AutoSave and AutoRecovery
    of documents - including features of an EmergencySave in
    case a GPF occurs.
 */

typedef ::cppu::WeakComponentImplHelper<
            css::lang::XServiceInfo,
            css::frame::XDispatch,
            css::document::XDocumentEventListener, // => css.lang.XEventListener
            css::util::XChangesListener,      // => css.lang.XEventListener
            css::util::XModifyListener >      // => css.lang.XEventListener
         AutoRecovery_BASE;

class AutoRecovery  : private cppu::BaseMutex
                    , public  AutoRecovery_BASE
                    , public  ::cppu::OPropertySetHelper            // => XPropertySet, XFastPropertySet, XMultiPropertySet
{
public:
    /** @short  indicates the results of a FAILURE_SAFE operation

        @descr  We must know, which reason was the real one in case
                we couldn't copy a "failure document" to a user specified path.
                We must know, if we can forget our cache entry or not.
     */

    enum EFailureSafeResult
    {
        E_COPIED,
        E_ORIGINAL_FILE_MISSING,
        E_WRONG_TARGET_PATH
    };

    // TODO document me
    enum ETimerType
    {
        /** the timer should not be used next time */
        E_DONT_START_TIMER,
        /** timer (was/must be) started with normal AutoSaveTimeIntervall */
        E_NORMAL_AUTOSAVE_INTERVALL,
        /** timer must be started with special short time interval,
            to poll for an user idle period */

        E_POLL_FOR_USER_IDLE,
        /** timer must be started with a very(!) short time interval,
            to poll for the end of an user action, which does not allow saving documents in general */

        E_POLL_TILL_AUTOSAVE_IS_ALLOWED,
        /** don't start the timer - but calls the same action then before immediately again! */
        E_CALL_ME_BACK
    };

    /** @short  combine different information about one office document. */
    struct TDocumentInfo
    {
        public:

            TDocumentInfo()
                : DocumentState   (DocState::Unknown)
                , UsedForSaving   (false)
                , ListenForModify (false)
                , IgnoreClosing   (false)
                , ID              (-1       )
            {}

            /** @short  points to the document. */
            css::uno::Reference< css::frame::XModel > Document;

            /** @short  knows, if the document is really modified since the last autosave,
                        or  was postponed, because it was an active one etcpp...

                @descr  Because we have no CHANGE TRACKING mechanism, based on office document,
                        we implements it by ourself. We listen for MODIFIED events
                        of each document and update this state flag here.

                        Further we postpone saving of active documents, e.g. if the user
                        works currently on it. We wait for an idle period then...
             */

            DocState DocumentState;

            /** Because our applications not ready for concurrent save requests at the same time,
                we have suppress our own AutoSave for the moment, a document will be already saved
                by others.
             */

            bool UsedForSaving;

            /** For every user action, which modifies a document (e.g. key input) we get
                a notification as XModifyListener. That seems to be a "performance issue" .-)
                So we decided to listen for such modify events only for the time in which the document
                was stored as temp. file and was not modified again by the user.
            */

            bool ListenForModify;

            /** For SessionSave we must close all open documents by ourself.
                But because we are listen for documents events, we get some ...
                and deregister these documents from our configuration.
                That's why we mark these documents as "Closed by ourself" so we can
                ignore these "OnUnload" or disposing() events .-)
            */

            bool IgnoreClosing;

            /** TODO: document me */
            OUString OrgURL;
            OUString FactoryURL;
            OUString TemplateURL;

            OUString OldTempURL; // previous recovery file (filename_0.odf) which will be removed
            OUString NewTempURL; // new recovery file (filename_1.odf) that is being created

            OUString AppModule;      // e.g. com.sun.star.text.TextDocument - used to identify app module
            OUString FactoryService; // the service to create a document of the module
            OUString RealFilter;     // real filter, which was used at loading time
            OUString DefaultFilter;  // supports saving of the default format without losing data
            OUString Extension;      // file extension of the default filter
            OUString Title;          // can be used as "DisplayName" on every recovery UI!
            css::uno::Sequence< OUString >
                            ViewNames;      // names of the view which were active at emergency-save time

            sal_Int32 ID;
    };

    /** @short  used to know every currently open document. */
    typedef ::std::vector< TDocumentInfo > TDocumentList;

// member

private:

    /** @short  the global uno service manager.
        @descr  Must be used to create own needed services.
     */

    css::uno::Reference< css::uno::XComponentContext > m_xContext;

    /** @short  points to the underlying recovery configuration.
        @descr  This instance does not cache - it calls directly the
                configuration API!
      */

    css::uno::Reference< css::container::XNameAccess > m_xRecoveryCFG;

    /** @short  proxy weak binding to forward Events to ourself without
                an ownership cycle
      */

    css::uno::Reference< css::util::XChangesListener > m_xRecoveryCFGListener;

    /** @short  points to the used configuration package or.openoffice.Setup
        @descr  This instance does not cache - it calls directly the
                configuration API!
      */

    css::uno::Reference< css::container::XNameAccess > m_xModuleCFG;

    /** @short  holds the global event broadcaster alive,
                where we listen for new created documents.
      */

    css::uno::Reference< css::frame::XGlobalEventBroadcaster > m_xNewDocBroadcaster;

    /** @short  proxy weak binding to forward Events to ourself without
                an ownership cycle
      */

    css::uno::Reference< css::document::XDocumentEventListener > m_xNewDocBroadcasterListener;

    /** @short  because we stop/restart listening sometimes, it's a good idea to know
                if we already registered as listener .-)
    */

    bool m_bListenForDocEvents;
    bool m_bListenForConfigChanges;

    /** @short  for an asynchronous operation we must know, if there is
                at least one running job (may be asynchronous!).
     */

    Job m_eJob;

    /** @short  the timer, which is used to be informed about the next
                saving time ...
        @remark must lock SolarMutex to use
     */

    Timer m_aTimer;

    /** @short  make our dispatch asynchronous ... if required to do so! */
    std::unique_ptr<vcl::EventPoster> m_xAsyncDispatcher;

    /** @see    DispatchParams
     */

    DispatchParams m_aDispatchParams;

    /** @short  indicates, which time period is currently used by the
                internal timer.
     */

    ETimerType m_eTimerType;

    /** @short  this cache is used to hold all information about
                recovery/emergency save documents alive.
     */

    TDocumentList m_lDocCache;

    // TODO document me
    sal_Int32 m_nIdPool;

    /** @short  contains all status listener registered at this instance.
     */

    comphelper::OMultiTypeInterfaceContainerHelperVar3<css::frame::XStatusListener, OUString> m_lListener;

    /** @descr  This member is used to prevent us against re-entrance problems.
                A mutex can't help to prevent us from concurrent using of members
                inside the same thread. But e.g. our internally used stl structures
                are not threadsafe ... and furthermore they can't be used at the same time
                for iteration and add/remove requests!
                So we have to detect such states and ... show a warning.
                May be there will be a better solution next time ... (copying the cache temp.
                bevor using).

                And further it's not possible to use a simple boolean value here.
                Because if more than one operation iterates over the same stl container ...
                (only to modify it's elements but don't add new or removing existing ones!)
                it should be possible doing so. But we must guarantee that the last operation reset
                this lock ... not the first one ! So we use a "ref count" mechanism for that."
     */

    sal_Int32 m_nDocCacheLock;

    /** @descr  These members are used to check the minimum disc space, which must exists
                to start the corresponding operation.
     */

    sal_Int32 m_nMinSpaceDocSave;
    sal_Int32 m_nMinSpaceConfigSave;

// interface

public:

    explicit AutoRecovery(css::uno::Reference< css::uno::XComponentContext >  xContext);
    virtual ~AutoRecovery(                                                                   ) override;

    virtual OUString SAL_CALL getImplementationName() override
    {
        return u"com.sun.star.comp.framework.AutoRecovery"_ustr;
    }

    virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
    {
        return cppu::supportsService(this, ServiceName);
    }

    virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
    {
        return {u"com.sun.star.frame.AutoRecovery"_ustr};
    }

    // XInterface
    virtual void SAL_CALL acquire() noexcept override
        { OWeakObject::acquire(); }
    virtual void SAL_CALL release() noexcept override
        { OWeakObject::release(); }
    virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& type) override;

    /// Initialization function after having acquire()'d.
    void initListeners();

    // XTypeProvider
    virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes(  ) override;

    // css.frame.XDispatch
    virtual void SAL_CALL dispatch(const css::util::URL&                                  aURL      ,
                                   const css::uno::Sequence< css::beans::PropertyValue >& lArguments) override;

    virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
                                            const css::util::URL&                                     aURL     ) override;

    virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
                                               const css::util::URL&                                     aURL     ) override;

    // css.document.XDocumentEventListener
    /** @short  informs about created/opened documents.

        @descr  Every new opened/created document will be saved internally
                so it can be checked if it's modified. This modified state
                is used later to decide, if it must be saved or not.

        @param  aEvent
                points to the new created/opened document.
     */

    virtual void SAL_CALL documentEventOccured(const css::document::DocumentEvent& aEvent) override;

    // css.util.XChangesListener
    virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override;

    // css.util.XModifyListener
    virtual void SAL_CALL modified(const css::lang::EventObject& aEvent) override;

    // css.lang.XEventListener
    virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override;

protected:

    // OPropertySetHelper

    virtual sal_Bool SAL_CALL convertFastPropertyValue(      css::uno::Any& aConvertedValue,
                                                             css::uno::Any& aOldValue      ,
                                                             sal_Int32      nHandle        ,
                                                       const css::uno::Any& aValue         ) override;

    virtual void SAL_CALL setFastPropertyValue_NoBroadcast(      sal_Int32      nHandle,
                                                           const css::uno::Any& aValue ) override;
    using cppu::OPropertySetHelper::getFastPropertyValue;
    virtual void SAL_CALL getFastPropertyValue(css::uno::Any& aValue ,
                                               sal_Int32      nHandle) const override;

    virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override;

    virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;

private:
    virtual void SAL_CALL disposing() final override;

    /** @short  open the underlying configuration.

        @descr  This method must be called every time
                a configuration call is needed. Because
                method works together with the member
                m_xCFG, open it on demand and cache it
                afterwards.

        @throw  [com.sun.star.uno.RuntimeException]
                if config could not be opened successfully!

        @threadsafe
      */

    void implts_openConfig();

    /** @short  read the underlying configuration.

        @descr  After that we know the initial state - means:
                - if AutoSave was enabled by the user
                - which time interval has to be used
                - which recovery entries may already exists

        @throw  [com.sun.star.uno.RuntimeException]
                if config could not be opened or read successfully!

        @threadsafe
      */

    void implts_readConfig();

    /** @short  read the underlying configuration...

        @descr  ... but only keys related to the AutoSave mechanism.
                Means: State and Timer interval.
                E.g. the recovery list is not addressed here.

        @throw  [com.sun.star.uno.RuntimeException]
                if config could not be opened or read successfully!

        @threadsafe
      */

    void implts_readAutoSaveConfig();

    /** After the fact documentation
     * @short adds/updates/removes entries in the RecoveryList - files to be recovered at startup
     *
     * @descr Deciding whether to add or remove an entry is very dependent on the context!
     *        EmergencySave and SessionSave are interested in all open documents (which may not
     *        even be available at next start - i.e. /tmp files might be lost after a reboot,
     *        or removable media / server access might not be connected).
     *        TODO: On the other hand, timer-based autorecovery in theory
     *        would not be interested in recovering the session,
     *        but only modified documents that are recoverable.
     *        (TODO: unless the user always wants to recover a session).
     */

    void implts_flushConfigItem(AutoRecovery::TDocumentInfo& rInfo, bool bRemoveIt = false,
                                bool bAllowAdd = true);

    // TODO document me
    void implts_startListening();
    void implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo);

    // TODO document me
    void implts_stopListening();
    void implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo);

    /** @short  stops and may be(!) restarts the timer.

        @descr  A running timer is stopped every time here.
                But starting depends from the different internal
                timer variables (e.g. AutoSaveEnabled, AutoSaveTimeIntervall,
                TimerType etcpp.)

        @throw  [com.sun.star.uno.RuntimeException]
                if timer could not be stopped or started!

        @threadsafe
     */

    void implts_updateTimer();

    /** @short  stop the timer.

        @descr  Double calls will be ignored - means we do
                nothing here, if the timer is already disabled.

        @throw  [com.sun.star.uno.RuntimeException]
                if timer could not be stopped!

        @threadsafe
     */

    void implts_stopTimer();

    /** @short  callback of our internal timer.
     */

    DECL_LINK(implts_timerExpired, Timer*, void);

    /** @short  makes our dispatch() method asynchronous!
     */

    DECL_LINK(implts_asyncDispatch, LinkParamNone*, void);

    /** @short  implements the dispatch real. */
    void implts_dispatch(const DispatchParams& aParams);

    /** @short  validate new detected document and add it into the internal
                document list.

        @descr  This method should be called only if it's clear that a new
                document was opened/created during office runtime.
                This method checks if it's a top level document (means not an embedded one).
                Only such top level documents can be recognized by this auto save mechanism.

        @param  xDocument
                the new document, which should be checked and registered.

        @threadsafe
     */

    void implts_registerDocument(const css::uno::Reference< css::frame::XModel3 >& xDocument);

    /** @short  remove the specified document from our internal document list.

        @param  xDocument
                the closing document, which should be deregistered.

        @param  bStopListening
                sal_False: must be used in case this method is called within disposing() of the document,
                       where it makes no sense to deregister our listener. The container dies...
                sal_True : must be used in case this method is used on "deregistration" of this document, where
                       we must deregister our listener .-)

        @threadsafe
     */

    void implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument                ,
                                         bool                                   bStopListening = true);

    // TODO document me
    void implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument);

    // TODO document me
    void implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument);

    // TODO document me
    void implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument      ,
                                                       bool                                   bSaveInProgress);

    // TODO document me
    void implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument);

    /** @short  search a document inside given list.

        @param  rList
                reference to a vector, which can contain such
                document.

        @param  xDocument
                the document, which should be located inside the
                given list.

        @return [TDocumentList::iterator]
                which points to the located document.
                If document does not exists - it's set to
                rList.end()!
     */

    static TDocumentList::iterator impl_searchDocument(      AutoRecovery::TDocumentList& ;              rList    ,
                                                       const css::uno::Reference< css::frame::XModel >& xDocument);

    /** TODO document me */
    void implts_changeAllDocVisibility(bool bVisible);
    void implts_prepareSessionShutdown();

    /** @short  save all current opened documents to a specific
                backup directory.

        @descr  Only really changed documents will be saved here.

                Further this method returns a suggestion, if and how it should
                be called again. May be some documents was not saved yet
                and must wait for an user idle period ...

        @param  bAllowUserIdleLoop
                Because this method is used for different uses cases, it must
                know, which actions are allowed or not.
                Job::AutoSave =>
                             If a document is the most active one, saving it
                             will be postponed if there exists other unsaved
                             documents. This feature was implemented, because
                             we don't wish to disturb the user on it's work.
                             ... bAllowUserIdleLoop should be set to sal_True
                Job::EmergencySave / Job::SessionSave =>
                             Here we must finish our work ASAP! It's not allowed
                             to postpone any document.
                             ... bAllowUserIdleLoop must(!) be set to sal_False

        @param  pParams
                sometimes this method is required inside an external dispatch request.
                The it contains some special environment variables, which overwrites
                our normal environment.
                AutoSave              => pParams == 0
                SessionSave/CrashSave => pParams != 0

        @return A suggestion, how the timer (if it's not already disabled!)
                should be restarted to fulfill the requirements.

        @threadsafe
     */

    AutoRecovery::ETimerType implts_saveDocs(      bool        bAllowUserIdleLoop,
                                                   bool        bRemoveLockFiles,
                                             const DispatchParams* pParams        = nullptr);

    /** @short  save one of the current documents to a specific
                backup directory.

        @descr  It:
                - defines a new(!) unique temp file name
                - save the new temp file
                - remove the old temp file
                - patch the given info struct
                - and return errors.

                It does not:
                - patch the configuration.

                Note further: it patches the info struct
                more than ones. E.g. the new temp URL is set
                before the file is saved. And the old URL is removed
                only if removing of the old file was successful.
                If this method returns without an exception - everything
                was OK. Otherwise the info struct can be analyzed to
                get more information, e.g. when the problem occurs.

        @param  sBackupPath
                the base path for saving such temp files.

        @param  rInfo
                points to an information structure, where
                e.g. the document, its modified state, the count
                of autosave-retries etcpp. exists.
                It's used also to return the new temp file name
                and some other state values!

        @threadsafe
      */

    void implts_saveOneDoc(const OUString&                                    sBackupPath      ,
                                 AutoRecovery::TDocumentInfo&                        rInfo            ,
                           const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress);

    /** @short  recovery all documents, which was saved during
                a crash before.

        @return A suggestion, how this method must be called back!

        @threadsafe
     */

    AutoRecovery::ETimerType implts_openDocs(const DispatchParams& aParams);

    // TODO document me
    void implts_openOneDoc(const OUString&               sURL       ,
                                 utl::MediaDescriptor& lDescriptor,
                                 AutoRecovery::TDocumentInfo&   rInfo      );

    // TODO document me
    void implts_generateNewTempURL(const OUString&               sBackupPath     ,
                                         utl::MediaDescriptor& rMediaDescriptor,
                                         AutoRecovery::TDocumentInfo&   rInfo           );

    /** @short  notifies all interested listener about the current state
                of the currently running operation.

        @descr  We support different set's of functions. Job::AutoSave, Job::EmergencySave,
                Job::Recovery ... etcpp.
                Listener can register itself for any type of supported
                functionality ... but not for document URL's in special.

        @param  eJob
                is used to know, which set of listener we must notify.

        @param  aEvent
                describe the event more in detail.

        @threadsafe
      */

    void implts_informListener(      Job                      eJob  ,
                               const css::frame::FeatureStateEvent& aEvent);

    /** short   create a feature event struct, which can be send
                to any interested listener.

        @param  eJob
                describe the current running operation
                Job::AutoSave, Job::EmergencySave, Job::Recovery

        @param  sEventType
                describe the type of this event
                START, STOP, UPDATE

        @param  pInfo
                if sOperation is an update, this parameter must be different from NULL
                and is used to send information regarding the current handled document.

        @return [css::frame::FeatureStateEvent]
                the event structure for sending.
     */

    static css::frame::FeatureStateEvent implst_createFeatureStateEvent(      Job                    eJob      ,
                                                                        const OUString&             sEventType,
                                                                        AutoRecovery::TDocumentInfo const * pInfo     );

    class ListenerInformer
    {
    private:
        AutoRecovery &m_rRecovery;
        Job m_eJob;
        bool m_bStopped;
    public:
        ListenerInformer(AutoRecovery &rRecovery, Job eJob)
            : m_rRecovery(rRecovery), m_eJob(eJob), m_bStopped(false)
        {
        }
        void start();
        void stop();
        ~ListenerInformer()
        {
            stop();
        }
    };

    // TODO document me
    void implts_resetHandleStates();

    // TODO document me
    void implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo);

    // TODO document me
    void implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo);

    /** retrieves the names of all active views of the given document
        @param rInfo
            the document info, whose <code>Document</code> member must not be <NULL/>.
    */

    void implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& rInfo );

    /** updates the configuration so that for all documents, their current view/names are stored
    */

    void implts_persistAllActiveViewNames();

    // TODO document me
    void implts_prepareEmergencySave();

    // TODO document me
    void implts_doEmergencySave(const DispatchParams& aParams);

    // TODO document me
    void implts_doRecovery(const DispatchParams& aParams);

    // TODO document me
    void implts_doSessionSave(const DispatchParams& aParams);

    // TODO document me
    void implts_doSessionQuietQuit();

    // TODO document me
    void implts_doSessionRestore(const DispatchParams& aParams);

    // TODO document me
    void implts_backupWorkingEntry(const DispatchParams& aParams);

    // TODO document me
    void implts_cleanUpWorkingEntry(const DispatchParams& aParams);

    /** try to make sure that all changed config items (not our used
        config access only) will be flushed back to disc.

        E.g. our svtools::ConfigItems() has to be flushed explicitly .-(

        Note: This method can't fail. Flushing of config entries is an
              optional feature. Errors can be ignored.
     */

    static void impl_flushALLConfigChanges();

    // TODO document me
    AutoRecovery::EFailureSafeResult implts_copyFile(const OUString& sSource    ,
                                                     const OUString& sTargetPath,
                                                     const OUString& sTargetName);

    /** @short  converts m_eJob into a job description, which
                can be used to inform an outside listener
                about the current running operation

        @param  eJob
                describe the current running operation
                Job::AutoSave, Job::EmergencySave, Job::Recovery

        @return [string]
                a suitable job description of form:
                    vnd.sun.star.autorecovery:/do...
     */

    static OUString implst_getJobDescription(Job eJob);

    /** @short  map the given URL to an internal int representation.

        @param  aURL
                the url, which describe the next starting or may be already running
                operation.

        @return [long]
                the internal int representation
                see enum class Job
     */

    static Job implst_classifyJob(const css::util::URL& aURL);

    /// TODO
    void implts_verifyCacheAgainstDesktopDocumentList();

    /// TODO document me
    bool impl_enoughDiscSpace(sal_Int32 nRequiredSpace);

    /// TODO document me
    static void impl_showFullDiscError();

    /** @short  try to create/use a progress and set it inside the
                environment.

        @descr  The problem behind: There exists different use case of this method.
                a) An external progress is provided by our CrashSave or Recovery dialog.
                b) We must create our own progress e.g. for an AutoSave
                c) Sometimes our application filters don't use the progress
                   provided by the MediaDescriptor. They use the Frame every time to create
                   its own progress. So we implemented a HACK for these and now we set
                   an InterceptedProgress there for the time WE use this frame for loading/storing documents .-)

        @param  xNewFrame
                must be set only in case WE create a new frame (e.g. for loading documents
                on session restore or recovery). Then search for a frame using rInfo.Document must
                be suppressed and xFrame must be preferred instead .-)

        @param  rInfo
                used e.g. to find the frame corresponding to a document.
                This frame must be used to create a new progress e.g. for an AutoSave.

        @param  rArgs
                is used to set the new created progress as parameter on these set.
     */

    static void impl_establishProgress(const AutoRecovery::TDocumentInfo&               rInfo    ,
                                      utl::MediaDescriptor&             rArgs    ,
                                const css::uno::Reference< css::frame::XFrame >& xNewFrame);

    static void impl_forgetProgress(const AutoRecovery::TDocumentInfo&               rInfo    ,
                                   utl::MediaDescriptor&             rArgs    ,
                             const css::uno::Reference< css::frame::XFrame >& xNewFrame);

    /** try to remove the specified file from disc.

        Every URL supported by our UCB component can be used here.
        Further it doesn't matter if the file really exists or not.
        Because removing a non existent file will have the same
        result at the end... a non existing file .-)

        On the other side removing of files from disc is an optional
        feature. If we are not able doing so... it's not a real problem.
        Ok - users disc place will be smaller then... but we should produce
        a crash during crash save because we can't delete a temporary file only!

        @param  sURL
                the url of the file, which should be removed.
     */

    void st_impl_removeFile(const OUString& sURL);

    /** try to remove ".lock" file from disc if office will be terminated
        not using the official way .-)

        This method has to be handled "optional". So every error inside
        has to be ignored ! This method CAN NOT FAIL ... it can forget something only .-)
     */

    void st_impl_removeLockFile();
};

// recovery.xcu
constexpr OUStringLiteral CFG_PACKAGE_RECOVERY = u"/org.openoffice.Office.Recovery";

const char CFG_ENTRY_AUTOSAVE_ENABLED[] = "AutoSave/Enabled";
const char CFG_ENTRY_AUTOSAVE_USERAUTOSAVE_ENABLED[] = "AutoSave/UserAutoSaveEnabled";

constexpr OUStringLiteral CFG_ENTRY_REALDEFAULTFILTER = u"ooSetupFactoryActualFilter";

constexpr OUString CFG_ENTRY_PROP_TEMPURL = u"TempURL"_ustr;
constexpr OUString CFG_ENTRY_PROP_ORIGINALURL = u"OriginalURL"_ustr;
constexpr OUString CFG_ENTRY_PROP_TEMPLATEURL = u"TemplateURL"_ustr;
constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYURL = u"FactoryURL";
constexpr OUString CFG_ENTRY_PROP_MODULE = u"Module"_ustr;
constexpr OUString CFG_ENTRY_PROP_DOCUMENTSTATE = u"DocumentState"_ustr;
constexpr OUString CFG_ENTRY_PROP_FILTER = u"Filter"_ustr;
constexpr OUString CFG_ENTRY_PROP_TITLE = u"Title"_ustr;
constexpr OUStringLiteral CFG_ENTRY_PROP_ID = u"ID";
constexpr OUString CFG_ENTRY_PROP_VIEWNAMES = u"ViewNames"_ustr;

constexpr OUStringLiteral FILTER_PROP_TYPE = u"Type";
constexpr OUStringLiteral TYPE_PROP_EXTENSIONS = u"Extensions";

// setup.xcu
constexpr OUStringLiteral CFG_ENTRY_PROP_EMPTYDOCUMENTURL = u"ooSetupFactoryEmptyDocumentURL";
constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYSERVICE = u"ooSetupFactoryDocumentService";

constexpr OUStringLiteral EVENT_ON_NEW = u"OnNew";
constexpr OUStringLiteral EVENT_ON_LOAD = u"OnLoad";
constexpr OUStringLiteral EVENT_ON_UNLOAD = u"OnUnload";
constexpr OUStringLiteral EVENT_ON_MODIFYCHANGED = u"OnModifyChanged";
constexpr OUStringLiteral EVENT_ON_SAVE = u"OnSave";
constexpr OUStringLiteral EVENT_ON_SAVEAS = u"OnSaveAs";
constexpr OUStringLiteral EVENT_ON_SAVETO = u"OnCopyTo";
constexpr OUStringLiteral EVENT_ON_SAVEDONE = u"OnSaveDone";
constexpr OUStringLiteral EVENT_ON_SAVEASDONE = u"OnSaveAsDone";
constexpr OUStringLiteral EVENT_ON_SAVETODONE = u"OnCopyToDone";
constexpr OUStringLiteral EVENT_ON_SAVEFAILED = u"OnSaveFailed";
constexpr OUStringLiteral EVENT_ON_SAVEASFAILED = u"OnSaveAsFailed";
constexpr OUStringLiteral EVENT_ON_SAVETOFAILED = u"OnCopyToFailed";

constexpr OUString RECOVERY_ITEM_BASE_IDENTIFIER = u"recovery_item_"_ustr;

constexpr OUString CMD_PROTOCOL = u"vnd.sun.star.autorecovery:"_ustr;

constexpr OUString CMD_DO_AUTO_SAVE = u"/doAutoSave"_ustr;    // force AutoSave ignoring the AutoSave timer
constexpr OUString CMD_DO_PREPARE_EMERGENCY_SAVE = u"/doPrepareEmergencySave"_ustr;    // prepare the office for the following EmergencySave step (hide windows etcpp.)
constexpr OUString CMD_DO_EMERGENCY_SAVE = u"/doEmergencySave"_ustr;    // do EmergencySave on crash
constexpr OUString CMD_DO_RECOVERY = u"/doAutoRecovery"_ustr;    // recover all crashed documents
constexpr OUString CMD_DO_ENTRY_BACKUP = u"/doEntryBackup"_ustr;    // try to store a temp or original file to a user defined location
constexpr OUString CMD_DO_ENTRY_CLEANUP = u"/doEntryCleanUp"_ustr;    // remove the specified entry from the recovery cache
constexpr OUString CMD_DO_SESSION_SAVE = u"/doSessionSave"_ustr;    // save all open documents if e.g. a window manager closes an user session
constexpr OUString CMD_DO_SESSION_QUIET_QUIT = u"/doSessionQuietQuit"_ustr;    // let the current session be quietly closed ( the saving should be done using doSessionSave previously ) if e.g. a window manager closes an user session
constexpr OUString CMD_DO_SESSION_RESTORE = u"/doSessionRestore"_ustr;    // restore a saved user session from disc
constexpr OUString CMD_DO_DISABLE_RECOVERY = u"/disableRecovery"_ustr;    // disable recovery and auto save (!) temp. for this office session
constexpr OUString CMD_DO_SET_AUTOSAVE_STATE = u"/setAutoSaveState"_ustr;    // disable/enable auto save (not crash save) for this office session

constexpr OUStringLiteral REFERRER_USER = u"private:user";

constexpr OUStringLiteral PROP_DISPATCH_ASYNCHRON = u"DispatchAsynchron";
constexpr OUStringLiteral PROP_PROGRESS = u"StatusIndicator";
constexpr OUStringLiteral PROP_SAVEPATH = u"SavePath";
constexpr OUStringLiteral PROP_ENTRY_ID = u"EntryID";
constexpr OUStringLiteral PROP_AUTOSAVE_STATE = u"AutoSaveState";

constexpr OUString OPERATION_START = u"start"_ustr;
constexpr OUString OPERATION_STOP = u"stop"_ustr;
constexpr OUString OPERATION_UPDATE = u"update"_ustr;

const sal_Int32       MIN_DISCSPACE_DOCSAVE                  =   5; // [MB]
const sal_Int32       MIN_DISCSPACE_CONFIGSAVE               =   1; // [MB]
const sal_Int32       RETRY_STORE_ON_FULL_DISC_FOREVER       = 300; // not forever ... but often enough .-)
const sal_Int32       RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL =   3; // in case FULL DISC does not seem the real problem
const sal_Int32       GIVE_UP_RETRY                          =   1; // in case FULL DISC does not seem the real problem

#define SAVE_IN_PROGRESS            true
#define SAVE_FINISHED               false

#define LOCK_FOR_CACHE_ADD_REMOVE   true
#define LOCK_FOR_CACHE_USE          false

#define MIN_TIME_FOR_USER_IDLE 10000 // 10s user idle

// enable the following defines in case you wish to simulate a full disc for debug purposes .-)

// this define throws every time a document is stored or a configuration change
// should be flushed an exception ... so the special error handler for this scenario is triggered
// #define TRIGGER_FULL_DISC_CHECK

// force "return sal_False" for the method impl_enoughDiscSpace().
// #define SIMULATE_FULL_DISC

class CacheLockGuard
{
    private:

        // holds the outside caller alive, so it's shared resources
        // are valid every time
        css::uno::Reference< css::uno::XInterface > m_xOwner;

        // mutex shared with outside caller!
        osl::Mutex& m_rSharedMutex;

        // this variable knows the state of the "cache lock"
        sal_Int32& m_rCacheLock;

        // to prevent increasing/decreasing of m_rCacheLock more than once
        // we must know if THIS guard has an actual lock set there!
        bool m_bLockedByThisGuard;

    public:

        CacheLockGuard(AutoRecovery* pOwner                      ,
                       osl::Mutex&   rMutex                      ,
                       sal_Int32&    rCacheLock                  ,
                       bool      bLockForAddRemoveVectorItems);
        ~CacheLockGuard();

        void lock(bool bLockForAddRemoveVectorItems);
        void unlock();
};

CacheLockGuard::CacheLockGuard(AutoRecovery* pOwner                      ,
                               osl::Mutex&   rMutex                      ,
                               sal_Int32&    rCacheLock                  ,
                               bool      bLockForAddRemoveVectorItems)
    : m_xOwner            (static_cast< css::frame::XDispatch* >(pOwner))
    , m_rSharedMutex      (rMutex                                       )
    , m_rCacheLock        (rCacheLock                                   )
    , m_bLockedByThisGuard(false                                    )
{
    lock(bLockForAddRemoveVectorItems);
}

CacheLockGuard::~CacheLockGuard()
{
    unlock();
    m_xOwner.clear();
}

void CacheLockGuard::lock(bool bLockForAddRemoveVectorItems)
{
    /* SAFE */
    osl::MutexGuard g(m_rSharedMutex);

    if (m_bLockedByThisGuard)
        return;

    // This cache lock is needed only to prevent us from removing/adding
    // items from/into the recovery cache ... during it's used at another code place
    // for iterating .-)

    // Modifying of item properties is allowed and sometimes needed!
    // So we should detect only the dangerous state of concurrent add/remove
    // requests and throw an exception then ... which can of course break the whole
    // operation. On the other side a crash reasoned by an invalid stl iterator
    // will have the same effect .-)

    if ( (m_rCacheLock > 0) && bLockForAddRemoveVectorItems )
    {
        OSL_FAIL("Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp.");
        throw css::uno::RuntimeException(
                u"Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp."_ustr,
                m_xOwner);
    }

    ++m_rCacheLock;
    m_bLockedByThisGuard = true;
    /* SAFE */
}

void CacheLockGuard::unlock()
{
    /* SAFE */
    osl::MutexGuard g(m_rSharedMutex);

    if ( ! m_bLockedByThisGuard)
        return;

    --m_rCacheLock;
    m_bLockedByThisGuard = false;

    if (m_rCacheLock < 0)
    {
        OSL_FAIL("Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)");
        throw css::uno::RuntimeException(
                u"Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)"_ustr,
                m_xOwner);
    }
    /* SAFE */
}

DispatchParams::DispatchParams()
    : m_nWorkingEntryID(-1)
{
};

DispatchParams::DispatchParams(const ::comphelper::SequenceAsHashMap&             lArgs ,
                               const css::uno::Reference< css::uno::XInterface >& xOwner)
{
    m_nWorkingEntryID         = lArgs.getUnpackedValueOrDefault(PROP_ENTRY_ID, sal_Int32(-1)                                       );
    m_xProgress               = lArgs.getUnpackedValueOrDefault(PROP_PROGRESS, css::uno::Reference< css::task::XStatusIndicator >());
    m_sSavePath               = lArgs.getUnpackedValueOrDefault(PROP_SAVEPATH, OUString()                                   );
    m_xHoldRefForAsyncOpAlive = xOwner;
};

void DispatchParams::forget()
{
    m_sSavePath.clear();
    m_nWorkingEntryID = -1;
    m_xProgress.clear();
    m_xHoldRefForAsyncOpAlive.clear();
};

AutoRecovery::AutoRecovery(css::uno::Reference< css::uno::XComponentContext >  xContext)
    : AutoRecovery_BASE         (m_aMutex)
    , ::cppu::OPropertySetHelper(cppu::WeakComponentImplHelperBase::rBHelper)
    , m_xContext                (std::move(xContext                                           ))
    , m_bListenForDocEvents     (false                                          )
    , m_bListenForConfigChanges (false                                          )
    , m_eJob                    (Job::NoJob)
    , m_aTimer( "framework::AutoRecovery m_aTimer" )
    , m_xAsyncDispatcher        (new vcl::EventPoster( LINK( this, AutoRecovery, implts_asyncDispatch )  ))
    , m_eTimerType              (E_DONT_START_TIMER                                 )
    , m_nIdPool                 (0                                                  )
    , m_lListener               (cppu::WeakComponentImplHelperBase::rBHelper.rMutex)
    , m_nDocCacheLock           (0                                                  )
    , m_nMinSpaceDocSave        (MIN_DISCSPACE_DOCSAVE                              )
    , m_nMinSpaceConfigSave     (MIN_DISCSPACE_CONFIGSAVE                           )
{
}

void AutoRecovery::initListeners()
{
    // read configuration to know if autosave/recovery is on/off etcpp...
    implts_readConfig();

    implts_startListening();

    // establish callback for our internal used timer.
    // Note: Its only active, if the timer will be started ...
    SolarMutexGuard g;
    m_aTimer.SetInvokeHandler(LINK(this, AutoRecovery, implts_timerExpired));
}

AutoRecovery::~AutoRecovery()
{
    assert(!m_aTimer.IsActive());
}

void AutoRecovery::disposing()
{
    implts_stopTimer();
    SolarMutexGuard g;
    m_xAsyncDispatcher.reset();
}

Any SAL_CALL AutoRecovery::queryInterface( const css::uno::Type& _rType )
{
    Any aRet = AutoRecovery_BASE::queryInterface( _rType );
    if ( !aRet.hasValue() )
        aRet = OPropertySetHelper::queryInterface( _rType );
    return aRet;
}

Sequence< css::uno::Type > SAL_CALL AutoRecovery::getTypes(  )
{
    return comphelper::concatSequences(
        AutoRecovery_BASE::getTypes(),
        ::cppu::OPropertySetHelper::getTypes()
    );
}

void SAL_CALL AutoRecovery::dispatch(const css::util::URL&                                  aURL      ,
                                     const css::uno::Sequence< css::beans::PropertyValue >& lArguments)
{
    SAL_INFO("fwk.autorecovery""AutoRecovery::dispatch() starts ..." << aURL.Complete);

    // valid request ?
    Job eNewJob = AutoRecovery::implst_classifyJob(aURL);
    if (eNewJob == Job::NoJob)
        return;

    bool bAsync;
    DispatchParams aParams;
    /* SAFE */ {
    osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);

    // still running operation ... ignoring Job::AutoSave.
    // All other requests has higher prio!
    if (
        ( m_eJob                               != Job::NoJob   ) &&
        ((m_eJob & Job::AutoSave ) != Job::AutoSave)
       )
    {
        SAL_INFO("fwk.autorecovery""AutoRecovery::dispatch(): There is already an asynchronous dispatch() running. New request will be ignored!");
        return;
    }

    ::comphelper::SequenceAsHashMap lArgs(lArguments);

    // check if somewhere wish to disable recovery temp. for this office session
    // This can be done immediately... must not been done asynchronous.
    if ((eNewJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
    {
        // it's important to set a flag internally, so AutoRecovery will be suppressed - even if it's requested.
        m_eJob |= eNewJob;
        implts_stopTimer();
        implts_stopListening();
        return;
    }

    // disable/enable AutoSave for this office session only
    // independent from the configuration entry.
    if ((eNewJob & Job::SetAutosaveState) == Job::SetAutosaveState)
    {
        bool bOn = lArgs.getUnpackedValueOrDefault(PROP_AUTOSAVE_STATE, true);
        if (bOn)
        {
            // don't enable AutoSave hardly !
            // reload configuration to know the current state.
            implts_readAutoSaveConfig();
            g.clear();
            implts_updateTimer();
            // can it happen that might be the listener was stopped? .-)
            // make sure it runs always... even if AutoSave itself was disabled temporarily.
            implts_startListening();
        }
        else
        {
            implts_stopTimer();
            m_eJob       &= ~Job::AutoSave;
            m_eTimerType  =  AutoRecovery::E_DONT_START_TIMER;
        }
        return;
    }

    m_eJob |= eNewJob;

    bAsync = lArgs.getUnpackedValueOrDefault(PROP_DISPATCH_ASYNCHRON, false);
    aParams = DispatchParams(lArgs, static_cast< css::frame::XDispatch* >(this));

    // Hold this instance alive till the asynchronous operation will be finished.
    if (bAsync)
        m_aDispatchParams = aParams;

    } /* SAFE */

    if (bAsync)
        m_xAsyncDispatcher->Post();
    else
        implts_dispatch(aParams);
}

void AutoRecovery::ListenerInformer::start()
{
    m_rRecovery.implts_informListener(m_eJob,
        AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_START, nullptr));
}

void AutoRecovery::ListenerInformer::stop()
{
    if (m_bStopped)
        return;
    m_rRecovery.implts_informListener(m_eJob,
        AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_STOP, nullptr));
    m_bStopped = true;
}

void AutoRecovery::implts_dispatch(const DispatchParams& aParams)
{
    Job eJob;
    /* SAFE */ {
    osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
    eJob = m_eJob;
    } /* SAFE */

    // in case a new dispatch overwrites a may ba active AutoSave session
    // we must restore this session later. see below ...
    bool bWasAutoSaveActive = ((eJob & Job::AutoSave) == Job::AutoSave);
    bool bWasUserAutoSaveActive =
        ((eJob & Job::UserAutoSave) == Job::UserAutoSave);

    // On the other side it makes no sense to reactivate the AutoSave operation
    // if the new dispatch indicates a final decision...
    // E.g. an EmergencySave/SessionSave indicates the end of life of the current office session.
    // It makes no sense to reactivate an AutoSave then.
    // But a Recovery or SessionRestore should reactivate a may be already active AutoSave.
    bool bAllowAutoSaveReactivation = true;

    implts_stopTimer();
    implts_stopListening();

    ListenerInformer aListenerInformer(*this, eJob);
    aListenerInformer.start();

    try
    {
        //  Auto save is called from our internal timer ... not via dispatch() API !
        // else
        if (
            ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave) &&
            ((eJob & Job::DisableAutorecovery      ) != Job::DisableAutorecovery      )
           )
        {
            SAL_INFO("fwk.autorecovery""... prepare emergency save ...");
            bAllowAutoSaveReactivation = false;
            implts_prepareEmergencySave();
        }
        else
        if (
            ((eJob & Job::EmergencySave  ) == Job::EmergencySave  ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
           )
        {
            SAL_INFO("fwk.autorecovery""... do emergency save ...");
            bAllowAutoSaveReactivation = false;
            implts_doEmergencySave(aParams);
        }
        else
        if (
            ((eJob & Job::Recovery        ) == Job::Recovery        ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
           )
        {
            SAL_INFO("fwk.autorecovery""... do recovery ...");
            implts_doRecovery(aParams);
        }
        else
        if (
            ((eJob & Job::SessionSave    ) == Job::SessionSave    ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
            )
        {
            SAL_INFO("fwk.autorecovery""... do session save ...");
            bAllowAutoSaveReactivation = false;
            implts_doSessionSave(aParams);
        }
        else
        if (
            ((eJob & Job::SessionQuietQuit    ) == Job::SessionQuietQuit ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
            )
        {
            SAL_INFO("fwk.autorecovery""... do session quiet quit ...");
            bAllowAutoSaveReactivation = false;
            implts_doSessionQuietQuit();
        }
        else
        if (
            ((eJob & Job::SessionRestore ) == Job::SessionRestore ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
            )
        {
            SAL_INFO("fwk.autorecovery""... do session restore ...");
            implts_doSessionRestore(aParams);
        }
        else
        if (
            ((eJob & Job::EntryBackup    ) == Job::EntryBackup    ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
            )
            implts_backupWorkingEntry(aParams);
        else
        if (
            ((eJob & Job::EntryCleanup   ) == Job::EntryCleanup   ) &&
            ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
            )
            implts_cleanUpWorkingEntry(aParams);
    }
    catch(const css::uno::RuntimeException&)
    {
        throw;
    }
    catch(const css::uno::Exception&)
    {
        // TODO better error handling
    }

    aListenerInformer.stop();

    /* SAFE */ {
    osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
    m_eJob = Job::NoJob;
    if ( bAllowAutoSaveReactivation && bWasAutoSaveActive )
    {
        m_eJob |= Job::AutoSave;

        if (bWasUserAutoSaveActive)
        {
            m_eJob |= Job::UserAutoSave;
        }
    }

    } /* SAFE */

    // depends on bAllowAutoSaveReactivation implicitly by looking on m_eJob=Job::AutoSave! see before ...
    implts_updateTimer();

    if (bAllowAutoSaveReactivation)
        implts_startListening();
}

void SAL_CALL AutoRecovery::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
                                              const css::util::URL&                                     aURL     )
{
    if (!xListener.is())
        throw css::uno::RuntimeException(u"Invalid listener reference."_ustr, static_cast< css::frame::XDispatch* >(this));
    // container is threadsafe by using a shared mutex!
    m_lListener.addInterface(aURL.Complete, xListener);

    // REENTRANT !? -> --------------------------------
    CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);

    /* SAFE */ {
    osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);

    for (auto const& elem : m_lDocCache)
    {
        css::frame::FeatureStateEvent aEvent = AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_UPDATE, &elem);

        // } /* SAFE */
        g.clear();
        xListener->statusChanged(aEvent);
        g.reset();
        // /* SAFE */ {
    }

    } /* SAFE */
}

void SAL_CALL AutoRecovery::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
                                                 const css::util::URL&                                     aURL     )
{
    if (!xListener.is())
        throw css::uno::RuntimeException(u"Invalid listener reference."_ustr, static_cast< css::frame::XDispatch* >(this));
    // container is threadsafe by using a shared mutex!
    m_lListener.removeInterface(aURL.Complete, xListener);
}

void SAL_CALL AutoRecovery::documentEventOccured(const css::document::DocumentEvent&&nbsp;aEvent)
{
    css::uno::Reference< css::frame::XModel3 > xDocument(aEvent.Source, css::uno::UNO_QUERY);

    // new document => put it into the internal list
    if (
        (aEvent.EventName == EVENT_ON_NEW) ||
        (aEvent.EventName == EVENT_ON_LOAD)
       )
    {
        implts_registerDocument(xDocument);
    }
    // document modified => set its modify state new (means modified against the original file!)
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=90 H=100 G=95

¤ Dauer der Verarbeitung: 0.22 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge