Page MenuHome

Fix for T87867: The Blender File Open dialog triggers OneDrive Files On-Demand downloads (macOS).
ClosedPublic

Authored by Leon Zandman (lzandman) on Jun 2 2021, 5:01 PM.

Details

Summary

This fixes the macOS part of the issue I reported in T87621 (and for which sub task T87867 was created). This fix should prevent Blender from accidentally triggering OneDrive on-demand file downloads when browsing a OneDrive folder using the File Dialog and should also prevent thumbnail generation for those files.


Remarks
I did some research and unfortunately OneDrive currently doesn't use macOS's native placeholder file implementation (as iCloud Drive uses. Probably should be based on NSFileProviderReplicatedExtension). Instead it appears OneDrive adds the com.microsoft.OneDrive.RecallOnOpen extended file attribute to placeholder files. So this code checks for the presence of that extended file attribute to determine if it is merely a placeholder.

Technically that file attribute can also be present on files that aren't located inside a OneDrive folder. So a more thorough check would be to also check if the file is indeed located inside the active OneDrive sync root folder. However, this requires substantially more code, as determining the OneDrive root folder location isn't very straightforward (I do have some experimental code for this).

I'm obtaining the extended file attributes using listxattr(). I tried using macOS Foundation Framework's FileManager, but somehow I couldn't get it to return non-Apple extended file attributes.

I've tried to make the code somewhat generic, so other cloud drive providers can also be added in the future.

Diff Detail

Repository
rB Blender

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
Leon Zandman (lzandman) requested review of this revision.Jun 2 2021, 5:01 PM
Leon Zandman (lzandman) retitled this revision from Fix for T87867: The Blender File Open dialog triggers OneDrive Files On-Demand downloads. to Fix for T87867: The Blender File Open dialog triggers OneDrive Files On-Demand downloads (macOS)..Jun 2 2021, 5:04 PM

Please create the future diffs with git diff -U100 so that unchanged lines can be seen too.
Better yet would be arcanist. https://wiki.blender.org/wiki/Tools/CodeReview#Use_Arcanist

source/blender/blenlib/intern/storage_apple.mm
70

See the blender::StringRef::find* functions.

101
110

Is std::string a better choice here ? It has methods to resize storage, cleans up after itself and gives access to underlying buffer by .data().
Later on, searching for substrings is easy with some extra care for the \0. https://godbolt.org/z/xf5xz3KnP

123

Can be a global variable in case others are added.

124

const

139

