Custom data saved on the "search path"

So here is the trick I’m trying to pull. There are some topics that cover part of it but they are old, and Apple likes to change the rules every 10 mins so though it was best to ask all of it fresh.

I need to store lots of data that is generated by my app, so it should not be “cache” as the data is needed for the app to operate( think of it as downloaded data ), but it also should not “backup to iCloud” (as that will cause Apple to fail it), I will also need to do this on Android so having a “generic” solution provided by Cocos2d-x for the “folder” path is best. And have it set the “don’t back me up” flag if on iOS.

The next issue is I need to be able to put stuff that is stored into this folder in the “search path” as in FileUtils::getInstance()->addSearchPath("common", false); from what I can tell this defaults on iOS to the bundled resources and any path you add on top of this is then append to the “root”, while the “path that stuff is saved to from the app” is not a child of this root. OR if I give it an absolute path obtained from what ever functions I use above will it detect that it is an absolute path and not try and append it to its nominal “root” ?

That out there huh?

Getting a path with some custom Obj-C code is probably not that hard to do, I would rather have avoided doing custom stuff, but oh well.

However the search path, should it work if I add a abs path, or do I need to modify the engine?

I’m not quite sure that I understand what you’re trying to achieve, but have you looked at CCFileUtils.cpp/.h to see what functionality is available to you? You can always just test the addSearchPath and other methods in that file to see what results you get, because at least then you will be sure of how they work.

Its not complicated, and the sheer simpleness is probably tripping you up.

So
Lets say I have a thing, called Test.file and I want to put Test.file into a folder called TestF. So I need somewhere on the device that I can write Test.file into a TestF folder such that it is saved onto the device, with the caveat that on iOS it is not backed up to their iCloud service. Test.file is needed for the operation of the application and is not “disposable” such as a temp file on say Windows is, so it needs to be somewhere iOS won’t just delete it for fun.
Then I need to add something to the search path that allows me do to Sprite::Create("TestF/test.file"); for example. I.e the folder that is on the device is now also in the search path.

I have looked at the CCFileUtils, I note the iOS version has a “getWritablePath” function which gets a directory, not 100% sure if its best directory for my case, under to Apples’ rules. So I’m asking for people who have done recent submissions etc what is the best path for my case.

However looking closer at the iOS version of the file utils, I see virtual bool writeToFile(const ValueMap& dict, const std::string& fullPath) override; there only seems to be a ValueMap version, how does one write Binary data?

std::string filepath = CCFileUtils::getInstance()->getWritablePath() + "TestF/test.file";

Create/save binary file with:
FILE* fs = fopen(filepath.c_str(), "w");

1 Like

This is the correct way to get where you can write on each platform.

FileUtils class has many useful functions:

virtual ValueMap getValueMapFromFile(const std::string& filename);
virtual ValueMap getValueMapFromData(const char* filedata, int filesize);
virtual bool writeToFile(const ValueMap& dict, const std::string& fullPath);
virtual bool writeStringToFile(const std::string& dataStr, const std::string& fullPath);
virtual void writeStringToFile(std::string dataStr, const std::string& fullPath, std::function<void(bool)> callback);
virtual bool writeDataToFile(const Data& data, const std::string& fullPath);
virtual void writeDataToFile(Data data, const std::string& fullPath, std::function<void(bool)> callback);
virtual bool writeValueMapToFile(const ValueMap& dict, const std::string& fullPath);
virtual void writeValueMapToFile(ValueMap dict, const std::string& fullPath, std::function<void(bool)> callback);

Now that you’ve clarified your requirements a bit better, I understand what you’re trying to achieve. For the game I am currently developing, I’ve also had to consider this issue carefully, specifically for iOS.

This is the Apple documentation you are referring to:
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW4

and

https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1

The method getWritablePath() will return a suitable path on Windows, Android and perhaps other platforms, but it will NOT return a suitable path on iOS (tested on Cocos2dx 3.17), and here’s why:

In CCFileUtils.mm:

std::string FileUtilsApple::getWritablePath() const
{
    if (_writablePath.length())
    {
        return _writablePath;
    }

    // save to document folder
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    std::string strRet = [documentsDirectory UTF8String];
    strRet.append("/");
    return strRet;
}

That is the Documents directory, which is not the one you should be using. It’s quite unfortunate that this has not been corrected.

