Does FSEventStreamCreate really require directories? - macos

According to the documentation, the fourth parameter of FSEventStreamCreate (a function used to receive events about file changes) is "A CFArray of CFStringRefs, each specifying a path to a directory, ..." but I'm interested in changes to a specific file only. I know that I can pass the directory the file is in and then filter the events, which is what I've been doing up until now, but I have found out, that if I simply pass the file's entire path, it works perfectly fine (and allows me to simplify my code significantly).
So my question is, since I want to put this into production software, is this something I can rely on to work (e.g. in other versions of the OS), or is it a fluke? Is it documented somewhere to work this way? Does anyone else use it this way? My best guess is that the documentation is phrased like this because by default, only filesystem changes are reported, and the flag kFSEventStreamCreateFlagFileEvents has to be added to make it also report file content changes, but maybe I'm wrong.

The only thing Apple says about this is here
Request file-level notifications. Your stream will receive events about individual files in the hierarchy you're watching instead of only receiving directory level notifications.
Regarding other developers using this feature: yes, there are some.
You can probably leave a feedback on Apple, asking to improve the documentation, and maybe one day they will.

Related

How to copy a folder, recursively, with progress feedback, while preserving all metadata on OS X?

I need to copy a folder from one location to another while providing my users with a progress bar.
The only appropiate API that I can find that isn't deprecated is copyfile(3). I've implemented this and the results are nearly perfect, however I've since discovered that neither the Finder comments nor the tags associated with original are copied over. Not only do I need them to be copied, but I'm worried about what other metadata isn't being copied that I'm not yet aware of.
Note that I am setting all the appropriate flags on copyfile to copy over the metadata.
How can I achieve my goal without resorting to awful techniques such as an Applescript bridge to read/write the comments using the Finder at the end of the copy?
UPDATE
After much research I have discovered that both the comment and the tags are copied over in the file's extended attributes. However, the comment refuses to display in the Finder.
I saw it mentioned that the comment is also stored in the .DS_Store. As a test I deleted the .DS_Store file and then relaunched the Finder so that it would be regenerated. The comment still doesn't appear.
What needs to be done for the Finder to recognise this metadata?
Note: this answer assumes you are correct about comments etc. not being copied, this has not been verified.
copyfile is available in source form from Apple. You might need to tweak it a bit though to actually compile it. Once you've done that you can modify to add the features you wish.
HTH

Where is the format of .desktop files described (e.g. autostart)?

I am adding programs to autostart by adding files to ~/.config/autostart and everything is working fine but I would like to know what some of the properties mean. When I google this I just see many examples of desktop entries. I am interested in a technical definition. Where is this defined?
I am interested in the general question, but for example what does the following do?
Hidden=true
NoDisplay=true
Here is the formal specification: http://standards.freedesktop.org/desktop-entry-spec/latest/
Hidden=true means to treat the desktop entry as if it had been deleted. NoDisplay=true means not to show the application in any menus, but still treat it as if it is installed, for example to associate a MIME type with it. Setting both at once seems to be redundant, or perhaps undefined.

NSFileCoordinator correct usage