Comment style (// vs /*) should be uniform in a file. Also remove the ellipses.

154โ€“157

Shorter, drive service agnostic comment would be fine too.

Querying NSURLIsReadableKey and NSURLIsWritableKey keys for placeholder files in cloud services triggers their download.

159

const bool ...

163

What are the values of is_readable and is_writable in the case of a placeholder file ?

Please create the future diffs with git diff -U100 so that unchanged lines can be seen too.
Better yet would be arcanist. https://wiki.blender.org/wiki/Tools/CodeReview#Use_Arcanist

Oops, forgot the -U1000. OK, I will check Arcanist out. And process your remarks.

Added some remarks to the code review. Managed to install Arcanist. Will try to use it for my next diff push.

source/blender/blenlib/intern/storage_apple.mm
101

and check for error (-1).
Also print corresponding errno to error message in that case. https://en.cppreference.com/w/cpp/string/byte/strerror

I did, but I didn't know what to do with the error. I opted for returning false, which is kind of weird. But you're saying I should print the error to the console? I didn't know that made sense for Blender. What should happen to the flow? I'm used to higher level programming languages that throw exceptions. Should I just print the error and be maintain my original flow?

110

Funny, because I started out using NSString type. But I figured since most of Blender seems to consist of low-level C-code these "fancy" types should be avoided. I will look into it.

123

As in: global to this file? Or should it be globally defined in some other (header) file?

163

I wondered what to do with those. They are false for placeholder files, as no corresponding attribute is found in the collection (because I didn't query for them). The result is the file is marked FILE_ATTR_SYSTEM which does seem kind of weird, but it seems to work out. I think currently the only consequence is that the file is displayed using ICON_SYSTEM (line 1240 in source/blender/editors/space_file/filelist.c).

source/blender/blenlib/intern/storage_apple.mm
101

size_t is unsigned. So in case of error, its value would be a large positive number instead of -1. That case will not be caught by size < 1. Thus ssize_t should be used and it is what is returned by listxattr also.

Returning false is fine. A debug statement to the console for the error is what I am suggesting:
"Listing attributes failed: no such file or directory: /Users/t/a/b.jpeg"

110

That might have been an obj-C file, not an obj-C++ file. C++ containers/ STL (and their replacements like blender::Map are preferred over low level fiddling).

123

this file

source/blender/blenlib/intern/storage_apple.mm
154โ€“157

But currently I only noticed this issue for OneDrive placeholder files. iCloud Drive doesn't seem affected by this. And I haven't tested other cloud storage services. I'm in favor of a shorter comment, but I do think it should be OneDrive-specific for now.

Leon Zandman (lzandman) edited the summary of this revision. (Show Details)
  • Applied code review remarks.
Leon Zandman (lzandman) marked 12 inline comments as done.Jun 3 2021, 2:31 PM

Processed most, if not all, code review remarks. I'm still a bit unsure and unsatified about the listxattr() error handling. It should work, but it seems convoluted.

I also kept the malloc call, because I didn't see a way to replace that with std::string. I didn't print anything to console in case the malloc fails, though. Should I? Also, I noticed Blender has some things like MEM_mallocN(). Should I've used those?

source/blender/blenlib/intern/storage_apple.mm
110

I've used the std::string find() method and threw away my custom code.

Leon Zandman (lzandman) marked an inline comment as done.
  • Now using std::string instead of char * buffer.

ankitm - What are the values of is_readable and is_writable in the case of a placeholder file ?

@lzandman- I wondered what to do with those. They are false for placeholder files, as no corresponding attribute is found in the collection (because I didn't query for them). The result is the file is marked FILE_ATTR_SYSTEM which does seem kind of weird, but it seems to work out. I think currently the only consequence is that the file is displayed using ICON_SYSTEM

Files in this state should have FILE_ATTR_OFFLINE, but not FILE_ATTR_SYSTEM as they are not "protected" and not FILE_ATTR_READONLY as they are writable (with a delay). Could you just set both is_readable and is_writable to true when fileIsPlaceholder to avoid the setting of FILE_ATTR_SYSTEM?

  • Better error handling. Refactoring and correct setting of Blender's own file attributes.
  • Applied ClangFormat.
  • Using nullptr instead of NULL.

One does not simple switch to std::string ;-) @Ankit Meel (ankitm)

source/blender/blenlib/intern/storage_apple.mm
114

It just occurred to me that in the switch to using std::string find() method I've introduced a bug. Semantically we are no longer comparing each individual attribute, but doing a substring search on the complete list of attributes as a string. This produces wrong results when the attribute searched for isn't included itself, but a different attribute that happens to start with the same characters is present.

So this result of listxattr() would generate a positive result, even though it doesn't contain the attribute:

"com.microsoft.OneDrive.RecallOnOpen.SomethingElse\0com.microsoft.OneDrive.RecallOnOpen.NothingElseMatters\0"

Guess I'll update the code. Or revert back to my "dumb", but perfect code :-)

  • Fixed attribute compare bug.

Turned out to be quite a challenge to split a std::string filled with concattened null-terminated strings...

  • Type change from 'long' to 'const size_t'.
  • Better attribute comparison function. Also using std::string_view.

Searching Google or Github for com.microsoft.OneDrive.RecallOnOpen gives me no matches of this being used in any code. Which makes me wonder why that is, if OneDrive on macOS is rare enough that no one else bothered yet, or if there is some other mechanism used in other apps.

source/blender/blenlib/intern/storage_apple.mm
90

The "\0"s at the start seems wrong. From the listxattr man page it does not seem like it starts with a null terminator, so the first attribute would not be matched.

112

Don't print any errors to the console, this message is not useful to users as something they can fix, it will just be an annoyance.

Leon Zandman (lzandman) marked an inline comment as done.Jun 7 2021, 1:31 PM

Searching Google or Github for com.microsoft.OneDrive.RecallOnOpen gives me no matches of this being used in any code. Which makes me wonder why that is, if OneDrive on macOS is rare enough that no one else bothered yet, or if there is some other mechanism used in other apps.

It indeed seems undocumented. But there's no other official way. It seems Microsoft implemented its own system for these on-demand files. Apple has since introduced an API for iCloud Library files and an extension model that apparently also can be used by third-party developers. But OneDrive doesn't seem to be using this API.

source/blender/blenlib/intern/storage_apple.mm
90

The "\0"s at the start seems wrong. From the listxattr man page it does not seem like it starts with a null terminator, so the first attribute would not be matched.

The first attribute should be handled by if-statement above. By now I've done a dozen implementations of this function. The first one, which was C only, worked pretty well. But @Ankit Meel (ankitm) preferred a std::string solution and we've gone back and forth over a proper implementation. I hope this one suffices.

112

Getting conflicting review remarks, as @Ankit Meel (ankitm) asked me to include the error printing :-) I will remove it.

Leon Zandman (lzandman) marked an inline comment as done.Jun 7 2021, 1:33 PM

Forgot to mention, I did find an official method to check some of the OneDrive file pinning state. But it involves executing the OneDrive app itself. Doesn't seem like a viable way to run from Blender:
https://docs.microsoft.com/en-us/onedrive/files-on-demand-mac

  • Removed printing error to console.
Leon Zandman (lzandman) marked an inline comment as done.Jun 7 2021, 1:51 PM

The additional complexity on top of the one-liner find function call arises from
the possibility raised by Leon of some prefix on the attribute:
prefix.com.microsoft.OneDrive.RecallOnOpen.

Optimize by returning early and delaying second search by doing a
char equality check. Most files will not be in OneDrive, so exit
ASAP.

Add static, set return type to bool, add comments.

Add suffix \0 to attribute itself because it will always be there.

Please let's not overcomplicate this implementation by trying to use C++ style at all costs, it's too hard to understand. This can simply be:

static const char *ONEDRIVE_RECALLONOPEN_ATTRIBUTE = "com.microsoft.OneDrive.RecallOnOpen";

static bool find_attribute(const std::string &attributes, const char *search_attribute)
{
  /* Attributes is a list of consecutive null-terminated strings. */
  const char *end = attributes.data() + attributes.size();
  for (const char *item = attributes.data(); item < end; item += strlen(item) + 1) {
    if (STREQ(item, search_attribute)) {
      return true;
    }
  } 

  return false;
}
  • Now using Brecht's find_attribute() implementation.

Please let's not overcomplicate this implementation by trying to use C++ style at all costs, it's too hard to understand. This can simply be:

Oh boy, now we've almost done a full circle :-) This somewhat resembles my original code. I've updated the diff with your implementation.

When reading the following keep in mind that I don't use a Mac (and so could say dumb things) and my opinion should be treated with less authority than others wading in here (so can be ignored).

But I think you should reconsider some word usages and function names to make this more generic for later work. I realize that this is about OneDrive in particular, but you are really just testing for a type of file state that will also be used by other services.

Whether a user is using OneDrive, iCloud, Google Drive, or a host of other services, there will be a time when files are listed but aren't immediately available without a delay. The files in this state will have FILE_ATTR_OFFLINE to help notify the user of this, then once they are made available (brought online, transferred to a faster medium, unarchived, uncompressed, or downloaded) the file will be like any other file and will not have the FILE_ATTR_OFFLINE attribute.

So first I would consider the use of "is_placeholder" and "checkIfFileIsPlaceholder". Microsoft might use this term in its implementation to refer to the (almost empty) files that are temporarily in place of the target files, but we are less interested in what is there now, and more in the current state of the target file itself. So we aren't checking if "when the file is a placeholder" but if the target file is temporarily in this state. So I would use "is_offline" in place of "is_placeholder" and rename "checkIfFileIsPlaceholder" to "checkIfFileIsOffline". That way it will be pretty obvious on what to do for anyone adding support for other similar services.

But I think you should reconsider some word usages and function names to make this more generic for later work. I realize that this is about OneDrive in particular, but you are really just testing for a type of file state that will also be used by other services.

I tried to keep it generic. That's why I added two functions, checkIfFileIsPlaceholder() and oneDrive_checkIfFileIsPlaceholder(), whereby the first is the generic one that currently simply calls the seconds one. But in the future could also contain additional logic for other services/scenarios.

So first I would consider the use of "is_placeholder" and "checkIfFileIsPlaceholder". So I would use "is_offline" in place of "is_placeholder" and rename "checkIfFileIsPlaceholder" to "checkIfFileIsOffline". That way it will be pretty obvious on what to do for anyone adding support for other similar services.

Good point! I do think placeholder is a OneDrive-specific term. iCloud Drive calls them "ubiquitous" files. I'll update the code.

  • Made code more generic by replacing 'placeholder' with 'offline'.

Microsoft just announced they will improve the OneDrive experience on macOS later this year. It seems they will replace their custom files on-demand implementation and use the official APIs:

Later this year we will be revamping the OneDrive sync experience on the latest version of macOS based on Appleโ€™s new File Provider platform to improve the Finder experience for OneDrive.

https://techcommunity.microsoft.com/t5/microsoft-onedrive-blog/microsoft-works-to-ensure-a-great-onedrive-experience-on-apple/ba-p/2400803

Good to hear! Should make OneDrive and iCloud Library implementations more similar.

So what about this? I think it's ready for a final review. I've been using it privately for some time now and it seems to work just fine. Would be great to have this in an official build.

@Leon Zandman (lzandman) - So what about this? I think it's ready for a final review. I've been using it privately for some time now and it seems to work just fine. Would be great to have this in an official build.

Don't worry yet that this might be forgotten. Release target would be 3.0 and that will be getting much more development time (will get an extension of a 2-3 months announced fairly soon).

This revision is now accepted and ready to land.Jun 14 2021, 12:26 PM