Perform operations in a plugin when its is being uninstalled

Yes, actually Ruby itself provides a way. It is called subclassing, where you use a modified “child” class of the SketchupExtension class.

I have just tested it and it does work. However, in the realm of best practices, it might be considered fragile, because the API SketchupExtension class’ behavior could change in the future and you would then need to issue an update to work around these possible changes. (To be fair however, the class has not changed at all in the last 11 years, since 2013.)


In the following example, I have a test extension with the DC Training components copied into the extension’s “Components” subfolder. When the extension is registered with SketchUp’s ExtensionsManager the subclass will copy the components files over into the user’s %AppData% Components folder within a destination collection folder.
When the extension is uninstalled this %AppData% Components collection subfolder is removed.

AcmeInc_RoadrunnerCatcher_1.0.2.rbz (173.0 KB) - UPDATED to v1.0.2

TL;DR - For those wanting a quick look, here is the example registrar file that also defines the subclass:

# encoding: UTF-8
#
# Example Extension Registrar File
#
# This example shows subclassing the SketchupExtension class to provide
# automated extension specific resource installation and removal.
#
# This example coding pattern is valid for SketchUp 2017 & higher that saves
# custom user resources in the user's %AppData% or "Library/Application Support"
# paths.
# Also does not work on old versions that did not have the Ruby Standard
# Library "fileutils.rb" available.
#
# v 1.0.1 - Moved the `super` calls to the end of the callback overrides hoping
#           to suppress the error message output to the console:
#           "Extension returned wrong type for method mark_as_uninstalled"
#
# v 1.0.2 - Return `nil` from the `SketchupExtension` class callback overrides
#           in order to suppress the core's complaint to STDOUT that it did
#           not receive the expected (NULL) return type.

require 'fileutils' unless defined?(FileUtils)

module AcmeInc
  module RoadrunnerCatcher

    class Extension < ::SketchupExtension

      COMPONENTS_PATH ||= Sketchup.find_support_file('Components')
      COLLECTION_PATH ||= File.join(COMPONENTS_PATH, 'Roadrunner Weapons')

      # Method to copy component files specific to this extension.
      def copy_components
        # Create the directories if they do not yet exist:
        unless File.directory?(COLLECTION_PATH)
          FileUtils.mkdir_p(COLLECTION_PATH)
        end
        # Copy the extension components from the extension distribution source path
        # to the components collection path:
        source = File.join(__dir__, 'AcmeInc_RoadrunnerCatcher', 'Components')
        Dir.glob('*.{skp,SKP}', base: source) do |source_filename|
          # This will add or overwrite destination files so that components can
          # be added, fixed or updated when the extension is updated:
          dest_path = File.join(COLLECTION_PATH, source_filename)
          source_path = File.join(source, source_filename)
          # Copy if not yet copied, Overwrite if source modification time is newer:
          if !File.exist?(dest_path) || File.mtime(source_path) > File.mtime(dest_path)
            FileUtils.install(source_path, COLLECTION_PATH)
          end
        end
      end

      # Method to delete component files specific to this extension.
      def delete_components
        # If the collection path does not exist, then there is nothing to delete:
        return unless File.directory?(COLLECTION_PATH)
        FileUtils.remove_dir(COLLECTION_PATH)
      end

      #{# NOTE: The following callbacks should never be called directly. They
      #         are called by the SketchUp engine at the appropriate times.

      # Callback called by SketchUp when the extension is uninstalled via the UI.
      def mark_as_uninstalled
        # Call a method to delete extension specific component files:
        delete_components()
        # Call the superclass' method which does some housekeeping:
        super
        return nil # The EM core wants a NULL result
      end

      # Callback called by SketchUp when the extension is registered via the
      # Sketchup.register_extension method.
      def register_from_sketchup()
        # Call a method to copy extension specific component files:
        copy_components()
        # Call the superclass' method which does some housekeeping:
        super
        return nil # The EM core wants a NULL result
      end
    #
    #}#

    end # subclass

    # Create the Extension informational instance object:
    EXTENSION = Extension.new("Roadrunner Catcher", "AcmeInc_RoadrunnerCatcher/main")

    # Set the extension detail properties:
    EXTENSION.creator     = "Wile E. Coyote"
    EXTENSION.description = "Catches those pesky roadrunner birds."
    EXTENSION.version     = "1.0.2"
    EXTENSION.copyright   = "©2024, Crazy Clown Utilites"

    # Register this extension with SketchUp's ExtensionsManager collection:
    Sketchup.register_extension(EXTENSION, true)

  end
end
6 Likes