Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always store UTIs for include/exclude types in FieSystemObjectSources (supersedes #1681) #1708

Merged
merged 13 commits into from Jan 17, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion Quicksilver/Code-QuickStepFoundation/QSUTI.h
Expand Up @@ -7,7 +7,8 @@
*
*/


BOOL QSIsUTI(NSString *utiString);
BOOL QSTypeConformsTo(NSString *inUTI, NSString *inConformsToUTI);
NSString *QSUTIOfFile(NSString *path);
NSString *QSUTIOfURL(NSURL *url);
NSString *QSUTIWithLSInfoRec(NSString *path, LSItemInfoRecord *infoRec);
Expand Down
65 changes: 61 additions & 4 deletions Quicksilver/Code-QuickStepFoundation/QSUTI.m
Expand Up @@ -9,6 +9,62 @@

#include "QSUTI.h"

/**
* Determines if a given string is an existing UTI or not
*
* @param UTIString a string to test whether or not it is a UTI (as defined by Apple's launch service database)
*
* @return Boolean value specifying if UTIString is a valid UTI
*/
BOOL QSIsUTI(NSString *UTIString) {
if (UTTypeConformsTo((__bridge CFStringRef)UTIString, kUTTypeItem)) {
// UTIString conforms to public.item - it must be a UTI
return YES;
}
CFDictionaryRef dict = UTTypeCopyDeclaration((__bridge CFStringRef)UTIString);
if (dict != NULL) {
// UTIString has a declaration dictionary - it must be a UTI
CFRelease(dict);
return YES;
}

NSUInteger dotLocation = [UTIString rangeOfString:@"."].location;
if (dotLocation > 0 && dotLocation < [UTIString length] -1) {
// UTIString contains a . somewhere in the middle. Since UTIs use reverse DNS we can guess it is a UTI
return YES;
}
return NO;
}

/**
* Returns whether a uniform type identifier conforms to another uniform type identifier. It's better than the UTType function. See discussion
*
* @param inUTI A uniform type identifier to compare.
* @param inConformsToUTI The uniform type identifier to compare it to.
*
* @return Returns true if the uniform type identifier is equal to or conforms to the second type.
*
* @discussion The UTTypeConformsTo() function isn't great in all cases. If a UTI for an unknown file extension has been created (e.g. "dyn-xxxxx" was created for the extension "myextension"), and subsequently an application regisers the extension "myextension" with the UTI "com.me.myextension", the OS will not say that "dyn-xxxxx" conforms to "com.me.myextension" (or vice-versa) when, in fact, they do. This function first resolves the extensions for the two UTIs, then attempts to convert them back to UTIs in order to check for UTI conformance
*/
BOOL QSTypeConformsTo(NSString *inUTI, NSString *inConformsToUTI) {
if (UTTypeConformsTo((__bridge CFStringRef)inUTI, (__bridge CFStringRef)inConformsToUTI)) {
return YES;
}
CFStringRef inUTIExtension = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)inUTI, kUTTagClassFilenameExtension);
NSString *resolvedInUTI = nil;
if (inUTIExtension) {
resolvedInUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, inUTIExtension, NULL);
CFRelease(inUTIExtension);
}
CFStringRef inConformsToUTIExtension = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)inConformsToUTI, kUTTagClassFilenameExtension);
NSString *resolvedInConformsToUTI = nil;
if (inConformsToUTIExtension) {
resolvedInConformsToUTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, inConformsToUTIExtension, NULL);
CFRelease(inConformsToUTIExtension);
}
return UTTypeConformsTo((__bridge CFStringRef)(resolvedInUTI ? resolvedInUTI : inUTI), (__bridge CFStringRef)(resolvedInConformsToUTI ? resolvedInConformsToUTI : inConformsToUTI));
}

NSString *QSUTIOfURL(NSURL *fileURL) {
LSItemInfoRecord infoRec;
LSCopyItemInfoForURL((__bridge CFURLRef)fileURL, kLSRequestTypeCreator|kLSRequestBasicFlagsOnly, &infoRec);
Expand All @@ -34,17 +90,18 @@
if (infoRec->flags & kLSItemInfoIsVolume)
return (NSString *)kUTTypeVolume;

NSString *extensionUTI = (NSString *)CFBridgingRelease(UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL));
if (extensionUTI && ![extensionUTI hasPrefix:@"dyn"])
return extensionUTI;

