DSAppServices provides copy and login item services. When run for the first time, from either a disk image or from the user's download folder, you can use it to ask if the user would like to copy it somewhere on their drive(s), and can also ask if the user would like your application to run when they log in.
The class writes out two or three defaults keys to your application's user defaults (depending on if you run both checks or just one). Two of the keys correspond to the copy and login items checks, and the third key saves the value returned from your application's CFBundleVersion as the last version checked against. The check keys will prevent the dialogs from being shown again, and the version checked key is there so that you can remove the defaults keys when you release a new version to have your users asked again (if you wish).
The dialog windows that are shown are customizable if you wish/need to have them match your application (i.e. if you're using a custom window scheme).
This may be a sticking point for a few people — I decided not to implement authorization in the framework. This means that it won't, by default, copy an application in an area where the user can't write to, nor will it allow them to pick a destination that they can't write to. However, I did implement some hooks where if you feel you need authorization, you can implement it yourself and the class will call on the delegate to authorize and copy the application bundle to the destination if it's needed. This was an implementation decision on my part; I don't feel it's something that really should be done. However, if I get a lot of feedback over this issue, I may be persuaded to implement it.
The simplest implementation of this class would be two calls:
[[DSAppServices sharedInstance] checkIfShouldCopy];
[[DSAppServices sharedInstance] checkLoginItems];
That's, of course, if you want to check if you should copy AND you want to ask if they want it to run when they log in. If you only want one or the other, then you only need to call one of the methods.
If you do wish to check both, I'd advise you to do the checks in the order listed above. There's nothing stopping you from checking the login items first, but it would be a bit of a mistake. If you do it that way, the old copy will be the one that gets run when they log in, not the one that was copied.
The dialogs that are shown are created entirely in code, so it should be fairly easy to localize. All you need to do is work with the Localizable.strings file. The UI elements will resize if they need to.
If you run into an issue with the layout not rendering correctly with a localization, please do contact me.
Returns an instance of the class.
An instance of the class.
DSAppServices isn't a true singleton class; this method is simply there to negate any need for passing around a pointer to the class or instantiating multiple instances.
Selector for the "Yes" button in the login items dialog.
Returns the SEL for the "Yes" button in the login items dialog shown to the user.
Use this method if you're replacing the login items dialog to set your "Yes" button to the correct action.
Adds a new login item.
The path to the item you wish to add to the login items list.
Returns YES if the method successfully adds the path to the login items list, NO otherwise.
Adds a new login item.
A file URL pointing to the item you would like to add to the login items list.
Returns YES if the method successfully adds the path to the login items list, NO otherwise.
This is a convenience method for those who are using URLs rather than file paths. This simply calls —addLoginItemWithPath: with the URL's path.
Adds the calling application to the login items list.
Returns YES if we were successfully added to the login items list, NO otherwise.
This is a convenience method to easily add the calling application to the login items list. This calls —addLoginItemWithPath: with the calling application's bundle path.
Callback for when the delegate fails to authorize or copy the application to the copy path.
An optional error message that you would like displayed.
This method should be called if the delegate fails to authorize or copy the application's bundle to its final location. It will display an error message to the user.
Callback for when the delegate successfully authorizes and copies the application to the copy path.
The path the app's bundle was copied to.
This method should be called after the delegate authorizes and copies the application's bundle to the final location. It will launch the new copy and terminate the current one.
The authorized copy selector.
Returns the authorized copy selector.
This method only returns a valid selector if you've set it with —setAuthorizedCopySelector. Authorization isn't provided by default, but this is part of the hooks in place to do so if you feel you need to.
The selector for the "No" button in the login items dialog.
Returns the SEL for the "No" button in the login items dialog shown to the user.
Use this method if you're replacing the login items dialog to set your "No" button to the correct action.
The Cancel button.
Returns the "No" button for the login items dialog, or the "Don't Copy" button for the copy dialog.
This instance variable is shared between the copy and login items dialogs. It returns the correct button for whichever dialog is open, or nil if there is no dialog open. When the dialog windows are closed, it is set to nil. Don't cache this value.
If you have replaced the default dialog, then this method will return nil regardless of whether or not the window is visible.
Cancels copying the application.
Sender of the action.
This action method is called when the user decides not to copy the application elsewhere. Method closes the copy dialog.
The selector for the "Don't Copy" button in the copy dialog.
Returns the SEL for the "Don't Copy" button in the copy dialog shown to the user.
Use this method if you're replacing the copy dialog to set your "Don't Copy" button to the correct action.
Checks if copying should occur.
This method starts the check on if the calling application should be copied. It checks if you're running on unwritable media (read-only disk image, CD, etc), or if you're running out of the user's download folder, and if so will display the copy dialog.
This method returns quickly. Don't release the instance at this point. If you do want to release the class, implement the delegate method letting you know the check has finished.
Checks if the calling application is a login item.
This method starts the check on if the calling application is a login item or not. If not, it will display a dialog asking the user if they'd like the appication to run when they log in.
Note that if the user is running off of unwritable media, this method will return without asking the user if they want to add your application as a login item.
This method returns quickly. Don't release the instance at this point. If you do want to release the class, implement the delegate method letting you know the check has finished.
The copy path.
Returns the path the app's bundle will be copied to. Only valid while the copy dialog window is open.
Copys and launches a new copy of the calling application.
Sender of the action.
This action method is called when the user decides to copy the application elsewhere. Method copies the current application's bundle to the copy destination, then launches it and terminates the current copy.
The "Copy Location:" text field.
Returns the text field used to display the text "Copy Location:"
If you have replaced the default copy dialog, then this method will return nil regardless of whether or not the copy window is visible.
The matrix used to display the copy choices to the user.
Returns the matrix used to display the copy choices to the user.
If you have replaced the default copy dialog, then this method will return nil regardless of whether or not the copy window is visible.
Copies the given file to the given destination.
The path to the current app's bundle.
The path to the folder the app is going to be copied to.
A pointer to a nil string instance.
Returns YES if the copy was successfull, otherwise returns NO and sets an error message in errorString.
The errorString parameter must be a nil string pointer. If an error occurs, it will hold an error message. You are responsible for releasing the string if it's not nil on return.
The text field used to display the copy path.
Returns the text field used to display the copy path.
If you have replaced the default copy dialog, then this method will return nil regardless of whether or not the copy window is visible.
The selector for the "Copy & Relaunch" button in the copy dialog.
Returns the SEL for the "Copy & Relaunch" button in the copy dialog shown to the user.
Use this method if you're replacing the copy dialog to set your "Copy & Relaunch" button to the correct action.
The class's delegate.
Returns the class's delegate.
The dialog window shown to the user.
Returns the dialog currently being shown to the user, or nil if there is no dialog open.
This instance variable is shared between the copy and login items dialogs. When one is open, it returns the window for that dialog. When they are closed, it is set to nil. Don't cache this value.
If you have replaced the default dialog, then this method will return nil regardless of whether or not the window is visible.
Adds the calling application as a login item.
Sender of the action.
This action method is called when the user decides to add the calling application as a login item. It adds it as a login item and closes the dialog window.
Closes the login item dialog.
Sender of the action.
This action method is called when the user decides not to add the calling application as a login item. Method closes the dialog window.
Developement setting.
Returns YES if we're ignoring Polish's defaults keys, NO otherwise.
The calling application's icon image view.
Returns the image view used to display the calling application's icon in the copy and login items dialogs.
This instance variable is shared between the copy and login items dialogs. It returns the correct image view for whichever dialog is open, or nil if there is no dialog open. When the dialog windows are closed, it is set to nil. Don't cache this value.
If you have replaced the default dialog, then this method will return nil regardless of whether or not the window is visible.
Initalize a new instance of the class with a delegate.
The object to set as the new instance's delegate.
A new instance of the DSAppServices class.
This is the designated initializer for the class.
Checks if the calling application is already a login item.
Returns YES if the calling application is already a login item, NO otherwise.
Checks if the calling application is running from the user's download folder.
Returns YES if the calling application is running from the user's download folder, NO otherwise
Checks if the calling application is running from unwritable media.
Returns YES if the calling application is running from unwritable media, NO otherwise.
This method checks if the calling application is running from removable media (disk images, CDs, external file systems), and if so then checks to see if the media is writable or not.
List of login items.
Returns an array of dictionaries, each a login item. Keys to the entries in the login item info dictionary can be found in Constants.
The message field.
Returns the text field where the message Would you like to run [appName] when you login? is displayed for the login items dialog, or Would you like to copy [appName] to another location? for the copy dialog.
This instance variable is shared between the copy and login items dialogs. It returns the correct text field for whichever dialog is open, or nil if there is no dialog open. When the dialog windows are closed, it is set to nil. Don't cache this value.
If you have replaced the default dialog, then this method will return nil regardless of whether or not the window is visible.
The OK button.
Returns the "Yes" button for the login items dialog, or the "Copy & Relaunch" button for the copy dialog.
This instance variable is shared between the copy and login items dialogs. It returns the correct button for whichever dialog is open, or nil if there is no dialog open. When the dialog windows are close, it is set to nil. Don't cache this value.
If you have replaced the default dialog, then this method will return nil regardless of whether or not the window is visible.
Removes Polish's user defaults keys.
This method checks against the application version saved in the use defaults. If the application version being checked doesn't match the saved version, removes the defaults keys Polish writes after the copy and login items checks are done. Useful if you want to have the user asked again when you put out a new version.
Removes the calling application from the login items list.
Returns YES if the calling application was successfully removed from the login items list, NO otherwise.
This is a convenience method to easily remove the calling application from the login items list. This calls —removeLoginItemWithPath: with the calling application's bundle path.
Removes a login item.
The path to the item you wish to remove from the login items list.
Returns YES if the method successfully removes the path from the login items list, NO otherwise.
Removes a login item.
A file URL pointing to the item you would like to remove from the login items list.
Returns YES if the method successfully removes the path from the login items list, NO otherwise.
This is a convenience method for those who are using URLs rather than file paths. This simply calls —removeLoginItemWithPath: with the URL's path.
Sets the authorized copy selector.
The selector you wish performed on the delegate when an authorized copy is needed.
Selector needs to take a string argument, something like: — authorizedCopyBundleToPath:(NSString *)copyPath
I will probably get some negative feedback over this, but authorization isn't provided by default. However, I know not everyone feels this way so I've provided this hook to allow authorization.
When an authorized copy is needed, this selector is called on the delegate, with the copy path as the argument. When you have finished copying the file, the delegate should call back to finish things up. If the operation was completely successfully, the delegate should call —authorizedCopyFinishedSuccessfully:. If it failed, the delegate should call —authorizedCopyFailed:.
The path passed to this selector is the path to the folder where the application bundle should be copied to (i.e. it doesn't contain the name of the application bundle). Note that there is a chance that this folder might not exist — it's a small chance, and mostly is if the /Applications folder can't be found.
Sets the copy path.
The path to the folder where the calling application's bundle will be copied to.
This method only needs to be called if you replace the default copy dialog with a custom one.
Sets the class's delegate.
Object you wish to set as the class's delegate.
If the class is initalized by calling +sharedInstance, it sets [NSApp delegate] as its delegate. Whether that's nil or not depends on if you've setup NSApplication's delegate or not. If you have, and you want it to handle this class's delegate methods, then you don't need to use this method to set that up.
This class only keeps a weak reference to its delegate (i.e. not retained), so if you're setting the delegate to a short lived object, make sure to reset the delegate (or set it to nil) before that object is released.
Development setting.
Sets if the class ignores its defaults keys.
If called with an argument of YES, this method makes the class ignore the defaults keys it sets and doesn't write them out. This causes the copy dialog to show up on every run that the user is running from unwritable media or their download folder, and / or causes it to ask the user if they want to add the application as a login item until they capitulate and say "Yes."
This is a development setting, which you can use to test out that custom dialogs are appearing correctly. I'd heavily recommend that you don't use this setting in shipping apps!
Sends a file to the Trash.
The path to the file you want to send to the Trash.
A pointer to a nil string instance.
Returns YES if the file was successfully sent to the Trash, otherwise returns NO and sets an error message in errorString.
The errorString parameter must be a nil string pointer. If an error occurs, it will hold an error message. You are responsible for releasing the string if it's not nil on return.
Informs the delegate when the login item check finishes.
If you're interested in knowing when the login items check finishes, you can implement this method in your delegate. This can be useful if, for instance, you have a number of dialogs that are being shown to the user during the first run. You can use this to know when the check has completed and you can move on to the next dialog you wish to show.
Informs the delegate when the copy check finishes.
If you're interested in knowing when the copy check finishes, you can implement this method in your delegate. This can be useful if, for instance, you have a number of dialogs that are being opened up during the first run. You can use this to know when the copy window has closed and you can move on to the next dialog you wish to show.
Just to note, if you are also checking to add yourself to the login items you may wish to implement that method rather than this one, unless you have some specific reason for knowing when the copy check finishes.
If the user chooses to copy your application somewhere, this delegate method is not called (at least, not from the instance of your app that is about to terminate).
Informs the delegate that the new copy failed to launch.
The path to the new copy of the application.
If you would like the application to terminate anyway, return YES, otherwise return NO.
This informs the delegate that the new copy at copyPath failed to launch. You can use this to try to launch the new copy again if you wish, or to display an error message to the user.
If you return NO, things will proceed as if the user had never been asked if they'd like to copy.
Asks the delegate if the old copy should be placed in the Trash.
The path to the current application path.
If you wish for the current application to be sent to the Trash after it has been copied, return YES, otherwise return NO.
This method will only be called if the current application is running from the user's download folder.
Lets the delegate customize the copy dialog shown to the user.
The window that will be displayed to the user before any customizations are done to it.
You can either customize the passed in window and return it, or you can replace it and return your own custom window.
You can use this method to customize the look of the copy dialog to match your application if you wish. For instance, if you are using custom windows in your application, you can use this method to substitute a custom window for the default one. Or you could change the default text show to the user. Or… well, you get the point.
If you replace the default window, and don't copy over the default window's subviews, then it becomes your responsibility to set the copy path.
Note for anyone replacing the window with one of their own — the action methods call —close on the window, so if you need to keep the window around for any reason, you'll need to keep this in mind.
Lets the delegate customize the add as login item dialog shown to the user.
The window that will be displayed to the user before any customizations are done to it.
You can either customize the passed in window and return it, or you can replace it and return your own custom window.
You can use this method to customize the look of the login items dialog to match your application if you wish. For instance, if you are using custom windows in your application, you can use this method to substitute a custom window for the default one. Or you could change the default text shown to the user. Or… well, you get the point.
Note for anyone replacing the window with one of their own — the action methods call —close on the window, so if you need to keep the window around for any reason, you'll need to keep this in mind.
Informs the delegate that System Events could not be launched.
This message will (hopefully) never be sent, but if it is it means that none of the login item methods will work, as they all call on System Events.
Informs the delegate that the file could not be sent to the Trash.
The path to the file that failed to be Trashed.
You could get this message if the file at filePath is on a mounted file system that doesn't support sending files to the Trash. You can use this method to delete the file directly if you wish to.
Informs the delgate that a login entry may be an old entry for the current application.
The path to where the login item is located.
If you wish for the login entry to be removed, return YES, otherwise return NO to leave it intact.
It is up to you to decide if the file the passed in path points to is indeed a copy of your application or a different one. Note that the path may point to nothing at all, in which case it is probably safe to remove from the login items.