Autosave periodically into a "journal"

Hi,

as we know, the native autosave overwrites older files on save. I’m looking to add functionality of autosaving files with a timestamp to create a journal of sketchup files at different points of time.

As far as I understand, using a timer, threads etc is not supported or harder to achieve in ruby extensions because of its embedded nature. I thought about using an Observer, but this requires user actions to trigger the time differential and subsequent save. Third option I can imagine would be a external tool that lives in the tray for example, that observers the AutoSave file directly and copy it after Sketchup is done, but this is a lot more effort to get reliable.

Any other ideas, am I missing something obvious?

Edit:
After further investigation, there is the ModelObserver.onSaveModel (with the corresponding pre/post hooks). I think this is all I need from my perspective.

What types of objects are you trying to store? Not everything serialize properly.

Storing objects? I wanted to read the model path set in preferences and according to my registry it’s stored there…

Ah, you are storing a string. I though you tried to store a Hash perhaps.

Looks like it chokes on \ - that might need to be escaped. (I think we have a bug logged for this.) You might want to try to use File.expand_path() to convert the path strings to Ruby paths instead of Windows paths. (Or escape manually.)

No Thomas. He simply wants to know the user’s default path for saving models.

And he cannot escape anything, nor run any output through any method, because it is the SketchUp C-side that does not store paths on PC into the registry that can make it thru eval(). So an exception is raised when attempting to read any of the path string attributes, (with read_default,) in the “File Locations” key, … and the exception is raised in such a weird way that it cannot be trapped.

If the paths had been written with forward slashes, or escaped double backslashes, then everything would be dandy.


@Bliss: I would recommend using:

save_path = File.dirname(File.expand_path(Sketchup.active_model.path))

… then your auto-saves will save in the same directory as the original SKP file.

But you also need to test for "." for unsaved models, or query Sketchup.active_model.modified?.

Yes, that’s what I used as a workaround.

Sketchup.read_default 'AutoSave', 'AutoSaveFile' would have been equally useful to have a reliable multi-platform solution to get the path of the AutoSave file.

  dir = File.dirname(model.path)
  basename = File.basename(model.path, '.*')
  return if model.path == ''
  if Sketchup.platform == :platform_win 
    File.join(dir, 'AutoSave_' + basename + '.skp') 
  else 
    File.join(dir, basename + '~.skp') 
  end 

This is what I’m using now, any obvious problems?

maybe…

why are you using the ‘backup’ file path on a mac?

File.join(dir, basename + '~.skp') is the mac equivalent of File.join(dir, basename + '.skb')

File.join(dir, 'AutoSave_' + basename + '.skp') is the same on both platforms…

john

Oh, that wasn’t my intention, apparently I read the article wrong. Thanks for the correction

Yours:

  dir = File.dirname(model.path)
  basename = File.basename(model.path, '.*')
  return if model.path == ''
  if Sketchup.platform == :platform_win 
    File.join(dir, 'AutoSave_' + basename + '.skp') 
  else 
    File.join(dir, basename + '~.skp') 
  end

Mine:

  dir = File.dirname(File.expand_path(model.path))
  basename = model.title
  return if model.path.empty? || model.path == '.'
  if Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/i
    File.join(dir, "AutoSave_#{basename}.skp") 
  else 
    File.join(dir, basename + '~.skp') 
  end

line 1: File.expand_path() converts backslashes to forward slashes

line 2: model.title is suffcient

line 3: It is more effcient to simply call the empty?() method upon a string,
than create a new empty string object and pass it to the ==() method.
(When you express a literal string even an empty one, the Ruby interpreter must take
that as an argument, and pass it to String::new() in order to create a string object.)
line 3: The current directory dot string can be returned for unsaved models.

line 4: Older versions of SketchUp do not have the platform method, so a NoMethodError
would be raised. Adding a rescue modifier can trap this and fallback to testing
the global Ruby constant.

… BUT you do not really need a platform conditional in this case (as John points out.)

line 5: As mentioned in “line 3” (above,) literals create string objects.
So, this: ‘AutoSave_’ + basename + ‘.skp’ creates two new objects.
Then each one of the calls to the +() method creates yet another 2 string objects.
Most effcient here is create one new string and use #{} interpolation to insert the
already existing basename string into it’s middle.


What I would do, is ask the user to create a default path for autosaves on unsaved models.
You could also save the last used autosave path to your plugin’s defaults (just be sure it has forward slashes!)

Say this fallback directory is loaded into a module var: @@fallback, then the code would look like:

Possible:

  dir = File.dirname(File.expand_path(model.path))
  basename = model.title
  if basename.empty?
    t = Time.now
    basename = t.strftime("%Y-%m-%d_%s")
  end
  dir = @@fallback if dir.empty? || dir == '.'
  @autopath = File.join( dir, "AutoSave_#{basename}.skp" ) 
  # save it

Ref Ruby doc: Time#strftime()

Q: _Why did I use strftime("%Y-%m-%d_%s") instead of just splitting the string output of the Time instance and using the first element of the returned array ?
ie,

date = Time.now.to_s.split.first

A: Because date separators are locale dependent. I toyed with the idea of gsub'ing all the invalid filename characters to dashes, but it creates more code and work for Ruby, then just formatting an output string with strftime("%Y-%m-%d_%s").