// the order with which we try to resolve the UTI is important. First we use the OSType, *then* the file extension. This is wise since it's possible to give (possible misleading) extensions to folders - like myfolder.js (it is of type public.folder, not com.netscape.javascript-source)
NSString *hfsType = (NSString *)CFBridgingRelease(UTCreateStringForOSType(infoRec->filetype));
if (![hfsType length] && isDirectory)
return (NSString *)kUTTypeFolder;

NSString *hfsUTI = (NSString *)CFBridgingRelease(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, (__bridge CFStringRef)hfsType, NULL));
if (![hfsUTI hasPrefix:@"dyn"])
return hfsUTI;

NSString *extensionUTI = (NSString *)CFBridgingRelease(UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL));
if (extensionUTI && ![extensionUTI hasPrefix:@"dyn"])
return extensionUTI;

if ([[NSFileManager defaultManager] isExecutableFileAtPath:path])
return @"public.executable";
Expand Down
10 changes: 8 additions & 2 deletions Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSDirectoryParser.m
Expand Up @@ -27,12 +27,18 @@ - (NSArray *)objectsFromPath:(NSString *)path withSettings:(NSDictionary *)setti
NSMutableArray *types = [NSMutableArray array];
for (NSString *type in [settings objectForKey:kItemFolderTypes]) {
NSString *realType = QSUTIForAnyTypeString(type);
if (!realType && !QSIsUTI(type)) {
realType = QSUTIForExtensionOrType(type,0);
}
[types addObject:(realType ? realType : type)];
}

