// // P4FileItem.m // Perforce // // Created by Adam Czubernat on 29.05.2013. // Copyright (c) 2013 Perforce Software, Inc. All rights reserved. // #import "P4FileItem.h" #import "PSFileEvents.h" @interface P4FileItem () { NSColor *overlay; } - (id)initWithURL:(NSURL *)url parentItem:(P4Item *)parent; - (void)loadMetadata:(NSDictionary *)metadata; - (void)propagatePathChange:(NSString *)path; @end @implementation P4FileItem #pragma mark - P4Item Override - (id)init { NSURL *url = [NSURL fileURLWithPath:[P4Workspace sharedInstance].root]; self = [self initWithURL:url parentItem:nil]; if (self) { flags.directory = YES; name = @"Workspace"; remotePath = @"//"; [[P4Workspace sharedInstance] addObserver:self]; } return self; } - (NSColor *)overlay { return overlay; } - (BOOL)isEditable { return YES; } - (id)defaultAction { if (!flags.tracked) return nil; if (flags.directory) { return [P4ItemAction actionForItem:self name:@"Check out all files" selector:@selector(checkout)]; } else { return [P4ItemAction actionForItem:self name:@"Open and Checkout" selector:@selector(openWithCheckout)]; } } - (NSArray *)actions { NSMutableArray *actions = (NSMutableArray *)[super actions]; // Override actions return actions; } - (void)loadPath:(NSString *)preloadPath { NSAssert([preloadPath hasSuffix:@"/"], @"Loading path without trailing slash"); NSString *rootPath = [[P4Workspace sharedInstance] root]; NSString *client = [[P4Workspace sharedInstance] workspace]; NSString *clientPrefix = [NSString stringWithFormat:@"//%@/", client]; NSMutableArray *items = [NSMutableArray array]; children = nil; flags.loading = YES; NSString *relative = [preloadPath stringByRemovingPrefix:localPath]; relative = [relative stringByRemovingSuffix:@"/"]; NSString *subPath = localPath; NSMutableArray *subPaths = [NSMutableArray arrayWithObject:subPath]; for (NSString *component in [relative pathComponents]) { subPath = [subPath stringByAppendingFormat:@"%@/", component]; [subPaths addObject:subPath]; } for (NSString *subPath in subPaths) { __block NSError *enumeratorError = nil; NSDirectoryEnumerator *enumerator; enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:subPath] includingPropertiesForKeys:@[ NSURLIsDirectoryKey, NSURLNameKey, NSURLLabelColorKey ] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsPackageDescendants | NSDirectoryEnumerationSkipsHiddenFiles) errorHandler:^(NSURL *url, NSError *error) { PSLog(@"File enumeration error: %@", error.localizedDescription); enumeratorError = error; return YES; }]; P4FileItem *subPathParent = self; for (P4FileItem *item in items.reverseObjectEnumerator) { if ([item.localPath isEqualToString:subPath]) { subPathParent = item; break; } } NSMutableArray *subPathChildren = [NSMutableArray array]; for (NSURL *url in enumerator) { P4FileItem *item = [[P4FileItem alloc] initWithURL:url parentItem:subPathParent]; item->flags.loading = YES; [subPathChildren addObject:item]; } if (enumeratorError) { [self failWithError:enumeratorError]; children = nil; return; } [items addObjectsFromArray:subPathChildren]; subPathParent->children = subPathChildren; [subPathParent sortChildren]; } [[P4Workspace sharedInstance] listFiles:subPaths response:^(P4Operation *operation, NSArray *response) { if (operation.errors && ([self failWithError:operation.error], 1)) return; NSMutableArray *remotePaths = [NSMutableArray array]; NSMutableDictionary *records = [NSMutableDictionary dictionary]; for (NSDictionary *record in response) { NSString *remote = [record objectForKey:@"depotFile"]; NSString *local = [record objectForKey:@"clientFile"]; if (!local) { NSString *clientPath = [record objectForKey:@"dir"]; NSString *clientRelative = [clientPath stringByRemovingPrefix:clientPrefix]; local = [NSString stringWithFormat:@"%@%@/", rootPath, clientRelative]; } [records setObject:record forKey:local]; if (remote) [remotePaths addObject:remote]; } NSArray *unread = nil; NSArray *shelved = nil; // Get shelved files list if (remotePaths.count) shelved = [[P4Workspace sharedInstance] shelvedForPaths:remotePaths]; // Get unread files list if (records.count) unread = [[P4Workspace sharedInstance] unreadForPaths:[records allKeys]]; for (P4FileItem *item in items) { NSDictionary *record = [records objectForKey:item->localPath]; [item loadMetadata:record]; item->flags.loading = NO; item->flags.shelved = [shelved containsObject:item->remotePath]; item->flags.unread = [unread containsObject:item->localPath]; } [self finishLoading]; }]; } #pragma mark - Private - (id)initWithURL:(NSURL *)url parentItem:(P4Item *)parentItem { if (self = [super init]) { parent = parentItem; // Get directory info NSNumber *dir = nil; [url getResourceValue:&dir forKey:NSURLIsDirectoryKey error:NULL]; flags.directory = dir.boolValue; NSString *resourceName; [url getResourceValue:&resourceName forKey:NSURLNameKey error:NULL]; name = resourceName; NSColor *color; [url getResourceValue:&color forKey:NSURLLabelColorKey error:NULL]; overlay = color; localPath = dir.boolValue ? [url.path stringByAppendingString:@"/"] : url.path; } return self; } - (void)loadMetadata:(NSDictionary *)metadataDictionary { metadata = metadataDictionary; [self refreshTags]; remotePath = nil; status = nil; flags.tracked = NO; if (!metadata) return; if (flags.directory) { NSString *client = [[P4Workspace sharedInstance] workspace]; NSString *clientPrefix = [NSString stringWithFormat:@"//%@/", client]; remotePath = [metadata objectForKey:@"dir"]; remotePath = [remotePath stringByRemovingPrefix:clientPrefix]; remotePath = [NSString stringWithFormat:@"//%@/", remotePath]; flags.tracked = remotePath != nil; } else { remotePath = [metadata objectForKey:@"depotFile"]; // Set metadata status = [metadata objectForKey:@"action"]; NSString *user = [metadata objectForKey:@"otherOpen0"]; NSDictionary *info = [[P4Workspace sharedInstance] userInfo:user]; statusOwner = [info objectForKey:@"Email"] ?: user; flags.tracked = [metadata objectForKey:@"isMapped"] != nil; } } - (void)propagatePathChange:(NSString *)newPath { if ([newPath isEqualToString:localPath]) return; name = newPath.lastPathComponent; localPath = newPath; if (![self isDirectory]) return; // Traversing NSMutableArray *items = [NSMutableArray array]; if (children.count) [items addObjectsFromArray:children]; int count = 0; while (items.count) { P4FileItem *item = [items lastObject]; [items removeLastObject]; item->localPath = [item.parent->localPath stringByAppendingPath:item->name]; if (item->flags.directory) [item->localPath stringByAppendingString:@"/"]; PSLog(@"%@", item->localPath); if (item->children.count) [items addObjectsFromArray:item->children]; count++; } } #pragma mark P4Workspace Events - (void)fileCreated:(NSString *)filePath { P4Item *item = [self cachedItemForPath:filePath]; if (item) { PSLog(@"WARNING! Creating duplicate %@", filePath); [self fileModified:filePath]; return; } NSString *parentPath = [filePath stringByDeletingPath]; P4Item *parentItem = [self cachedItemForPath:parentPath]; if (!parentItem || !parentItem->children) // Not loaded yet return; // Update children by adding new item P4Item *newItem = [[P4FileItem alloc] initWithURL:[NSURL fileURLWithPath:filePath] parentItem:parentItem]; // Add item to parent NSMutableArray *array = [NSMutableArray arrayWithArray:parentItem->children]; [array addObject:newItem]; // Sort files alphanumerically parentItem->children = array; [parentItem sortChildren]; [parentItem finishLoading]; } - (void)fileRemoved:(NSString *)filePath { P4Item *item = [self cachedItemForPath:filePath]; P4Item *parentItem = (P4Item *)item.parent; if (item.isUnread) [item markAsRead]; if (!parentItem || !parentItem->children || !item) return; NSMutableArray *array = [NSMutableArray arrayWithArray:parentItem->children]; [array removeObject:item]; parentItem->children = array; [parentItem finishLoading]; [item invalidate]; } - (void)fileMoved:(NSString *)oldPath toPath:(NSString *)newPath { P4Item *oldItem = [self cachedItemForPath:oldPath]; P4Item *oldParentItem = (P4Item *)oldItem.parent; if (oldItem.isUnread) [oldItem markAsRead]; // Remove old item from displaying if (oldParentItem && oldItem) { NSMutableArray *array = [NSMutableArray arrayWithArray:oldParentItem->children]; [array removeObject:oldItem]; oldParentItem->children = array; } [oldParentItem finishLoading]; [oldItem invalidate]; // Create new item for display NSString *newParentPath = [newPath stringByDeletingPath]; P4Item *newParentItem = [self cachedItemForPath:newParentPath]; if (!newParentItem || !newParentItem->children) return; P4Item *newItem = [[P4FileItem alloc] initWithURL:[NSURL fileURLWithPath:newPath] parentItem:newParentItem]; // Add item to parent NSMutableArray *array = [NSMutableArray arrayWithArray:newParentItem->children]; [array addObject:newItem]; // Sort files alphanumerically newParentItem->children = array; [newParentItem sortChildren]; [newParentItem finishLoading]; } - (void)fileRenamed:(NSString *)oldPath toPath:(NSString *)newPath { P4Item *oldItem = [self cachedItemForPath:oldPath]; if (oldItem.isUnread) [oldItem markAsRead]; [(P4FileItem *)oldItem propagatePathChange:newPath]; [oldItem finishLoading]; } - (void)fileModified:(NSString *)path { P4FileItem *item = (P4FileItem *)[self cachedItemForPath:path]; if (!item) return; // Update overlay NSURL *url = [NSURL fileURLWithPath:path]; NSColor *color; [url getResourceValue:&color forKey:NSURLLabelColorKey error:NULL]; item->overlay = color; [item finishLoading]; } - (void)file:(NSString *)filePath updated:(NSDictionary *)info { BOOL remote = [filePath hasPrefix:@"//"]; NSString *parentPath = remote ? [remotePath substringFromIndex:1] : localPath; if ([filePath hasSuffix:@"/"]) filePath = [filePath substringToIndex:filePath.length-1]; if ([filePath isEqualToString:parentPath]) return; NSString *relative = [filePath substringFromIndex:parentPath.length]; NSArray *components = [relative pathComponents]; // Traversing P4Item *item = self; for (NSString *component in components) { if (!item->children.count) // Not loaded return; for (P4Item *child in item->children) { if ([child->name isEqualToString:component]) { item = child; if (item.isDirectory && !item.isUnread) { if (!item->flags.tracked) { // Directory wasn't tracked NSString *relativePath = [filePath stringByRemovingPrefix:item.localPath]; NSString *depotPath = [info objectForKey:@"depotFile"]; depotPath = [depotPath stringByRemovingSuffix:relativePath]; item->remotePath = depotPath; item->flags.tracked = depotPath.length > 0; } // Mark as unread item->flags.unread = YES; [item finishLoading]; } break; } } } NSString *itemPath = remote ? item->remotePath : item->localPath; if ([itemPath isEqualToString:filePath]) { if (!item->metadata) { item->remotePath = [info objectForKey:@"depotFile"]; item->metadata = info; [item refreshTags]; } item->flags.tracked = YES; item->flags.unread = YES; [item finishLoading]; } } @end