Mac equivalent of ENV['APPDATA']

Again … not if he will be saving data in a version independent location.

I said it above, … and in the previous topic thread on the same subject.

It is often desirable for both developers and users to save data / settings in a version independent location (ie, a vendor folder structure,) because SketchUp installers still do not migrate settings to newer versions (although this has been one of the longest running peeves about the yearly update cycle.)

So the long term solution has been to do an “end around” the problem and use a location that does not need migration. This means creating appdata / app support “vendor folder” and subfolders for the products / plugins.

ok, but there is a user independent ‘Shared’ folder for that purpose on a mac…

any app or extension can create sub directories…

on mine GraphicConverter.app has made it…

graphic_converter = '/Users/Shared/Library/Application Support/GraphicConverter/'
another_app_creates = '/Users/Shared/Library/Preferences/'

the gotcha is that SU’s ENV["PWD"] causes File.exists? to return false making it tricky to access…

shared = '/Users/Shared/Library/Application Support/SketchUp/JcB_Dev'

%x[mkdir "#{shared}"]

$LOAD_PATH << shared

# drop in some ruby and
load 'sqrt(targets).rb'

john

@CAUL Another thing you can do because ENV["APPDATA"] will be undefined (nil) on Macs, is …

if Sketchup.platform == :platform_osx && !ENV["APPDATA"]
  ENV["APPDATA"]= File.expand_path('~/Library/Application Support')
end

Then, you use ENV["APPDATA"] interchangeably in your directory maintenance methods.

John, drink another cup of coffee (or tea.)

We are talking here (SketchUp) VERSION independent NOT USER independent !

You are muddling the conversation.

pot kettle black…

john

… taking part of a sentence which rejected that idea because of permissions issues. :roll_eyes:

1 Like

Hi Dan,
I tried using this technique in my plugin, and while it worked fin on Mac, I got this error (running on Windows):
Error: #<Errno::ENOENT: No such file or directory @ dir_s_mkdir - C:\Users\Dave\AppData\Roaming/DPPlugins/DPLineStyler>

I had tried using File.join to build my ‘company directory’ for preferences outside the Plugins directory. Looks like ENV[“APPDATA”] uses backslashes, but File.join uses forward slashes, resulting in the messed-up path.

Any suggestions?

  • Dave

@dpc

Below returns a string with the replacement done:

ENV['APPDATA'].tr File::ALT_SEPARATOR, File::SEPARATOR
1 Like

Thanks!!

Windows doesn’t usually have any problem with pathnames containing a mixture of forward and back slashes.

However, Ruby double quoted strings see backslash as escape for special sequences.
Ie … "\n" is a newline character, "\r" carriage return, "\t" tab, "\d" and "\D" are decimal number and not decimal number, "\u" is unicode character sequence with 2 byte hexidecimal number following, etc., etc. …

Beware of the File.expand_path method. It is relatively dumb. It will prepend the path to the current working directory to the argument (if the argument does not resolve to an absolute path.) And the method does not test whether the result exists. (Ie, it can result in an invalid path if the relative path argument is not in or beneath the current working dir.)
MacOS and other *nix systems Use tilde (~) to represent the users HOME path (ENV["HOME"]) which on Windows is ENV["USERPROFILE"]. SketchUp has been adding a ENV["HOME"] var to it’s environment for awhile (SU2014?) so that it’s embedded Ruby can resolve "~" to the user’s home path.

So I revised my code to make and then use a prefs directory in ENV[‘APPDATA’] (or its Mac equivalent). Thanks to MSP_Greg for the ALT_SEPARATOR tip!
I test for the existence of my plugin’s prefs directory, and if it does NOT exist, it will mkdir it and then move my prefs data (a TXT file and a couple folders of small SKP files) from the Plugins directory to my prefs directory.
I intend that this only happens once in the life of the plugin. It worked just fine on my own test machines (Mac Mojave & Win 10) so I updated my extension accordingly. So far so good.
Now a customer of mine writes that he got the weird result that the prefs directory got made all right, but the prefs stuff just disappeared. I was using File.rename to move everything over. The customer showed me that the prefs directory had been made, but was empty, and that the prefs stuff was no longer in the Plugins directory.
Any idea what happened here?

The only file that should ever be in the “Plugins” directory, can be a extension registrar file.
Anything else should be in the extension’s subfolder (with same name as the registrar file minus the .rb,) or your vendor %AppData% subfolder.

