File Provider API on macOS
How to Work with the File Provider API on macOS
July 07, 2022 15:40 pm
File Provider API on macOS
July 07, 2022 15:40 pm
File Provider is a system extension that allows applications to access files and folders synchronized with a small server. It was first introduced by Apple for iOS 11.
This API lets creators make applications like Dropbox, iCloud, and OneDrive without the requirement to utilize third-party solutions like FUSE. Another important advantage of File Provider is that you don’t require to create any additional kernel extensions to make your file system.
The foremost sign that it’s likely to use the File Provider API on macOS occurred when the “macOS 10.15+” line was added to the extension’s Availability section. Beginning from macOS 11 Big Sur, Apple added more details to the official documentation and even explained how File Provider works on macOS.
Unlike iOS, for which the File Provider API implementation is explained in detail, macOS lacks official data and detailed models for allowing this extension. For those who are interested in using the File Provider API on macOS, here’s what we have at the moment:
Using the File Provider API instead of third-party solutions can keep lots of evolution time and simplify the method of building applications.
Although Apple hasn’t provided any code instances, we chose to make a File Provider extension for macOS on our own. Our attempt was successful, and we will now explain how we did it in the segment.
File Provider is an extension for macOS and not a particular application, though it’s distributed inside a usual Apple application in the Plugins folder. Any application can keep the File Provider extension as long as both the app and the extension are made by the same creator and have the same Bundle ID.
We can use a template to save time while making a File Provider extension for macOS. An evident solution would be to use a template from Xcode, but it doesn’t have one for the File Provider extension yet. That’s why we’ll take another template as a base. — Finder Sync Extension.
For our experiment, we’ll create a simple proof of concept (POC) that can make a virtual disk and display a list of files on it. Let’s begin with making the virtual disk.
First, we make a new application (a so-called container) using a standard template. Then, we make a new target inside this application using the Finder Sync Extension template:
If we look into the File Provider extension on iOS, we can see that all differences between the Finder Sync extension template and the iOS File Provider template are located in the Info.list file that defines the features of the application.
We’re not sure whether all the areas are needed for our application, but let’s fill out all of them. Pay particular attention to the FileProviderClass, which is responsible for creating the whole extension and inherited from the NSFileProviderReplicatedExtension protocol.
The next phase is to execute the primary class — FileProviderClass — we just mentioned above. Here’s a piece from Apple’s documentation that will allow us.
So, we must inherit FileProviderClass from the NSFileProviderReplicatedExtension protocol and execute the needed functions noted in the code below (for now, let’s use stubs): Then, we can proceed to establish the File Provider extension in macOS. Let’s do it the same way as in iOS.
Now, we operate our application, which initializes the File Provider extension installation into the system (using the NSFileProviderManager.add() installer), and voilà — we see a new disk in the system.
We worked to make a virtual disk, but it’s open for now. In the next section, we analyze the primary functionality for displaying files.
Initially, our extension doesn’t try to load a file to our virtual disk (we have to initialize file uploading) or read its contents. To display files, the system requires metadata that defines the hierarchy of files and folders.
File Provider represents files using the NSFileProviderItem protocol. We require to inherit it and determine the minimum number of needed fields. Initially, the protocol lists only three fields that are needed to be filled out for redeclaration:
But it’s not that easy. If we go to the documentation (under the NSFileProviderItem title), we’ll see the following:
So, we have another required field we have to fill out: contentType. In addition, we executed a few more domains:
There are also lots of other fields, but they aren’t required for basic File Provider operation. Also, we must implement the NSFileProviderEnumerator protocol and several of its methods:
The code comments in the Apple framework’s headers also have the following message that we should keep in mind:
So, we must implement a few more methods:
Now, let’s look closer at the realistic implementation of some classes and procedures.
To confirm the necessary functionality of our virtual disk, we require to implement at least the following:
Let’s take a closer look at each of these topics.
NSFileProviderItem is a protocol that describes the possessions of an item contained by the File Provider extension. Here, we just must fill out the areas and select the file name.
must have only the file’s name, not the whole file path.
a unique file identifier. In this field, we must determine the entire path to the file.
a field has the name of the parent item identifier. If the file is located in a subfolder, then its parentItemIdentifier is the item identifier of its parent folder.
establishes the size of the file.
a class that defines a file or folder. It can be represented as RTF, MP3, DOC, PDF, or nearly any other common file type. To choose the file type, you can use the [UTType typeWithFilenameExtension:…] function.
Also, there are three basic Item Identifiers that we ought to manage:
NSFileProviderRootContainerItemIdentifier — the persistent identifier for the root directory of the File Provider’s send file hierarchy. All files in the origin folder must have NSFileProviderRootContainerItemIdentifier as their Parent Identifier.
NSFileProviderWorkingSetContainerItemIdentifier — the persistent identifier representing the operating set of documents and directories that will be displayed in Favorites, Recents, and other folders.
NSFileProviderTrashContainerItemIdentifier — the persistent recognize for the parent folder of all deleted items.
Note: If you make any error in the item identifier–parentItemIdentifier hierarchy or identify the wrong contentType, all the files in the current folder won’t be displayed. Also, there won’t be any recognition of mistakes in logs, which isn’t suitable for debugging.
The NSFileProviderEnumerator protocol is liable for overriding a particular directory or suggesting modifications made to it if an override was already performed. Functions in this protocol will be called only if a system demands a list of files in the directory or if external triggers are called that signal structural modification inside this directory.
The mandated functions of the NSFileProviderEnumerator protocol
This process is called by the system when you require to recover the contents of the target directory: for instance, when a user unlocks a folder or a system accepts a request to refresh the directory’s content.
Once we obtain the list of files, we have to call two ways from the observer object: didEnumerateItems and finishEnumeratingUpToPage. We’re not obliged to call these techniques in the context of the current function, so we can recover the list of files later and it won’t halt the system’s work.
If there are a substantial number of files to display, the page parameter is needed for continued file uploading. However, in our instance, we don’t have a lot of files to display, so we don’t require to use this parameter.
This process is called when our server mails us a message saying that files on the server have been modified and that we ought to download transformations. To trigger the call of the enumerateChangesForObserver function, we require to call the signalEnumeratorForContainerItemIdentifier function:
The system reaches this function to specify whether the modification in the current folder was synchronized. We must deliver the NSFileProviderSyncAnchor class with user data. For instance, a simple SyncAnchor class can use the time and date of the last successful update. After that, a request for the list of modifications for this SyncAnchor class will only recover modifications downloaded after the determined date.
NSFileProviderReplicatedExtension is our primary class made by the system at the launch of the File Provider extension. Using this class, we can recover metadata about a file and its contents. Also, the system will use this class to call procedures that describe interactions between a user and files on our virtual disk. In this class, we can also make all NSFileProviderEnumerator classes for our folders.
The mandated functions of the NSFileProviderReplicatedExtension class include:
-(NSProgress *)itemForIdentifier:(NSFileProviderItemIdentifier) request:(NSFileProviderRequest *) completion handler:(void(^)(NSFileProviderItem, NSError *))
This function allows the system to obtain metadata about a file. Also, it recovers the NSProgress object that describes the current improvement of downloading files from the server. Here’s how it functions:
-(NSProgress *)fetchContentsForItemWithIdentifier:(NSFileProviderItemIdentifier) version:(NSFileProviderItemVersion *) request:(NSFileProviderRequest *) completion handler:(void(^)(NSURL *, NSFileProviderItem, NSError *))
This function allows the system acquires the path to the already downloaded file in the temporary folder. After obtaining the file path, the system can use the file and copy it to other locations. Here’s an illustration of how this process performs.
-(NSProgress *)createItemBasedOnTemplate:(NSFileProviderItem) fields:(NSFileProviderItemFields) contents:(NSURL *) possibilities:(NSFileProviderCreateItemOptions) request:(NSFileProviderRequest *) completion handler:(void (^)(NSFileProviderItem, NSFileProviderItemFields, BOOL, NSError *))
-(NSProgress *)modify them:(NSFileProviderItem) base version:(NSFileProviderItemVersion *) varied fields:(NSFileProviderItemFields) contents:(NSURL *) possibilities:(NSFileProviderModifyItemOptions) request:(NSFileProviderRequest *) completion handler:(void(^)(NSFileProviderItem, NSFileProviderItemField, BOOL, NSError *))
-(NSProgress *)deleteItemWithIdentifier:(NSFileProviderItemIdentifier) base version:(NSFileProviderItemVersion *) possibilities:(NSFileProviderDeleteItemOptions) recommendation:(NSFileProviderRequest *) completion handler:(void (^)(NSError *))
These three parts will be called when a user makes, changes, or deletes a downloaded file on the virtual disk. For our PoC, we didn’t execute these functions, since our objective was to only list files without their contents and with an opportunity to change them. However, we will require them when making a real application.
The system calls this process to list the folders on our virtual disk. Here, we have to recover our custom class inherited from the NSFileProviderEnumerator protocol. This class will be called by the system when it likes to get the contents of a particular folder.