Threading - Autosave

Hi,

I would like my users to have an option of an Autosave… I would like to use an async task to kick in and save the doc at a user defined time.

My code all seems to run fine apart from the sleep part… I’ve done various versions, this one has Sleep in it, others using DispatchTimer. Seemingly i am getting a bit caught between ironpython and cpython with some bits working in some and not in others… Would anyone have a suggestion as to why it isn’t working?

(I am saving a little file to help the code know whether to start or stop, I would prefer to pull the current icon state, but it didn’t seem available to me).

Any thoughts welcome!

Mark

import os
from Autodesk.Revit.UI import TaskDialog
from pyrevit import forms, revit, HOST_APP, script

from Autodesk.Revit.UI import IExternalEventHandler, ExternalEvent

import clr
clr.AddReference('System')
from System.Threading import Timer, TimerCallback

# Define the file path for storing the auto-save state
STATE_FILE_PATH = os.path.join(os.path.dirname(__file__), 'auto_save_state.txt')

# Function to read the auto-save state from the file
def read_auto_save_state():
    if os.path.exists(STATE_FILE_PATH):
        with open(STATE_FILE_PATH, 'r') as file:
            return file.read().strip() == 'True'
    return False

# Function to write the auto-save state to the file
def write_auto_save_state(state):
    with open(STATE_FILE_PATH, 'w') as file:
        file.write('True' if state else 'False')

# Flag to track auto-save status (only for this session)
auto_save_on = read_auto_save_state()
auto_save_timer = None

uiapp = HOST_APP.uiapp
doc = revit.doc

button = None

# External event handler for performing auto-save
class AutoSaveHandler(IExternalEventHandler):
    def __init__(self, doc):
        self.doc = doc

    def Execute(self, app):
        try:
            self.doc.Save()
            TaskDialog.Show('AutoSave', 'Document saved!')
        except Exception as e:
            TaskDialog.Show('Error', str(e))

    def GetName(self):
        return "AutoSaveHandler"

# Create an instance of the handler and external event
auto_save_handler = AutoSaveHandler(doc)
external_event = ExternalEvent.Create(auto_save_handler)

# Function to initialize button icon based on auto-save state
def __selfinit__(script_cmp, ui_button_cmp, __rvt__):
    global auto_save_on
    global button

    button = ui_button_cmp
    button_icon = script_cmp.get_bundle_file('on.png' if auto_save_on else 'off.png')
    ui_button_cmp.set_icon(button_icon, icon_size=script.ICON_MEDIUM)

# Function to handle the timer callback (this replaces the Elapsed event)
def on_timer_elapsed(state):
    external_event.Raise()  # Raise external event to save the document

# Function to start the timer for auto-saving
def start_auto_save_timer(interval_minutes):
    global auto_save_timer
    interval_ms = interval_minutes * 60 * 1000  # Convert minutes to milliseconds
    auto_save_timer = Timer(TimerCallback(on_timer_elapsed), None, interval_ms, interval_ms)

# Function to stop the auto-save timer
def stop_auto_save_timer():
    global auto_save_timer
    if auto_save_timer is not None:
        auto_save_timer.Dispose()
        auto_save_timer = None

# Function to toggle the save process
def toggle_auto_save():
    global auto_save_on

    auto_save_on = not auto_save_on  # Toggle the state
    write_auto_save_state(auto_save_on)  # Save the state to the file

    if auto_save_on:
        # Get the time interval in minutes from the user
        text = forms.ask_for_string(default='15', prompt='Enter time in minutes:', title='Autosave Set Interval')
        if text is not None and text.isdigit():
            minutes = int(text)
        else:
            TaskDialog.Show('Error', 'Invalid input. Please enter a number.')
            return False

        TaskDialog.Show('AutoSave', 'Auto-save is now ON.')
        start_auto_save_timer(minutes)  # Start the timer
        return True  # Returning True when auto-save is ON

    else:
        TaskDialog.Show('AutoSave', 'Auto-save is now OFF.')
        stop_auto_save_timer()  # Stop the timer
        return False  # Returning False when auto-save is OFF

# Main execution - Runs only when the script is executed (button clicked)
if __name__ == '__main__':
    # Toggle auto-save and update button icon based on the new state
    is_active = toggle_auto_save()
    # Update button icon to reflect the new state
    script.toggle_icon(is_active)

Please have a look at the pull request section of pyrevit repo
You will find something pretty nicely done, that we are fearing to integrate in the code base at the same time.

1 Like
1 Like

Awesome, so it fires on the view activated event…

I’m worried about it also, but I know users want the option…

Thanks again :slight_smile:

I haven’t looked at the code for quite some time. But it is well designed.
If people want it!

:fire: :fire_engine: :fire_extinguisher: :firefighter:

1 Like

It’s working nicely, the only thing I added was an app-init to add the config ‘autosave’ at startup…

1 Like

Any update on this interesting feature? I’m trying to make it working adding all the required files but nothing happens. Don’t know what I’m doing wrong, but this doesn’t work with any of the two codes.

Hey,

You need at least 3 pieces of code…

On doc-opened, the user config file has the file name and datetime saved to it
C:\Users\user_name\AppData\Roaming\pyRevit

When doc-saved, the datetime is updated in the config file

When view-activated, the config is checked, if the current datetime is less than the datetime + interval, the file is saved.

I added a 4th, app-init so that when PyRevit starts, if the config does not contain [autosave], it adds it. You might not need this if you are pushing config files centrally.

Hope that helps,

Mark

1 Like