1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <config_features.h>
21 
22 #include <loadenv/loadenv.hxx>
23 
24 #include <strings.hrc>
25 #include <classes/fwkresid.hxx>
26 #include <properties.h>
27 #include <targets.h>
28 
29 #include <helper/mischelper.hxx>
30 
31 #include <com/sun/star/ucb/NameClash.hpp>
32 #include <com/sun/star/container/XNameAccess.hpp>
33 #include <com/sun/star/configuration/theDefaultProvider.hpp>
34 #include <com/sun/star/frame/Desktop.hpp>
35 #include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
36 #include <com/sun/star/frame/XLoadable.hpp>
37 #include <com/sun/star/frame/XModel3.hpp>
38 #include <com/sun/star/frame/ModuleManager.hpp>
39 #include <com/sun/star/frame/XTitle.hpp>
40 #include <com/sun/star/frame/XFrame.hpp>
41 #include <com/sun/star/frame/XController.hpp>
42 #include <com/sun/star/frame/XModel.hpp>
43 #include <com/sun/star/frame/XStorable.hpp>
44 #include <com/sun/star/util/XModifiable.hpp>
45 #include <com/sun/star/util/URLTransformer.hpp>
46 #include <com/sun/star/util/XURLTransformer.hpp>
47 #include <com/sun/star/frame/XDesktop.hpp>
48 #include <com/sun/star/container/XNameContainer.hpp>
49 #include <com/sun/star/util/XChangesNotifier.hpp>
50 #include <com/sun/star/beans/XPropertySet.hpp>
51 #include <com/sun/star/beans/PropertyAttribute.hpp>
52 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
53 #include <com/sun/star/document/XDocumentRecovery.hpp>
54 #include <com/sun/star/document/XExtendedFilterDetection.hpp>
55 #include <com/sun/star/util/XCloseable.hpp>
56 #include <com/sun/star/awt/XWindow2.hpp>
57 #include <com/sun/star/task/XStatusIndicatorFactory.hpp>
58 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
59 #include <com/sun/star/lang/XTypeProvider.hpp>
60 #include <com/sun/star/lang/XServiceInfo.hpp>
61 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
62 #include <com/sun/star/frame/XDispatch.hpp>
63 #include <com/sun/star/document/XDocumentEventListener.hpp>
64 #include <com/sun/star/document/XDocumentEventBroadcaster.hpp>
65 #include <com/sun/star/util/XChangesListener.hpp>
66 #include <com/sun/star/task/XStatusIndicator.hpp>
67 #include <com/sun/star/util/XModifyListener.hpp>
68 
69 #include <comphelper/configuration.hxx>
70 #include <cppuhelper/basemutex.hxx>
71 #include <cppuhelper/exc_hlp.hxx>
72 #include <cppuhelper/compbase.hxx>
73 #include <cppuhelper/propshlp.hxx>
74 #include <cppuhelper/supportsservice.hxx>
75 #include <o3tl/safeint.hxx>
76 #include <o3tl/typed_flags_set.hxx>
77 #include <unotools/mediadescriptor.hxx>
78 #include <comphelper/namedvaluecollection.hxx>
79 #include <comphelper/sequence.hxx>
80 #include <vcl/evntpost.hxx>
81 #include <vcl/svapp.hxx>
82 #include <vcl/timer.hxx>
83 #include <unotools/pathoptions.hxx>
84 #include <tools/diagnose_ex.h>
85 #include <unotools/tempfile.hxx>
86 #include <ucbhelper/content.hxx>
87 
88 #include <vcl/weld.hxx>
89 #include <osl/file.hxx>
90 #include <sal/log.hxx>
91 #include <unotools/bootstrap.hxx>
92 #include <unotools/configmgr.hxx>
93 #include <svl/documentlockfile.hxx>
94 #include <tools/urlobj.hxx>
95 #include <officecfg/Office/Recovery.hxx>
96 #include <officecfg/Setup.hxx>
97 
98 #include <stdtypes.h>
99 
100 using namespace css::uno;
101 using namespace css::document;
102 using namespace css::frame;
103 using namespace css::lang;
104 using namespace framework;
105 
106 namespace {
107 
108 /** @short  hold all needed information for an asynchronous dispatch alive.
109 
110     @descr  Because some operations are forced to be executed asynchronously
111             (e.g. requested by our CreashSave/Recovery dialog) ... we must make sure
112             that this information won't be set as "normal" members of our AutoRecovery
113             instance. Otherwise they can disturb our normal AutoSave-timer handling.
114             e.g. it can be unclear then, which progress has to be used for storing documents...
115  */
116 struct DispatchParams
117 {
118 public:
119      DispatchParams();
120      DispatchParams(const ::comphelper::SequenceAsHashMap&             lArgs ,
121                     const css::uno::Reference< css::uno::XInterface >& xOwner);
122 
123      void forget();
124 
125 public:
126 
127     /** @short  can be set from outside and is provided to
128                 our internal started operations.
129 
130         @descr  Normally we use the normal status indicator
131                 of the document windows to show a progress.
132                 But in case we are used by any special UI,
133                 it can provide its own status indicator object
134                 to us - so we use it instead of the normal one.
135      */
136     css::uno::Reference< css::task::XStatusIndicator > m_xProgress;
137 
138     /** TODO document me */
139     OUString m_sSavePath;
140 
141     /** @short  define the current cache entry, which should be used for current
142                 backup or cleanUp operation ... which is may be done asynchronous */
143     sal_Int32 m_nWorkingEntryID;
144 
145     /** @short  used for async operations, to prevent us from dying.
146 
147         @descr  If our dispatch() method was forced to start the
148                 internal operation asynchronous... we send an event
149                 to start and return immediately. But we must be sure that
150                 our instance live if the event callback reach us.
151                 So we hold a uno reference to ourself.
152      */
153     css::uno::Reference< css::uno::XInterface > m_xHoldRefForAsyncOpAlive;
154 };
155 
156 /** These values are used as flags and represent the current state of a document.
157     Every state of the life time of a document has to be recognized here.
158 
159     @attention  Do not change (means reorganize) already used numbers.
160                 There exists some code inside SVX, which uses the same numbers,
161                 to analyze such document states.
162                 Not the best design ... but may be it will be changed later .-)
163 */
164 enum class DocState: sal_Int32
165 {
166     /* TEMP STATES */
167 
168     /// default state, if a document was new created or loaded
169     Unknown = 0,
170     /// modified against the original file
171     Modified = 1,
172     /// an active document can be postponed to be saved later.
173     Postponed = 2,
174     /// was already handled during one AutoSave/Recovery session.
175     Handled = 4,
176     /** an action was started (saving/loading) ... Can be interesting later if the process may be was interrupted by an exception. */
177     TrySave = 8,
178     TryLoadBackup = 16,
179     TryLoadOriginal = 32,
180 
181     /* FINAL STATES */
182 
183     /// the Auto/Emergency saved document is not usable any longer
184     Damaged = 64,
185     /// the Auto/Emergency saved document is not really up-to-date (some changes can be missing)
186     Incomplete = 128,
187     /// the Auto/Emergency saved document was processed successfully
188     Succeeded = 512
189 };
190 
191 }
192 
193 template<> struct o3tl::typed_flags<DocState>: o3tl::is_typed_flags<DocState, 0x2FF> {};
194 
195 namespace {
196 
197 // TODO document me ... flag field
198 // Emergency_Save and Recovery overwrites Auto_Save!
199 enum class Job
200 {
201     NoJob                     =   0,
202     AutoSave                  =   1,
203     EmergencySave             =   2,
204     Recovery                  =   4,
205     EntryBackup               =   8,
206     EntryCleanup              =  16,
207     PrepareEmergencySave      =  32,
208     SessionSave               =  64,
209     SessionRestore            = 128,
210     DisableAutorecovery       = 256,
211     SetAutosaveState          = 512,
212     SessionQuietQuit          = 1024,
213     UserAutoSave              = 2048
214 };
215 
216 }
217 
218 template<> struct o3tl::typed_flags<Job>: o3tl::is_typed_flags<Job, 0xFFF> {};
219 
220 namespace {
221 
222 /**
223     implements the functionality of AutoSave and AutoRecovery
224     of documents - including features of an EmergencySave in
225     case a GPF occurs.
226  */
227 typedef ::cppu::WeakComponentImplHelper<
228             css::lang::XServiceInfo,
229             css::frame::XDispatch,
230             css::document::XDocumentEventListener, // => css.lang.XEventListener
231             css::util::XChangesListener,      // => css.lang.XEventListener
232             css::util::XModifyListener >      // => css.lang.XEventListener
233          AutoRecovery_BASE;
234 
235 class AutoRecovery  : private cppu::BaseMutex
236                     , public  AutoRecovery_BASE
237                     , public  ::cppu::OPropertySetHelper            // => XPropertySet, XFastPropertySet, XMultiPropertySet
238 {
239 public:
240     /** @short  indicates the results of a FAILURE_SAFE operation
241 
242         @descr  We must know, which reason was the real one in case
243                 we couldn't copy a "failure document" to a user specified path.
244                 We must know, if we can forget our cache entry or not.
245      */
246     enum EFailureSafeResult
247     {
248         E_COPIED,
249         E_ORIGINAL_FILE_MISSING,
250         E_WRONG_TARGET_PATH
251     };
252 
253     // TODO document me
254     enum ETimerType
255     {
256         /** the timer should not be used next time */
257         E_DONT_START_TIMER,
258         /** timer (was/must be) started with normal AutoSaveTimeIntervall */
259         E_NORMAL_AUTOSAVE_INTERVALL,
260         /** timer must be started with special short time interval,
261             to poll for an user idle period */
262         E_POLL_FOR_USER_IDLE,
263         /** timer must be started with a very(!) short time interval,
264             to poll for the end of an user action, which does not allow saving documents in general */
265         E_POLL_TILL_AUTOSAVE_IS_ALLOWED,
266         /** don't start the timer - but calls the same action then before immediately again! */
267         E_CALL_ME_BACK
268     };
269 
270     /** @short  combine different information about one office document. */
271     struct TDocumentInfo
272     {
273         public:
274 
TDocumentInfo__anondec722860311::AutoRecovery::TDocumentInfo275             TDocumentInfo()
276                 : DocumentState   (DocState::Unknown)
277                 , UsedForSaving   (false)
278                 , ListenForModify (false)
279                 , IgnoreClosing   (false)
280                 , ID              (-1       )
281             {}
282 
283             /** @short  points to the document. */
284             css::uno::Reference< css::frame::XModel > Document;
285 
286             /** @short  knows, if the document is really modified since the last autosave,
287                         or  was postponed, because it was an active one etcpp...
288 
289                 @descr  Because we have no CHANGE TRACKING mechanism, based on office document,
290                         we implements it by ourself. We listen for MODIFIED events
291                         of each document and update this state flag here.
292 
293                         Further we postpone saving of active documents, e.g. if the user
294                         works currently on it. We wait for an idle period then...
295              */
296             DocState DocumentState;
297 
298             /** Because our applications not ready for concurrent save requests at the same time,
299                 we have suppress our own AutoSave for the moment, a document will be already saved
300                 by others.
301              */
302             bool UsedForSaving;
303 
304             /** For every user action, which modifies a document (e.g. key input) we get
305                 a notification as XModifyListener. That seems to be a "performance issue" .-)
306                 So we decided to listen for such modify events only for the time in which the document
307                 was stored as temp. file and was not modified again by the user.
308             */
309             bool ListenForModify;
310 
311             /** For SessionSave we must close all open documents by ourself.
312                 But because we are listen for documents events, we get some ...
313                 and deregister these documents from our configuration.
314                 That's why we mark these documents as "Closed by ourself" so we can
315                 ignore these "OnUnload" or disposing() events .-)
316             */
317             bool IgnoreClosing;
318 
319             /** TODO: document me */
320             OUString OrgURL;
321             OUString FactoryURL;
322             OUString TemplateURL;
323 
324             OUString OldTempURL;
325             OUString NewTempURL;
326 
327             OUString AppModule;      // e.g. com.sun.star.text.TextDocument - used to identify app module
328             OUString FactoryService; // the service to create a document of the module
329             OUString RealFilter;     // real filter, which was used at loading time
330             OUString DefaultFilter;  // supports saving of the default format without losing data
331             OUString Extension;      // file extension of the default filter
332             OUString Title;          // can be used as "DisplayName" on every recovery UI!
333             css::uno::Sequence< OUString >
334                             ViewNames;      // names of the view which were active at emergency-save time
335 
336             sal_Int32 ID;
337     };
338 
339     /** @short  used to know every currently open document. */
340     typedef ::std::vector< TDocumentInfo > TDocumentList;
341 
342 // member
343 
344 private:
345 
346     /** @short  the global uno service manager.
347         @descr  Must be used to create own needed services.
348      */
349     css::uno::Reference< css::uno::XComponentContext > m_xContext;
350 
351     /** @short  points to the underlying recovery configuration.
352         @descr  This instance does not cache - it calls directly the
353                 configuration API!
354       */
355     css::uno::Reference< css::container::XNameAccess > m_xRecoveryCFG;
356 
357     /** @short  proxy weak binding to forward Events to ourself without
358                 an ownership cycle
359       */
360     css::uno::Reference< css::util::XChangesListener > m_xRecoveryCFGListener;
361 
362     /** @short  points to the used configuration package or.openoffice.Setup
363         @descr  This instance does not cache - it calls directly the
364                 configuration API!
365       */
366     css::uno::Reference< css::container::XNameAccess > m_xModuleCFG;
367 
368     /** @short  holds the global event broadcaster alive,
369                 where we listen for new created documents.
370       */
371     css::uno::Reference< css::frame::XGlobalEventBroadcaster > m_xNewDocBroadcaster;
372 
373     /** @short  proxy weak binding to forward Events to ourself without
374                 an ownership cycle
375       */
376     css::uno::Reference< css::document::XDocumentEventListener > m_xNewDocBroadcasterListener;
377 
378     /** @short  because we stop/restart listening sometimes, it's a good idea to know
379                 if we already registered as listener .-)
380     */
381     bool m_bListenForDocEvents;
382     bool m_bListenForConfigChanges;
383 
384     /** @short  specify the time interval between two save actions.
385         @descr  tools::Time is measured in [min].
386      */
387     sal_Int32 m_nAutoSaveTimeIntervall;
388 
389     /** @short  for an asynchronous operation we must know, if there is
390                 at least one running job (may be asynchronous!).
391      */
392     Job m_eJob;
393 
394     /** @short  the timer, which is used to be informed about the next
395                 saving time ...
396         @remark must lock SolarMutex to use
397      */
398     Timer m_aTimer;
399 
400     /** @short  make our dispatch asynchronous ... if required to do so! */
401     std::unique_ptr<vcl::EventPoster> m_xAsyncDispatcher;
402 
403     /** @see    DispatchParams
404      */
405     DispatchParams m_aDispatchParams;
406 
407     /** @short  indicates, which time period is currently used by the
408                 internal timer.
409      */
410     ETimerType m_eTimerType;
411 
412     /** @short  this cache is used to hold all information about
413                 recovery/emergency save documents alive.
414      */
415     TDocumentList m_lDocCache;
416 
417     // TODO document me
418     sal_Int32 m_nIdPool;
419 
420     /** @short  contains all status listener registered at this instance.
421      */
422     ListenerHash m_lListener;
423 
424     /** @descr  This member is used to prevent us against re-entrance problems.
425                 A mutex can't help to prevent us from concurrent using of members
426                 inside the same thread. But e.g. our internally used stl structures
427                 are not threadsafe ... and furthermore they can't be used at the same time
428                 for iteration and add/remove requests!
429                 So we have to detect such states and ... show a warning.
430                 May be there will be a better solution next time ... (copying the cache temp.
431                 bevor using).
432 
433                 And further it's not possible to use a simple boolean value here.
434                 Because if more than one operation iterates over the same stl container ...
435                 (only to modify it's elements but don't add new or removing existing ones!)
436                 it should be possible doing so. But we must guarantee that the last operation reset
437                 this lock ... not the first one ! So we use a "ref count" mechanism for that."
438      */
439     sal_Int32 m_nDocCacheLock;
440 
441     /** @descr  These members are used to check the minimum disc space, which must exists
442                 to start the corresponding operation.
443      */
444     sal_Int32 m_nMinSpaceDocSave;
445     sal_Int32 m_nMinSpaceConfigSave;
446 
447 // interface
448 
449 public:
450 
451     explicit AutoRecovery(const css::uno::Reference< css::uno::XComponentContext >& xContext);
452     virtual ~AutoRecovery(                                                                   ) override;
453 
getImplementationName()454     virtual OUString SAL_CALL getImplementationName() override
455     {
456         return "com.sun.star.comp.framework.AutoRecovery";
457     }
458 
supportsService(OUString const & ServiceName)459     virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
460     {
461         return cppu::supportsService(this, ServiceName);
462     }
463 
getSupportedServiceNames()464     virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
465     {
466         return {"com.sun.star.frame.AutoRecovery"};
467     }
468 
469     // XInterface
acquire()470     virtual void SAL_CALL acquire() noexcept override
471         { OWeakObject::acquire(); }
release()472     virtual void SAL_CALL release() noexcept override
473         { OWeakObject::release(); }
474     virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& type) override;
475 
476     /// Initialization function after having acquire()'d.
477     void initListeners();
478 
479     // XTypeProvider
480     virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes(  ) override;
481 
482     // css.frame.XDispatch
483     virtual void SAL_CALL dispatch(const css::util::URL&                                  aURL      ,
484                                    const css::uno::Sequence< css::beans::PropertyValue >& lArguments) override;
485 
486     virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
487                                             const css::util::URL&                                     aURL     ) override;
488 
489     virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
490                                                const css::util::URL&                                     aURL     ) override;
491 
492     // css.document.XDocumentEventListener
493     /** @short  informs about created/opened documents.
494 
495         @descr  Every new opened/created document will be saved internally
496                 so it can be checked if it's modified. This modified state
497                 is used later to decide, if it must be saved or not.
498 
499         @param  aEvent
500                 points to the new created/opened document.
501      */
502     virtual void SAL_CALL documentEventOccured(const css::document::DocumentEvent& aEvent) override;
503 
504     // css.util.XChangesListener
505     virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override;
506 
507     // css.util.XModifyListener
508     virtual void SAL_CALL modified(const css::lang::EventObject& aEvent) override;
509 
510     // css.lang.XEventListener
511     virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override;
512 
513 protected:
514 
515     // OPropertySetHelper
516 
517     virtual sal_Bool SAL_CALL convertFastPropertyValue(      css::uno::Any& aConvertedValue,
518                                                              css::uno::Any& aOldValue      ,
519                                                              sal_Int32      nHandle        ,
520                                                        const css::uno::Any& aValue         ) override;
521 
522     virtual void SAL_CALL setFastPropertyValue_NoBroadcast(      sal_Int32      nHandle,
523                                                            const css::uno::Any& aValue ) override;
524     using cppu::OPropertySetHelper::getFastPropertyValue;
525     virtual void SAL_CALL getFastPropertyValue(css::uno::Any& aValue ,
526                                                sal_Int32      nHandle) const override;
527 
528     virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override;
529 
530     virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
531 
532 private:
533     virtual void SAL_CALL disposing() final override;
534 
535     /** @short  open the underlying configuration.
536 
537         @descr  This method must be called every time
538                 a configuration call is needed. Because
539                 method works together with the member
540                 m_xCFG, open it on demand and cache it
541                 afterwards.
542 
543         @throw  [com.sun.star.uno.RuntimeException]
544                 if config could not be opened successfully!
545 
546         @threadsafe
547       */
548     void implts_openConfig();
549 
550     /** @short  read the underlying configuration.
551 
552         @descr  After that we know the initial state - means:
553                 - if AutoSave was enabled by the user
554                 - which time interval has to be used
555                 - which recovery entries may already exists
556 
557         @throw  [com.sun.star.uno.RuntimeException]
558                 if config could not be opened or read successfully!
559 
560         @threadsafe
561       */
562     void implts_readConfig();
563 
564     /** @short  read the underlying configuration...
565 
566         @descr  ... but only keys related to the AutoSave mechanism.
567                 Means: State and Timer interval.
568                 E.g. the recovery list is not addressed here.
569 
570         @throw  [com.sun.star.uno.RuntimeException]
571                 if config could not be opened or read successfully!
572 
573         @threadsafe
574       */
575     void implts_readAutoSaveConfig();
576 
577     // TODO document me
578     void implts_flushConfigItem(const AutoRecovery::TDocumentInfo& rInfo                ,
579                                       bool                     bRemoveIt = false);
580 
581     // TODO document me
582     void implts_startListening();
583     void implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo);
584 
585     // TODO document me
586     void implts_stopListening();
587     void implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo);
588 
589     /** @short  stops and may be(!) restarts the timer.
590 
591         @descr  A running timer is stopped every time here.
592                 But starting depends from the different internal
593                 timer variables (e.g. AutoSaveEnabled, AutoSaveTimeIntervall,
594                 TimerType etcpp.)
595 
596         @throw  [com.sun.star.uno.RuntimeException]
597                 if timer could not be stopped or started!
598 
599         @threadsafe
600      */
601     void implts_updateTimer();
602 
603     /** @short  stop the timer.
604 
605         @descr  Double calls will be ignored - means we do
606                 nothing here, if the timer is already disabled.
607 
608         @throw  [com.sun.star.uno.RuntimeException]
609                 if timer could not be stopped!
610 
611         @threadsafe
612      */
613     void implts_stopTimer();
614 
615     /** @short  callback of our internal timer.
616      */
617     DECL_LINK(implts_timerExpired, Timer*, void);
618 
619     /** @short  makes our dispatch() method asynchronous!
620      */
621     DECL_LINK(implts_asyncDispatch, LinkParamNone*, void);
622 
623     /** @short  implements the dispatch real. */
624     void implts_dispatch(const DispatchParams& aParams);
625 
626     /** @short  validate new detected document and add it into the internal
627                 document list.
628 
629         @descr  This method should be called only if it's clear that a new
630                 document was opened/created during office runtime.
631                 This method checks if it's a top level document (means not an embedded one).
632                 Only such top level documents can be recognized by this auto save mechanism.
633 
634         @param  xDocument
635                 the new document, which should be checked and registered.
636 
637         @threadsafe
638      */
639     void implts_registerDocument(const css::uno::Reference< css::frame::XModel3 >& xDocument);
640 
641     /** @short  remove the specified document from our internal document list.
642 
643         @param  xDocument
644                 the new document, which should be deregistered.
645 
646         @param  bStopListening
647                 sal_False: must be used in case this method is called within disposing() of the document,
648                        where it makes no sense to deregister our listener. The container dies...
649                 sal_True : must be used in case this method is used on "deregistration" of this document, where
650                        we must deregister our listener .-)
651 
652         @threadsafe
653      */
654     void implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument                ,
655                                          bool                                   bStopListening = true);
656 
657     // TODO document me
658     void implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument);
659 
660     // TODO document me
661     void implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument);
662 
663     // TODO document me
664     void implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument      ,
665                                                        bool                                   bSaveInProgress);
666 
667     // TODO document me
668     void implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument);
669 
670     /** @short  search a document inside given list.
671 
672         @param  rList
673                 reference to a vector, which can contain such
674                 document.
675 
676         @param  xDocument
677                 the document, which should be located inside the
678                 given list.
679 
680         @return [TDocumentList::iterator]
681                 which points to the located document.
682                 If document does not exists - its set to
683                 rList.end()!
684      */
685     static TDocumentList::iterator impl_searchDocument(      AutoRecovery::TDocumentList&               rList    ,
686                                                        const css::uno::Reference< css::frame::XModel >& xDocument);
687 
688     /** TODO document me */
689     void implts_changeAllDocVisibility(bool bVisible);
690     void implts_prepareSessionShutdown();
691 
692     /** @short  save all current opened documents to a specific
693                 backup directory.
694 
695         @descr  Only really changed documents will be saved here.
696 
697                 Further this method returns a suggestion, if and how it should
698                 be called again. May be some documents was not saved yet
699                 and must wait for an user idle period ...
700 
701         @param  bAllowUserIdleLoop
702                 Because this method is used for different uses cases, it must
703                 know, which actions are allowed or not.
704                 Job::AutoSave =>
705                              If a document is the most active one, saving it
706                              will be postponed if there exists other unsaved
707                              documents. This feature was implemented, because
708                              we don't wish to disturb the user on it's work.
709                              ... bAllowUserIdleLoop should be set to sal_True
710                 Job::EmergencySave / Job::SessionSave =>
711                              Here we must finish our work ASAP! It's not allowed
712                              to postpone any document.
713                              ... bAllowUserIdleLoop must(!) be set to sal_False
714 
715         @param  pParams
716                 sometimes this method is required inside an external dispatch request.
717                 The it contains some special environment variables, which overwrites
718                 our normal environment.
719                 AutoSave              => pParams == 0
720                 SessionSave/CrashSave => pParams != 0
721 
722         @return A suggestion, how the timer (if it's not already disabled!)
723                 should be restarted to fulfill the requirements.
724 
725         @threadsafe
726      */
727     AutoRecovery::ETimerType implts_saveDocs(      bool        bAllowUserIdleLoop,
728                                                    bool        bRemoveLockFiles,
729                                              const DispatchParams* pParams        = nullptr);
730 
731     /** @short  save one of the current documents to a specific
732                 backup directory.
733 
734         @descr  It:
735                 - defines a new(!) unique temp file name
736                 - save the new temp file
737                 - remove the old temp file
738                 - patch the given info struct
739                 - and return errors.
740 
741                 It does not:
742                 - patch the configuration.
743 
744                 Note further: it patches the info struct
745                 more than ones. E.g. the new temp URL is set
746                 before the file is saved. And the old URL is removed
747                 only if removing of the old file was successful.
748                 If this method returns without an exception - everything
749                 was OK. Otherwise the info struct can be analyzed to
750                 get more information, e.g. when the problem occurs.
751 
752         @param  sBackupPath
753                 the base path for saving such temp files.
754 
755         @param  rInfo
756                 points to an information structure, where
757                 e.g. the document, its modified state, the count
758                 of autosave-retries etcpp. exists.
759                 It's used also to return the new temp file name
760                 and some other state values!
761 
762         @threadsafe
763       */
764     void implts_saveOneDoc(const OUString&                                    sBackupPath      ,
765                                  AutoRecovery::TDocumentInfo&                        rInfo            ,
766                            const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress);
767 
768     /** @short  recovery all documents, which was saved during
769                 a crash before.
770 
771         @return A suggestion, how this method must be called back!
772 
773         @threadsafe
774      */
775     AutoRecovery::ETimerType implts_openDocs(const DispatchParams& aParams);
776 
777     // TODO document me
778     void implts_openOneDoc(const OUString&               sURL       ,
779                                  utl::MediaDescriptor& lDescriptor,
780                                  AutoRecovery::TDocumentInfo&   rInfo      );
781 
782     // TODO document me
783     void implts_generateNewTempURL(const OUString&               sBackupPath     ,
784                                          utl::MediaDescriptor& rMediaDescriptor,
785                                          AutoRecovery::TDocumentInfo&   rInfo           );
786 
787     /** @short  notifies all interested listener about the current state
788                 of the currently running operation.
789 
790         @descr  We support different set's of functions. Job::AutoSave, Job::EmergencySave,
791                 Job::Recovery ... etcpp.
792                 Listener can register itself for any type of supported
793                 functionality ... but not for document URL's in special.
794 
795         @param  eJob
796                 is used to know, which set of listener we must notify.
797 
798         @param  aEvent
799                 describe the event more in detail.
800 
801         @threadsafe
802       */
803     void implts_informListener(      Job                      eJob  ,
804                                const css::frame::FeatureStateEvent& aEvent);
805 
806     /** short   create a feature event struct, which can be send
807                 to any interested listener.
808 
809         @param  eJob
810                 describe the current running operation
811                 Job::AutoSave, Job::EmergencySave, Job::Recovery
812 
813         @param  sEventType
814                 describe the type of this event
815                 START, STOP, UPDATE
816 
817         @param  pInfo
818                 if sOperation is an update, this parameter must be different from NULL
819                 and is used to send information regarding the current handled document.
820 
821         @return [css::frame::FeatureStateEvent]
822                 the event structure for sending.
823      */
824     static css::frame::FeatureStateEvent implst_createFeatureStateEvent(      Job                    eJob      ,
825                                                                         const OUString&             sEventType,
826                                                                         AutoRecovery::TDocumentInfo const * pInfo     );
827 
828     class ListenerInformer
829     {
830     private:
831         AutoRecovery &m_rRecovery;
832         Job m_eJob;
833         bool m_bStopped;
834     public:
ListenerInformer(AutoRecovery & rRecovery,Job eJob)835         ListenerInformer(AutoRecovery &rRecovery, Job eJob)
836             : m_rRecovery(rRecovery), m_eJob(eJob), m_bStopped(false)
837         {
838         }
839         void start();
840         void stop();
~ListenerInformer()841         ~ListenerInformer()
842         {
843             stop();
844         }
845     };
846 
847     // TODO document me
848     void implts_resetHandleStates();
849 
850     // TODO document me
851     void implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo);
852 
853     // TODO document me
854     void implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo);
855 
856     /** retrieves the names of all active views of the given document
857         @param rInfo
858             the document info, whose <code>Document</code> member must not be <NULL/>.
859     */
860     void implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& rInfo );
861 
862     /** updates the configuration so that for all documents, their current view/names are stored
863     */
864     void implts_persistAllActiveViewNames();
865 
866     // TODO document me
867     void implts_prepareEmergencySave();
868 
869     // TODO document me
870     void implts_doEmergencySave(const DispatchParams& aParams);
871 
872     // TODO document me
873     void implts_doRecovery(const DispatchParams& aParams);
874 
875     // TODO document me
876     void implts_doSessionSave(const DispatchParams& aParams);
877 
878     // TODO document me
879     void implts_doSessionQuietQuit();
880 
881     // TODO document me
882     void implts_doSessionRestore(const DispatchParams& aParams);
883 
884     // TODO document me
885     void implts_backupWorkingEntry(const DispatchParams& aParams);
886 
887     // TODO document me
888     void implts_cleanUpWorkingEntry(const DispatchParams& aParams);
889 
890     /** try to make sure that all changed config items (not our used
891         config access only) will be flushed back to disc.
892 
893         E.g. our svtools::ConfigItems() has to be flushed explicitly .-(
894 
895         Note: This method can't fail. Flushing of config entries is an
896               optional feature. Errors can be ignored.
897      */
898     void impl_flushALLConfigChanges();
899 
900     // TODO document me
901     AutoRecovery::EFailureSafeResult implts_copyFile(const OUString& sSource    ,
902                                                      const OUString& sTargetPath,
903                                                      const OUString& sTargetName);
904 
905     /** @short  converts m_eJob into a job description, which
906                 can be used to inform an outside listener
907                 about the current running operation
908 
909         @param  eJob
910                 describe the current running operation
911                 Job::AutoSave, Job::EmergencySave, Job::Recovery
912 
913         @return [string]
914                 a suitable job description of form:
915                     vnd.sun.star.autorecovery:/do...
916      */
917     static OUString implst_getJobDescription(Job eJob);
918 
919     /** @short  map the given URL to an internal int representation.
920 
921         @param  aURL
922                 the url, which describe the next starting or may be already running
923                 operation.
924 
925         @return [long]
926                 the internal int representation
927                 see enum class Job
928      */
929     static Job implst_classifyJob(const css::util::URL& aURL);
930 
931     /// TODO
932     void implts_verifyCacheAgainstDesktopDocumentList();
933 
934     /// TODO document me
935     bool impl_enoughDiscSpace(sal_Int32 nRequiredSpace);
936 
937     /// TODO document me
938     static void impl_showFullDiscError();
939 
940     /** @short  try to create/use a progress and set it inside the
941                 environment.
942 
943         @descr  The problem behind: There exists different use case of this method.
944                 a) An external progress is provided by our CrashSave or Recovery dialog.
945                 b) We must create our own progress e.g. for an AutoSave
946                 c) Sometimes our application filters don't use the progress
947                    provided by the MediaDescriptor. They use the Frame every time to create
948                    its own progress. So we implemented a HACK for these and now we set
949                    an InterceptedProgress there for the time WE use this frame for loading/storing documents .-)
950 
951         @param  xNewFrame
952                 must be set only in case WE create a new frame (e.g. for loading documents
953                 on session restore or recovery). Then search for a frame using rInfo.Document must
954                 be suppressed and xFrame must be preferred instead .-)
955 
956         @param  rInfo
957                 used e.g. to find the frame corresponding to a document.
958                 This frame must be used to create a new progress e.g. for an AutoSave.
959 
960         @param  rArgs
961                 is used to set the new created progress as parameter on these set.
962      */
963     void impl_establishProgress(const AutoRecovery::TDocumentInfo&               rInfo    ,
964                                       utl::MediaDescriptor&             rArgs    ,
965                                 const css::uno::Reference< css::frame::XFrame >& xNewFrame);
966 
967     void impl_forgetProgress(const AutoRecovery::TDocumentInfo&               rInfo    ,
968                                    utl::MediaDescriptor&             rArgs    ,
969                              const css::uno::Reference< css::frame::XFrame >& xNewFrame);
970 
971     /** try to remove the specified file from disc.
972 
973         Every URL supported by our UCB component can be used here.
974         Further it doesn't matter if the file really exists or not.
975         Because removing a non existent file will have the same
976         result at the end... a non existing file .-)
977 
978         On the other side removing of files from disc is an optional
979         feature. If we are not able doing so... it's not a real problem.
980         Ok - users disc place will be smaller then... but we should produce
981         a crash during crash save because we can't delete a temporary file only!
982 
983         @param  sURL
984                 the url of the file, which should be removed.
985      */
986     void st_impl_removeFile(const OUString& sURL);
987 
988     /** try to remove ".lock" file from disc if office will be terminated
989         not using the official way .-)
990 
991         This method has to be handled "optional". So every error inside
992         has to be ignored ! This method CAN NOT FAIL ... it can forget something only .-)
993      */
994     void st_impl_removeLockFile();
995 };
996 
997 // recovery.xcu
998 constexpr OUStringLiteral CFG_PACKAGE_RECOVERY = u"org.openoffice.Office.Recovery/";
999 
1000 const char CFG_ENTRY_AUTOSAVE_ENABLED[] = "AutoSave/Enabled";
1001 const char CFG_ENTRY_AUTOSAVE_TIMEINTERVALL[] = "AutoSave/TimeIntervall"; //sic!
1002 
1003 constexpr OUStringLiteral CFG_ENTRY_REALDEFAULTFILTER = u"ooSetupFactoryActualFilter";
1004 
1005 constexpr OUStringLiteral CFG_ENTRY_PROP_TEMPURL = u"TempURL";
1006 constexpr OUStringLiteral CFG_ENTRY_PROP_ORIGINALURL = u"OriginalURL";
1007 constexpr OUStringLiteral CFG_ENTRY_PROP_TEMPLATEURL = u"TemplateURL";
1008 constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYURL = u"FactoryURL";
1009 constexpr OUStringLiteral CFG_ENTRY_PROP_MODULE = u"Module";
1010 constexpr OUStringLiteral CFG_ENTRY_PROP_DOCUMENTSTATE = u"DocumentState";
1011 constexpr OUStringLiteral CFG_ENTRY_PROP_FILTER = u"Filter";
1012 constexpr OUStringLiteral CFG_ENTRY_PROP_TITLE = u"Title";
1013 constexpr OUStringLiteral CFG_ENTRY_PROP_ID = u"ID";
1014 constexpr OUStringLiteral CFG_ENTRY_PROP_VIEWNAMES = u"ViewNames";
1015 
1016 constexpr OUStringLiteral FILTER_PROP_TYPE = u"Type";
1017 constexpr OUStringLiteral TYPE_PROP_EXTENSIONS = u"Extensions";
1018 
1019 // setup.xcu
1020 constexpr OUStringLiteral CFG_ENTRY_PROP_EMPTYDOCUMENTURL = u"ooSetupFactoryEmptyDocumentURL";
1021 constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYSERVICE = u"ooSetupFactoryDocumentService";
1022 
1023 const char EVENT_ON_NEW[] = "OnNew";
1024 const char EVENT_ON_LOAD[] = "OnLoad";
1025 const char EVENT_ON_UNLOAD[] = "OnUnload";
1026 const char EVENT_ON_MODIFYCHANGED[] = "OnModifyChanged";
1027 const char EVENT_ON_SAVE[] = "OnSave";
1028 const char EVENT_ON_SAVEAS[] = "OnSaveAs";
1029 const char EVENT_ON_SAVETO[] = "OnCopyTo";
1030 const char EVENT_ON_SAVEDONE[] = "OnSaveDone";
1031 const char EVENT_ON_SAVEASDONE[] = "OnSaveAsDone";
1032 const char EVENT_ON_SAVETODONE[] = "OnCopyToDone";
1033 const char EVENT_ON_SAVEFAILED[] = "OnSaveFailed";
1034 const char EVENT_ON_SAVEASFAILED[] = "OnSaveAsFailed";
1035 const char EVENT_ON_SAVETOFAILED[] = "OnCopyToFailed";
1036 
1037 constexpr OUStringLiteral RECOVERY_ITEM_BASE_IDENTIFIER = u"recovery_item_";
1038 
1039 const char CMD_PROTOCOL[] = "vnd.sun.star.autorecovery:";
1040 
1041 const char CMD_DO_AUTO_SAVE[] = "/doAutoSave";    // force AutoSave ignoring the AutoSave timer
1042 const char CMD_DO_PREPARE_EMERGENCY_SAVE[] = "/doPrepareEmergencySave";    // prepare the office for the following EmergencySave step (hide windows etcpp.)
1043 const char CMD_DO_EMERGENCY_SAVE[] = "/doEmergencySave";    // do EmergencySave on crash
1044 const char CMD_DO_RECOVERY[] = "/doAutoRecovery";    // recover all crashed documents
1045 const char CMD_DO_ENTRY_BACKUP[] = "/doEntryBackup";    // try to store a temp or original file to a user defined location
1046 const char CMD_DO_ENTRY_CLEANUP[] = "/doEntryCleanUp";    // remove the specified entry from the recovery cache
1047 const char CMD_DO_SESSION_SAVE[] = "/doSessionSave";    // save all open documents if e.g. a window manager closes an user session
1048 const char CMD_DO_SESSION_QUIET_QUIT[] = "/doSessionQuietQuit";    // 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
1049 const char CMD_DO_SESSION_RESTORE[] = "/doSessionRestore";    // restore a saved user session from disc
1050 const char CMD_DO_DISABLE_RECOVERY[] = "/disableRecovery";    // disable recovery and auto save (!) temp. for this office session
1051 const char CMD_DO_SET_AUTOSAVE_STATE[] = "/setAutoSaveState";    // disable/enable auto save (not crash save) for this office session
1052 
1053 constexpr OUStringLiteral REFERRER_USER = u"private:user";
1054 
1055 constexpr OUStringLiteral PROP_DISPATCH_ASYNCHRON = u"DispatchAsynchron";
1056 constexpr OUStringLiteral PROP_PROGRESS = u"StatusIndicator";
1057 constexpr OUStringLiteral PROP_SAVEPATH = u"SavePath";
1058 constexpr OUStringLiteral PROP_ENTRY_ID = u"EntryID";
1059 constexpr OUStringLiteral PROP_AUTOSAVE_STATE = u"AutoSaveState";
1060 
1061 constexpr OUStringLiteral OPERATION_START = u"start";
1062 constexpr OUStringLiteral OPERATION_STOP = u"stop";
1063 constexpr OUStringLiteral OPERATION_UPDATE = u"update";
1064 
1065 const sal_Int32       MIN_DISCSPACE_DOCSAVE                  =   5; // [MB]
1066 const sal_Int32       MIN_DISCSPACE_CONFIGSAVE               =   1; // [MB]
1067 const sal_Int32       RETRY_STORE_ON_FULL_DISC_FOREVER       = 300; // not forever ... but often enough .-)
1068 const sal_Int32       RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL =   3; // in case FULL DISC does not seem the real problem
1069 const sal_Int32       GIVE_UP_RETRY                          =   1; // in case FULL DISC does not seem the real problem
1070 
1071 #define SAVE_IN_PROGRESS            true
1072 #define SAVE_FINISHED               false
1073 
1074 #define LOCK_FOR_CACHE_ADD_REMOVE   true
1075 #define LOCK_FOR_CACHE_USE          false
1076 
1077 #define MIN_TIME_FOR_USER_IDLE 10000 // 10s user idle
1078 
1079 // enable the following defines in case you wish to simulate a full disc for debug purposes .-)
1080 
1081 // this define throws every time a document is stored or a configuration change
1082 // should be flushed an exception ... so the special error handler for this scenario is triggered
1083 // #define TRIGGER_FULL_DISC_CHECK
1084 
1085 // force "return sal_False" for the method impl_enoughDiscSpace().
1086 // #define SIMULATE_FULL_DISC
1087 
1088 class CacheLockGuard
1089 {
1090     private:
1091 
1092         // holds the outside caller alive, so it's shared resources
1093         // are valid every time
1094         css::uno::Reference< css::uno::XInterface > m_xOwner;
1095 
1096         // mutex shared with outside caller!
1097         osl::Mutex& m_rSharedMutex;
1098 
1099         // this variable knows the state of the "cache lock"
1100         sal_Int32& m_rCacheLock;
1101 
1102         // to prevent increasing/decreasing of m_rCacheLock more than once
1103         // we must know if THIS guard has an actual lock set there!
1104         bool m_bLockedByThisGuard;
1105 
1106     public:
1107 
1108         CacheLockGuard(AutoRecovery* pOwner                      ,
1109                        osl::Mutex&   rMutex                      ,
1110                        sal_Int32&    rCacheLock                  ,
1111                        bool      bLockForAddRemoveVectorItems);
1112         ~CacheLockGuard();
1113 
1114         void lock(bool bLockForAddRemoveVectorItems);
1115         void unlock();
1116 };
1117 
CacheLockGuard(AutoRecovery * pOwner,osl::Mutex & rMutex,sal_Int32 & rCacheLock,bool bLockForAddRemoveVectorItems)1118 CacheLockGuard::CacheLockGuard(AutoRecovery* pOwner                      ,
1119                                osl::Mutex&   rMutex                      ,
1120                                sal_Int32&    rCacheLock                  ,
1121                                bool      bLockForAddRemoveVectorItems)
1122     : m_xOwner            (static_cast< css::frame::XDispatch* >(pOwner))
1123     , m_rSharedMutex      (rMutex                                       )
1124     , m_rCacheLock        (rCacheLock                                   )
1125     , m_bLockedByThisGuard(false                                    )
1126 {
1127     lock(bLockForAddRemoveVectorItems);
1128 }
1129 
~CacheLockGuard()1130 CacheLockGuard::~CacheLockGuard()
1131 {
1132     unlock();
1133     m_xOwner.clear();
1134 }
1135 
lock(bool bLockForAddRemoveVectorItems)1136 void CacheLockGuard::lock(bool bLockForAddRemoveVectorItems)
1137 {
1138     /* SAFE */
1139     osl::MutexGuard g(m_rSharedMutex);
1140 
1141     if (m_bLockedByThisGuard)
1142         return;
1143 
1144     // This cache lock is needed only to prevent us from removing/adding
1145     // items from/into the recovery cache ... during it's used at another code place
1146     // for iterating .-)
1147 
1148     // Modifying of item properties is allowed and sometimes needed!
1149     // So we should detect only the dangerous state of concurrent add/remove
1150     // requests and throw an exception then ... which can of course break the whole
1151     // operation. On the other side a crash reasoned by an invalid stl iterator
1152     // will have the same effect .-)
1153 
1154     if ( (m_rCacheLock > 0) && bLockForAddRemoveVectorItems )
1155     {
1156         OSL_FAIL("Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp.");
1157         throw css::uno::RuntimeException(
1158                 "Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp.",
1159                 m_xOwner);
1160     }
1161 
1162     ++m_rCacheLock;
1163     m_bLockedByThisGuard = true;
1164     /* SAFE */
1165 }
1166 
unlock()1167 void CacheLockGuard::unlock()
1168 {
1169     /* SAFE */
1170     osl::MutexGuard g(m_rSharedMutex);
1171 
1172     if ( ! m_bLockedByThisGuard)
1173         return;
1174 
1175     --m_rCacheLock;
1176     m_bLockedByThisGuard = false;
1177 
1178     if (m_rCacheLock < 0)
1179     {
1180         OSL_FAIL("Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)");
1181         throw css::uno::RuntimeException(
1182                 "Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)",
1183                 m_xOwner);
1184     }
1185     /* SAFE */
1186 }
1187 
DispatchParams()1188 DispatchParams::DispatchParams()
1189     : m_nWorkingEntryID(-1)
1190 {
1191 };
1192 
DispatchParams(const::comphelper::SequenceAsHashMap & lArgs,const css::uno::Reference<css::uno::XInterface> & xOwner)1193 DispatchParams::DispatchParams(const ::comphelper::SequenceAsHashMap&             lArgs ,
1194                                const css::uno::Reference< css::uno::XInterface >& xOwner)
1195 {
1196     m_nWorkingEntryID         = lArgs.getUnpackedValueOrDefault(PROP_ENTRY_ID, sal_Int32(-1)                                       );
1197     m_xProgress               = lArgs.getUnpackedValueOrDefault(PROP_PROGRESS, css::uno::Reference< css::task::XStatusIndicator >());
1198     m_sSavePath               = lArgs.getUnpackedValueOrDefault(PROP_SAVEPATH, OUString()                                   );
1199     m_xHoldRefForAsyncOpAlive = xOwner;
1200 };
1201 
forget()1202 void DispatchParams::forget()
1203 {
1204     m_sSavePath.clear();
1205     m_nWorkingEntryID = -1;
1206     m_xProgress.clear();
1207     m_xHoldRefForAsyncOpAlive.clear();
1208 };
1209 
AutoRecovery(const css::uno::Reference<css::uno::XComponentContext> & xContext)1210 AutoRecovery::AutoRecovery(const css::uno::Reference< css::uno::XComponentContext >& xContext)
1211     : AutoRecovery_BASE         (m_aMutex)
1212     , ::cppu::OPropertySetHelper(cppu::WeakComponentImplHelperBase::rBHelper)
1213     , m_xContext                (xContext                                           )
1214     , m_bListenForDocEvents     (false                                          )
1215     , m_bListenForConfigChanges (false                                          )
1216     , m_nAutoSaveTimeIntervall  (0                                                  )
1217     , m_eJob                    (Job::NoJob)
1218     , m_aTimer                  ( "Auto save timer" )
1219     , m_xAsyncDispatcher        (new vcl::EventPoster( LINK( this, AutoRecovery, implts_asyncDispatch )  ))
1220     , m_eTimerType              (E_DONT_START_TIMER                                 )
1221     , m_nIdPool                 (0                                                  )
1222     , m_lListener               (cppu::WeakComponentImplHelperBase::rBHelper.rMutex)
1223     , m_nDocCacheLock           (0                                                  )
1224     , m_nMinSpaceDocSave        (MIN_DISCSPACE_DOCSAVE                              )
1225     , m_nMinSpaceConfigSave     (MIN_DISCSPACE_CONFIGSAVE                           )
1226 {
1227     m_aTimer.SetDebugName( "framework::AutoRecovery m_aTimer" );
1228 }
1229 
initListeners()1230 void AutoRecovery::initListeners()
1231 {
1232     // read configuration to know if autosave/recovery is on/off etcpp...
1233     implts_readConfig();
1234 
1235     implts_startListening();
1236 
1237     // establish callback for our internal used timer.
1238     // Note: Its only active, if the timer will be started ...
1239     SolarMutexGuard g;
1240     m_aTimer.SetInvokeHandler(LINK(this, AutoRecovery, implts_timerExpired));
1241 }
1242 
~AutoRecovery()1243 AutoRecovery::~AutoRecovery()
1244 {
1245     assert(!m_aTimer.IsActive());
1246 }
1247 
disposing()1248 void AutoRecovery::disposing()
1249 {
1250     implts_stopTimer();
1251     SolarMutexGuard g;
1252     m_xAsyncDispatcher.reset();
1253 }
1254 
queryInterface(const css::uno::Type & _rType)1255 Any SAL_CALL AutoRecovery::queryInterface( const css::uno::Type& _rType )
1256 {
1257     Any aRet = AutoRecovery_BASE::queryInterface( _rType );
1258     if ( !aRet.hasValue() )
1259         aRet = OPropertySetHelper::queryInterface( _rType );
1260     return aRet;
1261 }
1262 
getTypes()1263 Sequence< css::uno::Type > SAL_CALL AutoRecovery::getTypes(  )
1264 {
1265     return comphelper::concatSequences(
1266         AutoRecovery_BASE::getTypes(),
1267         ::cppu::OPropertySetHelper::getTypes()
1268     );
1269 }
1270 
dispatch(const css::util::URL & aURL,const css::uno::Sequence<css::beans::PropertyValue> & lArguments)1271 void SAL_CALL AutoRecovery::dispatch(const css::util::URL&                                  aURL      ,
1272                                      const css::uno::Sequence< css::beans::PropertyValue >& lArguments)
1273 {
1274     SAL_INFO("fwk.autorecovery", "AutoRecovery::dispatch() starts ..." << aURL.Complete);
1275 
1276     // valid request ?
1277     Job eNewJob = AutoRecovery::implst_classifyJob(aURL);
1278     if (eNewJob == Job::NoJob)
1279         return;
1280 
1281     bool bAsync;
1282     DispatchParams aParams;
1283     /* SAFE */ {
1284     osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1285 
1286     // still running operation ... ignoring Job::AutoSave.
1287     // All other requests has higher prio!
1288     if (
1289         ( m_eJob                               != Job::NoJob   ) &&
1290         ((m_eJob & Job::AutoSave ) != Job::AutoSave)
1291        )
1292     {
1293         SAL_INFO("fwk.autorecovery", "AutoRecovery::dispatch(): There is already an asynchronous dispatch() running. New request will be ignored!");
1294         return;
1295     }
1296 
1297     ::comphelper::SequenceAsHashMap lArgs(lArguments);
1298 
1299     // check if somewhere wish to disable recovery temp. for this office session
1300     // This can be done immediately... must not been done asynchronous.
1301     if ((eNewJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
1302     {
1303         // it's important to set a flag internally, so AutoRecovery will be suppressed - even if it's requested.
1304         m_eJob |= eNewJob;
1305         implts_stopTimer();
1306         implts_stopListening();
1307         return;
1308     }
1309 
1310     // disable/enable AutoSave for this office session only
1311     // independent from the configuration entry.
1312     if ((eNewJob & Job::SetAutosaveState) == Job::SetAutosaveState)
1313     {
1314         bool bOn = lArgs.getUnpackedValueOrDefault(PROP_AUTOSAVE_STATE, true);
1315         if (bOn)
1316         {
1317             // don't enable AutoSave hardly !
1318             // reload configuration to know the current state.
1319             implts_readAutoSaveConfig();
1320             g.clear();
1321             implts_updateTimer();
1322             // can it happen that might be the listener was stopped? .-)
1323             // make sure it runs always... even if AutoSave itself was disabled temporarily.
1324             implts_startListening();
1325         }
1326         else
1327         {
1328             implts_stopTimer();
1329             m_eJob       &= ~Job::AutoSave;
1330             m_eTimerType  =  AutoRecovery::E_DONT_START_TIMER;
1331         }
1332         return;
1333     }
1334 
1335     m_eJob |= eNewJob;
1336 
1337     bAsync = lArgs.getUnpackedValueOrDefault(PROP_DISPATCH_ASYNCHRON, false);
1338     aParams = DispatchParams(lArgs, static_cast< css::frame::XDispatch* >(this));
1339 
1340     // Hold this instance alive till the asynchronous operation will be finished.
1341     if (bAsync)
1342         m_aDispatchParams = aParams;
1343 
1344     } /* SAFE */
1345 
1346     if (bAsync)
1347         m_xAsyncDispatcher->Post();
1348     else
1349         implts_dispatch(aParams);
1350 }
1351 
start()1352 void AutoRecovery::ListenerInformer::start()
1353 {
1354     m_rRecovery.implts_informListener(m_eJob,
1355         AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_START, nullptr));
1356 }
1357 
stop()1358 void AutoRecovery::ListenerInformer::stop()
1359 {
1360     if (m_bStopped)
1361         return;
1362     m_rRecovery.implts_informListener(m_eJob,
1363         AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_STOP, nullptr));
1364     m_bStopped = true;
1365 }
1366 
implts_dispatch(const DispatchParams & aParams)1367 void AutoRecovery::implts_dispatch(const DispatchParams& aParams)
1368 {
1369     Job eJob;
1370     /* SAFE */ {
1371     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1372     eJob = m_eJob;
1373     } /* SAFE */
1374 
1375     // in case a new dispatch overwrites a may ba active AutoSave session
1376     // we must restore this session later. see below ...
1377     bool bWasAutoSaveActive = ((eJob & Job::AutoSave) == Job::AutoSave);
1378     bool bWasUserAutoSaveActive =
1379         ((eJob & Job::UserAutoSave) == Job::UserAutoSave);
1380 
1381     // On the other side it makes no sense to reactivate the AutoSave operation
1382     // if the new dispatch indicates a final decision...
1383     // E.g. an EmergencySave/SessionSave indicates the end of life of the current office session.
1384     // It makes no sense to reactivate an AutoSave then.
1385     // But a Recovery or SessionRestore should reactivate a may be already active AutoSave.
1386     bool bAllowAutoSaveReactivation = true;
1387 
1388     implts_stopTimer();
1389     implts_stopListening();
1390 
1391     ListenerInformer aListenerInformer(*this, eJob);
1392     aListenerInformer.start();
1393 
1394     try
1395     {
1396         //  Auto save is called from our internal timer ... not via dispatch() API !
1397         // else
1398         if (
1399             ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave) &&
1400             ((eJob & Job::DisableAutorecovery      ) != Job::DisableAutorecovery      )
1401            )
1402         {
1403             SAL_INFO("fwk.autorecovery", "... prepare emergency save ...");
1404             bAllowAutoSaveReactivation = false;
1405             implts_prepareEmergencySave();
1406         }
1407         else
1408         if (
1409             ((eJob & Job::EmergencySave  ) == Job::EmergencySave  ) &&
1410             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1411            )
1412         {
1413             SAL_INFO("fwk.autorecovery", "... do emergency save ...");
1414             bAllowAutoSaveReactivation = false;
1415             implts_doEmergencySave(aParams);
1416         }
1417         else
1418         if (
1419             ((eJob & Job::Recovery        ) == Job::Recovery        ) &&
1420             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1421            )
1422         {
1423             SAL_INFO("fwk.autorecovery", "... do recovery ...");
1424             implts_doRecovery(aParams);
1425         }
1426         else
1427         if (
1428             ((eJob & Job::SessionSave    ) == Job::SessionSave    ) &&
1429             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1430             )
1431         {
1432             SAL_INFO("fwk.autorecovery", "... do session save ...");
1433             bAllowAutoSaveReactivation = false;
1434             implts_doSessionSave(aParams);
1435         }
1436         else
1437         if (
1438             ((eJob & Job::SessionQuietQuit    ) == Job::SessionQuietQuit ) &&
1439             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1440             )
1441         {
1442             SAL_INFO("fwk.autorecovery", "... do session quiet quit ...");
1443             bAllowAutoSaveReactivation = false;
1444             implts_doSessionQuietQuit();
1445         }
1446         else
1447         if (
1448             ((eJob & Job::SessionRestore ) == Job::SessionRestore ) &&
1449             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1450             )
1451         {
1452             SAL_INFO("fwk.autorecovery", "... do session restore ...");
1453             implts_doSessionRestore(aParams);
1454         }
1455         else
1456         if (
1457             ((eJob & Job::EntryBackup    ) == Job::EntryBackup    ) &&
1458             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1459             )
1460             implts_backupWorkingEntry(aParams);
1461         else
1462         if (
1463             ((eJob & Job::EntryCleanup   ) == Job::EntryCleanup   ) &&
1464             ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
1465             )
1466             implts_cleanUpWorkingEntry(aParams);
1467     }
1468     catch(const css::uno::RuntimeException&)
1469     {
1470         throw;
1471     }
1472     catch(const css::uno::Exception&)
1473     {
1474         // TODO better error handling
1475     }
1476 
1477     aListenerInformer.stop();
1478 
1479     /* SAFE */ {
1480     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1481     m_eJob = Job::NoJob;
1482     if ( bAllowAutoSaveReactivation && bWasAutoSaveActive )
1483     {
1484         m_eJob |= Job::AutoSave;
1485 
1486         if (bWasUserAutoSaveActive)
1487         {
1488             m_eJob |= Job::UserAutoSave;
1489         }
1490     }
1491 
1492     } /* SAFE */
1493 
1494     // depends on bAllowAutoSaveReactivation implicitly by looking on m_eJob=Job::AutoSave! see before ...
1495     implts_updateTimer();
1496 
1497     if (bAllowAutoSaveReactivation)
1498         implts_startListening();
1499 }
1500 
addStatusListener(const css::uno::Reference<css::frame::XStatusListener> & xListener,const css::util::URL & aURL)1501 void SAL_CALL AutoRecovery::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
1502                                               const css::util::URL&                                     aURL     )
1503 {
1504     if (!xListener.is())
1505         throw css::uno::RuntimeException("Invalid listener reference.", static_cast< css::frame::XDispatch* >(this));
1506     // container is threadsafe by using a shared mutex!
1507     m_lListener.addInterface(aURL.Complete, xListener);
1508 
1509     // REENTRANT !? -> --------------------------------
1510     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
1511 
1512     /* SAFE */ {
1513     osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1514 
1515     for (auto const& elem : m_lDocCache)
1516     {
1517         css::frame::FeatureStateEvent aEvent = AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_UPDATE, &elem);
1518 
1519         // } /* SAFE */
1520         g.clear();
1521         xListener->statusChanged(aEvent);
1522         g.reset();
1523         // /* SAFE */ {
1524     }
1525 
1526     } /* SAFE */
1527 }
1528 
removeStatusListener(const css::uno::Reference<css::frame::XStatusListener> & xListener,const css::util::URL & aURL)1529 void SAL_CALL AutoRecovery::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
1530                                                  const css::util::URL&                                     aURL     )
1531 {
1532     if (!xListener.is())
1533         throw css::uno::RuntimeException("Invalid listener reference.", static_cast< css::frame::XDispatch* >(this));
1534     // container is threadsafe by using a shared mutex!
1535     m_lListener.removeInterface(aURL.Complete, xListener);
1536 }
1537 
documentEventOccured(const css::document::DocumentEvent & aEvent)1538 void SAL_CALL AutoRecovery::documentEventOccured(const css::document::DocumentEvent& aEvent)
1539 {
1540     css::uno::Reference< css::frame::XModel3 > xDocument(aEvent.Source, css::uno::UNO_QUERY);
1541 
1542     // new document => put it into the internal list
1543     if (
1544         (aEvent.EventName == EVENT_ON_NEW) ||
1545         (aEvent.EventName == EVENT_ON_LOAD)
1546        )
1547     {
1548         implts_registerDocument(xDocument);
1549     }
1550     // document modified => set its modify state new (means modified against the original file!)
1551     else if ( aEvent.EventName == EVENT_ON_MODIFYCHANGED )
1552     {
1553         implts_updateModifiedState(xDocument);
1554     }
1555     /* at least one document starts saving process =>
1556        Our application code is not ready for multiple save requests
1557        at the same time. So we have to suppress our AutoSave feature
1558        for the moment, till this other save requests will be finished.
1559      */
1560     else if (
1561         (aEvent.EventName == EVENT_ON_SAVE) ||
1562         (aEvent.EventName == EVENT_ON_SAVEAS) ||
1563         (aEvent.EventName == EVENT_ON_SAVETO)
1564        )
1565     {
1566         implts_updateDocumentUsedForSavingState(xDocument, SAVE_IN_PROGRESS);
1567     }
1568     // document saved => remove tmp. files - but hold config entries alive!
1569     else if (
1570         (aEvent.EventName == EVENT_ON_SAVEDONE) ||
1571         (aEvent.EventName == EVENT_ON_SAVEASDONE)
1572        )
1573     {
1574         SolarMutexGuard g;
1575         implts_markDocumentAsSaved(xDocument);
1576         implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED);
1577     }
1578     /* document saved as copy => mark it as "non used by concurrent save operation".
1579        so we can try to create a backup copy if next time AutoSave is started too.
1580        Don't remove temp. files or change the modified state of the document!
1581        It was not really saved to the original file...
1582     */
1583     else if ( aEvent.EventName == EVENT_ON_SAVETODONE )
1584     {
1585         implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED);
1586     }
1587     // If saving of a document failed by an error ... we have to save this document
1588     // by ourself next time AutoSave or EmergencySave is triggered.
1589     // But we can reset the state "used for other save requests". Otherwise
1590     // these documents will never be saved!
1591     else if (
1592         (aEvent.EventName == EVENT_ON_SAVEFAILED) ||
1593         (aEvent.EventName == EVENT_ON_SAVEASFAILED) ||
1594         (aEvent.EventName == EVENT_ON_SAVETOFAILED)
1595        )
1596     {
1597         implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED);
1598     }
1599     // document closed => remove temp. files and configuration entries
1600     else if ( aEvent.EventName == EVENT_ON_UNLOAD )
1601     {
1602         implts_deregisterDocument(xDocument); // sal_True => stop listening for disposing() !
1603     }
1604 }
1605 
changesOccurred(const css::util::ChangesEvent & aEvent)1606 void SAL_CALL AutoRecovery::changesOccurred(const css::util::ChangesEvent& aEvent)
1607 {
1608     const css::uno::Sequence< css::util::ElementChange > lChanges (aEvent.Changes);
1609     const css::util::ElementChange*                      pChanges = lChanges.getConstArray();
1610 
1611     sal_Int32 c = lChanges.getLength();
1612     sal_Int32 i = 0;
1613 
1614     /* SAFE */ {
1615     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1616 
1617     // Changes of the configuration must be ignored if AutoSave/Recovery was disabled for this
1618     // office session. That can happen if e.g. the command line arguments "--norestore" or "--headless"
1619     // was set.
1620     if ((m_eJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
1621        return;
1622 
1623     for (i=0; i<c; ++i)
1624     {
1625         OUString sPath;
1626         pChanges[i].Accessor >>= sPath;
1627 
1628         if ( sPath == CFG_ENTRY_AUTOSAVE_ENABLED )
1629         {
1630             bool bEnabled = false;
1631             if (pChanges[i].Element >>= bEnabled)
1632             {
1633                 if (bEnabled)
1634                 {
1635                     m_eJob       |= Job::AutoSave;
1636                     m_eTimerType  = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL;
1637                 }
1638                 else
1639                 {
1640                     m_eJob       &= ~Job::AutoSave;
1641                     m_eTimerType  = AutoRecovery::E_DONT_START_TIMER;
1642                 }
1643             }
1644         }
1645         else
1646         if ( sPath == CFG_ENTRY_AUTOSAVE_TIMEINTERVALL )
1647             pChanges[i].Element >>= m_nAutoSaveTimeIntervall;
1648     }
1649 
1650     } /* SAFE */
1651 
1652     // Note: This call stops the timer and starts it again.
1653     // But it checks the different timer states internally and
1654     // may be suppress the restart!
1655     implts_updateTimer();
1656 }
1657 
modified(const css::lang::EventObject & aEvent)1658 void SAL_CALL AutoRecovery::modified(const css::lang::EventObject& aEvent)
1659 {
1660     css::uno::Reference< css::frame::XModel > xDocument(aEvent.Source, css::uno::UNO_QUERY);
1661     if (! xDocument.is())
1662         return;
1663 
1664     implts_markDocumentModifiedAgainstLastBackup(xDocument);
1665 }
1666 
disposing(const css::lang::EventObject & aEvent)1667 void SAL_CALL AutoRecovery::disposing(const css::lang::EventObject& aEvent)
1668 {
1669     /* SAFE */
1670     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1671 
1672     if (aEvent.Source == m_xNewDocBroadcaster)
1673     {
1674         m_xNewDocBroadcaster.clear();
1675         return;
1676     }
1677 
1678     if (aEvent.Source == m_xRecoveryCFG)
1679     {
1680         m_xRecoveryCFG.clear();
1681         return;
1682     }
1683 
1684     // dispose from one of our cached documents ?
1685     // Normally they should send a OnUnload message ...
1686     // But some stacktraces shows another possible use case .-)
1687     css::uno::Reference< css::frame::XModel > xDocument(aEvent.Source, css::uno::UNO_QUERY);
1688     if (xDocument.is())
1689     {
1690         implts_deregisterDocument(xDocument, false); // sal_False => don't call removeEventListener() .. because it's not needed here
1691         return;
1692     }
1693 
1694     /* SAFE */
1695 }
1696 
implts_openConfig()1697 void AutoRecovery::implts_openConfig()
1698 {
1699     /* SAFE */ {
1700     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1701 
1702     if (m_xRecoveryCFG.is())
1703         return;
1704     } /* SAFE */
1705 
1706     css::uno::Reference<css::lang::XMultiServiceFactory> xConfigProvider(
1707             css::configuration::theDefaultProvider::get(m_xContext));
1708 
1709     std::vector<css::uno::Any> lParams;
1710     css::beans::PropertyValue aParam;
1711 
1712     // set root path
1713     aParam.Name = "nodepath";
1714     aParam.Value <<= OUString(CFG_PACKAGE_RECOVERY);
1715     lParams.push_back(css::uno::Any(aParam));
1716 
1717     // throws a RuntimeException if an error occurs!
1718     css::uno::Reference<css::container::XNameAccess> xCFG(
1719             xConfigProvider->createInstanceWithArguments(
1720                     "com.sun.star.configuration.ConfigurationAccess",
1721                     comphelper::containerToSequence(lParams)),
1722             css::uno::UNO_QUERY);
1723 
1724     sal_Int32 nMinSpaceDocSave    = MIN_DISCSPACE_DOCSAVE;
1725     sal_Int32 nMinSpaceConfigSave = MIN_DISCSPACE_CONFIGSAVE;
1726 
1727     try
1728     {
1729         nMinSpaceDocSave = officecfg::Office::Recovery::AutoSave::MinSpaceDocSave::get(m_xContext);
1730         nMinSpaceConfigSave = officecfg::Office::Recovery::AutoSave::MinSpaceConfigSave::get(m_xContext);
1731     }
1732     catch(const css::uno::Exception&)
1733     {
1734         // These config keys are not sooooo important, that
1735         // we are interested on errors here really .-)
1736         nMinSpaceDocSave    = MIN_DISCSPACE_DOCSAVE;
1737         nMinSpaceConfigSave = MIN_DISCSPACE_CONFIGSAVE;
1738     }
1739 
1740     /* SAFE */ {
1741     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1742     m_xRecoveryCFG        = xCFG;
1743     m_nMinSpaceDocSave    = nMinSpaceDocSave;
1744     m_nMinSpaceConfigSave = nMinSpaceConfigSave;
1745     } /* SAFE */
1746 }
1747 
implts_readAutoSaveConfig()1748 void AutoRecovery::implts_readAutoSaveConfig()
1749 {
1750     implts_openConfig();
1751 
1752     // AutoSave [bool]
1753     bool bEnabled(officecfg::Office::Recovery::AutoSave::Enabled::get(m_xContext));
1754 
1755     /* SAFE */ {
1756     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1757     if (bEnabled)
1758     {
1759         bool bUserEnabled(officecfg::Office::Recovery::AutoSave::UserAutoSaveEnabled::get(m_xContext));
1760 
1761         m_eJob       |= Job::AutoSave;
1762         m_eTimerType  = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL;
1763 
1764         if (bUserEnabled)
1765         {
1766             m_eJob |= Job::UserAutoSave;
1767         }
1768         else
1769         {
1770             m_eJob &= ~Job::UserAutoSave;
1771         }
1772     }
1773     else
1774     {
1775         m_eJob       &= ~Job::AutoSave;
1776         m_eTimerType  = AutoRecovery::E_DONT_START_TIMER;
1777     }
1778     } /* SAFE */
1779 
1780     // AutoSaveTimeIntervall [int] in min
1781     sal_Int32 nTimeIntervall(officecfg::Office::Recovery::AutoSave::TimeIntervall::get(m_xContext));
1782 
1783     /* SAFE */ {
1784     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1785     m_nAutoSaveTimeIntervall = nTimeIntervall;
1786     } /* SAFE */
1787 }
1788 
implts_readConfig()1789 void AutoRecovery::implts_readConfig()
1790 {
1791     implts_readAutoSaveConfig();
1792 
1793     // REENTRANT -> --------------------------------
1794     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE);
1795 
1796     /* SAFE */ {
1797     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1798     // reset current cache load cache
1799     m_lDocCache.clear();
1800     m_nIdPool = 0;
1801     } /* SAFE */
1802 
1803     aCacheLock.unlock();
1804     // <- REENTRANT --------------------------------
1805 
1806     css::uno::Reference<css::container::XNameAccess> xRecoveryList(
1807             officecfg::Office::Recovery::RecoveryList::get(m_xContext));
1808     const OUString sRECOVERY_ITEM_BASE_IDENTIFIER(RECOVERY_ITEM_BASE_IDENTIFIER);
1809     const css::uno::Sequence< OUString > lItems = xRecoveryList->getElementNames();
1810     const OUString*                      pItems = lItems.getConstArray();
1811     sal_Int32                            c      = lItems.getLength();
1812     sal_Int32                            i      = 0;
1813 
1814     // REENTRANT -> --------------------------
1815     aCacheLock.lock(LOCK_FOR_CACHE_ADD_REMOVE);
1816 
1817     for (i=0; i<c; ++i)
1818     {
1819         css::uno::Reference< css::beans::XPropertySet > xItem;
1820         xRecoveryList->getByName(pItems[i]) >>= xItem;
1821         if (!xItem.is())
1822             continue;
1823 
1824         AutoRecovery::TDocumentInfo aInfo;
1825         aInfo.NewTempURL.clear();
1826         aInfo.Document.clear();
1827         xItem->getPropertyValue(CFG_ENTRY_PROP_ORIGINALURL) >>= aInfo.OrgURL;
1828         xItem->getPropertyValue(CFG_ENTRY_PROP_TEMPURL) >>= aInfo.OldTempURL;
1829         xItem->getPropertyValue(CFG_ENTRY_PROP_TEMPLATEURL) >>= aInfo.TemplateURL;
1830         xItem->getPropertyValue(CFG_ENTRY_PROP_FILTER) >>= aInfo.RealFilter;
1831         sal_Int32 tmp = 0;
1832         xItem->getPropertyValue(CFG_ENTRY_PROP_DOCUMENTSTATE) >>= tmp;
1833         aInfo.DocumentState = DocState(tmp);
1834         xItem->getPropertyValue(CFG_ENTRY_PROP_MODULE) >>= aInfo.AppModule;
1835         xItem->getPropertyValue(CFG_ENTRY_PROP_TITLE) >>= aInfo.Title;
1836         xItem->getPropertyValue(CFG_ENTRY_PROP_VIEWNAMES) >>= aInfo.ViewNames;
1837         implts_specifyAppModuleAndFactory(aInfo);
1838         implts_specifyDefaultFilterAndExtension(aInfo);
1839 
1840         if (pItems[i].startsWith(sRECOVERY_ITEM_BASE_IDENTIFIER))
1841         {
1842             OUString sID = pItems[i].copy(sRECOVERY_ITEM_BASE_IDENTIFIER.getLength());
1843             aInfo.ID = sID.toInt32();
1844             /* SAFE */ {
1845             osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1846             if (aInfo.ID > m_nIdPool)
1847             {
1848                 m_nIdPool = aInfo.ID+1;
1849                 SAL_WARN_IF(m_nIdPool<0, "fwk.autorecovery", "AutoRecovery::implts_readConfig(): Overflow of IDPool detected!");
1850             }
1851             } /* SAFE */
1852         }
1853         else
1854             SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_readConfig(): Who changed numbering of recovery items? Cache will be inconsistent then! I do not know, what will happen next time .-)");
1855 
1856         /* SAFE */ {
1857         osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1858         m_lDocCache.push_back(aInfo);
1859         } /* SAFE */
1860     }
1861 
1862     aCacheLock.unlock();
1863     // <- REENTRANT --------------------------
1864 
1865     implts_updateTimer();
1866 }
1867 
implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo & rInfo)1868 void AutoRecovery::implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo)
1869 {
1870     if (rInfo.AppModule.isEmpty())
1871     {
1872         throw css::uno::RuntimeException(
1873                 "Can not find out the default filter and its extension, if no application module is known!",
1874                 static_cast< css::frame::XDispatch* >(this));
1875     }
1876 
1877     css::uno::Reference< css::container::XNameAccess> xCFG;
1878     /* SAFE */ {
1879     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1880     xCFG = m_xModuleCFG;
1881     } /* SAFE */
1882 
1883     try
1884     {
1885         if (! xCFG.is())
1886         {
1887             implts_openConfig();
1888             // open module config on demand and cache the update access
1889             xCFG.set(officecfg::Setup::Office::Factories::get(m_xContext),
1890                     css::uno::UNO_SET_THROW);
1891 
1892             /* SAFE */ {
1893             osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1894             m_xModuleCFG = xCFG;
1895             } /* SAFE */
1896         }
1897 
1898         css::uno::Reference< css::container::XNameAccess > xModuleProps(
1899             xCFG->getByName(rInfo.AppModule),
1900             css::uno::UNO_QUERY_THROW);
1901 
1902         xModuleProps->getByName(CFG_ENTRY_REALDEFAULTFILTER) >>= rInfo.DefaultFilter;
1903 
1904         css::uno::Reference< css::container::XNameAccess > xFilterCFG(
1905                 m_xContext->getServiceManager()->createInstanceWithContext(
1906                     "com.sun.star.document.FilterFactory", m_xContext), css::uno::UNO_QUERY_THROW);
1907         css::uno::Reference< css::container::XNameAccess > xTypeCFG(
1908                 m_xContext->getServiceManager()->createInstanceWithContext(
1909                     "com.sun.star.document.TypeDetection", m_xContext), css::uno::UNO_QUERY_THROW);
1910 
1911         ::comphelper::SequenceAsHashMap       lFilterProps        (xFilterCFG->getByName(rInfo.DefaultFilter));
1912         OUString                       sTypeRegistration   = lFilterProps.getUnpackedValueOrDefault(FILTER_PROP_TYPE, OUString());
1913         ::comphelper::SequenceAsHashMap       lTypeProps          (xTypeCFG->getByName(sTypeRegistration));
1914         css::uno::Sequence< OUString > lExtensions         = lTypeProps.getUnpackedValueOrDefault(TYPE_PROP_EXTENSIONS, css::uno::Sequence< OUString >());
1915         if (lExtensions.hasElements())
1916         {
1917             rInfo.Extension = "." + lExtensions[0];
1918         }
1919         else
1920             rInfo.Extension = ".unknown";
1921     }
1922     catch(const css::uno::Exception&)
1923     {
1924         rInfo.DefaultFilter.clear();
1925         rInfo.Extension.clear();
1926     }
1927 }
1928 
implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo & rInfo)1929 void AutoRecovery::implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo)
1930 {
1931     ENSURE_OR_THROW2(
1932         !rInfo.AppModule.isEmpty() || rInfo.Document.is(),
1933         "Can not find out the application module nor its factory URL, if no application module (or a suitable) document is known!",
1934         *this );
1935 
1936     css::uno::Reference< css::frame::XModuleManager2 > xManager = ModuleManager::create(m_xContext);
1937 
1938     if (rInfo.AppModule.isEmpty())
1939         rInfo.AppModule = xManager->identify(rInfo.Document);
1940 
1941     ::comphelper::SequenceAsHashMap lModuleDescription(xManager->getByName(rInfo.AppModule));
1942     lModuleDescription[OUString(CFG_ENTRY_PROP_EMPTYDOCUMENTURL)] >>= rInfo.FactoryURL;
1943     lModuleDescription[OUString(CFG_ENTRY_PROP_FACTORYSERVICE)] >>= rInfo.FactoryService;
1944 }
1945 
implts_collectActiveViewNames(AutoRecovery::TDocumentInfo & i_rInfo)1946 void AutoRecovery::implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& i_rInfo )
1947 {
1948     ENSURE_OR_THROW2( i_rInfo.Document.is(), "need at document, at the very least", *this );
1949 
1950     i_rInfo.ViewNames.realloc(0);
1951 
1952     // obtain list of controllers of this document
1953     ::std::vector< OUString > aViewNames;
1954     const Reference< XModel2 > xModel( i_rInfo.Document, UNO_QUERY );
1955     if ( xModel.is() )
1956     {
1957         const Reference< css::container::XEnumeration > xEnumControllers( xModel->getControllers() );
1958         while ( xEnumControllers->hasMoreElements() )
1959         {
1960             const Reference< XController2 > xController( xEnumControllers->nextElement(), UNO_QUERY );
1961             OUString sViewName;
1962             if ( xController.is() )
1963                 sViewName = xController->getViewControllerName();
1964             OSL_ENSURE( !sViewName.isEmpty(), "AutoRecovery::implts_collectActiveViewNames: (no XController2 ->) no view name -> no recovery of this view!" );
1965 
1966             if ( !sViewName.isEmpty() )
1967                 aViewNames.push_back( sViewName );
1968         }
1969     }
1970 
1971     i_rInfo.ViewNames.realloc( aViewNames.size() );
1972     ::std::copy( aViewNames.begin(), aViewNames.end(), i_rInfo.ViewNames.getArray() );
1973 }
1974 
implts_persistAllActiveViewNames()1975 void AutoRecovery::implts_persistAllActiveViewNames()
1976 {
1977     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
1978 
1979     // This list will be filled with every document
1980     for (auto & elem : m_lDocCache)
1981     {
1982         implts_collectActiveViewNames(elem);
1983         implts_flushConfigItem(elem);
1984     }
1985 }
1986 
implts_flushConfigItem(const AutoRecovery::TDocumentInfo & rInfo,bool bRemoveIt)1987 void AutoRecovery::implts_flushConfigItem(const AutoRecovery::TDocumentInfo& rInfo, bool bRemoveIt)
1988 {
1989     std::shared_ptr<comphelper::ConfigurationChanges> batch(
1990             comphelper::ConfigurationChanges::create(m_xContext));
1991 
1992     try
1993     {
1994         implts_openConfig();
1995 
1996         css::uno::Reference<css::container::XNameAccess> xCheck(
1997                 officecfg::Office::Recovery::RecoveryList::get(batch));
1998 
1999         css::uno::Reference< css::container::XNameContainer >   xModify(xCheck, css::uno::UNO_QUERY_THROW);
2000         css::uno::Reference< css::lang::XSingleServiceFactory > xCreate(xCheck, css::uno::UNO_QUERY_THROW);
2001 
2002         OUString sID = RECOVERY_ITEM_BASE_IDENTIFIER + OUString::number(rInfo.ID);
2003 
2004         // remove
2005         if (bRemoveIt)
2006         {
2007             // Catch NoSuchElementException.
2008             // It's not a good idea inside multithreaded environments to call hasElement - removeElement.
2009             // DO IT!
2010             try
2011             {
2012                 xModify->removeByName(sID);
2013             }
2014             catch(const css::container::NoSuchElementException&)
2015             {
2016                 return;
2017             }
2018         }
2019         else
2020         {
2021             // new/modify
2022             css::uno::Reference< css::beans::XPropertySet > xSet;
2023             bool                                        bNew = !xCheck->hasByName(sID);
2024             if (bNew)
2025                 xSet.set(xCreate->createInstance(), css::uno::UNO_QUERY_THROW);
2026             else
2027                 xCheck->getByName(sID) >>= xSet;
2028 
2029             xSet->setPropertyValue(CFG_ENTRY_PROP_ORIGINALURL, css::uno::makeAny(rInfo.OrgURL       ));
2030             xSet->setPropertyValue(CFG_ENTRY_PROP_TEMPURL, css::uno::makeAny(rInfo.OldTempURL   ));
2031             xSet->setPropertyValue(CFG_ENTRY_PROP_TEMPLATEURL, css::uno::makeAny(rInfo.TemplateURL  ));
2032             xSet->setPropertyValue(CFG_ENTRY_PROP_FILTER, css::uno::makeAny(rInfo.RealFilter));
2033             xSet->setPropertyValue(CFG_ENTRY_PROP_DOCUMENTSTATE, css::uno::makeAny(sal_Int32(rInfo.DocumentState)));
2034             xSet->setPropertyValue(CFG_ENTRY_PROP_MODULE, css::uno::makeAny(rInfo.AppModule));
2035             xSet->setPropertyValue(CFG_ENTRY_PROP_TITLE, css::uno::makeAny(rInfo.Title));
2036             xSet->setPropertyValue(CFG_ENTRY_PROP_VIEWNAMES, css::uno::makeAny(rInfo.ViewNames));
2037 
2038             if (bNew)
2039                 xModify->insertByName(sID, css::uno::makeAny(xSet));
2040         }
2041     }
2042     catch(const css::uno::RuntimeException&)
2043     {
2044         throw;
2045     }
2046     catch(const css::uno::Exception&)
2047     {
2048         // ??? can it happen that a full disc let these set of operations fail too ???
2049     }
2050 
2051     sal_Int32 nRetry = RETRY_STORE_ON_FULL_DISC_FOREVER;
2052     do
2053     {
2054         try
2055         {
2056             batch->commit();
2057 
2058 #ifdef TRIGGER_FULL_DISC_CHECK
2059             throw css::uno::Exception("trigger full disk check");
2060 #else  // TRIGGER_FULL_DISC_CHECK
2061             nRetry = 0;
2062 #endif // TRIGGER_FULL_DISC_CHECK
2063         }
2064         catch(const css::uno::Exception&)
2065         {
2066             // a) FULL DISC seems to be the problem behind                              => show error and retry it forever (e.g. retry=300)
2067             // b) unknown problem (may be locking problem)                              => reset RETRY value to more useful value(!) (e.g. retry=3)
2068             // c) unknown problem (may be locking problem) + 1..2 repeating operations  => throw the original exception to force generation of a stacktrace !
2069 
2070             sal_Int32 nMinSpaceConfigSave;
2071             /* SAFE */ {
2072             osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2073             nMinSpaceConfigSave = m_nMinSpaceConfigSave;
2074             } /* SAFE */
2075 
2076             if (! impl_enoughDiscSpace(nMinSpaceConfigSave))
2077                 AutoRecovery::impl_showFullDiscError();
2078             else if (nRetry > RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL)
2079                 nRetry = RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL;
2080             else if (nRetry <= GIVE_UP_RETRY)
2081                 throw; // force stacktrace to know if there exist might other reasons, why an AutoSave can fail !!!
2082 
2083             --nRetry;
2084         }
2085     }
2086     while(nRetry>0);
2087 }
2088 
implts_startListening()2089 void AutoRecovery::implts_startListening()
2090 {
2091     css::uno::Reference< css::util::XChangesNotifier > xCFG;
2092     css::uno::Reference< css::frame::XGlobalEventBroadcaster > xBroadcaster;
2093     bool bListenForDocEvents;
2094     bool bListenForConfigChanges;
2095     /* SAFE */ {
2096     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2097     xCFG.set              (m_xRecoveryCFG, css::uno::UNO_QUERY);
2098     xBroadcaster        = m_xNewDocBroadcaster;
2099     bListenForDocEvents = m_bListenForDocEvents;
2100     bListenForConfigChanges = m_bListenForConfigChanges;
2101     } /* SAFE */
2102 
2103     if (
2104         (  xCFG.is()                ) &&
2105         (! bListenForConfigChanges)
2106        )
2107     {
2108         css::uno::Reference<css::util::XChangesListener> const xListener(
2109                 new WeakChangesListener(this));
2110         xCFG->addChangesListener(xListener);
2111         /* SAFE */ {
2112         osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2113         m_xRecoveryCFGListener = xListener;
2114         m_bListenForConfigChanges = true;
2115         } /* SAFE */
2116     }
2117 
2118     if (!xBroadcaster.is())
2119     {
2120         xBroadcaster = css::frame::theGlobalEventBroadcaster::get(m_xContext);
2121         /* SAFE */ {
2122         osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2123         m_xNewDocBroadcaster = xBroadcaster;
2124         } /* SAFE */
2125     }
2126 
2127     if (
2128         (  xBroadcaster.is()  ) &&
2129         (! bListenForDocEvents)
2130        )
2131     {
2132         css::uno::Reference<css::document::XDocumentEventListener> const
2133             xListener(new WeakDocumentEventListener(this));
2134         xBroadcaster->addDocumentEventListener(xListener);
2135         /* SAFE */ {
2136         osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2137         m_xNewDocBroadcasterListener = xListener;
2138         m_bListenForDocEvents = true;
2139         } /* SAFE */
2140     }
2141 }
2142 
implts_stopListening()2143 void AutoRecovery::implts_stopListening()
2144 {
2145     css::uno::Reference< css::util::XChangesNotifier > xCFG;
2146     css::uno::Reference< css::document::XDocumentEventBroadcaster > xGlobalEventBroadcaster;
2147     /* SAFE */ {
2148     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2149     // Attention: Don't reset our internal members here too.
2150     // May be we must work with our configuration, but don't wish to be informed
2151     // about changes any longer. Needed e.g. during Job::EmergencySave!
2152     xCFG.set                   (m_xRecoveryCFG      , css::uno::UNO_QUERY);
2153     xGlobalEventBroadcaster = m_xNewDocBroadcaster;
2154     } /* SAFE */
2155 
2156     if (xGlobalEventBroadcaster.is() && m_bListenForDocEvents)
2157     {
2158         xGlobalEventBroadcaster->removeDocumentEventListener(m_xNewDocBroadcasterListener);
2159         m_bListenForDocEvents = false;
2160     }
2161 
2162     if (xCFG.is() && m_bListenForConfigChanges)
2163     {
2164         xCFG->removeChangesListener(m_xRecoveryCFGListener);
2165         m_bListenForConfigChanges = false;
2166     }
2167 }
2168 
implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo & rInfo)2169 void AutoRecovery::implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo)
2170 {
2171     if (rInfo.ListenForModify)
2172         return;
2173 
2174     css::uno::Reference< css::util::XModifyBroadcaster > xBroadcaster(rInfo.Document, css::uno::UNO_QUERY);
2175     if (xBroadcaster.is())
2176     {
2177         css::uno::Reference< css::util::XModifyListener > xThis(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY);
2178         xBroadcaster->addModifyListener(xThis);
2179         rInfo.ListenForModify = true;
2180     }
2181 }
2182 
implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo & rInfo)2183 void AutoRecovery::implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo)
2184 {
2185     if (! rInfo.ListenForModify)
2186         return;
2187 
2188     css::uno::Reference< css::util::XModifyBroadcaster > xBroadcaster(rInfo.Document, css::uno::UNO_QUERY);
2189     if (xBroadcaster.is())
2190     {
2191         css::uno::Reference< css::util::XModifyListener > xThis(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY);
2192         xBroadcaster->removeModifyListener(xThis);
2193         rInfo.ListenForModify = false;
2194     }
2195 }
2196 
implts_updateTimer()2197 void AutoRecovery::implts_updateTimer()
2198 {
2199     implts_stopTimer();
2200 
2201     sal_Int32 nMilliSeconds = 0;
2202 
2203     /* SAFE */ {
2204     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2205 
2206     if (
2207         (m_eJob       == Job::NoJob          ) || // TODO may be superfluous - E_DONT_START_TIMER should be used only
2208         (m_eTimerType == AutoRecovery::E_DONT_START_TIMER)
2209        )
2210         return;
2211 
2212     if (m_eTimerType == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL)
2213     {
2214         nMilliSeconds = (m_nAutoSaveTimeIntervall*60000); // [min] => 60.000 ms
2215     }
2216     else if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE)
2217     {
2218         nMilliSeconds = MIN_TIME_FOR_USER_IDLE;
2219     }
2220     else if (m_eTimerType == AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED)
2221         nMilliSeconds = 300; // there is a minimum time frame, where the user can lose some key input data!
2222 
2223 
2224     } /* SAFE */
2225 
2226     SolarMutexGuard g;
2227     m_aTimer.SetTimeout(nMilliSeconds);
2228     m_aTimer.Start();
2229 }
2230 
implts_stopTimer()2231 void AutoRecovery::implts_stopTimer()
2232 {
2233     SolarMutexGuard g;
2234 
2235     if (!m_aTimer.IsActive())
2236         return;
2237     m_aTimer.Stop();
2238 }
2239 
IMPL_LINK_NOARG(AutoRecovery,implts_timerExpired,Timer *,void)2240 IMPL_LINK_NOARG(AutoRecovery, implts_timerExpired, Timer *, void)
2241 {
2242     try
2243     {
2244         // This method is called by using a pointer to us.
2245         // But we must be aware that we can be destroyed hardly
2246         // if our uno reference will be gone!
2247         // => Hold this object alive till this method finish its work.
2248         css::uno::Reference< css::uno::XInterface > xSelfHold(static_cast< css::lang::XTypeProvider* >(this));
2249 
2250         // Needed! Otherwise every reschedule request allow a new triggered timer event :-(
2251         implts_stopTimer();
2252 
2253         // The timer must be ignored if AutoSave/Recovery was disabled for this
2254         // office session. That can happen if e.g. the command line arguments "--norestore" or "--headless"
2255         // was set. But normally the timer was disabled if recovery was disabled ...
2256         // But so we are more "safe" .-)
2257         /* SAFE */ {
2258         osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2259         if ((m_eJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
2260            return;
2261         } /* SAFE */
2262 
2263         // check some "states", where it's not allowed (better: not a good idea) to
2264         // start an AutoSave. (e.g. if the user makes drag & drop ...)
2265         // Then we poll till this "disallowed" state is gone.
2266         bool bAutoSaveNotAllowed = Application::IsUICaptured();
2267         if (bAutoSaveNotAllowed)
2268         {
2269             /* SAFE */ {
2270             osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2271             m_eTimerType = AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED;
2272             } /* SAFE */
2273             implts_updateTimer();
2274             return;
2275         }
2276 
2277         // analyze timer type.
2278         // If we poll for an user idle period, may be we must
2279         // do nothing here and start the timer again.
2280         /* SAFE */ {
2281         osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2282 
2283         if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE)
2284         {
2285             bool bUserIdle = Application::GetLastInputInterval() > MIN_TIME_FOR_USER_IDLE;
2286             if (!bUserIdle)
2287             {
2288                 g.clear();
2289                 implts_updateTimer();
2290                 return;
2291             }
2292         }
2293 
2294         } /* SAFE */
2295 
2296         implts_informListener(Job::AutoSave,
2297             AutoRecovery::implst_createFeatureStateEvent(Job::AutoSave, OPERATION_START, nullptr));
2298 
2299         // force save of all currently open documents
2300         // The called method returns an info, if and how this
2301         // timer must be restarted.
2302         AutoRecovery::ETimerType eSuggestedTimer = implts_saveDocs(true/*bAllowUserIdleLoop*/, false);
2303 
2304         // If timer is not used for "short callbacks" (means polling
2305         // for special states) ... reset the handle state of all
2306         // cache items. Such handle state indicates, that a document
2307         // was already saved during the THIS(!) AutoSave session.
2308         // Of course NEXT AutoSave session must be started without
2309         // any "handle" state ...
2310         if (
2311             (eSuggestedTimer == AutoRecovery::E_DONT_START_TIMER         ) ||
2312             (eSuggestedTimer == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL)
2313            )
2314         {
2315             implts_resetHandleStates();
2316         }
2317 
2318         implts_informListener(Job::AutoSave,
2319             AutoRecovery::implst_createFeatureStateEvent(Job::AutoSave, OPERATION_STOP, nullptr));
2320 
2321         // restart timer - because it was disabled before ...
2322         /* SAFE */ {
2323         osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2324         m_eTimerType = eSuggestedTimer;
2325         } /* SAFE */
2326 
2327         implts_updateTimer();
2328     }
2329     catch(const css::uno::Exception&)
2330     {
2331     }
2332 }
2333 
IMPL_LINK_NOARG(AutoRecovery,implts_asyncDispatch,LinkParamNone *,void)2334 IMPL_LINK_NOARG(AutoRecovery, implts_asyncDispatch, LinkParamNone*, void)
2335 {
2336     DispatchParams aParams;
2337     /* SAFE */ {
2338         osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2339         aParams = m_aDispatchParams;
2340         css::uno::Reference< css::uno::XInterface > xHoldRefForMethodAlive = aParams.m_xHoldRefForAsyncOpAlive;
2341         m_aDispatchParams.forget(); // clears all members ... including the ref-hold object .-)
2342     } /* SAFE */
2343 
2344     try
2345     {
2346         implts_dispatch(aParams);
2347     }
2348     catch (...)
2349     {
2350     }
2351 }
2352 
implts_registerDocument(const css::uno::Reference<css::frame::XModel3> & xDocument)2353 void AutoRecovery::implts_registerDocument(const css::uno::Reference< css::frame::XModel3 > & xDocument)
2354 {
2355     // ignore corrupted events, where no document is given ... Runtime Error ?!
2356     if (!xDocument.is())
2357         return;
2358 
2359     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2360 
2361     // notification for already existing document !
2362     // Can happen if events came in asynchronous on recovery time.
2363     // Then our cache was filled from the configuration ... but now we get some
2364     // asynchronous events from the global event broadcaster. We must be sure that
2365     // we don't add the same document more than once.
2366     AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2367     if (pIt != m_lDocCache.end())
2368     {
2369         // Normally nothing must be done for this "late" notification.
2370         // But may be the modified state was changed inbetween.
2371         // Check it...
2372         implts_updateModifiedState(xDocument);
2373         return;
2374     }
2375 
2376     aCacheLock.unlock();
2377 
2378     utl::MediaDescriptor lDescriptor(xDocument->getArgs2( { utl::MediaDescriptor::PROP_FILTERNAME(), utl::MediaDescriptor::PROP_NOAUTOSAVE() } ));
2379 
2380     // check if this document must be ignored for recovery !
2381     // Some use cases don't wish support for AutoSave/Recovery ... as e.g. OLE-Server / ActiveX Control etcpp.
2382     bool bNoAutoSave = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_NOAUTOSAVE(), false);
2383     if (bNoAutoSave)
2384         return;
2385 
2386     // Check if doc is well known on the desktop. Otherwise ignore it!
2387     // Other frames mostly are used from external programs - e.g. the bean ...
2388     css::uno::Reference< css::frame::XController > xController = xDocument->getCurrentController();
2389     if (!xController.is())
2390         return;
2391 
2392     css::uno::Reference< css::frame::XFrame >   xFrame   = xController->getFrame();
2393     if (!xFrame.is())
2394         return;
2395     css::uno::Reference< css::frame::XDesktop > xDesktop (xFrame->getCreator(), css::uno::UNO_QUERY);
2396     if (!xDesktop.is())
2397         return;
2398 
2399     // if the document doesn't support the XDocumentRecovery interface, we're not interested in it.
2400     Reference< XDocumentRecovery > xDocRecovery( xDocument, UNO_QUERY );
2401     if ( !xDocRecovery.is() )
2402         return;
2403 
2404     // get all needed information of this document
2405     // We need it to update our cache or to locate already existing elements there!
2406     AutoRecovery::TDocumentInfo aNew;
2407     aNew.Document = xDocument;
2408 
2409     // TODO replace getLocation() with getURL() ... it's a workaround currently only!
2410     css::uno::Reference< css::frame::XStorable > xDoc(aNew.Document, css::uno::UNO_QUERY_THROW);
2411     aNew.OrgURL = xDoc->getLocation();
2412 
2413     css::uno::Reference< css::frame::XTitle > xTitle(aNew.Document, css::uno::UNO_QUERY_THROW);
2414     aNew.Title = xTitle->getTitle ();
2415 
2416     // classify the used application module, which is used by this document.
2417     implts_specifyAppModuleAndFactory(aNew);
2418 
2419     // Hack! Check for "illegal office documents"... as e.g. the Basic IDE
2420     // It's not really a full featured office document. It doesn't provide a URL, any filter, a factory URL etcpp.
2421     // TODO file bug to Basic IDE developers. They must remove the office document API from its service.
2422     if (
2423         (aNew.OrgURL.isEmpty()) &&
2424         (aNew.FactoryURL.isEmpty())
2425        )
2426     {
2427         OSL_FAIL( "AutoRecovery::implts_registerDocument: this should not happen anymore!" );
2428         // nowadays, the Basic IDE should already die on the "supports XDocumentRecovery" check. And no other known
2429         // document type fits in here ...
2430         return;
2431     }
2432 
2433     // By the way - get some information about the default format for saving!
2434     // and save an information about the real used filter by this document.
2435     // We save this document with DefaultFilter ... and load it with the RealFilter.
2436     implts_specifyDefaultFilterAndExtension(aNew);
2437     aNew.RealFilter = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME()  , OUString());
2438 
2439     // Further we must know, if this document base on a template.
2440     // Then we must load it in a different way.
2441     css::uno::Reference< css::document::XDocumentPropertiesSupplier > xSupplier(aNew.Document, css::uno::UNO_QUERY);
2442     if (xSupplier.is()) // optional interface!
2443     {
2444         css::uno::Reference< css::document::XDocumentProperties > xDocProps(xSupplier->getDocumentProperties(), css::uno::UNO_SET_THROW);
2445         aNew.TemplateURL = xDocProps->getTemplateURL();
2446     }
2447 
2448     css::uno::Reference< css::util::XModifiable > xModifyCheck(xDocument, css::uno::UNO_QUERY_THROW);
2449     if (xModifyCheck->isModified())
2450     {
2451         aNew.DocumentState |= DocState::Modified;
2452     }
2453 
2454     aCacheLock.lock(LOCK_FOR_CACHE_ADD_REMOVE);
2455 
2456     AutoRecovery::TDocumentInfo aInfo;
2457     /* SAFE */ {
2458     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2459 
2460     // create a new cache entry ... this document is not known.
2461     ++m_nIdPool;
2462     aNew.ID = m_nIdPool;
2463     SAL_WARN_IF(m_nIdPool<0, "fwk.autorecovery", "AutoRecovery::implts_registerDocument(): Overflow of ID pool detected.");
2464     m_lDocCache.push_back(aNew);
2465 
2466     AutoRecovery::TDocumentList::iterator pIt1  = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2467     aInfo = *pIt1;
2468 
2469     } /* SAFE */
2470 
2471     implts_flushConfigItem(aInfo);
2472     implts_startModifyListeningOnDoc(aInfo);
2473 
2474     aCacheLock.unlock();
2475 }
2476 
implts_deregisterDocument(const css::uno::Reference<css::frame::XModel> & xDocument,bool bStopListening)2477 void AutoRecovery::implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument     ,
2478                                                    bool                                   bStopListening)
2479 {
2480     AutoRecovery::TDocumentInfo aInfo;
2481     /* SAFE */ {
2482     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2483 
2484     // Attention: Don't leave SAFE section, if you work with pIt!
2485     // Because it points directly into the m_lDocCache list ...
2486     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2487 
2488     AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2489     if (pIt == m_lDocCache.end())
2490         return; // unknown document => not a runtime error! Because we register only a few documents. see registration ...
2491 
2492     aInfo = *pIt;
2493 
2494     aCacheLock.unlock();
2495 
2496     // Sometimes we close documents by ourself.
2497     // And these documents can't be deregistered.
2498     // Otherwise we lose our configuration data... but need it !
2499     // see SessionSave !
2500     if (aInfo.IgnoreClosing)
2501         return;
2502 
2503     CacheLockGuard aCacheLock2(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE);
2504     pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2505     if (pIt != m_lDocCache.end())
2506         m_lDocCache.erase(pIt);
2507     pIt = m_lDocCache.end(); // otherwise it's not specified what pIt means!
2508     aCacheLock2.unlock();
2509 
2510     } /* SAFE */
2511 
2512     /* This method is called within disposing() of the document too. But there it's not a good idea to
2513        deregister us as listener. Further it makes no sense - because the broadcaster dies.
2514        So we suppress deregistration in such case...
2515     */
2516     if (bStopListening)
2517         implts_stopModifyListeningOnDoc(aInfo);
2518 
2519     AutoRecovery::st_impl_removeFile(aInfo.OldTempURL);
2520     AutoRecovery::st_impl_removeFile(aInfo.NewTempURL);
2521     implts_flushConfigItem(aInfo, true); // sal_True => remove it from config
2522 }
2523 
implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference<css::frame::XModel> & xDocument)2524 void AutoRecovery::implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument)
2525 {
2526     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2527 
2528     /* SAFE */ {
2529     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2530 
2531     AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2532     if (pIt != m_lDocCache.end())
2533     {
2534         /* Now we know, that this document was modified again and must be saved next time.
2535            But we don't need this information for every e.g. key input of the user.
2536            So we stop listening here.
2537            But if the document was saved as temp. file we start listening for this event again.
2538         */
2539         implts_stopModifyListeningOnDoc(*pIt);
2540     }
2541 
2542     } /* SAFE */
2543 }
2544 
implts_updateModifiedState(const css::uno::Reference<css::frame::XModel> & xDocument)2545 void AutoRecovery::implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument)
2546 {
2547     // use true as fallback to get every document on EmergencySave/AutoRecovery!
2548     bool bModified = true;
2549     css::uno::Reference< css::util::XModifiable > xModify(xDocument, css::uno::UNO_QUERY);
2550     if (xModify.is())
2551         bModified = xModify->isModified();
2552 
2553     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2554 
2555     /* SAFE */ {
2556     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2557 
2558     AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2559     if (pIt != m_lDocCache.end())
2560     {
2561         AutoRecovery::TDocumentInfo& rInfo = *pIt;
2562 
2563         if (bModified)
2564         {
2565             rInfo.DocumentState |= DocState::Modified;
2566         }
2567         else
2568         {
2569             rInfo.DocumentState &= ~DocState::Modified;
2570         }
2571     }
2572 
2573     } /* SAFE */
2574 }
2575 
implts_updateDocumentUsedForSavingState(const css::uno::Reference<css::frame::XModel> & xDocument,bool bSaveInProgress)2576 void AutoRecovery::implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument      ,
2577                                                                  bool                                   bSaveInProgress)
2578 {
2579     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2580 
2581     /* SAFE */ {
2582     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2583 
2584     AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2585     if (pIt == m_lDocCache.end())
2586         return;
2587     AutoRecovery::TDocumentInfo& rInfo = *pIt;
2588     rInfo.UsedForSaving = bSaveInProgress;
2589 
2590     } /* SAFE */
2591 }
2592 
implts_markDocumentAsSaved(const css::uno::Reference<css::frame::XModel> & xDocument)2593 void AutoRecovery::implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument)
2594 {
2595     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2596 
2597     AutoRecovery::TDocumentInfo aInfo;
2598     OUString sRemoveURL1;
2599     OUString sRemoveURL2;
2600     /* SAFE */ {
2601     osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2602 
2603     AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
2604     if (pIt == m_lDocCache.end())
2605         return;
2606     aInfo = *pIt;
2607 
2608     /* Since the document has been saved, update its entry in the document
2609      * cache. We essentially reset the state of the document from an
2610      * autorecovery perspective, updating things like the filename (which
2611      * would change in the case of a 'Save as' operation) and the associated
2612      * backup file URL.  */
2613 
2614     aInfo.DocumentState = DocState::Unknown;
2615     // TODO replace getLocation() with getURL() ... it's a workaround currently only!
2616     css::uno::Reference< css::frame::XStorable > xDoc(aInfo.Document, css::uno::UNO_QUERY);
2617     aInfo.OrgURL = xDoc->getLocation();
2618 
2619     /* Save off the backup file URLs and then clear them. NOTE - it is
2620      * important that we clear them - otherwise, we could enter a state
2621      * where pIt->OldTempURL == pIt->NewTempURL and our backup algorithm
2622      * in implts_saveOneDoc will write to that URL and then delete the file
2623      * at that URL (bug #96607) */
2624     sRemoveURL1 = aInfo.OldTempURL;
2625     sRemoveURL2 = aInfo.NewTempURL;
2626     aInfo.OldTempURL.clear();
2627     aInfo.NewTempURL.clear();
2628 
2629     utl::MediaDescriptor lDescriptor(aInfo.Document->getArgs());
2630     aInfo.RealFilter = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME(), OUString());
2631 
2632     css::uno::Reference< css::frame::XTitle > xDocTitle(xDocument, css::uno::UNO_QUERY);
2633     if (xDocTitle.is ())
2634         aInfo.Title = xDocTitle->getTitle ();
2635     else
2636     {
2637         aInfo.Title      = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_TITLE()     , OUString());
2638         if (aInfo.Title.isEmpty())
2639             aInfo.Title  = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTTITLE(), OUString());
2640     }
2641 
2642     aInfo.UsedForSaving = false;
2643 
2644     *pIt = aInfo;
2645 
2646     } /* SAFE */
2647 
2648     implts_flushConfigItem(aInfo);
2649 
2650     aCacheLock.unlock();
2651 
2652     AutoRecovery::st_impl_removeFile(sRemoveURL1);
2653     AutoRecovery::st_impl_removeFile(sRemoveURL2);
2654 }
2655 
impl_searchDocument(AutoRecovery::TDocumentList & rList,const css::uno::Reference<css::frame::XModel> & xDocument)2656 AutoRecovery::TDocumentList::iterator AutoRecovery::impl_searchDocument(      AutoRecovery::TDocumentList&               rList    ,
2657                                                                         const css::uno::Reference< css::frame::XModel >& xDocument)
2658 {
2659     return std::find_if(rList.begin(), rList.end(),
2660         [&xDocument](const AutoRecovery::TDocumentInfo& rInfo) { return rInfo.Document == xDocument; });
2661 }
2662 
lcl_changeVisibility(const css::uno::Reference<css::frame::XFramesSupplier> & i_rFrames,bool i_bVisible)2663 void lcl_changeVisibility( const css::uno::Reference< css::frame::XFramesSupplier >& i_rFrames, bool i_bVisible )
2664 {
2665     css::uno::Reference< css::container::XIndexAccess > xFramesContainer = i_rFrames->getFrames();
2666     const sal_Int32 count = xFramesContainer->getCount();
2667 
2668     Any aElement;
2669     for ( sal_Int32 i=0; i < count; ++i )
2670     {
2671         aElement = xFramesContainer->getByIndex(i);
2672         // check for sub frames
2673         css::uno::Reference< css::frame::XFramesSupplier > xFramesSupp( aElement, css::uno::UNO_QUERY );
2674         if ( xFramesSupp.is() )
2675             lcl_changeVisibility( xFramesSupp, i_bVisible );
2676 
2677         css::uno::Reference< css::frame::XFrame > xFrame( aElement, css::uno::UNO_QUERY );
2678         if ( !xFrame.is() )
2679             continue;
2680 
2681         css::uno::Reference< css::awt::XWindow > xWindow( xFrame->getContainerWindow(), UNO_SET_THROW );
2682         xWindow->setVisible( i_bVisible );
2683     }
2684 }
2685 
implts_changeAllDocVisibility(bool bVisible)2686 void AutoRecovery::implts_changeAllDocVisibility(bool bVisible)
2687 {
2688     css::uno::Reference< css::frame::XFramesSupplier > xDesktop = css::frame::Desktop::create(m_xContext);
2689     lcl_changeVisibility( xDesktop, bVisible );
2690 }
2691 
2692 /* Currently the document is not closed in case of crash,
2693    so the lock file must be removed explicitly
2694 */
lc_removeLockFile(AutoRecovery::TDocumentInfo const & rInfo)2695 void lc_removeLockFile(AutoRecovery::TDocumentInfo const & rInfo)
2696 {
2697 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT || HAVE_FEATURE_MACOSX_SANDBOX
2698     (void) rInfo;
2699 #else
2700     if ( !rInfo.Document.is() )
2701         return;
2702 
2703     try
2704     {
2705         css::uno::Reference< css::frame::XStorable > xStore(rInfo.Document, css::uno::UNO_QUERY_THROW);
2706         OUString aURL = xStore->getLocation();
2707         if ( !aURL.isEmpty() )
2708         {
2709             ::svt::DocumentLockFile aLockFile( aURL );
2710             aLockFile.RemoveFile();
2711         }
2712     }
2713     catch( const css::uno::Exception& )
2714     {
2715     }
2716 #endif
2717 }
2718 
implts_prepareSessionShutdown()2719 void AutoRecovery::implts_prepareSessionShutdown()
2720 {
2721     SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_prepareSessionShutdown() starts ...");
2722 
2723     // a) reset modified documents (of course the must be saved before this method is called!)
2724     // b) close it without showing any UI!
2725 
2726     /* SAFE */ {
2727     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2728 
2729     for (auto & info : m_lDocCache)
2730     {
2731         // WORKAROUND... Since the documents are not closed the lock file must be removed explicitly
2732         // it is not done on documents saving since shutdown can be cancelled
2733         lc_removeLockFile( info );
2734 
2735         // Prevent us from deregistration of these documents.
2736         // Because we close these documents by ourself (see XClosable below) ...
2737         // it's fact, that we reach our deregistration method. There we
2738         // must not(!) update our configuration ... Otherwise all
2739         // session data are lost !!!
2740         info.IgnoreClosing = true;
2741 
2742         // reset modified flag of these documents (ignoring the notification about it!)
2743         // Otherwise a message box is shown on closing these models.
2744         implts_stopModifyListeningOnDoc(info);
2745 
2746         // if the session save is still running the documents should not be thrown away,
2747         // actually that would be a bad sign, that means that the SessionManager tries
2748         // to kill the session before the saving is ready
2749         if ((m_eJob & Job::SessionSave) != Job::SessionSave)
2750         {
2751             css::uno::Reference< css::util::XModifiable > xModify(info.Document, css::uno::UNO_QUERY);
2752             if (xModify.is())
2753                 xModify->setModified(false);
2754 
2755             // close the model.
2756             css::uno::Reference< css::util::XCloseable > xClose(info.Document, css::uno::UNO_QUERY);
2757             if (xClose.is())
2758             {
2759                 try
2760                 {
2761                     xClose->close(false);
2762                 }
2763                 catch(const css::uno::Exception&)
2764                 {
2765                     // At least it's only a try to close these documents before anybody else it does.
2766                     // So it seems to be possible to ignore any error here .-)
2767                 }
2768 
2769                 info.Document.clear();
2770             }
2771         }
2772     }
2773 
2774     aCacheLock.unlock();
2775     } /* SAFE */
2776 }
2777 
2778 /* TODO WORKAROUND:
2779 
2780         #i64599#
2781 
2782         Normally the MediaDescriptor argument NoAutoSave indicates,
2783         that a document must be ignored for AutoSave and Recovery.
2784         But sometimes XModel->getArgs() does not contained this information
2785         if implts_registerDocument() was called.
2786         So we have to check a second time, if this property is set...
2787         Best place doing so is to check it immediately before saving
2788         and suppressing saving the document then.
2789         Of course removing the corresponding cache entry is not an option.
2790         Because it would disturb iteration over the cache!
2791         So we ignore such documents only...
2792         Hopefully next time they are not inserted in our cache.
2793 */
lc_checkIfSaveForbiddenByArguments(AutoRecovery::TDocumentInfo const & rInfo)2794 bool lc_checkIfSaveForbiddenByArguments(AutoRecovery::TDocumentInfo const & rInfo)
2795 {
2796     if (! rInfo.Document.is())
2797         return true;
2798 
2799     utl::MediaDescriptor lDescriptor(rInfo.Document->getArgs());
2800     bool bNoAutoSave = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_NOAUTOSAVE(), false);
2801 
2802     return bNoAutoSave;
2803 }
2804 
implts_saveDocs(bool bAllowUserIdleLoop,bool bRemoveLockFiles,const DispatchParams * pParams)2805 AutoRecovery::ETimerType AutoRecovery::implts_saveDocs(       bool        bAllowUserIdleLoop,
2806                                                               bool        bRemoveLockFiles,
2807                                                         const DispatchParams* pParams           )
2808 {
2809     css::uno::Reference< css::task::XStatusIndicator > xExternalProgress;
2810     if (pParams)
2811         xExternalProgress = pParams->m_xProgress;
2812 
2813     css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext);
2814     OUString                              sBackupPath(SvtPathOptions().GetBackupPath());
2815 
2816     css::uno::Reference< css::frame::XController > xActiveController;
2817     css::uno::Reference< css::frame::XModel >      xActiveModel;
2818     css::uno::Reference< css::frame::XFrame >      xActiveFrame     = xDesktop->getActiveFrame();
2819     if (xActiveFrame.is())
2820         xActiveController = xActiveFrame->getController();
2821     if (xActiveController.is())
2822         xActiveModel = xActiveController->getModel();
2823 
2824     // Set the default timer action for our call.
2825     // Default = NORMAL_AUTOSAVE
2826     // We return a suggestion for an active timer only.
2827     // It will be ignored if the timer was disabled by the user ...
2828     // Further this state can be set to USER_IDLE only later in this method.
2829     // It's not allowed to reset such state then. Because we must know, if
2830     // there exists POSTPONED documents. see below ...
2831     AutoRecovery::ETimerType eTimer = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL;
2832 
2833     Job eJob = m_eJob;
2834 
2835     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
2836 
2837     /* SAFE */ {
2838     osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
2839 
2840     // This list will be filled with every document
2841     // which should be saved as last one. E.g. if it was used
2842     // already for a UI save operation => crashed ... and
2843     // now we try to save it again ... which can fail again ( of course .-) ).
2844     ::std::vector< AutoRecovery::TDocumentList::iterator > lDangerousDocs;
2845 
2846     AutoRecovery::TDocumentList::iterator pIt;
2847     for (  pIt  = m_lDocCache.begin();
2848            pIt != m_lDocCache.end();
2849          ++pIt                       )
2850     {
2851         AutoRecovery::TDocumentInfo aInfo = *pIt;
2852 
2853         // WORKAROUND... Since the documents are not closed the lock file must be removed explicitly
2854         if ( bRemoveLockFiles )
2855             lc_removeLockFile( aInfo );
2856 
2857         // WORKAROUND ... see comment of this method
2858         if (lc_checkIfSaveForbiddenByArguments(aInfo))
2859             continue;
2860 
2861         // already auto saved during this session :-)
2862         // This state must be reset for all documents
2863         // if timer is started with normal AutoSaveTimerIntervall!
2864         if ((aInfo.DocumentState & DocState::Handled) == DocState::Handled)
2865             continue;
2866 
2867         // Not modified documents are not saved.
2868         // We safe an information about the URL only!
2869         Reference< XDocumentRecovery > xDocRecover( aInfo.Document, UNO_QUERY_THROW );
2870         if ( !xDocRecover->wasModifiedSinceLastSave() )
2871         {
2872             aInfo.DocumentState |= DocState::Handled;
2873             continue;
2874         }
2875 
2876         // check if this document is still used by a concurrent save operation
2877         // e.g. if the user tried to save via UI.
2878         // Handle it in the following way:
2879         // i)   For an AutoSave ... ignore this document! It will be saved and next time we will (hopefully)
2880         //      get a notification about the state of this operation.
2881         //      And if a document was saved by the user we can remove our temp. file. But that will be done inside
2882         //      our callback for SaveDone notification.
2883         // ii)  For a CrashSave ... add it to the list of dangerous documents and
2884         //      save it after all other documents was saved successfully. That decrease
2885         //      the chance for a crash inside a crash.
2886         //      On the other side it's not necessary for documents, which are not modified.
2887         //      They can be handled normally - means we patch the corresponding configuration entry only.
2888         // iii) For a SessionSave ... ignore it! There is no time to wait for this save operation.
2889         //      Because the WindowManager will kill the process if it doesn't react immediately.
2890         //      On the other side we can't risk a concurrent save request ... because we know
2891         //      that it will produce a crash.
2892 
2893         // Attention: Because eJob is used as a flag field, you have to check for the worst case first.
2894         // E.g. a CrashSave can overwrite an AutoSave. So you have to check for a CrashSave before an AutoSave!
2895         if (aInfo.UsedForSaving)
2896         {
2897             if ((eJob & Job::EmergencySave) == Job::EmergencySave)
2898             {
2899                 lDangerousDocs.push_back(pIt);
2900                 continue;
2901             }
2902             else
2903             if ((eJob & Job::SessionSave) == Job::SessionSave)
2904             {
2905                 continue;
2906             }
2907             else
2908             if ((eJob & Job::AutoSave) == Job::AutoSave)
2909             {
2910                 eTimer = AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED;
2911                 aInfo.DocumentState |= DocState::Postponed;
2912                 continue;
2913             }
2914         }
2915 
2916         // a) Document was not postponed - and is     active now. => postpone it (restart timer, restart loop)
2917         // b) Document was not postponed - and is not active now. => save it
2918         // c) Document was     postponed - and is not active now. => save it
2919         // d) Document was     postponed - and is     active now. => save it (because user idle was checked already)
2920         bool bActive       = (xActiveModel == aInfo.Document);
2921         bool bWasPostponed = ((aInfo.DocumentState & DocState::Postponed) == DocState::Postponed);
2922 
2923         if (
2924             ! bWasPostponed &&
2925               bActive
2926            )
2927         {
2928             aInfo.DocumentState |= DocState::Postponed;
2929             *pIt = aInfo;
2930             // postponed documents will be saved if this method is called again!
2931             // That can be done by an outside started timer           => E_POLL_FOR_USER_IDLE (if normal AutoSave is active)
2932             // or it must be done directly without starting any timer => E_CALL_ME_BACK       (if Emergency- or SessionSave is active and must be finished ASAP!)
2933             eTimer = AutoRecovery::E_POLL_FOR_USER_IDLE;
2934             if (!bAllowUserIdleLoop)
2935                 eTimer = AutoRecovery::E_CALL_ME_BACK;
2936             continue;
2937         }
2938 
2939         // b, c, d)
2940         // } /* SAFE */
2941         g.clear();
2942         // changing of aInfo and flushing it is done inside implts_saveOneDoc!
2943         implts_saveOneDoc(sBackupPath, aInfo, xExternalProgress);
2944         implts_informListener(eJob, AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &aInfo));
2945         g.reset();
2946         // /* SAFE */ {
2947 
2948         *pIt = aInfo;
2949     }
2950 
2951     // Did we have some "dangerous candidates" ?
2952     // Try to save it ... but may be it will fail !
2953     for (auto const& dangerousDoc : lDangerousDocs)
2954     {
2955         pIt = dangerousDoc;
2956         AutoRecovery::TDocumentInfo aInfo = *pIt;
2957 
2958         // } /* SAFE */
2959         g.clear();
2960         // changing of aInfo and flushing it is done inside implts_saveOneDoc!
2961         implts_saveOneDoc(sBackupPath, aInfo, xExternalProgress);
2962         implts_informListener(eJob, AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &aInfo));
2963         g.reset();
2964         // /* SAFE */ {
2965 
2966         *pIt = aInfo;
2967     }
2968 
2969     } /* SAFE */
2970 
2971     return eTimer;
2972 }
2973 
implts_saveOneDoc(const OUString & sBackupPath,AutoRecovery::TDocumentInfo & rInfo,const css::uno::Reference<css::task::XStatusIndicator> & xExternalProgress)2974 void AutoRecovery::implts_saveOneDoc(const OUString&                                    sBackupPath      ,
2975                                            AutoRecovery::TDocumentInfo&                        rInfo            ,
2976                                      const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress)
2977 {
2978     // no document? => can occur if we loaded our configuration with files,
2979     // which couldn't be recovered successfully. In such case we have all needed information
2980     // excepting the real document instance!
2981 
2982     // TODO: search right place, where such "dead files" can be removed from the configuration!
2983     if (!rInfo.Document.is())
2984         return;
2985 
2986     utl::MediaDescriptor lOldArgs(rInfo.Document->getArgs());
2987     implts_generateNewTempURL(sBackupPath, lOldArgs, rInfo);
2988 
2989     // if the document was loaded with a password, it should be
2990     // stored with password
2991     utl::MediaDescriptor lNewArgs;
2992     css::uno::Sequence< css::beans::NamedValue > aEncryptionData =
2993         lOldArgs.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_ENCRYPTIONDATA(),
2994                 css::uno::Sequence< css::beans::NamedValue >());
2995     if (aEncryptionData.hasElements())
2996         lNewArgs[utl::MediaDescriptor::PROP_ENCRYPTIONDATA()] <<= aEncryptionData;
2997 
2998     // Further it must be saved using the default file format of that application.
2999     // Otherwise we will some data lost.
3000     if (!rInfo.DefaultFilter.isEmpty())
3001         lNewArgs[utl::MediaDescriptor::PROP_FILTERNAME()] <<= rInfo.DefaultFilter;
3002 
3003     // prepare frame/document/mediadescriptor in a way, that it uses OUR progress .-)
3004     if (xExternalProgress.is())
3005         lNewArgs[utl::MediaDescriptor::PROP_STATUSINDICATOR()] <<= xExternalProgress;
3006     impl_establishProgress(rInfo, lNewArgs, css::uno::Reference< css::frame::XFrame >());
3007 
3008     // #i66598# use special handling of property "DocumentBaseURL" (it must be an empty string!)
3009     // for make hyperlinks working
3010     lNewArgs[utl::MediaDescriptor::PROP_DOCUMENTBASEURL()] <<= OUString();
3011 
3012     // try to save this document as a new temp file every time.
3013     // Mark AutoSave state as "INCOMPLETE" if it failed.
3014     // Because the last temp file is too old and does not include all changes.
3015     Reference< XDocumentRecovery > xDocRecover(rInfo.Document, css::uno::UNO_QUERY_THROW);
3016 
3017     // safe the state about "trying to save"
3018     // ... we need it for recovery if e.g. a crash occurs inside next line!
3019     rInfo.DocumentState |= DocState::TrySave;
3020     implts_flushConfigItem(rInfo);
3021 
3022     // If userautosave is enabled, first try to save the original file.
3023     // Note that we must do it *before* calling storeToRecoveryFile, so in case of failure here
3024     // we won't remain with the modified flag set to true, even though the autorecovery save succeeded.
3025     try
3026     {
3027         // We must check here for an empty URL to avoid a "This operation is not supported on this operating system."
3028         // message during autosave.
3029         if ((m_eJob & Job::UserAutoSave) == Job::UserAutoSave && !rInfo.OrgURL.isEmpty())
3030         {
3031             Reference< XStorable > xDocSave(rInfo.Document, css::uno::UNO_QUERY_THROW);
3032             xDocSave->store();
3033         }
3034     }
3035     catch(const css::uno::Exception&)
3036     {
3037     }
3038 
3039     sal_Int32 nRetry = RETRY_STORE_ON_FULL_DISC_FOREVER;
3040     bool  bError = false;
3041     do
3042     {
3043         try
3044         {
3045             xDocRecover->storeToRecoveryFile( rInfo.NewTempURL, lNewArgs.getAsConstPropertyValueList() );
3046 
3047 #ifdef TRIGGER_FULL_DISC_CHECK
3048             throw css::uno::Exception("trigger full disk check");
3049 #else  // TRIGGER_FULL_DISC_CHECK
3050 
3051             bError = false;
3052             nRetry = 0;
3053 #endif // TRIGGER_FULL_DISC_CHECK
3054         }
3055         catch(const css::uno::Exception&)
3056         {
3057             bError = true;
3058 
3059             // a) FULL DISC seems to be the problem behind                              => show error and retry it forever (e.g. retry=300)
3060             // b) unknown problem (may be locking problem)                              => reset RETRY value to more useful value(!) (e.g. retry=3)
3061             // c) unknown problem (may be locking problem) + 1..2 repeating operations  => throw the original exception to force generation of a stacktrace !
3062 
3063             sal_Int32 nMinSpaceDocSave;
3064             /* SAFE */ {
3065             osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
3066             nMinSpaceDocSave = m_nMinSpaceDocSave;
3067             } /* SAFE */
3068 
3069             if (! impl_enoughDiscSpace(nMinSpaceDocSave))
3070                 AutoRecovery::impl_showFullDiscError();
3071             else if (nRetry > RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL)
3072                 nRetry = RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL;
3073             else if (nRetry <= GIVE_UP_RETRY)
3074                 throw; // force stacktrace to know if there exist might other reasons, why an AutoSave can fail !!!
3075 
3076             --nRetry;
3077         }
3078     }
3079     while(nRetry>0);
3080 
3081     if (! bError)
3082     {
3083         // safe the state about success
3084         // ... you know the reason: to know it on recovery time if next line crash .-)
3085         rInfo.DocumentState &= ~DocState::TrySave;
3086         rInfo.DocumentState |=  DocState::Handled;
3087         rInfo.DocumentState |=  DocState::Succeeded;
3088     }
3089     else
3090     {
3091         // safe the state about error ...
3092         rInfo.NewTempURL.clear();
3093         rInfo.DocumentState &= ~DocState::TrySave;
3094         rInfo.DocumentState |=  DocState::Handled;
3095         rInfo.DocumentState |=  DocState::Incomplete;
3096     }
3097 
3098     // make sure the progress is not referred any longer
3099     impl_forgetProgress(rInfo, lNewArgs, css::uno::Reference< css::frame::XFrame >());
3100 
3101     // try to remove the old temp file.
3102     // Ignore any error here. We have a new temp file, which is up to date.
3103     // The only thing is: we fill the disk with temp files, if we can't remove old ones :-)
3104     OUString sRemoveFile      = rInfo.OldTempURL;
3105     rInfo.OldTempURL = rInfo.NewTempURL;
3106     rInfo.NewTempURL.clear();
3107 
3108     implts_flushConfigItem(rInfo);
3109 
3110     // We must know if the user modifies the document again ...
3111     implts_startModifyListeningOnDoc(rInfo);
3112 
3113     AutoRecovery::st_impl_removeFile(sRemoveFile);
3114 }
3115 
implts_openDocs(const DispatchParams & aParams)3116 AutoRecovery::ETimerType AutoRecovery::implts_openDocs(const DispatchParams& aParams)
3117 {
3118     AutoRecovery::ETimerType eTimer = AutoRecovery::E_DONT_START_TIMER;
3119 
3120     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
3121 
3122     /* SAFE */ {
3123     osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
3124 
3125     Job                             eJob = m_eJob;
3126     for (auto & info : m_lDocCache)
3127     {
3128         // Such documents are already loaded by the last loop.
3129         // Don't check DocState::Succeeded here! It may be the final state of an AutoSave
3130         // operation before!!!
3131         if ((info.DocumentState & DocState::Handled) == DocState::Handled)
3132             continue;
3133 
3134         // a1,b1,c1,d2,e2,f2)
3135         if ((info.DocumentState & DocState::Damaged) == DocState::Damaged)
3136         {
3137             // don't forget to inform listener! May be this document was
3138             // damaged on last saving time ...
3139             // Then our listener need this notification.
3140             // If it was damaged during last "try to open" ...
3141             // it will be notified more than once. SH.. HAPPENS ...
3142             // } /* SAFE */
3143             g.clear();
3144             implts_informListener(eJob,
3145                 AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
3146             g.reset();
3147             // /* SAFE */ {
3148             continue;
3149         }
3150 
3151         utl::MediaDescriptor lDescriptor;
3152 
3153         // it's a UI feature - so the "USER" itself must be set as referrer
3154         lDescriptor[utl::MediaDescriptor::PROP_REFERRER()] <<= OUString(REFERRER_USER);
3155         lDescriptor[utl::MediaDescriptor::PROP_SALVAGEDFILE()] <<= OUString();
3156 
3157         // recovered documents are loaded hidden, and shown all at once, later
3158         lDescriptor[utl::MediaDescriptor::PROP_HIDDEN()] <<= true;
3159 
3160         if (aParams.m_xProgress.is())
3161             lDescriptor[utl::MediaDescriptor::PROP_STATUSINDICATOR()] <<= aParams.m_xProgress;
3162 
3163         bool bBackupWasTried   = (
3164                                         ((info.DocumentState & DocState::TryLoadBackup  ) == DocState::TryLoadBackup) || // temp. state!
3165                                         ((info.DocumentState & DocState::Incomplete       ) == DocState::Incomplete     )    // transport DocState::TryLoadBackup from last loop to this new one!
3166                                      );
3167         bool bOriginalWasTried = ((info.DocumentState & DocState::TryLoadOriginal) == DocState::TryLoadOriginal);
3168 
3169         if (bBackupWasTried)
3170         {
3171             if (!bOriginalWasTried)
3172             {
3173                 info.DocumentState |= DocState::Incomplete;
3174                 // try original URL ... ! don't continue with next item here ...
3175             }
3176             else
3177             {
3178                 info.DocumentState |= DocState::Damaged;
3179                 continue;
3180             }
3181         }
3182 
3183         OUString sLoadOriginalURL;
3184         OUString sLoadBackupURL;
3185 
3186         if (!bBackupWasTried)
3187             sLoadBackupURL = info.OldTempURL;
3188 
3189         if (!info.OrgURL.isEmpty())
3190         {
3191             sLoadOriginalURL = info.OrgURL;
3192         }
3193         else if (!info.TemplateURL.isEmpty())
3194         {
3195             sLoadOriginalURL = info.TemplateURL;
3196             lDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE()]   <<= true;
3197             lDescriptor[utl::MediaDescriptor::PROP_TEMPLATENAME()] <<= info.TemplateURL;
3198         }
3199         else if (!info.FactoryURL.isEmpty())
3200         {
3201             sLoadOriginalURL = info.FactoryURL;
3202             lDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE()] <<= true;
3203         }
3204 
3205         // A "Salvaged" item must exists every time. The core can make something special then for recovery.
3206         // Of course it should be the real file name of the original file, in case we load the temp. backup here.
3207         OUString sURL;
3208         if (!sLoadBackupURL.isEmpty())
3209         {
3210             sURL = sLoadBackupURL;
3211             info.DocumentState |= DocState::TryLoadBackup;
3212             lDescriptor[utl::MediaDescriptor::PROP_SALVAGEDFILE()] <<= sLoadOriginalURL;
3213         }
3214         else if (!sLoadOriginalURL.isEmpty())
3215         {
3216             sURL = sLoadOriginalURL;
3217             info.DocumentState |= DocState::TryLoadOriginal;
3218         }
3219         else
3220             continue; // TODO ERROR!
3221 
3222         LoadEnv::initializeUIDefaults( m_xContext, lDescriptor, true, nullptr );
3223 
3224         // } /* SAFE */
3225         g.clear();
3226 
3227         implts_flushConfigItem(info);
3228         implts_informListener(eJob,
3229             AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
3230 
3231         try
3232         {
3233             implts_openOneDoc(sURL, lDescriptor, info);
3234         }
3235         catch(const css::uno::Exception&)
3236         {
3237             info.DocumentState &= ~DocState::TryLoadBackup;
3238             info.DocumentState &= ~DocState::TryLoadOriginal;
3239             if (!sLoadBackupURL.isEmpty())
3240             {
3241                 info.DocumentState |= DocState::Incomplete;
3242                 eTimer               = AutoRecovery::E_CALL_ME_BACK;
3243             }
3244             else
3245             {
3246                 info.DocumentState |=  DocState::Handled;
3247                 info.DocumentState |=  DocState::Damaged;
3248             }
3249 
3250             implts_flushConfigItem(info, true);
3251             implts_informListener(eJob,
3252                 AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
3253 
3254             // /* SAFE */ {
3255             // Needed for next loop!
3256             g.reset();
3257             continue;
3258         }
3259 
3260         if (!info.RealFilter.isEmpty())
3261         {
3262             utl::MediaDescriptor lPatchDescriptor(info.Document->getArgs());
3263             lPatchDescriptor[utl::MediaDescriptor::PROP_FILTERNAME()] <<= info.RealFilter;
3264             info.Document->attachResource(info.Document->getURL(), lPatchDescriptor.getAsConstPropertyValueList());
3265                 // do *not* use sURL here. In case this points to the recovery file, it has already been passed
3266                 // to recoverFromFile. Also, passing it here is logically wrong, as attachResource is intended
3267                 // to take the logical file URL.
3268         }
3269 
3270         css::uno::Reference< css::util::XModifiable > xModify(info.Document, css::uno::UNO_QUERY);
3271         if ( xModify.is() )
3272         {
3273             bool bModified = ((info.DocumentState & DocState::Modified) == DocState::Modified);
3274             xModify->setModified(bModified);
3275         }
3276 
3277         info.DocumentState &= ~DocState::TryLoadBackup;
3278         info.DocumentState &= ~DocState::TryLoadOriginal;
3279         info.DocumentState |=  DocState::Handled;
3280         info.DocumentState |=  DocState::Succeeded;
3281 
3282         implts_flushConfigItem(info);
3283         implts_informListener(eJob,
3284             AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
3285 
3286         /* Normally we listen as XModifyListener on a document to know if a document was changed
3287            since our last AutoSave. And we deregister us in case we know this state.
3288            But directly after one document as recovered ... we must start listening.
3289            Otherwise the first "modify" doesn't reach us. Because we ourself called setModified()
3290            on the document via API. And currently we don't listen for any events (not at theGlobalEventBroadcaster
3291            nor at any document!).
3292         */
3293         implts_startModifyListeningOnDoc(info);
3294 
3295         // /* SAFE */ {
3296         // Needed for next loop. Don't unlock it again!
3297         g.reset();
3298     }
3299 
3300     } /* SAFE */
3301 
3302     return eTimer;
3303 }
3304 
implts_openOneDoc(const OUString & sURL,utl::MediaDescriptor & lDescriptor,AutoRecovery::TDocumentInfo & rInfo)3305 void AutoRecovery::implts_openOneDoc(const OUString&               sURL       ,
3306                                            utl::MediaDescriptor& lDescriptor,
3307                                            AutoRecovery::TDocumentInfo&   rInfo      )
3308 {
3309     css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext);
3310 
3311     ::std::vector< Reference< XComponent > > aCleanup;
3312     try
3313     {
3314         // create a new document of the desired type
3315         Reference< XModel2 > xModel(m_xContext->getServiceManager()->createInstanceWithContext(
3316                     rInfo.FactoryService, m_xContext), UNO_QUERY_THROW);
3317         aCleanup.emplace_back(xModel.get() );
3318 
3319         // put the filter name into the descriptor - we're not going to involve any type detection, so
3320         // the document might be lost without the FilterName property
3321         if ( (rInfo.DocumentState & DocState::TryLoadOriginal) == DocState::TryLoadOriginal)
3322             lDescriptor[ utl::MediaDescriptor::PROP_FILTERNAME() ] <<= rInfo.RealFilter;
3323         else
3324             lDescriptor[ utl::MediaDescriptor::PROP_FILTERNAME() ] <<= rInfo.DefaultFilter;
3325 
3326         if ( sURL == rInfo.FactoryURL )
3327         {
3328             // if the document was a new, unmodified document, then there's nothing to recover, just to init
3329             ENSURE_OR_THROW( ( rInfo.DocumentState & DocState::Modified ) == DocState(0),
3330                 "unexpected document state" );
3331             Reference< XLoadable > xModelLoad( xModel, UNO_QUERY_THROW );
3332             xModelLoad->initNew();
3333 
3334             // TODO: remove load-process specific arguments from the descriptor, e.g. the status indicator
3335             xModel->attachResource( sURL, lDescriptor.getAsConstPropertyValueList() );
3336         }
3337         else
3338         {
3339             OUString sFilterName;
3340             lDescriptor[utl::MediaDescriptor::PROP_FILTERNAME()] >>= sFilterName;
3341             if (!sFilterName.isEmpty()
3342                 && (   sFilterName == "Calc MS Excel 2007 XML"
3343                     || sFilterName == "Impress MS PowerPoint 2007 XML"
3344                     || sFilterName == "MS Word 2007 XML"))
3345                 // TODO: Probably need to check other affected formats + templates?
3346             {
3347                 // tdf#129096: in case of recovery of password protected OOXML document it is done not
3348                 // the same way as ordinal loading. Inside XDocumentRecovery::recoverFromFile
3349                 // there is a call to XFilter::filter which has constant media descriptor and thus
3350                 // all encryption data used in document is lost. To avoid this try to walkaround
3351                 // with explicit call to FormatDetector. It will try to load document, prompt for password
3352                 // and store this info in media descriptor we will use for recoverFromFile call.
3353                 Reference< css::document::XExtendedFilterDetection > xDetection(
3354                     m_xContext->getServiceManager()->createInstanceWithContext(
3355                         "com.sun.star.comp.oox.FormatDetector", m_xContext),
3356                     UNO_QUERY_THROW);
3357                 lDescriptor[utl::MediaDescriptor::PROP_URL()] <<= sURL;
3358                 Sequence< css::beans::PropertyValue > aDescriptorSeq = lDescriptor.getAsConstPropertyValueList();
3359                 OUString sType = xDetection->detect(aDescriptorSeq);
3360 
3361                 OUString sNewFilterName;
3362                 lDescriptor[utl::MediaDescriptor::PROP_FILTERNAME()] >>= sNewFilterName;
3363                 if (!sType.isEmpty() && sNewFilterName == sFilterName)
3364                 {
3365                     // Filter detection was okay, update media descriptor with one received from FilterDetect
3366                     lDescriptor = aDescriptorSeq;
3367                 }
3368             }
3369 
3370             // let it recover itself
3371             Reference< XDocumentRecovery > xDocRecover( xModel, UNO_QUERY_THROW );
3372             xDocRecover->recoverFromFile(
3373                 sURL,
3374                 lDescriptor.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_SALVAGEDFILE(), OUString() ),
3375                 lDescriptor.getAsConstPropertyValueList()
3376             );
3377 
3378             // No attachResource needed here. By definition (of XDocumentRecovery), the implementation is responsible
3379             // for completely initializing the model, which includes attachResource (or equivalent), if required.
3380         }
3381 
3382         // re-create all the views
3383         ::std::vector< OUString > aViewsToRestore( rInfo.ViewNames.begin(), rInfo.ViewNames.end() );
3384         // if we don't have views for whatever reason, then create a default-view, at least
3385         if ( aViewsToRestore.empty() )
3386             aViewsToRestore.emplace_back( );
3387 
3388         for (auto const& viewToRestore : aViewsToRestore)
3389         {
3390             // create a frame
3391             Reference< XFrame > xTargetFrame = xDesktop->findFrame( SPECIALTARGET_BLANK, 0 );
3392             aCleanup.emplace_back(xTargetFrame.get() );
3393 
3394             // create a view to the document
3395             Reference< XController2 > xController;
3396             if ( viewToRestore.getLength() )
3397             {
3398                 xController.set( xModel->createViewController( viewToRestore, Sequence< css::beans::PropertyValue >(), xTargetFrame ), UNO_SET_THROW );
3399             }
3400             else
3401             {
3402                 xController.set( xModel->createDefaultViewController( xTargetFrame ), UNO_SET_THROW );
3403             }
3404 
3405             // introduce model/view/controller to each other
3406             xController->attachModel( xModel );
3407             xModel->connectController( xController );
3408             xTargetFrame->setComponent( xController->getComponentWindow(), xController );
3409             xController->attachFrame( xTargetFrame );
3410             xModel->setCurrentController( xController );
3411         }
3412 
3413         rInfo.Document = xModel.get();
3414     }
3415     catch(const css::uno::RuntimeException&)
3416     {
3417         throw;
3418     }
3419     catch(const css::uno::Exception&)
3420     {
3421         Any aCaughtException( ::cppu::getCaughtException() );
3422 
3423         // clean up
3424         for (auto const& component : aCleanup)
3425         {
3426             css::uno::Reference< css::util::XCloseable > xClose(component, css::uno::UNO_QUERY);
3427             if ( xClose.is() )
3428                 xClose->close( true );
3429             else
3430                 component->dispose();
3431         }
3432 
3433         // re-throw
3434         throw css::lang::WrappedTargetException(
3435             "Recovery of \"" + sURL + "\" failed.",
3436             static_cast< css::frame::XDispatch* >(this),
3437             aCaughtException
3438         );
3439     }
3440 }
3441 
implts_generateNewTempURL(const OUString & sBackupPath,utl::MediaDescriptor &,AutoRecovery::TDocumentInfo & rInfo)3442 void AutoRecovery::implts_generateNewTempURL(const OUString&               sBackupPath     ,
3443                                                    utl::MediaDescriptor& /*rMediaDescriptor*/,
3444                                                    AutoRecovery::TDocumentInfo&   rInfo           )
3445 {
3446     // specify URL for saving (which points to a temp file inside backup directory)
3447     // and define a unique name, so we can locate it later.
3448     // This unique name must solve an optimization problem too!
3449     // In case we are asked to save unmodified documents too - and one of them
3450     // is an empty one (because it was new created using e.g. a URL private:factory/...)
3451     // we should not save it really. Then we put the information about such "empty document"
3452     // into the configuration and don't create any recovery file on disk.
3453     // We use the title of the document to make it unique.
3454     OUStringBuffer sUniqueName;
3455     if (!rInfo.OrgURL.isEmpty())
3456     {
3457         css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext));
3458         css::util::URL aURL;
3459         aURL.Complete = rInfo.OrgURL;
3460         xParser->parseStrict(aURL);
3461         sUniqueName.append(aURL.Name);
3462     }
3463     else if (!rInfo.FactoryURL.isEmpty())
3464         sUniqueName.append("untitled");
3465     sUniqueName.append("_");
3466 
3467     // TODO: Must we strip some illegal signes - if we use the title?
3468 
3469     OUString sName(sUniqueName.makeStringAndClear());
3470     OUString sExtension(rInfo.Extension);
3471     OUString sPath(sBackupPath);
3472     ::utl::TempFile aTempFile(sName, true, &sExtension, &sPath, true);
3473 
3474     rInfo.NewTempURL = aTempFile.GetURL();
3475 }
3476 
implts_informListener(Job eJob,const css::frame::FeatureStateEvent & aEvent)3477 void AutoRecovery::implts_informListener(      Job                      eJob  ,
3478                                          const css::frame::FeatureStateEvent& aEvent)
3479 {
3480     // Helper shares mutex with us -> threadsafe!
3481     ::cppu::OInterfaceContainerHelper* pListenerForURL = nullptr;
3482     OUString                           sJob            = AutoRecovery::implst_getJobDescription(eJob);
3483 
3484     // inform listener, which are registered for any URLs(!)
3485     pListenerForURL = m_lListener.getContainer(sJob);
3486     if(pListenerForURL == nullptr)
3487         return;
3488 
3489     ::cppu::OInterfaceIteratorHelper pIt(*pListenerForURL);
3490     while(pIt.hasMoreElements())
3491     {
3492         try
3493         {
3494             css::uno::Reference< css::frame::XStatusListener > xListener(static_cast<css::frame::XStatusListener*>(pIt.next()), css::uno::UNO_QUERY);
3495             xListener->statusChanged(aEvent);
3496         }
3497         catch(const css::uno::RuntimeException&)
3498         {
3499             pIt.remove();
3500         }
3501     }
3502 }
3503 
implst_getJobDescription(Job eJob)3504 OUString AutoRecovery::implst_getJobDescription(Job eJob)
3505 {
3506     // describe the current running operation
3507     OUStringBuffer sFeature(256);
3508     sFeature.append(CMD_PROTOCOL);
3509 
3510     // Attention: Because "eJob" is used as a flag field the order of checking these
3511     // flags is important. We must prefer job with higher priorities!
3512     // E.g. EmergencySave has an higher prio then AutoSave ...
3513     // On the other side there exist a well defined order between two different jobs.
3514     // e.g. PrepareEmergencySave must be done before EmergencySave is started of course.
3515 
3516     if ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave)
3517         sFeature.append(CMD_DO_PREPARE_EMERGENCY_SAVE);
3518     else if ((eJob & Job::EmergencySave) == Job::EmergencySave)
3519         sFeature.append(CMD_DO_EMERGENCY_SAVE);
3520     else if ((eJob & Job::Recovery) == Job::Recovery)
3521         sFeature.append(CMD_DO_RECOVERY);
3522     else if ((eJob & Job::SessionSave) == Job::SessionSave)
3523         sFeature.append(CMD_DO_SESSION_SAVE);
3524     else if ((eJob & Job::SessionQuietQuit) == Job::SessionQuietQuit)
3525         sFeature.append(CMD_DO_SESSION_QUIET_QUIT);
3526     else if ((eJob & Job::SessionRestore) == Job::SessionRestore)
3527         sFeature.append(CMD_DO_SESSION_RESTORE);
3528     else if ((eJob & Job::EntryBackup) == Job::EntryBackup)
3529         sFeature.append(CMD_DO_ENTRY_BACKUP);
3530     else if ((eJob & Job::EntryCleanup) == Job::EntryCleanup)
3531         sFeature.append(CMD_DO_ENTRY_CLEANUP);
3532     else if ((eJob & Job::AutoSave) == Job::AutoSave)
3533         sFeature.append(CMD_DO_AUTO_SAVE);
3534     else if ( eJob != Job::NoJob )
3535         SAL_INFO("fwk.autorecovery", "AutoRecovery::implst_getJobDescription(): Invalid job identifier detected.");
3536 
3537     return sFeature.makeStringAndClear();
3538 }
3539 
implst_classifyJob(const css::util::URL & aURL)3540 Job AutoRecovery::implst_classifyJob(const css::util::URL& aURL)
3541 {
3542     if ( aURL.Protocol == CMD_PROTOCOL )
3543     {
3544         if ( aURL.Path == CMD_DO_PREPARE_EMERGENCY_SAVE )
3545             return Job::PrepareEmergencySave;
3546         else if ( aURL.Path == CMD_DO_EMERGENCY_SAVE )
3547             return Job::EmergencySave;
3548         else if ( aURL.Path == CMD_DO_RECOVERY )
3549             return Job::Recovery;
3550         else if ( aURL.Path == CMD_DO_ENTRY_BACKUP )
3551             return Job::EntryBackup;
3552         else if ( aURL.Path == CMD_DO_ENTRY_CLEANUP )
3553             return Job::EntryCleanup;
3554         else if ( aURL.Path == CMD_DO_SESSION_SAVE )
3555             return Job::SessionSave;
3556         else if ( aURL.Path == CMD_DO_SESSION_QUIET_QUIT )
3557             return Job::SessionQuietQuit;
3558         else if ( aURL.Path == CMD_DO_SESSION_RESTORE )
3559             return Job::SessionRestore;
3560         else if ( aURL.Path == CMD_DO_DISABLE_RECOVERY )
3561             return Job::DisableAutorecovery;
3562         else if ( aURL.Path == CMD_DO_SET_AUTOSAVE_STATE )
3563             return Job::SetAutosaveState;
3564     }
3565 
3566     SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_classifyJob(): Invalid URL (protocol).");
3567     return Job::NoJob;
3568 }
3569 
implst_createFeatureStateEvent(Job eJob,const OUString & sEventType,AutoRecovery::TDocumentInfo const * pInfo)3570 css::frame::FeatureStateEvent AutoRecovery::implst_createFeatureStateEvent(      Job                    eJob      ,
3571                                                                            const OUString&             sEventType,
3572                                                                            AutoRecovery::TDocumentInfo const * pInfo     )
3573 {
3574     css::frame::FeatureStateEvent aEvent;
3575     aEvent.FeatureURL.Complete   = AutoRecovery::implst_getJobDescription(eJob);
3576     aEvent.FeatureDescriptor     = sEventType;
3577 
3578     if (pInfo && sEventType == OPERATION_UPDATE)
3579     {
3580         // pack rInfo for transport via UNO
3581         ::comphelper::NamedValueCollection aInfo;
3582         aInfo.put( OUString(CFG_ENTRY_PROP_ID), pInfo->ID );
3583         aInfo.put( OUString(CFG_ENTRY_PROP_ORIGINALURL), pInfo->OrgURL );
3584         aInfo.put( OUString(CFG_ENTRY_PROP_FACTORYURL), pInfo->FactoryURL );
3585         aInfo.put( OUString(CFG_ENTRY_PROP_TEMPLATEURL), pInfo->TemplateURL );
3586         aInfo.put( OUString(CFG_ENTRY_PROP_TEMPURL), pInfo->OldTempURL.isEmpty() ? pInfo->NewTempURL : pInfo->OldTempURL );
3587         aInfo.put( OUString(CFG_ENTRY_PROP_MODULE), pInfo->AppModule);
3588         aInfo.put( OUString(CFG_ENTRY_PROP_TITLE), pInfo->Title);
3589         aInfo.put( OUString(CFG_ENTRY_PROP_VIEWNAMES), pInfo->ViewNames);
3590         aInfo.put( OUString(CFG_ENTRY_PROP_DOCUMENTSTATE), sal_Int32(pInfo->DocumentState));
3591 
3592         aEvent.State <<= aInfo.getPropertyValues();
3593     }
3594 
3595     return aEvent;
3596 }
3597 
implts_resetHandleStates()3598 void AutoRecovery::implts_resetHandleStates()
3599 {
3600     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
3601 
3602     /* SAFE */ {
3603     osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
3604 
3605     for (auto & info : m_lDocCache)
3606     {
3607         info.DocumentState &= ~DocState::Handled;
3608         info.DocumentState &= ~DocState::Postponed;
3609 
3610         // } /* SAFE */
3611         g.clear();
3612         implts_flushConfigItem(info);
3613         g.reset();
3614         // /* SAFE */ {
3615     }
3616     } /* SAFE */
3617 }
3618 
implts_prepareEmergencySave()3619 void AutoRecovery::implts_prepareEmergencySave()
3620 {
3621     // Be sure to know all open documents really .-)
3622     implts_verifyCacheAgainstDesktopDocumentList();
3623 
3624     // hide all docs, so the user can't disturb our emergency save .-)
3625     implts_changeAllDocVisibility(false);
3626 }
3627 
implts_doEmergencySave(const DispatchParams & aParams)3628 void AutoRecovery::implts_doEmergencySave(const DispatchParams& aParams)
3629 {
3630     // Write a hint "we crashed" into the configuration, so
3631     // the error report tool is started too in case no recovery
3632     // documents exists and was saved.
3633 
3634     std::shared_ptr<comphelper::ConfigurationChanges> batch(
3635             comphelper::ConfigurationChanges::create(m_xContext));
3636     officecfg::Office::Recovery::RecoveryInfo::Crashed::set(true, batch);
3637     batch->commit();
3638 
3639     // for all docs, store their current view/names in the configuration
3640     implts_persistAllActiveViewNames();
3641 
3642     // The called method for saving documents runs
3643     // during normal AutoSave more than once. Because
3644     // it postpone active documents and save it later.
3645     // That is normally done by recalling it from a timer.
3646     // Here we must do it immediately!
3647     // Of course this method returns the right state -
3648     // because it knows, that we are running in EMERGENCY SAVE mode .-)
3649 
3650     bool const bAllowUserIdleLoop = false; // not allowed to change that .-)
3651     AutoRecovery::ETimerType eSuggestedTimer    = AutoRecovery::E_DONT_START_TIMER;
3652     do
3653     {
3654         eSuggestedTimer = implts_saveDocs(bAllowUserIdleLoop, true, &aParams);
3655     }
3656     while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
3657 
3658     // reset the handle state of all
3659     // cache items. Such handle state indicates, that a document
3660     // was already saved during the THIS(!) EmergencySave session.
3661     // Of course following recovery session must be started without
3662     // any "handle" state ...
3663     implts_resetHandleStates();
3664 
3665     // flush config cached back to disc.
3666     impl_flushALLConfigChanges();
3667 
3668     // try to make sure next time office will be started user won't be
3669     // notified about any other might be running office instance
3670     // remove ".lock" file from disc !
3671     AutoRecovery::st_impl_removeLockFile();
3672 }
3673 
implts_doRecovery(const DispatchParams & aParams)3674 void AutoRecovery::implts_doRecovery(const DispatchParams& aParams)
3675 {
3676     AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER;
3677     do
3678     {
3679         eSuggestedTimer = implts_openDocs(aParams);
3680     }
3681     while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
3682 
3683     // reset the handle state of all
3684     // cache items. Such handle state indicates, that a document
3685     // was already saved during the THIS(!) Recovery session.
3686     // Of course a may be following EmergencySave session must be started without
3687     // any "handle" state...
3688     implts_resetHandleStates();
3689 
3690     // Reset the configuration hint "we were crashed"!
3691     std::shared_ptr<comphelper::ConfigurationChanges> batch(
3692             comphelper::ConfigurationChanges::create(m_xContext));
3693     officecfg::Office::Recovery::RecoveryInfo::Crashed::set(false, batch);
3694     batch->commit();
3695 }
3696 
implts_doSessionSave(const DispatchParams & aParams)3697 void AutoRecovery::implts_doSessionSave(const DispatchParams& aParams)
3698 {
3699     SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionSave()");
3700 
3701     // Be sure to know all open documents really .-)
3702     implts_verifyCacheAgainstDesktopDocumentList();
3703 
3704     // for all docs, store their current view/names in the configuration
3705     implts_persistAllActiveViewNames();
3706 
3707     // The called method for saving documents runs
3708     // during normal AutoSave more than once. Because
3709     // it postpone active documents and save it later.
3710     // That is normally done by recalling it from a timer.
3711     // Here we must do it immediately!
3712     // Of course this method returns the right state -
3713     // because it knows, that we are running in SESSION SAVE mode .-)
3714 
3715     bool const bAllowUserIdleLoop = false; // not allowed to change that .-)
3716     AutoRecovery::ETimerType eSuggestedTimer    = AutoRecovery::E_DONT_START_TIMER;
3717     do
3718     {
3719         // do not remove lock files of the documents, it will be done on session quit
3720         eSuggestedTimer = implts_saveDocs(bAllowUserIdleLoop, false, &aParams);
3721     }
3722     while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
3723 
3724     // reset the handle state of all
3725     // cache items. Such handle state indicates, that a document
3726     // was already saved during the THIS(!) save session.
3727     // Of course following restore session must be started without
3728     // any "handle" state ...
3729     implts_resetHandleStates();
3730 
3731     // flush config cached back to disc.
3732     impl_flushALLConfigChanges();
3733 }
3734 
implts_doSessionQuietQuit()3735 void AutoRecovery::implts_doSessionQuietQuit()
3736 {
3737     SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionQuietQuit()");
3738 
3739     // try to make sure next time office will be started user won't be
3740     // notified about any other might be running office instance
3741     // remove ".lock" file from disc!
3742     // it is done as a first action for session save since Gnome sessions
3743     // do not provide enough time for shutdown, and the dialog looks to be
3744     // confusing for the user
3745     AutoRecovery::st_impl_removeLockFile();
3746 
3747     // reset all modified documents, so the don't show any UI on closing ...
3748     // and close all documents, so we can shutdown the OS!
3749     implts_prepareSessionShutdown();
3750 
3751     // Write a hint for "stored session data" into the configuration, so
3752     // the on next startup we know what's happen last time
3753     std::shared_ptr<comphelper::ConfigurationChanges> batch(
3754             comphelper::ConfigurationChanges::create(m_xContext));
3755     officecfg::Office::Recovery::RecoveryInfo::SessionData::set(true, batch);
3756     batch->commit();
3757 
3758     // flush config cached back to disc.
3759     impl_flushALLConfigChanges();
3760 }
3761 
implts_doSessionRestore(const DispatchParams & aParams)3762 void AutoRecovery::implts_doSessionRestore(const DispatchParams& aParams)
3763 {
3764     SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionRestore() ...");
3765 
3766     AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER;
3767     do
3768     {
3769         eSuggestedTimer = implts_openDocs(aParams);
3770     }
3771     while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
3772 
3773     // reset the handle state of all
3774     // cache items. Such handle state indicates, that a document
3775     // was already saved during the THIS(!) Restore session.
3776     // Of course a may be following save session must be started without
3777     // any "handle" state ...
3778     implts_resetHandleStates();
3779 
3780     // make all opened documents visible
3781     implts_changeAllDocVisibility(true);
3782 
3783     // Reset the configuration hint for "session save"!
3784     SAL_INFO("fwk.autorecovery", "... reset config key 'SessionData'");
3785     std::shared_ptr<comphelper::ConfigurationChanges> batch(
3786             comphelper::ConfigurationChanges::create(m_xContext));
3787     officecfg::Office::Recovery::RecoveryInfo::SessionData::set(false, batch);
3788     batch->commit();
3789 
3790     SAL_INFO("fwk.autorecovery", "... AutoRecovery::implts_doSessionRestore()");
3791 }
3792 
implts_backupWorkingEntry(const DispatchParams & aParams)3793 void AutoRecovery::implts_backupWorkingEntry(const DispatchParams& aParams)
3794 {
3795     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
3796 
3797     for (auto const& info : m_lDocCache)
3798     {
3799         if (info.ID != aParams.m_nWorkingEntryID)
3800             continue;
3801 
3802         OUString sSourceURL;
3803         // Prefer temp file. It contains the changes against the original document!
3804         if (!info.OldTempURL.isEmpty())
3805             sSourceURL = info.OldTempURL;
3806         else if (!info.NewTempURL.isEmpty())
3807             sSourceURL = info.NewTempURL;
3808         else if (!info.OrgURL.isEmpty())
3809             sSourceURL = info.OrgURL;
3810         else
3811             continue; // nothing real to save! An unmodified but new created document.
3812 
3813         INetURLObject aParser(sSourceURL);
3814         // AutoRecovery::EFailureSafeResult eResult =
3815         implts_copyFile(sSourceURL, aParams.m_sSavePath, aParser.getName());
3816 
3817         // TODO: Check eResult and react for errors (InteractionHandler!?)
3818         // Currently we ignore it ...
3819         // DON'T UPDATE THE CACHE OR REMOVE ANY TEMP. FILES FROM DISK.
3820         // That has to be forced from outside explicitly.
3821         // See implts_cleanUpWorkingEntry() for further details.
3822     }
3823 }
3824 
implts_cleanUpWorkingEntry(const DispatchParams & aParams)3825 void AutoRecovery::implts_cleanUpWorkingEntry(const DispatchParams& aParams)
3826 {
3827     CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE);
3828 
3829     AutoRecovery::TDocumentList::iterator pIt = std::find_if(m_lDocCache.begin(), m_lDocCache.end(),
3830         [&aParams](const AutoRecovery::TDocumentInfo& rInfo) { return rInfo.ID == aParams.m_nWorkingEntryID; });
3831     if (pIt != m_lDocCache.end())
3832     {
3833         AutoRecovery::TDocumentInfo& rInfo = *pIt;
3834         AutoRecovery::st_impl_removeFile(rInfo.OldTempURL);
3835         AutoRecovery::st_impl_removeFile(rInfo.NewTempURL);
3836         implts_flushConfigItem(rInfo, true); // sal_True => remove it from xml config!
3837 
3838         m_lDocCache.erase(pIt);
3839     }
3840 }
3841 
implts_copyFile(const OUString & sSource,const OUString & sTargetPath,const OUString & sTargetName)3842 AutoRecovery::EFailureSafeResult AutoRecovery::implts_copyFile(const OUString& sSource    ,
3843                                                                const OUString& sTargetPath,
3844                                                                const OUString& sTargetName)
3845 {
3846     // create content for the parent folder and call transfer on that content with the source content
3847     // and the destination file name as parameters
3848 
3849     css::uno::Reference< css::ucb::XCommandEnvironment > xEnvironment;
3850 
3851     ::ucbhelper::Content aSourceContent;
3852     ::ucbhelper::Content aTargetContent;
3853 
3854     try
3855     {
3856         aTargetContent = ::ucbhelper::Content(sTargetPath, xEnvironment, m_xContext);
3857     }
3858     catch(const css::uno::Exception&)
3859     {
3860         return AutoRecovery::E_WRONG_TARGET_PATH;
3861     }
3862 
3863     sal_Int32 nNameClash;
3864     nNameClash = css::ucb::NameClash::RENAME;
3865 
3866     try
3867     {
3868         bool bSuccess = ::ucbhelper::Content::create(sSource, xEnvironment, m_xContext, aSourceContent);
3869         if (!bSuccess)
3870             return AutoRecovery::E_ORIGINAL_FILE_MISSING;
3871         aTargetContent.transferContent(aSourceContent, ::ucbhelper::InsertOperation::Copy, sTargetName, nNameClash);
3872     }
3873     catch(const css::uno::Exception&)
3874     {
3875         return AutoRecovery::E_ORIGINAL_FILE_MISSING;
3876     }
3877 
3878     return AutoRecovery::E_COPIED;
3879 }
3880 
convertFastPropertyValue(css::uno::Any &,css::uno::Any &,sal_Int32,const css::uno::Any &)3881 sal_Bool SAL_CALL AutoRecovery::convertFastPropertyValue(      css::uno::Any& /*aConvertedValue*/,
3882                                                                css::uno::Any& /*aOldValue*/      ,
3883                                                                sal_Int32      /*nHandle*/        ,
3884                                                          const css::uno::Any& /*aValue*/         )
3885 {
3886     // not needed currently
3887     return false;
3888 }
3889 
setFastPropertyValue_NoBroadcast(sal_Int32,const css::uno::Any &)3890 void SAL_CALL AutoRecovery::setFastPropertyValue_NoBroadcast(      sal_Int32      /*nHandle*/,
3891                                                              const css::uno::Any& /*aValue*/ )
3892 {
3893     // not needed currently
3894 }
3895 
getFastPropertyValue(css::uno::Any & aValue,sal_Int32 nHandle) const3896 void SAL_CALL AutoRecovery::getFastPropertyValue(css::uno::Any& aValue ,
3897                                                  sal_Int32      nHandle) const
3898 {
3899     switch(nHandle)
3900     {
3901         case AUTORECOVERY_PROPHANDLE_EXISTS_RECOVERYDATA :
3902                 {
3903                     bool bSessionData = officecfg::Office::Recovery::RecoveryInfo::SessionData::get(m_xContext);
3904                     bool bRecoveryData = !m_lDocCache.empty();
3905 
3906                     // exists session data ... => then we can't say, that these
3907                     // data are valid for recovery. So we have to return sal_False then!
3908                     if (bSessionData)
3909                         bRecoveryData = false;
3910 
3911                     aValue <<= bRecoveryData;
3912                 }
3913                 break;
3914 
3915         case AUTORECOVERY_PROPHANDLE_CRASHED :
3916                 aValue <<= officecfg::Office::Recovery::RecoveryInfo::Crashed::get(m_xContext);
3917                 break;
3918 
3919         case AUTORECOVERY_PROPHANDLE_EXISTS_SESSIONDATA :
3920                 aValue <<= officecfg::Office::Recovery::RecoveryInfo::SessionData::get(m_xContext);
3921                 break;
3922     }
3923 }
3924 
impl_getStaticPropertyDescriptor()3925 css::uno::Sequence< css::beans::Property > impl_getStaticPropertyDescriptor()
3926 {
3927     return
3928     {
3929         css::beans::Property( AUTORECOVERY_PROPNAME_CRASHED            , AUTORECOVERY_PROPHANDLE_CRASHED            , cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ),
3930         css::beans::Property( AUTORECOVERY_PROPNAME_EXISTS_RECOVERYDATA, AUTORECOVERY_PROPHANDLE_EXISTS_RECOVERYDATA, cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ),
3931         css::beans::Property( AUTORECOVERY_PROPNAME_EXISTS_SESSIONDATA , AUTORECOVERY_PROPHANDLE_EXISTS_SESSIONDATA , cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ),
3932     };
3933 }
3934 
getInfoHelper()3935 ::cppu::IPropertyArrayHelper& SAL_CALL AutoRecovery::getInfoHelper()
3936 {
3937     static ::cppu::OPropertyArrayHelper ourInfoHelper(impl_getStaticPropertyDescriptor(), true);
3938 
3939     return ourInfoHelper;
3940 }
3941 
getPropertySetInfo()3942 css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL AutoRecovery::getPropertySetInfo()
3943 {
3944     static css::uno::Reference< css::beans::XPropertySetInfo > xInfo(
3945                     ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()));
3946 
3947     return xInfo;
3948 }
3949 
implts_verifyCacheAgainstDesktopDocumentList()3950 void AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList()
3951 {
3952     SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList() ...");
3953     try
3954     {
3955         css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext);
3956 
3957         css::uno::Reference< css::container::XIndexAccess > xContainer(
3958             xDesktop->getFrames(),
3959             css::uno::UNO_QUERY_THROW);
3960 
3961         sal_Int32 i = 0;
3962         sal_Int32 c = xContainer->getCount();
3963 
3964         for (i=0; i<c; ++i)
3965         {
3966             css::uno::Reference< css::frame::XFrame > xFrame;
3967             try
3968             {
3969                 xContainer->getByIndex(i) >>= xFrame;
3970                 if (!xFrame.is())
3971                     continue;
3972             }
3973             // can happen in multithreaded environments, that frames was removed from the container during this loop runs!
3974             // Ignore it.
3975             catch(const css::lang::IndexOutOfBoundsException&)
3976             {
3977                 continue;
3978             }
3979 
3980             // We are interested on visible documents only.
3981             // Note: It's n optional interface .-(
3982             css::uno::Reference< css::awt::XWindow2 > xVisibleCheck(
3983                 xFrame->getContainerWindow(),
3984                 css::uno::UNO_QUERY);
3985             if (
3986                 (!xVisibleCheck.is()        ) ||
3987                 (!xVisibleCheck->isVisible())
3988                )
3989             {
3990                 continue;
3991             }
3992 
3993             // extract the model from the frame.
3994             // Ignore "view only" frames, which does not have a model.
3995             css::uno::Reference< css::frame::XController > xController;
3996             css::uno::Reference< css::frame::XModel3 >     xModel;
3997 
3998             xController = xFrame->getController();
3999             if (xController.is())
4000                 xModel.set( xController->getModel(), UNO_QUERY_THROW );
4001             if (!xModel.is())
4002                 continue;
4003 
4004             // insert model into cache ...
4005             // If the model is already well known inside cache
4006             // it's information set will be updated by asking the
4007             // model again for its new states.
4008             implts_registerDocument(xModel);
4009         }
4010     }
4011     catch(const css::uno::RuntimeException&)
4012     {
4013         throw;
4014     }
4015     catch(const css::uno::Exception&)
4016     {
4017     }
4018 
4019     SAL_INFO("fwk.autorecovery", "... AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList()");
4020 }
4021 
impl_enoughDiscSpace(sal_Int32 nRequiredSpace)4022 bool AutoRecovery::impl_enoughDiscSpace(sal_Int32 nRequiredSpace)
4023 {
4024 #ifdef SIMULATE_FULL_DISC
4025     return sal_False;
4026 #else  // SIMULATE_FULL_DISC
4027     // In case an error occurs and we are not able to retrieve the needed information
4028     // it's better to "disable" the feature ShowErrorOnFullDisc !
4029     // Otherwise we start a confusing process of error handling ...
4030 
4031     sal_uInt64 nFreeSpace = SAL_MAX_UINT64;
4032 
4033     OUString     sBackupPath(SvtPathOptions().GetBackupPath());
4034     ::osl::VolumeInfo   aInfo      (osl_VolumeInfo_Mask_FreeSpace);
4035     ::osl::FileBase::RC aRC         = ::osl::Directory::getVolumeInfo(sBackupPath, aInfo);
4036 
4037     if (
4038         (aInfo.isValid(osl_VolumeInfo_Mask_FreeSpace)) &&
4039         (aRC == ::osl::FileBase::E_None         )
4040        )
4041     {
4042         nFreeSpace = aInfo.getFreeSpace();
4043     }
4044 
4045     sal_uInt64 nFreeMB = nFreeSpace/1048576;
4046     return (nFreeMB >= o3tl::make_unsigned(nRequiredSpace));
4047 #endif // SIMULATE_FULL_DISC
4048 }
4049 
impl_showFullDiscError()4050 void AutoRecovery::impl_showFullDiscError()
4051 {
4052     OUString sBtn(FwkResId(STR_FULL_DISC_RETRY_BUTTON));
4053     OUString sMsg(FwkResId(STR_FULL_DISC_MSG));
4054 
4055     OUString sBackupURL(SvtPathOptions().GetBackupPath());
4056     INetURLObject aConverter(sBackupURL);
4057     sal_Unicode aDelimiter;
4058     OUString sBackupPath = aConverter.getFSysPath(FSysStyle::Detect, &aDelimiter);
4059     if (sBackupPath.getLength() < 1)
4060         sBackupPath = sBackupURL;
4061 
4062     std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
4063                                               VclMessageType::Error, VclButtonsType::NONE,
4064                                               sMsg.replaceAll("%PATH", sBackupPath)));
4065     xBox->add_button(sBtn, RET_OK);
4066     xBox->run();
4067 }
4068 
impl_establishProgress(const AutoRecovery::TDocumentInfo & rInfo,utl::MediaDescriptor & rArgs,const css::uno::Reference<css::frame::XFrame> & xNewFrame)4069 void AutoRecovery::impl_establishProgress(const AutoRecovery::TDocumentInfo&               rInfo    ,
4070                                                 utl::MediaDescriptor&             rArgs    ,
4071                                           const css::uno::Reference< css::frame::XFrame >& xNewFrame)
4072 {
4073     // external well known frame must be preferred (because it was created by ourself
4074     // for loading documents into this frame)!
4075     // But if no frame exists... we can try to locate it using any frame bound to the provided
4076     // document. Of course we must live without any frame in case the document does not exists at this
4077     // point. But this state should not occur. In such case xNewFrame should be valid ... hopefully .-)
4078     css::uno::Reference< css::frame::XFrame > xFrame = xNewFrame;
4079     if (
4080         (!xFrame.is()       ) &&
4081         (rInfo.Document.is())
4082        )
4083     {
4084         css::uno::Reference< css::frame::XController > xController = rInfo.Document->getCurrentController();
4085         if (xController.is())
4086             xFrame = xController->getFrame();
4087     }
4088 
4089     // Any outside progress must be used ...
4090     // Only if there is no progress, we can create our own one.
4091     css::uno::Reference< css::task::XStatusIndicator > xInternalProgress;
4092     css::uno::Reference< css::task::XStatusIndicator > xExternalProgress = rArgs.getUnpackedValueOrDefault(
4093                                                                                 utl::MediaDescriptor::PROP_STATUSINDICATOR(),
4094                                                                                 css::uno::Reference< css::task::XStatusIndicator >() );
4095 
4096     // Normally a progress is set from outside (e.g. by the CrashSave/Recovery dialog, which uses our dispatch API).
4097     // But for a normal auto save we don't have such "external progress"... because this function is triggered by our own timer then.
4098     // In such case we must create our own progress !
4099     if (
4100         (! xExternalProgress.is()) &&
4101         (xFrame.is()             )
4102        )
4103     {
4104         css::uno::Reference< css::task::XStatusIndicatorFactory > xProgressFactory(xFrame, css::uno::UNO_QUERY);
4105         if (xProgressFactory.is())
4106             xInternalProgress = xProgressFactory->createStatusIndicator();
4107     }
4108 
4109     // HACK
4110     // An external provided progress (most given by the CrashSave/Recovery dialog)
4111     // must be preferred. But we know that some application filters query its own progress instance
4112     // at the frame method Frame::createStatusIndicator().
4113     // So we use a two step mechanism:
4114     // 1) we set the progress inside the MediaDescriptor, which will be provided to the filter
4115     // 2) and we set a special Frame property, which overwrites the normal behaviour of Frame::createStatusIndicator .-)
4116     // But we suppress 2) in case we uses an internal progress. Because then it doesn't matter
4117     // if our applications make it wrong. In such case the internal progress resists at the same frame
4118     // and there is no need to forward progress activities to e.g. an outside dialog .-)
4119     if (
4120         (xExternalProgress.is()) &&
4121         (xFrame.is()           )
4122        )
4123     {
4124         css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY);
4125         if (xFrameProps.is())
4126             xFrameProps->setPropertyValue(FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, css::uno::makeAny(xExternalProgress));
4127     }
4128 
4129     // But inside the MediaDescriptor we must set our own create progress ...
4130     // in case there is not already another progress set.
4131     rArgs.createItemIfMissing(utl::MediaDescriptor::PROP_STATUSINDICATOR(), xInternalProgress);
4132 }
4133 
impl_forgetProgress(const AutoRecovery::TDocumentInfo & rInfo,utl::MediaDescriptor & rArgs,const css::uno::Reference<css::frame::XFrame> & xNewFrame)4134 void AutoRecovery::impl_forgetProgress(const AutoRecovery::TDocumentInfo&               rInfo    ,
4135                                              utl::MediaDescriptor&             rArgs    ,
4136                                        const css::uno::Reference< css::frame::XFrame >& xNewFrame)
4137 {
4138     // external well known frame must be preferred (because it was created by ourself
4139     // for loading documents into this frame)!
4140     // But if no frame exists... we can try to locate it using any frame bound to the provided
4141     // document. Of course we must live without any frame in case the document does not exists at this
4142     // point. But this state should not occur. In such case xNewFrame should be valid ... hopefully .-)
4143     css::uno::Reference< css::frame::XFrame > xFrame = xNewFrame;
4144     if (
4145         (!xFrame.is()       ) &&
4146         (rInfo.Document.is())
4147        )
4148     {
4149         css::uno::Reference< css::frame::XController > xController = rInfo.Document->getCurrentController();
4150         if (xController.is())
4151             xFrame = xController->getFrame();
4152     }
4153 
4154     // stop progress interception on corresponding frame.
4155     css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY);
4156     if (xFrameProps.is())
4157         xFrameProps->setPropertyValue(FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, css::uno::makeAny(css::uno::Reference< css::task::XStatusIndicator >()));
4158 
4159     // forget progress inside list of arguments.
4160     utl::MediaDescriptor::iterator pArg = rArgs.find(utl::MediaDescriptor::PROP_STATUSINDICATOR());
4161     if (pArg != rArgs.end())
4162     {
4163         rArgs.erase(pArg);
4164         pArg = rArgs.end();
4165     }
4166 }
4167 
impl_flushALLConfigChanges()4168 void AutoRecovery::impl_flushALLConfigChanges()
4169 {
4170     try
4171     {
4172         // SOLAR SAFE ->
4173         SolarMutexGuard aGuard;
4174         ::utl::ConfigManager::storeConfigItems();
4175     }
4176     catch(const css::uno::Exception&)
4177     {
4178     }
4179 }
4180 
st_impl_removeFile(const OUString & sURL)4181 void AutoRecovery::st_impl_removeFile(const OUString& sURL)
4182 {
4183     if ( sURL.isEmpty())
4184         return;
4185 
4186     try
4187     {
4188         ::ucbhelper::Content aContent(sURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), m_xContext);
4189         aContent.executeCommand("delete", css::uno::makeAny(true));
4190     }
4191     catch(const css::uno::Exception&)
4192     {
4193     }
4194 }
4195 
st_impl_removeLockFile()4196 void AutoRecovery::st_impl_removeLockFile()
4197 {
4198     try
4199     {
4200         OUString sUserURL;
4201         ::utl::Bootstrap::locateUserInstallation( sUserURL );
4202 
4203         OUString sLockURL = sUserURL + "/.lock";
4204         AutoRecovery::st_impl_removeFile(sLockURL);
4205     }
4206     catch(const css::uno::Exception&)
4207     {
4208     }
4209 }
4210 
4211 }
4212 
4213 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_framework_AutoRecovery_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)4214 com_sun_star_comp_framework_AutoRecovery_get_implementation(
4215     css::uno::XComponentContext *context,
4216     css::uno::Sequence<css::uno::Any> const &)
4217 {
4218     rtl::Reference<AutoRecovery> xAutoRecovery = new AutoRecovery(context);
4219     // 2nd phase initialization needed
4220     xAutoRecovery->initListeners();
4221 
4222     return cppu::acquire(xAutoRecovery.get());
4223 }
4224 
4225 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4226