EE1D7C9616E0ECCF00B000EB /* NSError+Description.m in Sources */ = {isa = PBXBuildFile; fileRef = EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */; };
EE96929416E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m in Sources */ = {isa = PBXBuildFile; fileRef = EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */; };
EEAA9CE116E808E600256B64 /* NSMutableArray+MoveObject.m in Sources */ = {isa = PBXBuildFile; fileRef = EEAA9CE016E808E600256B64 /* NSMutableArray+MoveObject.m */; };
+ EEAA9CE416E816C800256B64 /* NSFileManager+UniqueNames.m in Sources */ = {isa = PBXBuildFile; fileRef = EEAA9CE316E816C800256B64 /* NSFileManager+UniqueNames.m */; };
+ EEAA9CE716E81C8400256B64 /* NSString+FixFilename.m in Sources */ = {isa = PBXBuildFile; fileRef = EEAA9CE616E81C8400256B64 /* NSString+FixFilename.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMenu+RepresentedObjectAccessors.m"; sourceTree = "<group>"; };
EEAA9CDF16E808E600256B64 /* NSMutableArray+MoveObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+MoveObject.h"; sourceTree = "<group>"; };
EEAA9CE016E808E600256B64 /* NSMutableArray+MoveObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+MoveObject.m"; sourceTree = "<group>"; };
+ EEAA9CE216E816C800256B64 /* NSFileManager+UniqueNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+UniqueNames.h"; sourceTree = "<group>"; };
+ EEAA9CE316E816C800256B64 /* NSFileManager+UniqueNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+UniqueNames.m"; sourceTree = "<group>"; };
+ EEAA9CE516E81C8400256B64 /* NSString+FixFilename.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+FixFilename.h"; sourceTree = "<group>"; };
+ EEAA9CE616E81C8400256B64 /* NSString+FixFilename.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+FixFilename.m"; sourceTree = "<group>"; };
EEF86B7316E2241000674B87 /* NJInputPathElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NJInputPathElement.h; sourceTree = "<group>"; };
EEF86B7416E298CD00674B87 /* NJEvents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NJEvents.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
EE1D5F8B16E403D600749C36 /* Categories */ = {
isa = PBXGroup;
children = (
+ EEAA9CE216E816C800256B64 /* NSFileManager+UniqueNames.h */,
+ EEAA9CE316E816C800256B64 /* NSFileManager+UniqueNames.m */,
EE1D7C9416E0ECCF00B000EB /* NSError+Description.h */,
EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */,
EE1D7C9016E01E7000B000EB /* NSView+FirstResponder.h */,
EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */,
EEAA9CDF16E808E600256B64 /* NSMutableArray+MoveObject.h */,
EEAA9CE016E808E600256B64 /* NSMutableArray+MoveObject.m */,
+ EEAA9CE516E81C8400256B64 /* NSString+FixFilename.h */,
+ EEAA9CE616E81C8400256B64 /* NSString+FixFilename.m */,
);
name = Categories;
sourceTree = "<group>";
EE1D7C9616E0ECCF00B000EB /* NSError+Description.m in Sources */,
EE96929416E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m in Sources */,
EEAA9CE116E808E600256B64 /* NSMutableArray+MoveObject.m in Sources */,
+ EEAA9CE416E816C800256B64 /* NSFileManager+UniqueNames.m in Sources */,
+ EEAA9CE716E81C8400256B64 /* NSString+FixFilename.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
#import "NSMenu+RepresentedObjectAccessors.h"
#import "NSView+FirstResponder.h"
#import "NSMutableArray+MoveObject.h"
+#import "NSFileManager+UniqueNames.h"
+#import "NSString+FixFilename.h"
- (NJOutput *)objectForKeyedSubscript:(NJInput *)input;
- (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input;
- (NSDictionary *)serialize;
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error;
@end
return @{ @"name": _name, @"entries": entries };
}
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error {
+ [NSProcessInfo.processInfo disableSuddenTermination];
+ NSDictionary *serialization = [self serialize];
+ NSData *json = [NSJSONSerialization dataWithJSONObject:serialization
+ options:NSJSONWritingPrettyPrinted
+ error:error];
+ BOOL success = json && [json writeToURL:url options:NSDataWritingAtomic error:error];
+ [NSProcessInfo.processInfo enableSuddenTermination];
+ return success;
+}
+
@end
@implementation NJMappingsController {
NSMutableArray *_mappings;
NJMapping *manualMapping;
+ NSString *draggingName;
}
- (id)init {
- (void)awakeFromNib {
[tableView registerForDraggedTypes:@[PB_ROW]];
+ [tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
}
- (NJMapping *)objectForKeyedSubscript:(NSString *)name {
if (result != NSFileHandlingPanelOKButton)
return;
[panel close];
- [NSProcessInfo.processInfo disableSuddenTermination];
NSError *error;
- NSDictionary *serialization = [mapping serialize];
- NSData *json = [NSJSONSerialization dataWithJSONObject:serialization
- options:NSJSONWritingPrettyPrinted
- error:&error];
- if (!error)
- [json writeToURL:panel.URL options:NSDataWritingAtomic error:&error];
-
- [NSProcessInfo.processInfo enableSuddenTermination];
+ [mapping writeToURL:panel.URL error:&error];
if (error) {
[window presentError:error
modalForWindow:window
}
- (IBAction)mappingPressed:(id)sender {
- [popover showRelativeToRect:popoverActivate.bounds ofView:popoverActivate preferredEdge:NSMinXEdge];
+ [popover showRelativeToRect:popoverActivate.bounds
+ ofView:popoverActivate
+ preferredEdge:NSMinXEdge];
}
- (void)popoverWillShow:(NSNotification *)notification {
}
}
-- (BOOL)tableView:(NSTableView *)tableView
+- (NSArray *)tableView:(NSTableView *)tableView_
+namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
+forDraggedRowsWithIndexes:(NSIndexSet *)indexSet {
+ NJMapping *toSave = self[indexSet.firstIndex];
+ NSString *filename = [[toSave.name stringByFixingPathComponent]
+ stringByAppendingPathExtension:@"enjoyable"];
+ NSURL *dst = [dropDestination URLByAppendingPathComponent:filename];
+ dst = [NSFileManager.defaultManager generateUniqueURLWithBase:dst];
+ NSError *error;
+ if (![toSave writeToURL:dst error:&error]) {
+ [tableView_ presentError:error];
+ return @[];
+ } else {
+ return @[dst.lastPathComponent];
+ }
+}
+
+- (BOOL)tableView:(NSTableView *)tableView_
writeRowsWithIndexes:(NSIndexSet *)rowIndexes
toPasteboard:(NSPasteboard *)pboard {
if (rowIndexes.count == 1 && rowIndexes.firstIndex != 0) {
- [pboard declareTypes:@[PB_ROW] owner:nil];
+ [pboard declareTypes:@[PB_ROW, NSFilesPromisePboardType] owner:nil];
[pboard setString:@(rowIndexes.firstIndex).stringValue forType:PB_ROW];
+ [pboard setPropertyList:@[@"enjoyable"] forType:NSFilesPromisePboardType];
return YES;
} else {
return NO;
}
-
}
@end
--- /dev/null
+//
+// NSFileManager+UniqueNames.h
+// Enjoyable
+//
+// Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSFileManager (UniqueNames)
+
+- (NSURL *)generateUniqueURLWithBase:(NSURL *)canonical;
+ // Generate a probably-unique URL by trying sequential indices, e.g.
+ // file://Test.txt
+ // file://Test (1).txt
+ // file://Test (2).txt
+ // and so on.
+ //
+ // The URL is only probably unique. It is subject to the usual
+ // race conditions associated with generating a filename before
+ // actually opening it. It also does not check remote resources,
+ // as it operates synchronously. Finally, it gives up after 10,000
+ // indices.
+
+@end
--- /dev/null
+//
+// NSFileManager+UniqueNames.m
+// Enjoyable
+//
+// Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import "NSFileManager+UniqueNames.h"
+
+@implementation NSFileManager (UniqueNames)
+
+- (NSURL *)generateUniqueURLWithBase:(NSURL *)canonical {
+ // Punt for cases that are just too hard.
+ if (!canonical.isFileURL)
+ return canonical;
+
+ NSString *trying = canonical.path;
+ NSString *dirname = [trying stringByDeletingLastPathComponent];
+ NSString *basename = [trying.lastPathComponent stringByDeletingPathExtension];
+ NSString *extension = trying.pathExtension;
+ int index = 1;
+ while ([self fileExistsAtPath:trying] && index < 10000) {
+ NSString *indexName = [NSString stringWithFormat:@"%@ (%d)", basename, index++];
+ indexName = [indexName stringByAppendingPathExtension:extension];
+ trying = [dirname stringByAppendingPathComponent:indexName];
+ }
+ return [NSURL fileURLWithPath:trying];
+}
+
+@end
--- /dev/null
+//
+// NSString+FixFilename.h
+// Enjoyable
+//
+// Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSString (FixFilename)
+
+- (NSString *)stringByFixingPathComponent;
+ // Does various operations to make this string suitable for use as
+ // a single path component of a normal filename. Removes / and :
+ // characters and prepends a _ to avoid leading .s or an empty
+ // name.
+
+@end
--- /dev/null
+//
+// NSString+FixFilename.m
+// Enjoyable
+//
+// Created by Joe Wreschnig on 3/7/13.
+//
+//
+
+#import "NSString+FixFilename.h"
+
+@implementation NSString (FixFilename)
+
+- (NSString *)stringByFixingPathComponent {
+ static NSCharacterSet *invalid;
+ if (!invalid)
+ invalid = [NSCharacterSet characterSetWithCharactersInString:@"/:"];
+ NSArray *parts = [self componentsSeparatedByCharactersInSet:invalid];
+ NSString *newName = [parts componentsJoinedByString:@""];
+ if (!newName.length)
+ return @"_";
+ if ([newName characterAtIndex:0] == '.')
+ newName = [@"_" stringByAppendingString:newName];
+ return newName;
+}
+
+@end