Observer on file creation / modification

Hi there! New to both Ruby and SketchUp API, I’m working on a plugin that grabs and imports files from a web platform, however the way it works is that it downloads the main model and then creates a simple ‘description.txt’ file to notify the app that a new model has been downloaded. However, I need to set an observer in order to see if said file was created/modified while SketchUp is running. Is there a way to set such an observer?

PS: If needed, I can just tell SketchUp to create a blank description.txt file at the start of its run course and then delete it with my onQuit observer.

Take a look at AppObserver. Its events may possibly cover what you need.

From what I can gather from the SketchUp API documentation, AppObserver can only gather information on events happening within a given instance of SketchUp and not on external files.

Yes. It is one of the lesser known abstract observer interfaces.

(Abstract in Ruby means it does not have a superclass name. It is just a barebones class you create as a normal subclass of Object. This is because the API does not care at all what it’s class name nor ancestry is. It only cares how it behaves. This is called “duck-typing”. The API only cares how the object “walks” and “talks” not whether it is actually “a duck.”)

So, because of this, it is not listed with the other observer classes, but it is called (informally,) a load handler.

You’ll find it’s explanation in the documentation for this method:

Sketchup::DefinitionList#load_from_url()

:nerd:

Be aware of the standard Ruby classes:

File
and
Dir

and module:
FileTest

you can run a timer to check when a file is ready:

max = 5
interval = 0.3
i = 0
@tid = UI.start_timer(interval,true) {
  i += interval
  if file.exist?(@filepath)
    UI.stop_timer(@tid)
    # call some method here
  elsif i >= max
    UI.stop_timer(@tid)
    # call some error routine
  end
}
1 Like

Another note about the documentation for:
Sketchup::DefinitionList#load_from_url()

The example is erroneous.
Either the load handler object needs to be a module with module methods, or if a class, then an instance of that class needs to be created:

# Replace this with a real URL...
url = 'http://www.sketchup.com/model.skp'
model = Sketchup.active_model

load_handler = LoadHandler::new() # <-- missing from example !

definition = model.definitions.load_from_url(url, load_handler)

Thaks for the help, Dan :wink: While it wasn’t quite the solution to my problem, your contribution did help me find a temporary workaround.

As I was running an external app to get my model and description.txt, the behavior on Mac and Windows were not the same: Using the SketchUp API to run an external program freezes SketchUp on Windows and lets SketchUp run in parallel in MacOS. However, I know that when the description.txt is created/modified, the external program automatically closes, so as a solution, I used the Windows behavior, and forced Mac into the exact same behavior though a while loop (Something like “while external program is running, endlessly loop”. Not optimal, but it works.

You mean like ?

%x{calc}

This is standard Ruby on Windows, not the SketchUp API.

The %x string literal actually calls the Kernel module’s backquote method.

The SketchUp API has the UI.openURL() method. Ie,:

UI.openURL("calc.exe")

… does not block SketchUp. The url is passed to the system, and the system understands to open the program in a new process. If the program is not in a directory found by the %path% then a local file url is needed.

UI.openURL("file:///C:/some/path/to/program.exe")

You can also use the Windows Scripting Host’s Shell object’s methods on Windows via the WIN32OLE class.

filepath = UI.openpanel("Choose text file to edit...","*.txt")
if filepath
  require "win32ole"
  shell = WIN32OLE::new("WScript.Shell")
  shell.Run( %[#{ENV['WINDIR']}/notepad "#{filepath}"], 7 )
end

The window style 7 is “Displays the window as a minimized window. The active window remains active.” And is our case, the active window is SketchUp.

Ref:


ADD: The above examples open the programs in a new process.
They will continue running when SketchUp is closed.

This Ruby method waits for the invoked command to complete and potentially return standard output. As a result, Ruby blocks while waiting. Because of the way the Ruby API is integrated into SketchUp, when Ruby blocks, so does SketchUp.

Well, seems like on Windows the current code uses C code to actually launch the external app in command line mode. Direct SketchUp integration through a system command or something might be possible, and is done through the system command on Mac. Would Windows support a similar command? (That might be why the app then runs independantly from SketchUp)

After which, if I manage to get that behavior working, I will need to setup the custom observer to check on the desciption.txt file existence. The load_from_url method seems like a good idea but I will still need to check if the file is new or “already used”.

IT is the global system() method, defined in the Kernel module.

Some background. Ruby was invented by guys on Unix-like operating systems. Most of the modules and methods that Ruby has works best on these kind of OSes. Mac is an *nix type OS.

But also, inside SketchUp, the Ruby interpreter is embedded. Things (in Ruby, especially system integration,) do not always work from an embedded interpreter the same as they work from a command line ruby instance. (ie, the IRB shell, etc.)

Well, as Ruby itself is written in C, of course this is true, but it is true for all OSes. Everything that happens in Ruby, is happening in the Ruby interpreter, which is compiled C.

To be clear. The system command invoking Ruby methods work better under *nix like OSes, and not so good from within embedded Ruby, especially under Windows. (Ie, try as we might we cannot suppress the dang command shell windows from popping up for a split second.)

Perhaps you should instead be using Kernel#spawn() … to spawn a new process ?
http://ruby-doc.org/core-2.0.0/Kernel.html#method-i-spawn


Just FYI, the Kernel module is mixed into the Object class, so all their methods are global. (Everything in Ruby is a descendant of Object.)

I would myself use the load_from_url() method to put the components into the “In Model” collection first, then use the Sketchup::ComponentDefinition#save_as() to save them locally to a user “Bim_and_Co” collection folder. The local skp files will have date stamps.

These can be accessed via standard Ruby File class methods atime(), ctime() and mtime().

So later any subsequent download would compare mod dates. If the web date was newer than the local date, an update is needed.

@DanRathbun that’s a good point and it helped to work. I think also some other part of the code should be modified.
One more thing to add to the API doc. I was not able to use the error instance variable in the onFailure method with self.error. So for me it worked in this way:

attr :error

def onFailure(error_message)
  error = error_message
  Sketchup::set_status_text('')
end

I kept the example as a Class and I got proper error messages.

1 Like