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()

Reply via email to