.monkeystate path to appropriate app data folder

Monkey Targets Forums/Desktop/.monkeystate path to appropriate app data folder

Soap(Posted 2014) [#1]
Based on target... is there a reason why topic titles are forced short?

Right now on Win/Mac/Linux the .monkeystate file is stored where the binary file is. This is a bad practice in most production cases.

A better folder would be for windows:

%appdata%\APPNAME

For mac:

~/Library/Application Support/APPNAME

For linux:

~/.config/APPNAME/

It would be good to have an option to choose to store the state next to the binary, or in the equivalent app data folder. Naming in an appdata folder would require an "appname" to be defined? It would be good to be able to define the name of the actual .monkeystate file - maybe default to .appnamestate instead.

It would be nice if these options were added to default monkey. I'm testing a modification to \modules\brl\native\gametarget.cpp to enable this for myself now.


Arjailer(Posted 2014) [#2]
I agree. The current scheme means that (on Windows at least) you can't store data per-user - it's a single save state for the entire machine. I presume it also means that you can't reliably install to the Program Files folder as many users won't have permission to write there. You're left installing to the user's profile, which isn't terrible, but then each user has to install the game separately.

Writing to the user's profile (AppData as you suggest) would mitigate all of these problems.

A choice between "user" and "shared" (the ProgramData folder on Windows 7) to allowed shared high scores etc. would be even better :-)


Soap(Posted 2014) [#3]
I think there should ideally be several functions. One which allows you to define absolute path in monkey code. Then you can use whatever path you want, and with #If HOST="linux" ... set the correct pathing based on target. LoadString is nice having relative to binary, but probably would be useful to have more flexibility along with a SaveString too. Thinking this was already discussed in the past.

Then change behavior of default savestate to save in the correct appdata folders based on appid. So in code block before "MonkeyGame" would be replaced with the define game id in code like "SuperPlatformer" then it would save to AppData/Roaming/SuperPlatformer/SuperPlatformer.state" file.


Nobuyuki(Posted 2014) [#4]
Hi,

For at least the desktop way of saving strings, I'd also like to request that "home://" be a valid protocol for a monkey path, being a link to the same paths specified by Soap based on the operating system. This would allow us to avoid having to modify SaveState() / LoadState() substantially while still giving an "easy" solution for desktop deployments where UAC / file permissions prevent us from saving to the state file for whatever reason.


Soap(Posted 2014) [#5]
As Arjailer noted it's also a problem of installed games and multiple user profiles on an OS even if everyone is admin. As is each user can modify another user's entire saves if the game is installed to program files.


Soap(Posted 2014) [#6]
.


Soap(Posted 2014) [#7]
.


Soap(Posted 2014) [#8]
Working for Windows now. Have not yet tested on Mac and Linux..

I'll look into adding in tags for "MonkeyApp" which can be defined in monkey code and if it's not defined then it will default to "MonkeyApp" for directory and state name if no one beats me to it. This should be easy... just edit gametarget.monkey and add something only for desktop targets? I think it should be relatively easy to add new functions for allowing monkey code to define the path and file name for desktop targets where it matters. For now this code should be workable if you need your save states to save and load in the appdata dirs.

Just noticed a bug with the "[ code ]" block \" displays as " so if you see any "" below you know it's the bug.

Space
\ "
No space
\"


#include <stdio.h>
#include <string.h>


int BBGame::SaveState( String state ){
  char appdata[100] = { 0 };
  String makemewide;
  char tempcmd[100] = { 0 };
  #ifdef _WIN32
    strcpy (appdata, getenv("APPDATA"));
    strcat (appdata, "\\MonkeyGame\\");
    makemewide = appdata;
    _wmkdir(makemewide.ToCString<wchar_t>());
    strcat (appdata, ".monkeystate");
  #elif __APPLE__

    // Make sure the directory is created
    strcpy (tempcmd, "mkdir -p \"");
		strcat (tempcmd, getenv("HOME"));
    strcat (tempcmd, "/Library/Application Support/MonkeyGame/\"");
    system (tempcmd);

    // Set save directory and file
    strcpy (appdata, getenv("HOME"));
    strcat (appdata, "/Library/Application Support/MonkeyGame/.monkeystate");


  #elif __linux

    // Make sure the directory is created
    strcpy (tempcmd, "mkdir -p \"");
		strcat (tempcmd, getenv("HOME"));
    strcat (tempcmd, "/.config/MonkeyGame/\"");
    system (tempcmd);

    // Set save directory and file
    strcpy (appdata, getenv("HOME"));
    strcat (appdata, "/.config/MonkeyGame/.monkeystate");

  #endif
  
	if( FILE *f=OpenFile( appdata,"wb" ) ){
		bool ok=state.Save( f );
		fclose( f );
		return ok ? 0 : -2;
	}

	return -1;
}

String BBGame::LoadState(){
  char appdata[100] = { 0 };
  String makemewide;
  char tempcmd[100] = { 0 };
  
  #ifdef _WIN32
      strcpy (appdata, getenv("APPDATA"));
      strcat (appdata, "\\MonkeyGame\\.monkeystate");
  #elif __APPLE__
      strcpy (appdata, getenv("HOME"));
      strcat (appdata, "/Library/Application Support/MonkeyGame/.monkeystate");

  #elif __linux
      strcpy (appdata, getenv("HOME"));
      strcat (appdata, "/.config/MonkeyGame/.monkeystate");

  #endif
  
	if( FILE *f=OpenFile( appdata,"rb" ) ){
		String str=String::Load( f );
		fclose( f );
		return str;
	}
	return "";
}



Soap(Posted 2014) [#9]
Got around to testing on Mac and Linux and had to make a few changes - updated the above.


Soap(Posted 2014) [#10]
Think I'm going to turn this into a module unless Mark has plans to add it in default brl module? I'm going to add two strings appname (replacing MonkeyGame if set) and statename (replacing monkeystate if set) to be passed in the load/save functions so the save dirs can be set right based on project.


dawlane(Posted 2014) [#11]
Soap why are you using strcpy and strcat? You do know you can use the String class from Monkey and avoid all that messy calling of shell commands.


Soap(Posted 2014) [#12]
I am mostly skilled in things non-coding related so fully expect me to do dumb things. :p Was just trying to make it work first as I don't know enough about working in C++. Edits to make it better for everyone are welcome.

You mean building the path in a .monkey file or using Monkey's String C++ class in the external file?


dawlane(Posted 2014) [#13]
I will post something for you to play with. I'm just about to update my system and I have to reboot.


dawlane(Posted 2014) [#14]
It's been years since I did any C/C++ coding, but this code will use CFG_GLFW_WINDOW_TITLE to make a string for naming the directory and make it part of the monkeystate state name.
First open main.h in targets\glfw\templates and add to the list in __APPLE__
#include <sys/types.h>
in the list for __linux add
#include <sys/stdlib.h>
#include <sys/types.h>
save the file.

Now backup gametarget.cpp in modules\brl\native, then open it and locate int BBGame::SaveState( String state ) then replace int BBGame::SaveState( String state ) and String BBGame::LoadState() with the code below.

Don't for get on Linux and OS X dot files are hidden and %APPDATA% point to Roaming. %LOCALAPPDATA% points to Local.


Raph(Posted 2014) [#15]
Did this ever get turned into a module?


Soap(Posted 2014) [#16]
No, but still possible to add in on your own. It's on my todo (if official solution isn't added first) but current thing solves my problem and I have a lot to do.


marksibly(Posted 2014) [#17]
How's this for a plan:

* monkey://internal gets mapped to the appropriate 'app data' folder as per the above code.

* Load/SaveState use monkey://internal/.monkeystate instead of ./.monkeystate

* An app config var called #CPP_APP_IDENT is added that must be set to a unique identifier in order to use the new system.

This both gives apps access to proper 'internal' storage on the desktop, and solves the load/save state issue.


dawlane(Posted 2014) [#18]
How's this for a plan:

* monkey://internal gets mapped to the appropriate 'app data' folder as per the above code.

* Load/SaveState use monkey://internal/.monkeystate instead of ./.monkeystate

* An app config var called #CPP_APP_IDENT is added that must be set to a unique identifier in order to use the new system.

This both gives apps access to proper 'internal' storage on the desktop, and solves the load/save state issue.
Well Mark that sounds like a plan. But what's your idea with #CPP_APP_IDENT?. Will it be used as a path so that we can use it like so /MyCompany/MyApplication/, then create the path in the users local storage space. And will the state file be fixed as .monkeystate or will it use the application name as?

The code I posted above is rough. The remove illegal character code would have been better as a regular expression to make it easier to handle the little differences between Windows and *nix file paths.


marksibly(Posted 2014) [#19]
On, say, linux, 'monkey://internal/' would get mapped to '~/.config/MyCoolApp/' (assuming #CPP_APP_IDENT="MyCoolApp").

Save/LoadState would use the path 'monkey://internal/.monkeystate', so the real path for the state file would be '~/.config/MyCoolApp/.monkeystate'


dawlane(Posted 2014) [#20]
On, say, linux, 'monkey://internal/' would get mapped to '~/.config/MyCoolApp/' (assuming #CPP_APP_IDENT="MyCoolApp").
And I can take it that paths are allowed, so a developer can do #CPP_APP_IDENT="MyCompany/MyCoolApp" and 'monkey://internal/' with be mapped to '~/.config/MyCompany/MyCoolApp/'.

Will the state file name be hard coded to monkeystate or will it implement something like the name change I did above?


Arjailer(Posted 2014) [#21]
Sounds good to me Mark - solves all of my concerns :-)


Soap(Posted 2014) [#22]
Mark,

Adding #CPP_APP_IDENT="My Cool App" '~/.config/My Cool App/' would be super awesome and would save me a lot of time in the future! :)

I use a custom filename MCA.state too possible to have that custom also?

#CPP_APP_STATENAME="MCA.state"

'~/.config/My Cool App/MCA.state'


marksibly(Posted 2014) [#23]
> And I can take it that paths are allowed, so a developer can do #CPP_APP_IDENT="MyCompany/MyCoolApp" and 'monkey://internal/' with be mapped to '~/.config/MyCompany/MyCoolApp/'.

Well, I wasn't planning on that but it's probably not a bad idea to have some kind 'subdir' support.

I was really intending APP_IDENT to be a valid identifier though, so that it could eventually be used as one of the config vars for properly renaming apps (which can be complex on some targets). Other vars involved in app renaming are APP_PACKAGE and APP_LABEL. Perhaps there should also be APP_PUBLISHER?

So new idea (on linux):

* If both CPP_APP_PUBLISHER and CPP_APP_LABEL are defined, monkey://internal maps to ~/.config/CPP_APP_PUBLISHER/CPP_APP_LABEL

* else if only CPP_APP_LABEL is defined, monkey://internal maps to ~/.config/CPP_APP_LABEL

* else monkey://internal maps to app-local internal dir as it currently does.

I'm preferring APP_LABEL here to APP_IDENT, as APP_LABEL is intended to be 'human-readable', now with the limitation that it can only contain filesystem-friendly chars.

If you really want something 'deeper' than PUBLISHER/LABEL, I'd rather think up a new name for it than allow '/' in APP_IDENT. But I think the PUBLISHER/LABEL scheme is fine myself.

> I use a custom filename MCA.state too possible to have that custom also?

I would kinda preferred you used fileio for this - souping up existing functions isn't much fun on a multi-target API. Also, it makes it harder to implement those functions on potential future targets. LoadState/SaveState were originally only intended to allows app to 'restart' cleanly after an abrupt shutdown, and I'd like to keep them this simple.
.


Soap(Posted 2014) [#24]
Hmm...


dawlane(Posted 2014) [#25]
If you really want something 'deeper' than PUBLISHER/LABEL, I'd rather think up a new name for it than allow '/' in APP_IDENT. But I think the PUBLISHER/LABEL scheme is fine myself.
#CPP_APP_IDENT to #CPP_APP_LOCALPATH and allow it for paths, so that the developer could do (linux) '~/.config/MyCompany/MyCoolAppSeries/'.
#CPP_APP_LABEL should reflect the fact that it's going to use local storage. e.g CPP_APP_LOCALLABEL

So new idea (on linux):
* If both CPP_APP_LOCALPATH and CPP_APP_LOCALLABEL are defined, monkey://internal maps to ~/.config/CPP_APP_LOCALPATH/CPP_APP_LOCALLABEL
* else if only CPP_APP_LOCALLABEL is defined, monkey://internal maps to ~/.config/CPP_APP_LOCALLABEL
* else monkey://internal maps to app-local internal dir as it currently does.