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