-
Notifications
You must be signed in to change notification settings - Fork 3
/
MCCMailAbstractor.m
182 lines (151 loc) · 6.24 KB
/
MCCMailAbstractor.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//
// MCCMailAbstractor.m
// MailCommonCode
//
// Created by Scott Little on 14/6/13.
// Copyright (c) 2013 Little Known Software. All rights reserved.
//
#import "MCCMailAbstractor.h"
@interface MCC_PREFIXED_NAME(MailAbstractor) : NSObject {
NSDictionary *_mappings;
NSInteger _testVersionOS;
}
@property (strong) NSDictionary *mappings;
@property (assign) NSInteger testVersionOS;
@end
@implementation MCC_PREFIXED_NAME(MailAbstractor)
@synthesize mappings = _mappings;
@synthesize testVersionOS = _testVersionOS;
- (id)init {
self = [super init];
if (self) {
self.testVersionOS = -1;
[self rebuildCurrentMappings];
}
return self;
}
- (void)dealloc {
#if !__has_feature(objc_arc)
self.mappings = nil;
#endif
MCC_DEALLOC();
}
- (void)rebuildCurrentMappings {
// This array could be a plist file that we read in
NSFileManager *manager = [NSFileManager defaultManager];
NSString *resourcePlistPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"MailVersionClassMappings" ofType:@"plist"];
NSArray *translationArray = [NSArray array];
if ([manager fileExistsAtPath:resourcePlistPath]) {
translationArray = [translationArray arrayByAddingObjectsFromArray:[NSArray arrayWithContentsOfFile:resourcePlistPath]];
}
resourcePlistPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"PluginClassMappings" ofType:@"plist"];
if ([manager fileExistsAtPath:resourcePlistPath]) {
translationArray = [translationArray arrayByAddingObjectsFromArray:[NSArray arrayWithContentsOfFile:resourcePlistPath]];
}
NSMutableDictionary *newMappings = [NSMutableDictionary dictionary];
// Extract all possible mappings
for (NSDictionary *aDict in translationArray) {
for (NSString *className in [aDict allValues]) {
[newMappings setObject:aDict forKey:className];
}
}
// Trim them down to ones that are relevant on this OS version
NSString *osName = [NSString stringWithFormat:@"10.%@", @([self osMinorVersion])];
NSMutableDictionary *trimmedMappings = [NSMutableDictionary dictionary];
for (NSString *mappingKey in [newMappings allKeys]) {
// Get the mapping for this OS version
NSDictionary * currentMappings = newMappings[mappingKey];
NSString * mappedClassName = currentMappings[osName];
NSInteger previousVersion = [self osMinorVersion];
while ((mappedClassName == nil) && (previousVersion > 7)) {
previousVersion--;
NSString * previousOsName = [NSString stringWithFormat:@"10.%@", @(previousVersion)];
mappedClassName = currentMappings[previousOsName];
}
NSAssert(mappedClassName != nil, @"Could not find a mapping for %@ or lower in %@", osName, translationArray);
if (mappedClassName && ![mappingKey isEqualToString:mappedClassName]) {
[trimmedMappings setObject:mappedClassName forKey:mappingKey];
}
}
self.mappings = [NSDictionary dictionaryWithDictionary:trimmedMappings];
}
// Method is used only once and could be included in-line, HOWEVER, having it here allows for testing the abstractor by setting the
// testVersionOS value via KVO (i.e. [[MailAbstractor sharedInstance] setValue:@(7) forKey:@"testVersionOS"])
// Though be sure to call -[rebuildCurrentMappings] after setthing the version
- (NSInteger)osMinorVersion {
if (self.testVersionOS > 0) {
return self.testVersionOS;
}
// use a static because we only really need to get the version once.
static NSInteger minVersion = 0; // 0 == notSet
if (minVersion == 0) {
/*
Using this method after reading this SO post:
http://stackoverflow.com/questions/11072804/mac-os-x-10-8-replacement-for-gestalt-for-testing-os-version-at-runtime
*/
NSDictionary *sv = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
NSString *versionString = [sv objectForKey:@"ProductVersion"];
NSArray *versionParts = [versionString componentsSeparatedByString:@"."];
if ([versionParts count] > 1) {
minVersion = [[versionParts objectAtIndex:1] integerValue];
}
}
return minVersion;
}
+ (MCC_PREFIXED_NAME(MailAbstractor)*)sharedInstance {
static MCC_PREFIXED_NAME(MailAbstractor) *myAbstractor = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
myAbstractor = [[[self class] alloc] init];
});
return myAbstractor;
}
@end
Class MCC_PREFIXED_NAME(ClassFromString)(NSString *aClassName) {
static NSMutableDictionary *classNameLookup = nil;
static dispatch_queue_t classNameDictAccessQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *queueName = [NSString stringWithFormat:@"com.mailPlugins.dictAccessQueue.%@", NSStringFromClass([MCC_PREFIXED_NAME(MailAbstractor) class])];
classNameDictAccessQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
classNameLookup = [[NSMutableDictionary alloc] init];
});
// Using a concurrent queue with a barrier for the reads will avoid any contention, it does
// mean that we might get a miss or two on the first time to find a class, but that is not a big deal
// Comes from https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html
Class __block resultClass = nil;
dispatch_sync(classNameDictAccessQueue, ^{
resultClass = [classNameLookup objectForKey:aClassName];
});
if (resultClass){
return resultClass;
}
else{
resultClass = NSClassFromString(aClassName);
if (!resultClass) {
resultClass = NSClassFromString([@"MF" stringByAppendingString:aClassName]);
if (!resultClass) {
resultClass = NSClassFromString([@"MC" stringByAppendingString:aClassName]);
if (!resultClass) {
MCC_PREFIXED_NAME(MailAbstractor) *abstractor = [MCC_PREFIXED_NAME(MailAbstractor) sharedInstance];
NSString *nameFound = [abstractor.mappings objectForKey:aClassName];
if (nameFound) {
resultClass = NSClassFromString(nameFound);
}
}
}
}
if (resultClass) {
// Stupid hack to ensure that the +initialize has been called on the class
// to avoid a deadlock seen occaisionally (at least on Mountain Lion)
Class newClass = [resultClass class];
NSString *newClassName = [aClassName copy];
dispatch_barrier_async(classNameDictAccessQueue, ^{
[classNameLookup setObject:newClass forKey:newClassName];
});
MCC_RELEASE(newClassName);
}
// NSLog(@"found class %@ -->%@",className,resultClass);
}
return resultClass;
}