// // PSFileEvents.m // Perforce // // Created by Adam Czubernat on 03.07.2013. // Copyright (c) 2013 Perforce Software, Inc. All rights reserved. // #import "PSFileEvents.h" //#define LOG_EVENTS static float kFSEventStreamLatency = 0.25; static float kFSEventFlushInterval = 1.0; NSString * const kFSEventCreated = @"kFSEventCreated"; NSString * const kFSEventRemoved = @"kFSEventRemoved"; NSString * const kFSEventModified = @"kFSEventModified"; void eventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]); void eventStreamDescription(char *path, FSEventStreamEventFlags flags, FSEventStreamEventId eventId); @interface PSFileEventsTimer : NSObject @property (nonatomic, retain) NSTimer *timer; @property (nonatomic, weak) PSFileEvents *fileEvents; - (void)start; - (void)invalidate; - (void)action; @end @interface PSFileEvents () { FSEventStreamRef eventStream; NSMutableDictionary *queue; PSFileEventsTimer *queueTimer; } @property (nonatomic, assign) NSUInteger eventId; @property (nonatomic, assign) BOOL historyDone; - (void)pathCreated:(NSString *)path; - (void)pathRemoved:(NSString *)path; - (void)pathMoved:(NSString *)path toPath:(NSString *)newPath; - (void)pathModified:(NSString *)path; @end @implementation PSFileEvents @synthesize delegate, eventId, historyDone; - (id)initWithRoot:(NSString *)path applicationEvents:(BOOL)watch eventId:(NSUInteger)lastEventId { if (self = [super init]) { NSAssert(PSInstanceCount([self class]) < 2, @"Should be one instance"); PSInstanceCreated([self class]); queue = [NSMutableDictionary dictionaryWithCapacity:512]; queueTimer = [[PSFileEventsTimer alloc] init]; queueTimer.fileEvents = self; [queueTimer start]; FSEventStreamContext context; context.version = 0; context.info = (__bridge void *)self; context.retain = NULL; context.release = NULL; context.copyDescription = NULL; FSEventStreamCreateFlags flags = (kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer); if (!watch) flags |= kFSEventStreamCreateFlagIgnoreSelf; eventId = lastEventId ?: FSEventsGetCurrentEventId(); eventStream = FSEventStreamCreate(NULL, &eventStreamCallback, &context, (__bridge CFArrayRef)@[ path ], eventId, kFSEventStreamLatency, flags); FSEventStreamScheduleWithRunLoop(eventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); FSEventStreamStart(eventStream); } return self; } - (void)dealloc { if (eventStream) { FSEventStreamStop(eventStream); FSEventStreamUnscheduleFromRunLoop(eventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); FSEventStreamInvalidate(eventStream); FSEventStreamRelease(eventStream); eventStream = NULL; } [queueTimer invalidate]; queueTimer = nil; PSInstanceDeallocated([self class]); } #pragma mark - Public - (void)flush { if (!queue.count) return; // PSLog(@"Flushing... %@", [NSDate date]); NSDictionary *dict = queue; queue = [NSMutableDictionary dictionaryWithCapacity:512]; NSMutableArray *created = [NSMutableArray arrayWithCapacity:512]; NSMutableArray *removed = created.mutableCopy; NSMutableArray *modified = created.mutableCopy; NSMutableArray *movedFrom = created.mutableCopy; NSMutableArray *movedTo = created.mutableCopy; NSMutableArray *renamedFrom = created.mutableCopy; NSMutableArray *renamedTo = created.mutableCopy; // Notify delegate [dict enumerateKeysAndObjectsUsingBlock:^(NSString *path, id event, BOOL *stop) { if (event == kFSEventCreated) { [created addObject:path]; } else if (event == kFSEventRemoved) { [removed addObject:path]; } else if (event == kFSEventModified) { [modified addObject:path]; } else { NSString *newDir = [path stringByDeletingLastPathComponent]; NSString *oldDir = [event stringByDeletingLastPathComponent]; if ([newDir isEqualToString:oldDir]) { [renamedFrom addObject:path]; [renamedTo addObject:event]; } else { [movedFrom addObject:path]; [movedTo addObject:event]; } } }]; // Notify delegate if ([delegate respondsToSelector:@selector(fileEventsWillChange:)]) [delegate fileEventsWillChange:self]; if (created.count && [delegate respondsToSelector:@selector(fileEvents:created:)]) [delegate fileEvents:self created:created]; if (removed.count && [delegate respondsToSelector:@selector(fileEvents:removed:)]) [delegate fileEvents:self removed:removed]; if (modified.count && [delegate respondsToSelector:@selector(fileEvents:modified:)]) [delegate fileEvents:self modified:modified]; if (renamedTo.count && [delegate respondsToSelector:@selector(fileEvents:renamed:to:)]) [delegate fileEvents:self renamed:renamedFrom to:renamedTo]; if (movedTo.count && [delegate respondsToSelector:@selector(fileEvents:moved:to:)]) [delegate fileEvents:self moved:movedFrom to:movedTo]; if ([delegate respondsToSelector:@selector(fileEventsDidChange:)]) [delegate fileEventsDidChange:self]; } - (void)pathCreated:(NSString *)path { id event = [queue objectForKey:path]; if (!event) [queue setObject:kFSEventCreated forKey:path]; else if (event == kFSEventRemoved) [queue setObject:kFSEventModified forKey:path]; } - (void)pathRemoved:(NSString *)path { id event = [queue objectForKey:path]; if (event == kFSEventCreated) [queue removeObjectForKey:path]; else [queue setObject:kFSEventRemoved forKey:path]; } - (void)pathMoved:(NSString *)path toPath:(NSString *)newPath { [queue setObject:newPath forKey:path]; } - (void)pathModified:(NSString *)path { id event = [queue objectForKey:path]; if (!event) [queue setObject:kFSEventModified forKey:path]; else if (event == kFSEventRemoved) [queue setObject:kFSEventModified forKey:path]; } @end #pragma mark - PSFileEventsTimer @implementation PSFileEventsTimer @synthesize timer, fileEvents; - (void)start { timer = [NSTimer scheduledTimerWithTimeInterval:kFSEventFlushInterval target:self selector:@selector(action) userInfo:nil repeats:YES]; } - (void)invalidate { [timer invalidate]; timer = nil; } - (void)action { [fileEvents flush]; } @end #pragma mark - C callbacks void eventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { FSEventStreamStop((FSEventStreamRef)streamRef); // Stop clears previous flags NSFileManager *filemanager = [NSFileManager defaultManager]; PSFileEvents *fileEvents = (__bridge id)(clientCallBackInfo); FSEventStreamEventId renameEventId = 0; NSString *renamePath = nil; for (int i=0; i<numEvents; i++) { FSEventStreamEventFlags event = eventFlags[i]; FSEventStreamEventId eventId = eventIds[i]; char *eventPath = ((char **)eventPaths)[i]; // Ignore end of history events marker if (event & kFSEventStreamEventFlagHistoryDone) { fileEvents.historyDone = YES; continue; } // Don't track hidden files with dot prefix if (eventPath[0] == '.' || strstr(eventPath, "/.")) continue; eventStreamDescription(((char **)eventPaths)[i], eventFlags[i], eventIds[i]); size_t len = strlen(eventPath); // Use zero terminator as place for slash BOOL isDir = (event & kFSEventStreamEventFlagItemIsDir) != 0; if (isDir) eventPath[len++] = '/'; // Apend slash on directories NSString *path = [[NSString alloc] initWithBytes:eventPath length:len encoding:NSUTF8StringEncoding]; if (!path) continue; // Check if file exist NSURL *url = [NSURL fileURLWithPath:path isDirectory:isDir]; BOOL exists = [url checkResourceIsReachableAndReturnError:NULL]; // Check if hidden NSNumber *hidden = nil; [url getResourceValue:&hidden forKey:NSURLIsHiddenKey error:NULL]; if (hidden.boolValue) continue; if (!fileEvents.historyDone) { // History events can't be tracked perfectly [fileEvents pathModified:path]; } else if (event & kFSEventStreamEventFlagItemRenamed) { if (!renamePath) { // Store first event of rename renamePath = path; renameEventId = eventId; continue; } // Check if consecutive rename events (events inside root) if (eventId <= renameEventId+2) { [fileEvents pathMoved:renamePath toPath:path]; renamePath = nil; // Not consecutive (events outside root) } else { // Finish last rename event if ([filemanager fileExistsAtPath:renamePath]) [fileEvents pathCreated:renamePath]; else [fileEvents pathRemoved:renamePath]; // Store new rename event renamePath = path; renameEventId = eventId; } } else if (!exists) { [fileEvents pathRemoved:path]; } else if (event & kFSEventStreamEventFlagItemCreated && ~event & kFSEventStreamEventFlagItemRemoved) { [fileEvents pathCreated:path]; } else { [fileEvents pathModified:path]; } } if (renamePath) { // Complete last rename event if ([filemanager fileExistsAtPath:renamePath]) [fileEvents pathCreated:renamePath]; else [fileEvents pathRemoved:renamePath]; } fileEvents.eventId = eventIds[numEvents-1]; // Store last eventId FSEventStreamStart((FSEventStreamRef)streamRef); } void eventStreamDescription(char *path, FSEventStreamEventFlags flags, FSEventStreamEventId eventId) { #ifndef LOG_EVENTS return; #endif printf("# Event %lld %s\n# ", eventId, path); //#define FLAG_CHECK(x, y) if (((x) & (y)) == (y)) printf("%s ", #y); #define FLAG_CHECK(x, y, z) if (((x) & (y)) == (y)) printf("%s ", z); else printf(". "); FLAG_CHECK(flags, kFSEventStreamEventFlagMustScanSubDirs, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagUserDropped, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagKernelDropped, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagEventIdsWrapped, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagHistoryDone, "H"); FLAG_CHECK(flags, kFSEventStreamEventFlagRootChanged, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagMount, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagUnmount, "?"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemCreated, "C"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemRemoved, "X"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemInodeMetaMod, "I"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemRenamed, "R"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemModified, "M"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemFinderInfoMod, "F"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemChangeOwner, "O"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemXattrMod, "A"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemIsFile, "."); FLAG_CHECK(flags, kFSEventStreamEventFlagItemIsDir, "D"); FLAG_CHECK(flags, kFSEventStreamEventFlagItemIsSymlink, "S"); printf("\t%d\n", flags); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 15071 | alan_petersen |
Populate -o //guest/perforce_software/piper/... //guest/alan_petersen/piper/.... |
||
//guest/perforce_software/piper/mac/main/Perforce/Classes/PSFileEvents.m | |||||
#2 | 12961 | alan_petersen |
Piper 2.0 Mega Update New Features/Functionality - Added help menu redirecting to URL. - Added readonly property for creating new workspaces. - Added html hyperlinks for Copy link functionality. - Added functionality for managing Finder Favorite items in sidebar. - Redesigned the way mapping is stored in Piper. - First version of syncing finder sidebar items with workspace mapping. - Small sorting improvements. - Creating Projects directory inside users home folder. - Adding Projects folder to finder sidebar item. - Creating and removing symbolic links accordingly to mapped folders. - Preventing duplicate names in symbolic links. - Refreshing symbolic links on mapping change inside application. - Storing workspace and server details in p4 configuration for other applications to use. - Added contextual menu items for Finder integration. - Added services menu for Adobe Illustrator integration. - Keyboard shortcuts for Illustrator integration. - Code refactoring and fixes for mapping issues. - Added Finder functionality to edit all files in folder. - Added user friendly message when editing a file using Finder outside the workspace. - Implemented hidden automatic login when opening application using Finder integration. - Logging to file in ~/Library/Logs - Unified workspace and all files views to show both local and depot files and folders. - Removed my workspace view references and logic. - Editing unmapped files on server. - First version of adding file to unmapped folders. - Showing opened by and edit actions in column details for all depot files. - Improved mappings functionality. - Enabled same feature options for mapped and unmapped folders and files. - Redesigned from scratch mapping and unmapping procedures for adding and removing files. - Implemented cleaning workspace using new mapping functionality. Removed debug overlay coloring. - Automated workspace creation - Improvements in editing files already mapped to workspace. - Implemented deleting remote files. - Implemented first version of move operation for remote files. - Removing last workspace information when disconnecting from workspace using app menu. - Implemented editing and submitting using symbolic links in project folder. New finder menu service for symbolic links Show in Piper which acts like share link functionality. - New icons for files and folders not tracked in the filesystem. - Improvements in showing file using share link. - Switched to new way of retrieving files in order to show user changes. - Redesigned and implemented new functionality for chaining operations with mapping. - Improvements and redesign of Edit/add actions to use new chaining logic . Fixed issue with file edit. - Improvements in window showing when using services. - Simplified file loading so the local files appears only when remote are also loaded. - Improved deleting of untracked files to avoid mapping and marking for delete. - Enabling simple copy paste and moving of remote and local files. - Added abort for exception handling in order to force crashing application on critical failures - Added custom exception handling for catching runtime errors to log and crash instead of continuing in unstable state. - Changed file copying to use mark for add . - Simplified and fixed responding file representations to mapping changes. Bug Fixes - Fixed crash when synchronizing. - Fixed sync issue when downloading directory without file size information. - Fixed issue with unread list crashing when file is not existing on disk. - Fixed incorrect sync progress calculation. - Removed relative path issues. - Fixed many of case-sensitivity problems. - Fixed deprecated methods and related issues in OS X 10.10. - Fixed folder rename not updating in column view. Revised and fixed many potential problems from implicit casting. - Fixed missing sync button on fast sync completion. - Refreshing mapping on synchronization. Fixed symbolic links not appearing until app is restarted. - Fixed latest crashing of autosync. - Fixed loading indicator issues. - Fixed and redesigned submit dialog to work correctly with Submit All Files option in Finder. - Fixed multiple error messages on network outage. Redesigned showing errors in main window. - Fixed opening random locations when using Finder integration. - Fixed issue when panel was detached from parent window. - Fixed bug when creating new workspace wouldn't store default settings. - Fixed memory issues with network operations. - Fixes in relogging mappings and file listing. - Improvements in editing unmapped files. - Fixed crash when adding file outside workspace. - Fixed breadcrumbs control issue. - Fixed issue with double parent folders when opening unmapped files. - Fixed crashes on sync after mapping new files. - Fixed issue with editing file using Finder -- Merging code and additional fixes in add button functionality. - Fixed unsync not working - Fixed submit panel issue not selecting files with different name case. - Fixed missing revert and sync to workspace actions in some cases. - Fixed issue with Submit and Edit finder actions. Improvements in stability of finder integration. - Fixed issue with unsubmitted folders breaking status of files inside. - Fixed issue with added files not showing correct icon and status. - Fixed bug with file edit resulting in a new directory named exactly like a file. - Fixed issue with reloading of subpath resulting in untracked folders. - Fixed mapping issue when result was always view mapping not relative. - Fixed submit panel showing more than once. - Fixed illustrator services not working. - Fixed userdefaults preferences problem with workspace name being null. - Fixed userdefaults keypath problem of dot-containing workspace names. - Forcing recreating of browser to possibly prevent pre-10.10 errors with automatic workspace selection. - Fixed adding file to depot not presenting correct icon. - Fixed issues with reverting a file that was marked for add. - Presenting error when trying to submit untracked files. - Fixed issue when submit files service crashed when using unmapped files. - Fixed file representation disappearing when removing file. - Fixed issue with symlinks resolving working on 10.10 only. Issue related to workspace selection not showing. - Fixed error panel method calls unavailable in Mac OS versions before 10.10. Issue related to hanging error panels. - Fixed removing a local file resulting in action progress freezing. - Fixed open file not working after edit. - Fixing crash when mapping changed. Issue related to moving local file to unmapped folder and other similar cases. |
||
#1 | 11252 | alan_petersen | Rename/move file(s) | ||
//guest/perforce_software/piper/mac/Perforce/Classes/PSFileEvents.m | |||||
#1 | 10744 | alan_petersen | Rename/move file(s) | ||
//guest/perforce_software/piper/Perforce/Classes/PSFileEvents.m | |||||
#1 | 8919 | Matt Attaway | Initial add of Piper, a lightweight Perforce client for artists and designers. |