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