But yea, I also break this rule testing simple concepts, etc.
But for a proper extension, teh rule should be followed.


EDIT: The docs do not say this can move files. You need to use the FileUtils library.

@dpc

Very odd. I tried File.rename with both Ruby 2.2.6 and Ruby 2.8.0, and both worked. I used full paths for both parameters. I have a space in my APPDATA path, so that also isn’t an issue (sometimes spaces cause issues when shell’ing out with paths/filenames as parameters). Using Windows 10.

Could it be an encoding issue? Windows or macOS? Any idea what SU version?

EDIT: File.rename will work across drives on Windows, but may not work across partitions on macOS…

Like I said, File.rename worked just fine on my test machines. I used the same files names but different paths. I tried it with both SKP 2017 & 2020 on my Mac and with SKP 2020 on Win 10. I was especially delighted that it moved all the contents of the folders (files and subfolders) as well.
My customer was on Windows - I’ll ask him what version, etc.
I’ll experiment with FileUtils as well…

Sorry I wasn’t clear, Dan. The prefs stuff was in my own extension’s folder inside Plugins. I was using “Plugins” as shorthand for the full path. Likewise, my prefs in ~/AppData/Roaming/ (or ~/Library/Application Support/ on a Mac) reside a couple more levels down, in a “company” folder and a “plugin-specific” subfolder (what you call the %AppData% subfolder).

1 Like

@dpc

Tested on SU 2017 (Ruby 2.2.4), Windows 10, File.rename worked fine on a file. The only other thing that I recall re Windows is that some file operations can’t be performed if the file is opened (File.open without a block). Otherwise, I have no idea. And, as you mentioned, it works locally for you as well…

I’m interested as to what the issue is.

So here’s a post just to wrap up this topic:
I changed from using File.rename to FileUtils.cp for a settings files and FileUtils.cp_r for a directory containing some user-editable SKP files. Again, everything tested OK. Again, a customer using SKP 2020 on Win10 reported a bug. Sigh…
Turns out, FileUtils.cp_r may not actually recurse (i.e., copy the folder’s contents) on some Win10 machines. The customer’s symbols directory was empty. Only the directory got copied, not its contents. So now I must code even more defensively: If FileUtils.cp_r does not work as advertised, I must copy the folder’s contents myself, using FileUtils.cp.
Hope this helps other plugin authors as they try to “do the right thing” with their preferences files! :slightly_smiling_face:

1 Like

For my preferences I first check if the preference folder and files exist. If not then I create the preference files and write the defaults to them. In this way I never actually copy or move any files only the contents.

  def self.read_settings
    settings_file = @settings_path + 'settings.json'
    if File.exist?(settings_file)
      begin
        json = File.open(settings_file, 'r', &:read)
        @settings = JSON.parse(json)
        restore_default_settings unless @settings
        restore_colors unless @settings.key?('colors')
        restore_default_colors unless @settings.key?('default_colors')
        restore_default_building_specs unless @settings.key?('default_building')
        restore_colors unless @settings.key?('colors')
      rescue StandardError => _e
        restore_default_settings
      end
    else
      restore_default_settings
    end
  end

  def self.save_settings
    return unless @settings

    Dir.mkdir(@settings_path) unless Dir.exist?(@settings_path)
    settings_file = @settings_path + 'settings.json'
    File.open(settings_file, 'w') { |f| f.write(@settings.to_json) }
  end

That’s my strategy with FlatText, where the prefs can be summed up in a few lines of text.

DPLineStyler maintains a collection of components to be used as symbols along a styled line. The User can add to, delete from, and edit the components in this collection. The last thing I want to do is clobber this collection every time my plugin or SketchUp itself updates. :frowning:

I’m no Windows expert (far from it!), but I suspect that MS disables certain FileUtils methods such as #cp_r unless the User has Admin privileges…?

BTW, nice example code, Neil!

MS doesn’t have anything to purposefully do with Ruby features working or not.
It is the low level Win32 system calls that are being protected using UAC.

The Ruby FileUtils library for the Windows platform happens to use these low level C calls.

This is why from within SketchUp’s Ruby process file operations are best done within the user’s %AppData% and %LocalAppData% filepaths.

If an extension needs (or wants) to save files (settings, components, etc.) into the %ProgramData% path, then it needs to use it’s own installer, and perhaps jump through some access hoops whenever it needs to save user files.