aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPFavoritesController.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/SPFavoritesController.m')
-rw-r--r--Source/SPFavoritesController.m340
1 files changed, 283 insertions, 57 deletions
diff --git a/Source/SPFavoritesController.m b/Source/SPFavoritesController.m
index 285cbd90..2d711c4b 100644
--- a/Source/SPFavoritesController.m
+++ b/Source/SPFavoritesController.m
@@ -24,18 +24,27 @@
// More info at <http://code.google.com/p/sequel-pro/>
#import "SPFavoritesController.h"
+#import "SPFavoriteNode.h"
+#import "SPGroupNode.h"
+#import "pthread.h"
static SPFavoritesController *sharedFavoritesController = nil;
@interface SPFavoritesController (PrivateAPI)
- (void)_loadFavorites;
+- (void)_constructFavoritesTree;
+- (void)_saveFavoritesDataInBackground:(NSDictionary *)data;
+- (void)_addNode:(SPTreeNode *)node asChildOfNode:(SPTreeNode *)parent;
+
+- (SPTreeNode *)_constructBranchForNodeData:(NSDictionary *)nodeData;
@end
@implementation SPFavoritesController
-@synthesize favorites;
+@synthesize favoritesTree;
+@synthesize favoritesData;
#pragma mark -
#pragma mark Initialisation
@@ -51,19 +60,25 @@ static SPFavoritesController *sharedFavoritesController = nil;
{
if ((self = [super init])) {
- favorites = nil;
+ favoritesTree = nil;
+ favoritesData = nil;
+
+ pthread_mutex_init(&writeLock, NULL);
+ pthread_mutex_init(&favoritesLock, NULL);
[self _loadFavorites];
+ [self _constructFavoritesTree];
}
return self;
}
#pragma mark -
-#pragma mark Public API
/**
* Returns the shared favorites controller.
+ *
+ * @return The shared controller instance.
*/
+ (SPFavoritesController *)sharedFavoritesController
{
@@ -76,6 +91,9 @@ static SPFavoritesController *sharedFavoritesController = nil;
return sharedFavoritesController;
}
+#pragma mark -
+#pragma mark Favorites data handling
+
/**
* Saves the current favorites dictionary in memory to disk. Note that the current favorites data file is moved
* rather than overwritten in the event that we can't write the new file, the original can simply be restored.
@@ -83,129 +101,333 @@ static SPFavoritesController *sharedFavoritesController = nil;
*/
- (void)saveFavorites
{
- NSError *error = nil;
- NSString *errorString = nil;
+ pthread_mutex_lock(&favoritesLock);
+
+ [NSThread detachNewThreadSelector:@selector(_saveFavoritesDataInBackground:) toTarget:self withObject:[[[favoritesTree childNodes] objectAtIndex:0] dictionaryRepresentation]];
+
+ pthread_mutex_unlock(&favoritesLock);
+}
+
+/**
+ * Reloads the favorites data from disk with the option to save before doing so.
+ *
+ * @param save Indicates whether the current favorites data in memory should be saved to disk before being
+ * reloaded. Specifying NO effectively discards any changes since the last save operation.
+ */
+- (void)reloadFavoritesWithSave:(BOOL)save
+{
+ if (save) [self saveFavorites];
+
+ if (favoritesData) {
+ [self _loadFavorites];
+ [self _constructFavoritesTree];
+ }
+}
+
+#pragma mark -
+#pragma mark Favorites interaction
+
+/**
+ * Adds a new group node with the supplied name to the children of the supplied parent node.
+ *
+ * @param name The name of the new group
+ * @param parent
+ *
+ * @return The node instance that was created and added
+ */
+- (SPTreeNode *)addGroupNodeWithName:(NSString *)name asChildOfNode:(SPTreeNode *)parent
+{
+ SPTreeNode *node = [SPTreeNode treeNodeWithRepresentedObject:[SPGroupNode groupNodeWithName:name]];
+
+ [node setIsGroup:YES];
+
+ [self _addNode:node asChildOfNode:parent];
+
+ return [node autorelease];
+}
+
+/**
+ * Adds a new favorite node with the supplied data to the children of the supplied parent node.
+ *
+ * @param data The data for the new favorite
+ * @param
+ *
+ * @return The node instance that was created and added
+ */
+- (SPTreeNode *)addFavoriteNodeWithData:(NSDictionary *)data asChildOfNode:(SPTreeNode *)parent
+{
+ SPTreeNode *node = [SPTreeNode treeNodeWithRepresentedObject:[SPFavoriteNode favoriteNodeWithDictionary:data]];
+
+ [self _addNode:node asChildOfNode:parent];
+ return [node autorelease];
+}
+
+/**
+ * Removes the supplied favorite node by asking the root node to remove it from it's children (i.e. the
+ * entire tree is searched.
+ *
+ * @param The node to be removed
+ */
+- (void)removeFavoriteNode:(SPTreeNode *)node
+{
+ [favoritesTree removeObjectFromChildren:node];
+
+ // Save data to disk
+ [self saveFavorites];
+}
+
+#pragma mark -
+#pragma mark Private API
+
+/**
+ * Attempts to load the users connection favorites from ~/Library/Application Support/Sequel Pro/Data/Favorites.plist
+ * If the 'Data' directory doesn't already exist it will be created, as well as an empty favorites plist.
+ */
+- (void)_loadFavorites
+{
+ pthread_mutex_lock(&favoritesLock);
+
+ NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
+ if (favoritesData) [favoritesData release], favoritesData = nil;
+
NSString *dataPath = [fileManager applicationSupportDirectoryForSubDirectory:SPDataSupportFolder error:&error];
if (error) {
NSLog(@"Error retrieving data directory path: %@", [error localizedDescription]);
+
+ pthread_mutex_unlock(&favoritesLock);
+
return;
}
NSString *favoritesFile = [dataPath stringByAppendingPathComponent:SPFavoritesDataFile];
- NSString *favoritesBackupFile = [dataPath stringByAppendingPathComponent:[@"~" stringByAppendingString:SPFavoritesDataFile]];
- // If the favorites data file already exists, attempt to move it to keep as a backup
+ // If the favorites data file already exists use it, otherwise create an empty one
if ([fileManager fileExistsAtPath:favoritesFile]) {
- [fileManager moveItemAtPath:favoritesFile toPath:favoritesBackupFile error:&error];
+ favoritesData = [[NSDictionary alloc] initWithContentsOfFile:favoritesFile];
}
-
- if (error) {
- NSLog(@"Unable to backup (move) existing favorites data file during save. Deleting instead: %@", [error localizedDescription]);
+ else {
+ NSDictionary *newFavorites = [NSMutableDictionary dictionaryWithObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Favorites", @"favorites label"), SPFavoritesGroupNameKey, [NSArray array], SPFavoriteChildrenKey, nil] forKey:SPFavoritesRootKey];
- error = nil;
+ NSError *error = nil;
+ NSString *errorString = nil;
- // We can't move it so try and delete it
- if (![fileManager removeItemAtPath:favoritesFile error:&error] && error) {
- NSLog(@"Unable to delete existing favorites data file during save. Something is wrong, permissions perhaps: %@", [error localizedDescription]);
- return;
- }
- }
- else {
- NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:favorites
+ NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:newFavorites
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorString];
-
if (plistData) {
[plistData writeToFile:favoritesFile options:NSAtomicWrite error:&error];
if (error) {
- NSLog(@"Error writing favorites data. Restoring backup if available: %@", [error localizedDescription]);
-
- // Restore the original data file
- [fileManager moveItemAtPath:favoritesBackupFile toPath:favoritesFile error:NULL];
- }
- else {
- // Remove the original backup
- [fileManager removeItemAtPath:favoritesBackupFile error:NULL];
+ NSLog(@"Error writing default favorites data: %@", [error localizedDescription]);
}
}
else if (errorString) {
- NSLog(@"Error converting favorites data to plist format: %@", errorString);
+ NSLog(@"Error converting default favorites data to plist format: %@", errorString);
[errorString release];
+
+ pthread_mutex_unlock(&favoritesLock);
+
+ return;
}
+
+ favoritesData = newFavorites;
}
+
+ pthread_mutex_unlock(&favoritesLock);
}
/**
- * Reloads the favorites data from disk with the option to save before doing so.
- *
- * @param save Indicates whether the current favorites data in memory should be saved to disk before being
- * reloaded. Specifying NO effectively discards any changes since the last save operation.
+ * Constructs the favorites tree by initialising an instance of SPFavoriteNode for every favorite and group.
*/
-- (void)reloadFavoritesWithSave:(BOOL)save
+- (void)_constructFavoritesTree
{
- if (save) [self saveFavorites];
+ pthread_mutex_lock(&favoritesLock);
+
+ if (!favoritesData) {
+ pthread_mutex_unlock(&favoritesLock);
+ return;
+ }
+
+ NSDictionary *root = [favoritesData objectForKey:SPFavoritesRootKey];
+
+ SPGroupNode *rootGroupNode = [[SPGroupNode alloc] init];
+ SPGroupNode *favoritesGroupNode = [[SPGroupNode alloc] initWithName:[[root objectForKey:SPFavoritesGroupNameKey] uppercaseString]];
+
+ SPTreeNode *rootNode = [[SPTreeNode alloc] initWithRepresentedObject:rootGroupNode];
+ SPTreeNode *favoritesNode = [[SPTreeNode alloc] initWithRepresentedObject:favoritesGroupNode];
+
+ [rootNode setIsGroup:YES];
+ [favoritesNode setIsGroup:YES];
+
+ for (NSDictionary *favorite in [root objectForKey:SPFavoriteChildrenKey])
+ {
+ SPTreeNode *node = [self _constructBranchForNodeData:favorite];
+
+ [[favoritesNode mutableChildNodes] addObject:node];
+
+ [node release];
+ }
+
+ [[rootNode mutableChildNodes] addObject:favoritesNode];
+
+ [rootGroupNode release];
+ [favoritesGroupNode release];
+ [favoritesNode release];
+
+ favoritesTree = rootNode;
- if (favorites) [self _loadFavorites];
+ pthread_mutex_unlock(&favoritesLock);
}
-#pragma mark -
-#pragma mark Private API
+/**
+ * Constructs the tree branch for the supplied favorites data. Note that depending on the contents of the
+ * branch (i.e. does it contain any groups and their depth) this method will recursively call itself.
+ *
+ * @param nodeData The favorites data dictionary
+ *
+ * @return The root node of the branch
+ */
+- (SPTreeNode *)_constructBranchForNodeData:(NSDictionary *)nodeData
+{
+ id node = nil;
+ SPTreeNode *treeNode = nil;
+
+ if ([nodeData objectForKey:SPFavoritesGroupNameKey] && [nodeData objectForKey:SPFavoriteChildrenKey]) {
+
+ node = [[SPGroupNode alloc] initWithName:[nodeData objectForKey:SPFavoritesGroupNameKey]];
+
+ treeNode = [[SPTreeNode alloc] initWithRepresentedObject:node];
+
+ [treeNode setIsGroup:YES];
+
+ for (NSDictionary *favorite in [nodeData objectForKey:SPFavoriteChildrenKey])
+ {
+ SPTreeNode *innerNode = [self _constructBranchForNodeData:favorite];
+
+ [innerNode setIsGroup:YES];
+
+ [[treeNode mutableChildNodes] addObject:innerNode];
+
+ [innerNode release];
+ }
+ }
+ else {
+ node = [[SPFavoriteNode alloc] initWithDictionary:nodeData];
+
+ treeNode = [[SPTreeNode alloc] initWithRepresentedObject:node];
+ }
+
+ return treeNode;
+}
/**
- * Attempts to load the users connection favorites from ~/Library/Application Support/Sequel Pro/Data/Favorites.plist
- * If the 'Data' directory doesn't already exist it will be created, as well as an empty favorites plist.
+ * Saves the supplied favorites data to disk on a background thread.
+ *
+ * @param data The raw plist data (serialized NSDictionary) to be saved
*/
-- (void)_loadFavorites
+- (void)_saveFavoritesDataInBackground:(NSDictionary *)data
{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ pthread_mutex_lock(&writeLock);
+
+ if (!favoritesTree) {
+ pthread_mutex_unlock(&writeLock);
+ return;
+ }
+
NSError *error = nil;
- NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *errorString = nil;
- if (favorites) [favorites release], favorites = nil;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *dataPath = [fileManager applicationSupportDirectoryForSubDirectory:SPDataSupportFolder error:&error];
if (error) {
NSLog(@"Error retrieving data directory path: %@", [error localizedDescription]);
+
+ pthread_mutex_unlock(&writeLock);
return;
}
NSString *favoritesFile = [dataPath stringByAppendingPathComponent:SPFavoritesDataFile];
+ NSString *favoritesBackupFile = [dataPath stringByAppendingPathComponent:[@"~" stringByAppendingString:SPFavoritesDataFile]];
- // If the favorites data file already exists use it, otherwise create an empty one
+ // If the favorites data file already exists, attempt to move it to keep as a backup
if ([fileManager fileExistsAtPath:favoritesFile]) {
- favorites = [[NSDictionary alloc] initWithContentsOfFile:favoritesFile];
+ [fileManager moveItemAtPath:favoritesFile toPath:favoritesBackupFile error:&error];
}
- else {
- NSDictionary *newFavorites = [NSMutableDictionary dictionaryWithObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Favorites", @"favorites label"), SPFavoritesGroupNameKey, [NSArray array], SPFavoriteChildrenKey, nil] forKey:SPFavoritesRootKey];
+
+ if (error) {
+ NSLog(@"Unable to backup (move) existing favorites data file during save. Deleting instead: %@", [error localizedDescription]);
- NSError *error = nil;
- NSString *errorString = nil;
+ error = nil;
- NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:newFavorites
+ // We can't move it so try and delete it
+ if (![fileManager removeItemAtPath:favoritesFile error:&error] && error) {
+ NSLog(@"Unable to delete existing favorites data file during save. Something is wrong, permissions perhaps: %@", [error localizedDescription]);
+
+ pthread_mutex_unlock(&writeLock);
+ return;
+ }
+ }
+ else {
+ NSDictionary *dictionary = [NSDictionary dictionaryWithObject:data forKey:SPFavoritesRootKey];
+
+ // Convert the current favorites tree to a dictionary representation to create the plist data
+ NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:dictionary
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorString];
+
if (plistData) {
[plistData writeToFile:favoritesFile options:NSAtomicWrite error:&error];
if (error) {
- NSLog(@"Error writing default favorites data: %@", [error localizedDescription]);
+ NSLog(@"Error writing favorites data. Restoring backup if available: %@", [error localizedDescription]);
+
+ // Restore the original data file
+ [fileManager moveItemAtPath:favoritesBackupFile toPath:favoritesFile error:NULL];
+ }
+ else {
+ // Remove the original backup
+ [fileManager removeItemAtPath:favoritesBackupFile error:NULL];
}
}
else if (errorString) {
- NSLog(@"Error converting default favorites data to plist format: %@", errorString);
+ NSLog(@"Error converting favorites data to plist format: %@", errorString);
[errorString release];
- return;
+
+ [fileManager removeItemAtPath:favoritesBackupFile error:NULL];
}
-
- favorites = newFavorites;
}
+
+ pthread_mutex_unlock(&writeLock);
+
+ [pool release];
+}
+
+/**
+ * Adds the supplied node to the children of the supplied parent and saves the tree to disk.
+ *
+ * @param node The node to be added
+ * @param asChild
+ */
+- (void)_addNode:(SPTreeNode *)node asChildOfNode:(SPTreeNode *)parent
+{
+ if (parent) {
+ [[parent mutableChildNodes] addObject:node];
+ }
+ else {
+ [[[[favoritesTree mutableChildNodes] objectAtIndex:0] mutableChildNodes] addObject:node];
+ }
+
+ [self saveFavorites];
}
#pragma mark -
@@ -215,7 +437,11 @@ static SPFavoritesController *sharedFavoritesController = nil;
*/
- (void)dealloc
{
- if (favorites) [favorites release], favorites = nil;
+ if (favoritesTree) [favoritesTree release], favoritesTree = nil;
+ if (favoritesData) [favoritesData release], favoritesData = nil;
+
+ pthread_mutex_destroy(&writeLock);
+ pthread_mutex_destroy(&favoritesLock);
[super dealloc];
}