Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
GetMetadataForFile.m
Go to the documentation of this file.
1/*
2
3GetMetadataForFile.m
4
5Spotlight metadata importer for Oolite
6Copyright (C) 2005-2010 Jens Ayton
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
25#import <CoreFoundation/CoreFoundation.h>
26#import <CoreServices/CoreServices.h>
27#import <Foundation/Foundation.h>
28#import <stdarg.h>
32
33
34#define kTitle (NSString *)kMDItemTitle
35#define kAuthors (NSString *)kMDItemAuthors
36#define kVersion (NSString *)kMDItemVersion
37#define kCopyright (NSString *)kMDItemCopyright
38#define kIdentifier (NSString *)kMDItemIdentifier
39#define kDescription (NSString *)kMDItemDescription
40#define kURL (NSString *)kMDItemURL
41#define kTextContent (NSString *)kMDItemTextContent
42#define kShipIDs @"org_aegidian_oolite_shipids"
43#define kShipClassNames @"org_aegidian_oolite_shipclassnames"
44#define kShipRoles @"org_aegidian_oolite_shiproles"
45#define kShipModels @"org_aegidian_oolite_shipmodels"
46#define kCombatRating @"org_aegidian_oolite_combatrating"
47#define kSystemName @"org_aegidian_oolite_systemname"
48#define kMinOoliteVersion @"org_aegidian_oolite_minversion"
49#define kMaxOoliteVersion @"org_aegidian_oolite_maxversion"
50
51
52static bool GetMetadataForSaveFile(void *thisInterface, NSMutableDictionary *attributes, NSString *pathToFile);
53static bool GetMetadataForExpansionPack(void *thisInterface, NSMutableDictionary *attributes, NSString *pathToFile);
54
55static id GetBundlePropertyList(NSString *inPListName);
56static NSDictionary *ConfigDictionary(NSString *basePath, NSString *name);
57static NSDictionary *MergeShipData(NSDictionary *shipData, NSDictionary *shipDataOverrides);
58static NSDictionary *MergeShipDataEntry(NSDictionary *baseDict, NSDictionary *overrideDict);
59
60static NSDictionary *OOParseRolesFromString(NSString *string);
61static NSMutableArray *ScanTokensFromString(NSString *values);
62
63
64/*
65 NOTE: this prototype differs from the one declared in main.c (which is mostly unmodified
66 Apple boilerplate code), but the types are entirely compatible.
67*/
68BOOL GetMetadataForFile(void *thisInterface,
69 NSMutableDictionary *attributes,
70 CFStringRef contentTypeUTI,
71 NSString *pathToFile)
72{
73 @autoreleasepool
74 {
75 @try
76 {
77 if (UTTypeConformsTo(contentTypeUTI, CFSTR("org.aegidian.oolite.save")))
78 {
79 return GetMetadataForSaveFile(thisInterface, attributes, pathToFile);
80 }
81 if (UTTypeConformsTo(contentTypeUTI, CFSTR("org.aegidian.oolite.expansion")))
82 {
83 return GetMetadataForExpansionPack(thisInterface, attributes, pathToFile);
84 }
85 }
86 @catch (id any)
87 {
88 return false;
89 }
90 }
91}
92
93static bool GetMetadataForSaveFile(void *thisInterface, NSMutableDictionary *attributes, NSString *pathToFile)
94{
95 NSDictionary *content = [NSDictionary dictionaryWithContentsOfFile:pathToFile];
96 if (content == nil) return false;
97
98 NSString *playerName = [content oo_stringForKey:@"player_name"];
99 if (playerName != nil) attributes[kTitle] = playerName;
100
101 NSString *shipDesc = [content oo_stringForKey:@"ship_desc"];
102 if (shipDesc != nil) attributes[kShipIDs] = shipDesc;
103
104 NSString *shipName = [content oo_stringForKey:@"ship_name"];
105 if (shipName != nil) attributes[kShipClassNames] = shipName;
106
107 NSString *currentSystemName = [content oo_stringForKey:@"current_system_name"];
108 if (currentSystemName != nil) attributes[kSystemName] = currentSystemName;
109
110 NSArray *commLog = [content oo_arrayForKey:@"comm_log"];
111 if (commLog.count != 0) attributes[kTextContent] = [commLog componentsJoinedByString:@"\n"];
112
113 NSInteger killCount = [content oo_integerForKey:@"ship_kills"];
114 if (killCount > 0)
115 {
116 NSArray *ratings = GetBundlePropertyList(@"Values")[@"ratings"];
117 if (ratings != nil)
118 {
119 int rating = 0;
120 const int kRequiredKills[8] = { 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0200, 0x0A00, 0x1900 };
121
122 while (rating < 8 && kRequiredKills[rating] <= killCount)
123 {
124 rating ++;
125 }
126
127 attributes[kCombatRating] = ratings[rating];
128 }
129 }
130
131 return true;
132}
133
134
135static bool GetMetadataForExpansionPack(void *thisInterface, NSMutableDictionary *attributes, NSString *pathToFile)
136{
137 NSDictionary *manifest = ConfigDictionary(pathToFile, @"manifest.plist");
138 if (manifest != nil)
139 {
140 NSString *title = [manifest oo_stringForKey:@"title"];
141 if (title != nil) attributes[kTitle] = title;
142
143 NSString *identifier = [manifest oo_stringForKey:@"identifier"];
144 if (identifier != nil) attributes[kIdentifier] = identifier;
145
146 NSString *version = [manifest oo_stringForKey:@"version"];
147 if (version != nil) attributes[kVersion] = version;
148
149 // Allow a string or array for author
150 id author = manifest[@"author"];
151 if ([author isKindOfClass:NSString.class]) author = @[author];
152 if ([author isKindOfClass:NSArray.class]) attributes[kAuthors] = author;
153
154 NSString *description = [manifest oo_stringForKey:@"description"];
155 if (description != nil) attributes[kDescription] = description;
156
157 NSString *copyright = [manifest oo_stringForKey:@"copyright"];
158 if (copyright == nil) copyright = [manifest oo_stringForKey:@"license"];
159 if (copyright != nil) attributes[kCopyright] = copyright;
160
161 NSString *url = [manifest oo_stringForKey:@"download_url"];
162 if (url == nil) url = [manifest oo_stringForKey:@"information_url"];
163 if (url != nil) attributes[kURL] = url;
164
165 NSString *minVersion = [manifest oo_stringForKey:@"required_oolite_version"];
166 if (minVersion != nil) attributes[kMinOoliteVersion] = minVersion;
167 }
168 else
169 {
170 // No manifest, look for requires.plist
171
172 NSDictionary *requires = ConfigDictionary(pathToFile, @"requires.plist");
173 if (requires != nil)
174 {
175 NSString *minVersion = [requires objectForKey:@"version"];
176 if (minVersion != nil) attributes[kMinOoliteVersion] = minVersion;
177
178 NSString *maxVersion = [requires objectForKey:@"max_version"];
179 if (maxVersion != nil) attributes[kMaxOoliteVersion] = maxVersion;
180 }
181
182 // Not "officially" supported, but exists in some OXPs.
183 NSDictionary *infoPList = ConfigDictionary(pathToFile, @"Info.plist");
184 NSString *version = [infoPList oo_stringForKey:@"CFBundleVersion"];
185 if (version != nil) attributes[kVersion] = version;
186 }
187
188 NSDictionary *shipData = ConfigDictionary(pathToFile, @"shipdata.plist");
189 NSDictionary *shipDataOverrides = ConfigDictionary(pathToFile, @"shipdata-overrides.plist");
190 shipData = MergeShipData(shipData, shipDataOverrides);
191
192 if (shipData.count != 0)
193 {
194 NSMutableSet *shipNames = [NSMutableSet setWithCapacity:shipData.count];
195 NSMutableSet *shipModels = [NSMutableSet setWithCapacity:shipData.count];
196 NSMutableSet *shipRoles = [NSMutableSet new];
197
198 attributes[kShipIDs] = shipData.allKeys;
199
200 for (NSDictionary *ship in shipData)
201 {
202 if (![ship isKindOfClass:NSDictionary.class]) continue;
203
204 NSString *name = [ship oo_stringForKey:@"name"];
205 if (name != nil) [shipNames addObject:name];
206
207 NSString *model = [ship oo_stringForKey:@"model"];
208 if (model != nil) [shipModels addObject:model];
209
210 NSString *role = [ship oo_stringForKey:@"roles"];
211 if (role != nil) [shipRoles addObjectsFromArray:OOParseRolesFromString(role).allKeys];
212 }
213
214 attributes[kShipClassNames] = shipNames.allObjects;
215 attributes[kShipModels] = shipModels.allObjects;
216 attributes[kShipRoles] = shipRoles.allObjects;
217 }
218
219 return YES;
220}
221
222
223#pragma mark -
224#pragma mark Helper functions
225
226static id GetBundlePropertyList(NSString *inPListName)
227{
228 NSBundle *bundle = [NSBundle bundleWithIdentifier:@"org.aegidian.oolite.md-importer"];
229 NSString *path = [bundle pathForResource:inPListName ofType:@"plist"];
230 NSData *data = [NSData dataWithContentsOfFile:path];
231 return [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:NULL];
232}
233
234
235static NSDictionary *ConfigDictionary(NSString *basePath, NSString *name)
236{
237 NSString *path = [[basePath stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:name];
238 NSData *data = [NSData oo_dataWithOXZFile:path];
239 if (data == nil)
240 {
241 path = [basePath stringByAppendingPathComponent:name];
242 data = [NSData oo_dataWithOXZFile:path];
243 }
244 if (data != nil)
245 {
246 id plist = [NSPropertyListSerialization propertyListWithData:data
247 options:0
248 format:NULL
249 error:NULL];
250 if ([plist isKindOfClass:NSDictionary.class]) return plist;
251 }
252
253 return nil;
254}
255
256
257static NSDictionary *MergeShipData(NSDictionary *shipData, NSDictionary *shipDataOverrides)
258{
259 NSDictionary *baseDict = nil;
260 NSDictionary *overrideDict = nil;
261
262 if (shipDataOverrides == nil) return shipData;
263 if (shipData == nil) return shipDataOverrides;
264
265 NSMutableDictionary *mutableShipData = [NSMutableDictionary dictionaryWithDictionary:shipData];
266 for (NSString *key in shipDataOverrides)
267 {
268 baseDict = [shipData oo_dictionaryForKey:key];
269 overrideDict = [shipDataOverrides oo_dictionaryForKey:key];
270 mutableShipData[key] = MergeShipDataEntry(baseDict, overrideDict);
271 }
272
273 return mutableShipData;
274}
275
276
277static NSDictionary *MergeShipDataEntry(NSDictionary *baseDict, NSDictionary *overrideDict)
278{
279 if (baseDict == nil) return overrideDict;
280
281 NSMutableDictionary *mutableEntry = [NSMutableDictionary dictionaryWithDictionary:baseDict];
282 [mutableEntry addEntriesFromDictionary:overrideDict];
283
284 return mutableEntry;
285}
286
287
288// Stuff lifted from messy files in Oolite
289
290static NSDictionary *OOParseRolesFromString(NSString *string)
291{
292 NSMutableDictionary *result = nil;
293 NSArray *tokens = nil;
294 NSUInteger i, count;
295 NSString *role = nil;
296 float probability;
297 NSScanner *scanner = nil;
298
299 // Split string at spaces, sanity checks, set-up.
300 if (string == nil) return nil;
301
302 tokens = ScanTokensFromString(string);
303 count = [tokens count];
304 if (count == 0) return nil;
305
306 result = [NSMutableDictionary dictionaryWithCapacity:count];
307
308 // Scan tokens, looking for probabilities.
309 for (i = 0; i != count; ++i)
310 {
311 role = [tokens objectAtIndex:i];
312
313 probability = 1.0f;
314 if ([role rangeOfString:@"("].location != NSNotFound)
315 {
316 scanner = [[NSScanner alloc] initWithString:role];
317 [scanner scanUpToString:@"(" intoString:&role];
318 [scanner scanString:@"(" intoString:NULL];
319 if (![scanner scanFloat:&probability]) probability = 1.0f;
320 // Ignore rest of string
321 }
322
323 // shipKey roles start with [ so other roles can't
324 if (0 <= probability && ![role hasPrefix:@"["])
325 {
326 [result setObject:[NSNumber numberWithFloat:probability] forKey:role];
327 }
328 }
329
330 if ([result count] == 0) result = nil;
331 return result;
332}
333
334
335static NSMutableArray *ScanTokensFromString(NSString *values)
336{
337 NSMutableArray *result = nil;
338 NSScanner *scanner = nil;
339 NSString *token = nil;
340 static NSCharacterSet *space_set = nil;
341
342 if (values == nil) return [NSMutableArray array];
343 if (space_set == nil) space_set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
344
345 result = [NSMutableArray array];
346 scanner = [NSScanner scannerWithString:values];
347
348 while (![scanner isAtEnd])
349 {
350 [scanner ooliteScanCharactersFromSet:space_set intoString:NULL];
351 if ([scanner ooliteScanUpToCharactersFromSet:space_set intoString:&token])
352 {
353 [result addObject:token];
354 }
355 }
356
357 return result;
358}
359
360
361/* Disable OOLog. The only logging in the importer at the time of writing is
362 "File not found" logging in NSDataOOExtensions, which is not an error. In
363 general, we are unlikely to want logging from the importer.
364 */
365void OOLogWithFunctionFileAndLine(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inFormat, ...)
366{
367
368}
369
370
371NSString * const kOOLogFileNotFound = @"";
static bool GetMetadataForSaveFile(void *thisInterface, NSMutableDictionary *attributes, NSString *pathToFile)
static NSDictionary * ConfigDictionary(NSString *basePath, NSString *name)
static bool GetMetadataForExpansionPack(void *thisInterface, NSMutableDictionary *attributes, NSString *pathToFile)
void OOLogWithFunctionFileAndLine(NSString *inMessageClass, const char *inFunction, const char *inFile, unsigned long inLine, NSString *inFormat,...)
static id GetBundlePropertyList(NSString *inPListName)
static NSMutableArray * ScanTokensFromString(NSString *values)
static NSDictionary * OOParseRolesFromString(NSString *string)
static NSDictionary * MergeShipDataEntry(NSDictionary *baseDict, NSDictionary *overrideDict)
NSString *const kOOLogFileNotFound
#define kAuthors
BOOL GetMetadataForFile(void *thisInterface, NSMutableDictionary *attributes, CFStringRef contentTypeUTI, NSString *pathToFile)
static NSDictionary * MergeShipData(NSDictionary *shipData, NSDictionary *shipDataOverrides)
unsigned count
return nil