NSMutableArray *excludedTypes = [NSMutableArray array];
for (NSString *excludedType in [settings objectForKey:kItemExcludeFiletypes]) {
NSString *realType = QSUTIForAnyTypeString(excludedType);
if (!realType && !QSIsUTI(excludedType)) {
realType = QSUTIForExtensionOrType(excludedType, 0);
}
[excludedTypes addObject:(realType ? realType : excludedType)];
}
return [[NSSet setWithArray:[self objectsFromPath:path depth:depthValue types:types excludeTypes:excludedTypes descend:descendIntoBundles]] allObjects];
Expand Down Expand Up @@ -83,14 +89,14 @@ - (NSArray *)objectsFromPath:(NSString *)path depth:(NSInteger)depth types:(NSAr
include = YES;
} else {
for(NSString * requiredType in types) {
if (UTTypeConformsTo((__bridge CFStringRef)type, (__bridge CFStringRef)requiredType)) {
if (QSTypeConformsTo(type, requiredType)) {
include = YES;
break;
}
}
}
for (NSString *excludedType in excludedTypes) {
if (UTTypeConformsTo((__bridge CFStringRef)type, (__bridge CFStringRef)excludedType)) {
if (QSTypeConformsTo(type, excludedType)) {
include = NO;
}
}
Expand Down
107 changes: 54 additions & 53 deletions Quicksilver/PlugIns-Main/QSCorePlugIn/Code/QSFileSystemObjectSource.m
Expand Up @@ -118,74 +118,75 @@ - (BOOL)isVisibleSource {return YES;}
- (BOOL)usesGlobalSettings {return NO;}

- (NSString *)tokenField:(NSTokenField *)tokenField editingStringForRepresentedObject:(id)representedObject {
return representedObject;
NSString * type = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)representedObject, kUTTagClassFilenameExtension);
if (!type) {
return representedObject;
}
return type;
}
- (id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString {
// show types such as .jpg as JPEG Image
if ([editingString hasPrefix:@"."]) {
editingString = [editingString substringFromIndex:1];

- (NSString *)UTIForString:(NSString *)editingString {
if (QSIsUTI(editingString)) {
// editing string is already a UTI
return editingString;
}
// Try and work out the type of file to display a nice name in the token field
NSString *type = QSUTIForAnyTypeString(editingString);
if (!type) {
if ([editingString hasPrefix:@"'"]) {
return editingString;
}
// if the user has entered 'folder' (to exclude a folder)
if ([[editingString lowercaseString] isEqualToString:@"folder"]) {
return (NSString *)kUTTypeFolder;

NSString *type = nil;
// Try to get the UTI from the extension/string
if ([editingString hasPrefix:@"'"]) {
// 'xxxx' strings are OS types
// p_j_r WARNING When a UTI manager is created, the trimming business should be dealt with there
NSString *OSTypeAsString = [editingString stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"'"]];
type = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, (__bridge CFStringRef)OSTypeAsString, NULL);
if ([type hasPrefix:@"dyn"]) {
// some OS types are all uppercase (e.g. 'APPL' == application, 'fold' == folder), some are all lower. Be forgiving to the user
for (NSString *caseChangedOSType in @[[OSTypeAsString uppercaseString], [OSTypeAsString lowercaseString]]) {
NSString *testType = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, (__bridge CFStringRef)caseChangedOSType, NULL);
if (![testType hasPrefix:@"dyn"]) {
type = testType;
break;
}
}
}
type = editingString;
}
} else if ([[editingString lowercaseString] isEqualToString:@"folder"]) {
// if the user has entered 'folder' (to exclude a folder), return its UTI
type = (NSString *)kUTTypeFolder;
} else {
if ([editingString hasPrefix:@"."]) {
editingString = [editingString substringFromIndex:1];
}
type = QSUTIForExtensionOrType(editingString, 0);
}
return type;
}

- (BOOL)tokenField:(NSTokenField *)tokenField hasMenuForRepresentedObject:(id)representedObject {
if ([representedObject hasPrefix:@"'"])
return NO;
return YES;
// The represented object should always be a UTI
- (id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString {
return [self UTIForString:editingString];
}
#if 0
- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject {
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
NSString *desc;
desc = (NSString *)UTTypeCopyDeclaration((CFStringRef)representedObject);
NSArray *conforms = [desc objectForKey:(NSString *)kUTTypeConformsToKey];
[desc release];
if (conforms) {
if (![conforms isKindOfClass:[NSArray class]]) conforms = [NSArray arrayWithObject:conforms];
for(NSString * type in conforms){
desc = (NSString *)UTTypeCopyDescription((CFStringRef)type);
[menu addItemWithTitle:desc action:nil keyEquivalent:@""];
[desc release];
}
}
return [menu autorelease];

- (BOOL)tokenField:(NSTokenField *)tokenField hasMenuForRepresentedObject:(id)representedObject {
return UTTypeConformsTo((__bridge CFStringRef)representedObject, (__bridge CFStringRef)@"public.item");
}
#else

- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject {
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
NSDictionary *dict = (__bridge_transfer NSDictionary *)UTTypeCopyDeclaration((__bridge CFStringRef)representedObject);
NSArray *conforms = [dict objectForKey:(NSString *)kUTTypeConformsToKey];
if (conforms) {
if (![conforms isKindOfClass:[NSArray class]]) conforms = [NSArray arrayWithObject:conforms];
for(NSString * type in conforms) {
NSString *title = (__bridge_transfer NSString *)UTTypeCopyDescription((__bridge CFStringRef)type);
[menu addItemWithTitle:title action:nil keyEquivalent:@""];
}
}
NSMenuItem *menuItem = [NSMenuItem new];
[menuItem setTitle:representedObject];
[menu addItem:menuItem];
return menu;
}
#endif

- (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject {

NSString *description = (__bridge_transfer NSString *)UTTypeCopyDescription((__bridge CFStringRef)representedObject);
if (!description) {
if ([representedObject hasPrefix:@"'"])
return [@"Type: " stringByAppendingString:representedObject];
else if ([representedObject rangeOfString:@"."].location == NSNotFound)
return [@"." stringByAppendingString:representedObject];
description = representedObject;
if (!description || [description isEqualToString:@"content"]) {
// show the file extension if there's no description, or if is the unhelpful 'content' string
NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)representedObject, kUTTagClassFilenameExtension);
if (!fileExtension && QSIsUTI(representedObject)) {
return representedObject;
}
return [NSString stringWithFormat:@".%@", fileExtension ? fileExtension : representedObject];
}
return description;
}
Expand Down