Provide file promises from the mapping list. Perform various file sanitization in...
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Thu, 7 Mar 2013 01:06:48 +0000 (02:06 +0100)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Thu, 7 Mar 2013 01:06:48 +0000 (02:06 +0100)
Enjoyable.xcodeproj/project.pbxproj
Enjoyable_Prefix.pch
NJMapping.h
NJMapping.m
NJMappingsController.m
NSFileManager+UniqueNames.h [new file with mode: 0644]
NSFileManager+UniqueNames.m [new file with mode: 0644]
NSString+FixFilename.h [new file with mode: 0644]
NSString+FixFilename.m [new file with mode: 0644]

index aef1ed2..e8badf9 100644 (file)
@@ -35,6 +35,8 @@
                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 */; };
                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 */
 /* 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>"; };
                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 */
                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 = (
                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 */,
                                EE1D7C9416E0ECCF00B000EB /* NSError+Description.h */,
                                EE1D7C9516E0ECCF00B000EB /* NSError+Description.m */,
                                EE1D7C9016E01E7000B000EB /* NSView+FirstResponder.h */,
                                EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */,
                                EEAA9CDF16E808E600256B64 /* NSMutableArray+MoveObject.h */,
                                EEAA9CE016E808E600256B64 /* NSMutableArray+MoveObject.m */,
                                EE96929316E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m */,
                                EEAA9CDF16E808E600256B64 /* NSMutableArray+MoveObject.h */,
                                EEAA9CE016E808E600256B64 /* NSMutableArray+MoveObject.m */,
+                               EEAA9CE516E81C8400256B64 /* NSString+FixFilename.h */,
+                               EEAA9CE616E81C8400256B64 /* NSString+FixFilename.m */,
                        );
                        name = Categories;
                        sourceTree = "<group>";
                        );
                        name = Categories;
                        sourceTree = "<group>";
                                EE1D7C9616E0ECCF00B000EB /* NSError+Description.m in Sources */,
                                EE96929416E54B480054A3C8 /* NSMenu+RepresentedObjectAccessors.m in Sources */,
                                EEAA9CE116E808E600256B64 /* NSMutableArray+MoveObject.m in Sources */,
                                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;
                };
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 4f50186..4845e9d 100644 (file)
@@ -12,3 +12,5 @@
 #import "NSMenu+RepresentedObjectAccessors.h"
 #import "NSView+FirstResponder.h"
 #import "NSMutableArray+MoveObject.h"
 #import "NSMenu+RepresentedObjectAccessors.h"
 #import "NSView+FirstResponder.h"
 #import "NSMutableArray+MoveObject.h"
+#import "NSFileManager+UniqueNames.h"
+#import "NSString+FixFilename.h"
index f997fa8..069f132 100644 (file)
@@ -18,5 +18,6 @@
 - (NJOutput *)objectForKeyedSubscript:(NJInput *)input;
 - (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input;
 - (NSDictionary *)serialize;
 - (NJOutput *)objectForKeyedSubscript:(NJInput *)input;
 - (void)setObject:(NJOutput *)output forKeyedSubscript:(NJInput *)input;
 - (NSDictionary *)serialize;
+- (BOOL)writeToURL:(NSURL *)url error:(NSError **)error;
 
 @end
 
 @end
index 4e7c4b2..7df321c 100644 (file)
     return @{ @"name": _name, @"entries": entries };
 }
 
     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
 @end
index cb5baf1..ff9b42c 100644 (file)
@@ -18,6 +18,7 @@
 @implementation NJMappingsController {
     NSMutableArray *_mappings;
     NJMapping *manualMapping;
 @implementation NJMappingsController {
     NSMutableArray *_mappings;
     NJMapping *manualMapping;
+    NSString *draggingName;
 }
 
 - (id)init {
 }
 
 - (id)init {
@@ -32,6 +33,7 @@
 
 - (void)awakeFromNib {
     [tableView registerForDraggedTypes:@[PB_ROW]];
 
 - (void)awakeFromNib {
     [tableView registerForDraggedTypes:@[PB_ROW]];
+    [tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; 
 }
 
 - (NJMapping *)objectForKeyedSubscript:(NSString *)name {
 }
 
 - (NJMapping *)objectForKeyedSubscript:(NSString *)name {
                       if (result != NSFileHandlingPanelOKButton)
                           return;
                       [panel close];
                       if (result != NSFileHandlingPanelOKButton)
                           return;
                       [panel close];
-                      [NSProcessInfo.processInfo disableSuddenTermination];
                       NSError *error;
                       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
                       if (error) {
                           [window presentError:error
                                 modalForWindow:window
 }
 
 - (IBAction)mappingPressed:(id)sender {
 }
 
 - (IBAction)mappingPressed:(id)sender {
-    [popover showRelativeToRect:popoverActivate.bounds ofView:popoverActivate preferredEdge:NSMinXEdge];
+    [popover showRelativeToRect:popoverActivate.bounds
+                         ofView:popoverActivate
+                  preferredEdge:NSMinXEdge];
 }
 
 - (void)popoverWillShow:(NSNotification *)notification {
 }
 
 - (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) {
 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 setString:@(rowIndexes.firstIndex).stringValue forType:PB_ROW];
+        [pboard setPropertyList:@[@"enjoyable"] forType:NSFilesPromisePboardType];
         return YES;
     } else {
         return NO;
     }
         return YES;
     } else {
         return NO;
     }
-    
 }
 
 @end
 }
 
 @end
diff --git a/NSFileManager+UniqueNames.h b/NSFileManager+UniqueNames.h
new file mode 100644 (file)
index 0000000..82c57d4
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  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
diff --git a/NSFileManager+UniqueNames.m b/NSFileManager+UniqueNames.m
new file mode 100644 (file)
index 0000000..c7eb768
--- /dev/null
@@ -0,0 +1,31 @@
+//
+//  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
diff --git a/NSString+FixFilename.h b/NSString+FixFilename.h
new file mode 100644 (file)
index 0000000..0850e74
--- /dev/null
@@ -0,0 +1,19 @@
+//
+//  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
diff --git a/NSString+FixFilename.m b/NSString+FixFilename.m
new file mode 100644 (file)
index 0000000..fc8ba57
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  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