framework/source/services/autorecovery.cxx | 44 +++++++++++++++++- include/sfx2/sfxbasemodel.hxx | 7 ++ offapi/UnoApi_offapi.mk | 1 offapi/com/sun/star/document/XDocumentRecovery2.idl | 47 ++++++++++++++++++++ sfx2/source/doc/sfxbasemodel.cxx | 44 ++++++++++++++---- 5 files changed, 127 insertions(+), 16 deletions(-)
New commits: commit 79113484cacb630f93f87c483b6c5d97c47b8728 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sun Jul 16 23:17:08 2023 +0300 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Tue Jul 18 19:15:33 2023 +0200 [API CHANGE] tdf#144512: keep autosave interval separately for each document Maybe this would make a better UX, as the request suggests ... or maybe not. css::document::XDocumentRecovery2 is introduced for this, allowing to query the document dirty state duration. Change-Id: I25997788bc5da261f7e4131616ab8d4a245de380 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/154505 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> diff --git a/framework/source/services/autorecovery.cxx b/framework/source/services/autorecovery.cxx index a1a984e7617e..ee58ba9212a2 100644 --- a/framework/source/services/autorecovery.cxx +++ b/framework/source/services/autorecovery.cxx @@ -50,7 +50,7 @@ #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/beans/PropertyAttribute.hpp> #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> -#include <com/sun/star/document/XDocumentRecovery.hpp> +#include <com/sun/star/document/XDocumentRecovery2.hpp> #include <com/sun/star/document/XExtendedFilterDetection.hpp> #include <com/sun/star/util/XCloseable.hpp> #include <com/sun/star/awt/XWindow2.hpp> @@ -995,7 +995,7 @@ private: }; // recovery.xcu -constexpr OUStringLiteral CFG_PACKAGE_RECOVERY = u"org.openoffice.Office.Recovery/"; +constexpr OUStringLiteral CFG_PACKAGE_RECOVERY = u"/org.openoffice.Office.Recovery"; const char CFG_ENTRY_AUTOSAVE_ENABLED[] = "AutoSave/Enabled"; @@ -2183,7 +2183,7 @@ void AutoRecovery::implts_updateTimer() { implts_stopTimer(); - sal_Int32 nMilliSeconds = 0; + sal_Int64 nMilliSeconds = 0; /* SAFE */ { osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); @@ -2196,7 +2196,27 @@ void AutoRecovery::implts_updateTimer() if (m_eTimerType == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL) { - nMilliSeconds = officecfg::Office::Recovery::AutoSave::TimeIntervall::get() * 60000; // [min] => 60.000 ms + const sal_Int64 nConfiguredAutoSaveInterval + = officecfg::Office::Recovery::AutoSave::TimeIntervall::get() + * sal_Int64(60000); // [min] => 60.000 ms + nMilliSeconds = nConfiguredAutoSaveInterval; + + // Calculate how soon the nearest dirty document's autosave time is; + // store the shortest document autosave timeout as the next timer timeout. + for (const auto& docInfo : m_lDocCache) + { + if (auto xDocRecovery2 = docInfo.Document.query<XDocumentRecovery2>()) + { + sal_Int64 nDirtyDuration = xDocRecovery2->getModifiedStateDuration(); + if (nDirtyDuration < 0) + continue; + if (nDirtyDuration > nConfiguredAutoSaveInterval) + nDirtyDuration = nConfiguredAutoSaveInterval; // nMilliSeconds will be 0 + + nMilliSeconds + = std::min(nMilliSeconds, nConfiguredAutoSaveInterval - nDirtyDuration); + } + } } else if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE) { @@ -2819,6 +2839,10 @@ AutoRecovery::ETimerType AutoRecovery::implts_saveDocs( bool bAllow CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + const sal_Int64 nConfiguredAutoSaveInterval + = officecfg::Office::Common::Save::Document::AutoSaveTimeIntervall::get() + * sal_Int64(60000); // min -> ms + /* SAFE */ { osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); @@ -2858,6 +2882,18 @@ AutoRecovery::ETimerType AutoRecovery::implts_saveDocs( bool bAllow continue; } + if (auto xDocRecovery2 = xDocRecover.query<XDocumentRecovery2>()) + { + const sal_Int64 nDirtyDuration = xDocRecovery2->getModifiedStateDuration(); + // If the document became modified not too long ago, don't autosave it yet. + // Round up to second - if this document is almost ready for autosave, do it now. + if (nDirtyDuration + 999 < nConfiguredAutoSaveInterval) + { + aInfo.DocumentState |= DocState::Handled; + continue; + } + } + // check if this document is still used by a concurrent save operation // e.g. if the user tried to save via UI. // Handle it in the following way: diff --git a/include/sfx2/sfxbasemodel.hxx b/include/sfx2/sfxbasemodel.hxx index 8ccd59292e58..2602387fad59 100644 --- a/include/sfx2/sfxbasemodel.hxx +++ b/include/sfx2/sfxbasemodel.hxx @@ -29,7 +29,7 @@ #include <com/sun/star/container/XChild.hpp> #include <com/sun/star/document/XCmisDocument.hpp> #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> -#include <com/sun/star/document/XDocumentRecovery.hpp> +#include <com/sun/star/document/XDocumentRecovery2.hpp> #include <com/sun/star/document/XUndoManagerSupplier.hpp> #include <com/sun/star/rdf/XDocumentMetadataAccess.hpp> #include <com/sun/star/document/XEventBroadcaster.hpp> @@ -116,7 +116,7 @@ typedef ::cppu::WeakImplHelper < css::container::XChild , css::document::XDocumentPropertiesSupplier , css::document::XCmisDocument , css::rdf::XDocumentMetadataAccess - , css::document::XDocumentRecovery + , css::document::XDocumentRecovery2 , css::document::XUndoManagerSupplier , css::document::XShapeEventBroadcaster , css::document::XDocumentEventBroadcaster @@ -572,6 +572,9 @@ public: virtual void SAL_CALL storeToRecoveryFile( const OUString& i_TargetLocation, const css::uno::Sequence< css::beans::PropertyValue >& i_MediaDescriptor ) override; virtual void SAL_CALL recoverFromFile( const OUString& i_SourceLocation, const OUString& i_SalvagedFile, const css::uno::Sequence< css::beans::PropertyValue >& i_MediaDescriptor ) override; + // css.document.XDocumentRecovery2 + virtual sal_Int64 SAL_CALL getModifiedStateDuration() override; + // css.document.XUndoManagerSupplier virtual css::uno::Reference< css::document::XUndoManager > SAL_CALL getUndoManager( ) override; diff --git a/offapi/UnoApi_offapi.mk b/offapi/UnoApi_offapi.mk index 7ee71bb984c1..7ba36b4bd065 100644 --- a/offapi/UnoApi_offapi.mk +++ b/offapi/UnoApi_offapi.mk @@ -2231,6 +2231,7 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/document,\ XDocumentProperties \ XDocumentPropertiesSupplier \ XDocumentRecovery \ + XDocumentRecovery2 \ XDocumentRevisionListPersistence \ XDocumentSubStorageSupplier \ XEmbeddedObjectResolver \ diff --git a/offapi/com/sun/star/document/XDocumentRecovery2.idl b/offapi/com/sun/star/document/XDocumentRecovery2.idl new file mode 100644 index 000000000000..2592c3c7ef07 --- /dev/null +++ b/offapi/com/sun/star/document/XDocumentRecovery2.idl @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +module com { module sun { module star { module document { + + +/** An optional interface to be implemented by documents that wish to participate + in the document emergency-save / recovery process. Extends XDocumentRecovery + by providing a method to query how much time elapsed since modified state of + the document was set. + + @since LibreOffice 24.2 + + */ +interface XDocumentRecovery2: ::com::sun::star::document::XDocumentRecovery +{ + /** queries the time elapsed since the document became modified + + @returns + duration in milliseconds since modified state of the document was set, + or -1 if not modified. + */ + hyper getModifiedStateDuration(); +}; + + +}; }; }; }; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sfx2/source/doc/sfxbasemodel.cxx b/sfx2/source/doc/sfxbasemodel.cxx index 82e25ed00f42..748ab9b20353 100644 --- a/sfx2/source/doc/sfxbasemodel.cxx +++ b/sfx2/source/doc/sfxbasemodel.cxx @@ -20,7 +20,9 @@ #include <sal/config.h> #include <algorithm> +#include <chrono> #include <memory> +#include <optional> #include <config_features.h> #include <sfx2/sfxbasemodel.hxx> @@ -216,7 +218,6 @@ struct IMPL_SfxBaseModel_DataContainer : public ::sfx2::IModifiableDocument bool m_bSaving ; bool m_bSuicide ; bool m_bExternalTitle ; - bool m_bModifiedSinceLastSave ; bool m_bDisposing ; Reference< view::XPrintable> m_xPrintable ; Reference< ui::XUIConfigurationManager2 > m_xUIConfigurationManager; @@ -228,6 +229,7 @@ struct IMPL_SfxBaseModel_DataContainer : public ::sfx2::IModifiableDocument ::rtl::Reference< ::sfx2::DocumentUndoManager > m_pDocumentUndoManager ; Sequence< document::CmisProperty> m_cmisProperties ; std::shared_ptr<SfxGrabBagItem> m_xGrabBagItem ; + std::optional<std::chrono::steady_clock::time_point> m_oDirtyTimestamp ; IMPL_SfxBaseModel_DataContainer( ::osl::Mutex& rMutex, SfxObjectShell* pObjectShell ) : m_pObjectShell ( pObjectShell ) @@ -238,7 +240,6 @@ struct IMPL_SfxBaseModel_DataContainer : public ::sfx2::IModifiableDocument , m_bSaving ( false ) , m_bSuicide ( false ) , m_bExternalTitle ( false ) - , m_bModifiedSinceLastSave( false ) , m_bDisposing ( false ) { // increase global instance counter. @@ -310,6 +311,19 @@ struct IMPL_SfxBaseModel_DataContainer : public ::sfx2::IModifiableDocument ::comphelper::getProcessComponentContext(), *m_pObjectShell) : nullptr; } + + void setModifiedForAutoSave(bool val) + { + if (val) + { + if (!m_oDirtyTimestamp) + m_oDirtyTimestamp.emplace(std::chrono::steady_clock::now()); + } + else + { + m_oDirtyTimestamp.reset(); + } + } }; // static member initialization. @@ -528,7 +542,7 @@ SfxBaseModel::~SfxBaseModel() Any SAL_CALL SfxBaseModel::queryInterface( const uno::Type& rType ) { if ( ( !m_bSupportEmbeddedScripts && rType.equals( cppu::UnoType<document::XEmbeddedScripts>::get() ) ) - || ( !m_bSupportDocRecovery && rType.equals( cppu::UnoType<XDocumentRecovery>::get() ) ) + || ( !m_bSupportDocRecovery && (rType.equals( cppu::UnoType<XDocumentRecovery>::get() ) || rType.equals( cppu::UnoType<XDocumentRecovery2>::get() )) ) ) return Any(); @@ -562,7 +576,7 @@ Sequence< uno::Type > SAL_CALL SfxBaseModel::getTypes() lcl_stripType( aTypes, cppu::UnoType<document::XEmbeddedScripts>::get() ); if ( !m_bSupportDocRecovery ) - lcl_stripType( aTypes, cppu::UnoType<XDocumentRecovery>::get() ); + lcl_stripType( aTypes, cppu::UnoType<XDocumentRecovery2>::get() ); return aTypes; } @@ -1796,7 +1810,7 @@ void SAL_CALL SfxBaseModel::storeToURL( const OUString& rURL sal_Bool SAL_CALL SfxBaseModel::wasModifiedSinceLastSave() { SfxModelGuard aGuard( *this ); - return m_pData->m_bModifiedSinceLastSave; + return m_pData->m_oDirtyTimestamp.has_value(); } void SAL_CALL SfxBaseModel::storeToRecoveryFile( const OUString& i_TargetLocation, const Sequence< PropertyValue >& i_MediaDescriptor ) @@ -1808,7 +1822,17 @@ void SAL_CALL SfxBaseModel::storeToRecoveryFile( const OUString& i_TargetLocatio impl_store( i_TargetLocation, i_MediaDescriptor, true ); // no need for subsequent calls to storeToRecoveryFile, unless we're modified, again - m_pData->m_bModifiedSinceLastSave = false; + m_pData->setModifiedForAutoSave(false); +} + +sal_Int64 SAL_CALL SfxBaseModel::getModifiedStateDuration() +{ + SfxModelGuard aGuard(*this); + if (!m_pData->m_oDirtyTimestamp) + return -1; + auto ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::steady_clock::now() + - *m_pData->m_oDirtyTimestamp); + return ms.count(); } void SAL_CALL SfxBaseModel::recoverFromFile( const OUString& i_SourceLocation, const OUString& i_SalvagedFile, const Sequence< PropertyValue >& i_MediaDescriptor ) @@ -2883,7 +2907,7 @@ void SfxBaseModel::Notify( SfxBroadcaster& rBC , { impl_getPrintHelper(); ListenForStorage_Impl( m_pData->m_pObjectShell->GetStorage() ); - m_pData->m_bModifiedSinceLastSave = false; + m_pData->setModifiedForAutoSave(false); } break; @@ -2902,13 +2926,13 @@ void SfxBaseModel::Notify( SfxBroadcaster& rBC , case SfxEventHintId::DocCreated: { impl_getPrintHelper(); - m_pData->m_bModifiedSinceLastSave = false; + m_pData->setModifiedForAutoSave(false); } break; case SfxEventHintId::ModifyChanged: { - m_pData->m_bModifiedSinceLastSave = isModified(); + m_pData->setModifiedForAutoSave(isModified()); } break; default: break; @@ -2947,7 +2971,7 @@ void SfxBaseModel::NotifyModifyListeners_Impl() const // this notification here is done too generously, we cannot simply assume that we're really modified // now, but we need to check it ... - m_pData->m_bModifiedSinceLastSave = const_cast< SfxBaseModel* >( this )->isModified(); + m_pData->setModifiedForAutoSave(const_cast<SfxBaseModel*>(this)->isModified()); } void SfxBaseModel::changing()