when writing a file using NSFileCoordinator i need to specify the correct NSFileCoordinatorWritingOptions. Although they are explained in detail, I am not sure when to use which one. The available options are:
NSFileCoordinatorWritingForDeleting
NSFileCoordinatorWritingForReplacing
NSFileCoordinatorWritingForMoving
NSFileCoordinatorWritingForMerging
For example, what option is the correct one if I want to create a file (a plist for example)?
Wich one when I modify a file?
Can someone explain the NSFileCoordinatorWritingOptions for a better understanding?
I agree, documentation is not complete on that front, and hard to understand. And no sample code is available even for basic operations like these.
I try to think of these options in the perspective of other apps that have that specific file open, that helps getting the whole picture.
Pass no option (0) to simply update the file and notify others of your changes.
Let's say you are deleting a file that TextEdit currently displays, by providing the NSFileCoordinatorWritingForDeleting option, you're telling TextEdit to close the file as it does not exist anymore (or it could propose to save it to another place if it's in memory). It acts because of deletion.
If you're overwriting a file (as opposed to updating a file), you want about that same behavior for other apps. That's NSFileCoordinatorWritingForReplacing.
NSFileCoordinatorWritingForMoving says other apps to track the file to it's new location, so that it can be later updated.
NSFileCoordinatorWritingForMerging asks other processes to first commit their changes so that you can then merge your own changes with those.
To answer your question, you should use NSFileCoordinatorWritingForReplacing when creating a new file (even when no file exists, as it was to appear in the mean time from another app, you'd be replacing it with your own, unrelated contents). And NSFileCoordinatorWritingForMerging should be used when updating an existing file with new data, as it allows integrating the latest changes to that file immediately (instead of doing later with conflict resolution).

Is there any way to give my sandboxed Mac app read only access to files in ~/Library?

I'm a little confused with the sandbox. Is this possible?
Thanks!
Yes, but it requires a "temporary exception" entitlement. "Temporary" means that it may go away in some future version of the OS, but that's not much of a risk. The bigger problem is that word "exception": It means you will have to justify your use of the entitlement, or the App Store reviewers will probably reject you.
File a bug report explaining exactly what it is you think you ought to be able to do, but can't do today without access to ~/Library, and ideally also start a forum thread on the topic. They may suggest a workaround to use instead of accessing ~/Library (maybe even using private APIs), in which case, do what they say. Or they may say to use the temporary exception for now, in which case, do that. Or they may not respond, in which case you use the temporary exception and cross your fingers. In any case, make sure your App Store submission review notes have a link to the bug report and/or forum thread.
You will have to edit your project's entitlements plist manually to do this, but it's not very hard. Create a com.apple.security.temporary-exception.files.home-relative-path.read-only array, with one string, "/Library/". Like this:
<key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
<array>
<string>/Library/</string>
</array>
The extra / at the end is how the sandbox knows you want to access a directory, rather than a file. If you leave it off, you will get access to all files in ~/Library, which is what you asked for, but not to files in (recursive) subdirectories of ~/Library, like, say, ~/Library/LaunchAgents/com.mycompany.myapp.myoldagent.12345678-ABCD-EF00-1234-567890ABCDEF.plist, which is what you probably want. See File Access Temporary Extensions in the Entitlement Key Reference documentation.
Also, notice that you already have access "for free" to certain things under ~/Library, either because they get copied into your container, or indirectly when you use the appropriate APIs instead of using paths. So, there may be a better way to accomplish what you're doing—e.g., to read files left by a previous non-sandboxed version of your app, you might be able to migrate them into the container and read them there.
One more thing: Just having access to ~/Library doesn't change what NSHomeDirectory(), URLsForDirectory:inDomains:, etc. will return--you'll still get ~/Containers/com.mycompany.myproduct/Data/Library instead. Apple's semi-official recommendation for dealing with this is to use BSD APIs to get the user's real home directory, and the simplest way is this:
const char *home = getpwuid(getuid())->pw_dir;
NSString *path = [[NSFileManager defaultManager]
stringWithFileSystemRepresentation:home
length:strlen(home)];
NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES];
A few notes:
It's generally not a good idea to call getpwuid too frequently. The best solution is to call this code once early, and then cache the resulting NSURL.
This can obviously also be used to get other users' home (and therefore Library) directories, but the App Store almost certainly won't allow any software that actually tries that.
The fact that the library is at ~/Library is considered an "implementation detail" that could change one day, which is another reason (on top of the entitlement) that this has to be considered a temporary workaround until Apple provides a real solution to your higher-level problem, and it might be worth mentioning in your review notes.

How to mimic DropBox functionality with Ruby script?

I would like to upload documents to GoogleDocs every time the OS hears that a file was added/dragged/saved in a designated folder, just the way DropBox uploads a file when you save it in the DropBox folder.
What would this take in Ruby, what are the parts?
How do you listen for when a File is Saved?
How do you listen for when a File is added to a Folder?
I understand how to use the GoogleDocs API and upload things once I get these events, but I'm not sure how this would work.
Update
While I still don't know how to check if a file is added to a directory, listening for when a file is saved is now dirt simple, thanks to Guard for ruby.
If I were faced with this, I would use something like git or bzr to handle the version checking and just call add then commit from your script and monitor which files have changed (and therefore need to be uploaded).
This adds the benefit of full version control over files and it's mostly cross platform (if you include binaries for each platform).
Note this doesn't handle your listening problem, just what you do when you know something has changed. You could schedule the task (via various routes) but I still like the idea of a proper VCS under the hood.
I just found this: http://www.codeforpeople.com/lib/ruby/dirwatch/
You'd need to read over it as I can't vouch for its efficiency or reliability. It appears to use SQLite, so it might be better just to manually check once every 10 seconds (or something along those lines).
Ruby doesn't include a built-in way to "listen" for updates to files. If you want to stick to pure Ruby, your best bet would be to perform the upload on a fixed schedule (say every 5 minutes) regardless of when the file is saved.
If this isn't an acceptable alternative, you could try writing the app (or at least certain parts of it) in Java, which does support this type of thing. Take a look at JRuby for integrating the Ruby and Java portions of your app.
Here is a pure ruby gem:
http://github.com/TwP/directory_watcher
I don't know the correct way of doing this, but a simple hack would be to have a script running in the background which checks the contents of a bunch of folders every n minutes and uses the associated timestamps to determine if the file was modified in that span of time
You would definitely need some native OS code here, to write the monitoring service/client. I'd select C++ if you want it to be cross platform. If you decide to go with .Net, for example, you can use the FileSystemWatcher class to achieve what you need (documentation and here's a related article).
Kind of an old thread, but I am faced with doing something similar and wanted to throw in my thoughts. The route I'm going is to have a ruby script that watches a given directory and checks the timestamps. Once all files have been uploaded, the script saves the latest timestamp and then polls the directory again, checking if any files/folders have been added. If files are found, then the script uploads them and updates the global timestamp, etc...
The downside is that setting up a ruby script to run continually (or as a service) is somewhat painful. But it's not an overwhelming task, just needs to be thought out properly.
Also depends on if your users are competent enough to have ruby installed or if you have to package everything up into a one-click installer as well. That, to me, is the hardest part to figure out.

Resources