There was a long discussion about this in a thread from 2013, but I don’t know why /Documents was the one that was settled on, because it’s clearly incorrect. Here is the previous thread for reference:

There should have been different methods to return each one of the different paths, regardless of whether all platforms had the same setup as iOS. For instance, there should have been a getDocumentsPath(), getCachePath(), getWritableDataPath() or something similar. If, for instance, Android didn’t have the same path design as iOS (like no Documents path), then what harm would there be if the returned path was the same as what getWriteablePath() returns at the moment? That way the API would have been consistent, but each platform would return whatever paths were relevant, even if the return values were the same between the different API methods.

Now, for your case, what you actually want is:

/Library/Application Support/

and not what is currently returned by getWritablePath(), which is:

/Documents/

The Apple documentation linked above explains why the /Documents/ should not be used for downloadable assets that are only required for the operation of the application.

If you do end up using /Library/Application Support/, then it’s best you create a sub-directory related to your application, and then remove it at any time your app is uninstalled.

What? Really? I upgraded me games to 3.17 and my app found all my files after upgrading. I didn’t have to move them. So the path must be working. I’ll look into this. Thanks for mentioning it.

On iOS getWriteablePath() has always returned the /Documents/ folder, but even after that long discussing in 2013, nothing has changed, so your files were found because they’ll still be in /Documents/. What I was referring to is that /Documents/ should only for user generated content (including any purchases that cannot be re-downloaded etc.).

For application support files, like extra assets and normal re-downloadable files, then Library/Caches/ or Library/Application Support/ is to be used. There is a difference between these two locations as well, since anything in /Library/Caches/ may be deleted by the OS if it needs the space, whereas anything in /Library/Application Support/ is safe.

This explains it really well:
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1

Could some one test this on a Device? Just a log("Path: %s", FileUtils::getInstance()->getWritablePath().c_str() ))

I’m currently not working on IOS. But I think I remember that getWritablePath does not return “User/Document” path. it was something else. Something with the App fingerprint/identifier. it has been a while… so I don’t remember it fully.

But if @R101 is right, then it’s really not a good solution.

Thanks for the link, it seems the Library/Applications is not the one I need as it will be backed up to iCloud, and Caches is probably the one Apple will want me to use, and I just have to add the logic to detect when it is wiped and rebuild it all again…

So custom function to get this path for iOS, however GetWritablePath is correct for Android it seems.

Now how do I get the search path to handle the case? Is it designed to work this way? I don’t like try it and see if it works, it works then it ok, this leads to lots of pain later on.

When I first did iOS ( back in ios 4 I got off the train wreck at 7) I did a lot of customisation of Cocos, adding async texture loading etc, and it was pain every time I need to upgrade the engine for this or that ios support, so I’m really trying to avoid it again if I can :wink:

You didn’t read the documentation completely. :wink:

Quoting a section of the document from the link in my previous post:

“Remember that files in Documents/ and Application Support/ are backed up by default. You can exclude files from the backup by calling -[NSURL setResourceValue:forKey:error:] using the NSURLIsExcludedFromBackupKey key. Any file that can be re-created or downloaded must be excluded from the backup. This is particularly important for large media files. If your application downloads video or audio files, make sure they are not included in the backup.”

I do not agree with that statement. GetWritablePath() is way too generic, currently points to /Documents/, and doesn’t give an indication of where the data will be stored. It’s over-simplified and needs a better API. It’s on my todo-list to add a better API for it in Cocos2d-x, and if the changes are accepted and it’s merged in, then great, and if not, that’s fine, because I will just use the code in my projects rather than relying on GetWritablePath().

The way I see it is that GetWritablePath() can stay in there, unchanged, but we can add new methods that better reflect the actual location we are referring to, along with relevant methods to enable/disable backup operations on those files.

I can set the flag, which is then another custom function just for iOS. While my “easy to regenerate” files matches what they say for Caches. Bare in mind Apple have reject an app from me in the past because I put ~230K in the wrong spot, and this might hit over a GB. So I’m being very cautious.

I agree that a better API is needed, but I also need to get this out before Christmas :wink: So will it hold “for now”(on Android, I’ve never released on Android before so not really up on their rules/bug bears) ?

I don’t know if it using the current getWritablePath() will get your app approved, since that’s up to you and Apple, as for Android, the value returned by getWritablePath() is the correct one, and here is more info for you about it:

https://developer.android.com/training/data-storage/files