diff --git a/Ample.xcodeproj/project.pbxproj b/Ample.xcodeproj/project.pbxproj index ddf026a..6ab6857 100644 --- a/Ample.xcodeproj/project.pbxproj +++ b/Ample.xcodeproj/project.pbxproj @@ -131,6 +131,8 @@ B66236A924FD9A34006CABD7 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = B66236A824FD9A34006CABD7 /* PreferencesWindowController.m */; }; B66236B524FDA527006CABD7 /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B66236B224FDA522006CABD7 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B66236C124FDB7A6006CABD7 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = B66236BF24FDB7A6006CABD7 /* Credits.rtf */; }; + B66D0FE72611386C000902F1 /* SoftwareList.m in Sources */ = {isa = PBXBuildFile; fileRef = B66D0FE62611386B000902F1 /* SoftwareList.m */; }; + B66D0FE82611386C000902F1 /* SoftwareList.m in Sources */ = {isa = PBXBuildFile; fileRef = B66D0FE62611386B000902F1 /* SoftwareList.m */; }; B6841BD7251EC926006A5C39 /* vmnet_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = B6841BCA251EC88E006A5C39 /* vmnet_helper.c */; }; B6841BDA251ECB1C006A5C39 /* mame64 in CopyFiles */ = {isa = PBXBuildFile; fileRef = B66236B824FDA698006CABD7 /* mame64 */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; B6841BDE251ECC29006A5C39 /* vmnet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6841BDD251ECC29006A5C39 /* vmnet.framework */; }; @@ -418,6 +420,8 @@ B66236B224FDA522006CABD7 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = embedded/SDL2.framework; sourceTree = ""; }; B66236B824FDA698006CABD7 /* mame64 */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = mame64; path = embedded/mame64; sourceTree = ""; }; B66236C024FDB7A6006CABD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = ""; }; + B66D0FE62611386B000902F1 /* SoftwareList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoftwareList.m; sourceTree = ""; }; + B66D0FE926113AA8000902F1 /* SoftwareList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SoftwareList.h; sourceTree = ""; }; B67BD48424EE249D0073E334 /* apple1.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = apple1.plist; sourceTree = ""; }; B6841BCA251EC88E006A5C39 /* vmnet_helper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = vmnet_helper.c; sourceTree = ""; }; B6841BD0251EC913006A5C39 /* vmnet_helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = vmnet_helper; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -657,12 +661,12 @@ B6BA257E24E99BE9005FB8FF /* AppDelegate.h */, B6BA257F24E99BE9005FB8FF /* AppDelegate.m */, B63C1B8924FF4B7100511A71 /* Ample.h */, - B6152B5825F5B4F100605E6E /* Media.h */, B63C1B8A24FF4BF700511A71 /* Ample.m */, B64AF1F4250ED5E400A09B9B /* TableCellView.h */, B64AF1F5250ED5E400A09B9B /* TableCellView.m */, B6152B5425F4549F00605E6E /* Slot.h */, B6152B5525F4549F00605E6E /* Slot.m */, + B6152B5825F5B4F100605E6E /* Media.h */, B6152B5925F5B57E00605E6E /* Media.m */, B6E9A17E25088B1B005E7525 /* NewSlotViewController.h */, B6E9A17F25088B1B005E7525 /* NewSlotViewController.m */, @@ -678,6 +682,8 @@ B60A6E1224EE0AE2004B7EEF /* FlippedView.h */, B608E17D2502FE0C00D53465 /* TransparentScroller.h */, B608E17E2502FE0C00D53465 /* TransparentScroller.m */, + B66D0FE62611386B000902F1 /* SoftwareList.m */, + B66D0FE926113AA8000902F1 /* SoftwareList.h */, B6BA563A251685DA00B0C47D /* Window Controllers */, B6B9EA652506A5550080E70D /* EjectButton.h */, B6B9EA642506A5550080E70D /* EjectButton.m */, @@ -1140,6 +1146,7 @@ B63C1F0F25B1447C0016A611 /* CheatSheetWindowController.m in Sources */, B64AF1F2250ECB2E00A09B9B /* DiskImagesWindowController.m in Sources */, B64AF1F6250ED5E400A09B9B /* TableCellView.m in Sources */, + B66D0FE72611386C000902F1 /* SoftwareList.m in Sources */, B63C1B9425008A2700511A71 /* DownloadWindowController.m in Sources */, B64AF1FA250EF6A500A09B9B /* Transformers.m in Sources */, ); @@ -1165,6 +1172,7 @@ B64AF1F7250ED5E400A09B9B /* TableCellView.m in Sources */, B6E4B5B424FDE2670094A35C /* FlippedView.m in Sources */, B6E4B5B524FDE2670094A35C /* AppDelegate.m in Sources */, + B66D0FE82611386C000902F1 /* SoftwareList.m in Sources */, B6E4B5B624FDE2670094A35C /* LogWindowController.m in Sources */, B6E4B5B724FDE2670094A35C /* PreferencesWindowController.m in Sources */, B63C1B9525008A2700511A71 /* DownloadWindowController.m in Sources */, diff --git a/Ample/Ample.m b/Ample/Ample.m index 63db253..2bb3560 100644 --- a/Ample/Ample.m +++ b/Ample/Ample.m @@ -111,15 +111,15 @@ NSDictionary *MameMachine(NSString *machine) { /* NSCache doesn't retain it's key. This essentially interns it. */ /* could just abuse NSSelectorFromString() */ NSString *InternString(NSString *key) { - static NSMutableDictionary *storage = nil; + static NSMutableSet *storage = nil; if (!storage) { - storage = [NSMutableDictionary new]; + storage = [NSMutableSet new]; } - NSString *copy = [storage objectForKey: key]; + NSString *copy = [storage member: key]; if (!copy) { copy = [key copy]; - [storage setObject: copy forKey: copy]; + [storage addObject: copy]; } return copy; } diff --git a/Ample/SoftwareList.h b/Ample/SoftwareList.h new file mode 100644 index 0000000..770af68 --- /dev/null +++ b/Ample/SoftwareList.h @@ -0,0 +1,29 @@ +// +// SoftwareList.h +// Ample +// +// Created by Kelvin Sherlock on 3/28/2021. +// Copyright © 2021 Kelvin Sherlock. All rights reserved. +// + +#ifndef SoftwareList_h +#define SoftwareList_h + +#import + +@interface SoftwareList : NSObject +@property NSString *name; +@property NSString *title; +@property NSArray *items; +@end + +@interface Software : NSObject +@property NSString *name; +@property NSString *title; +@end + + +NSArray *SoftwareListForMachine(NSString *machine); + + +#endif /* SoftwareList_h */ diff --git a/Ample/SoftwareList.m b/Ample/SoftwareList.m new file mode 100644 index 0000000..85cb3fc --- /dev/null +++ b/Ample/SoftwareList.m @@ -0,0 +1,204 @@ +// +// SoftwareList.m +// Ample +// +// Created by Kelvin Sherlock on 1/30/2021. +// Copyright © 2021 Kelvin Sherlock. All rights reserved. +// + +#import +#include "Ample.h" + +#import "SoftwareList.h" + +@implementation Software +@end + +@implementation SoftwareList +@end + + + +@interface SoftwareListDelegate : NSObject { + unsigned _state; + NSString *_name; + NSString *_description; + NSMutableArray *_array; + SoftwareList *_list; +} + +-(SoftwareList *)list; +@end + + +@implementation SoftwareListDelegate + +-(SoftwareList *)list;{ + return _list; +} + +-(void)parserDidStartDocument:(NSXMLParser *)parser { + _array = [NSMutableArray new]; + _list = [SoftwareList new]; + + _state = 0; + +} + +/* + The parts we care about: + + + + ... + + ... + + */ + +-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { + if ([@"softwarelist" isEqualToString: elementName]) { + if (_state == 0b0000) { + _state = 0b0001; + + NSString *name = [attributeDict objectForKey: @"name"]; + NSString *description = [attributeDict objectForKey: @"description"]; + if (!description) description = name; + [_list setTitle: description]; + [_list setName: name]; + } + return; + } + if ([@"software" isEqualToString: elementName]) { + if (_state == 0b0001) { + _name = [attributeDict objectForKey: @"name"]; + _state |= 0b0010; + } + return; + } + if ([@"description" isEqualToString: elementName]) { + if (_state == 0b0011) { + _state |= 0b0100; + } + return; + } +} + +-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + + if ([@"softwarelist" isEqualToString: elementName]) { + if (_state == 0b0001) { + _state = 0b0000; + } + + [_array sortUsingComparator: ^NSComparisonResult(id a, id b){ + NSString *aa = [(Software *)a title]; + NSString *bb = [(Software *)b title]; + return [aa compare: bb]; + }]; + + [_list setItems: _array]; + _array = nil; + return; + } + if ([@"software" isEqualToString: elementName]) { + if (_state == 0b0011) { + _state &= ~0b0010; + + if (_name) { + if (!_description) _description = _name; + Software *s = [Software new]; + [s setTitle: _description]; + [s setName: _name]; + [_array addObject: s]; + } + _name = nil; + _description = nil; + } + return; + } + if ([@"description" isEqualToString: elementName]) { + if (_state == 0b0111) { + _state &= ~0b0100; + } + return; + } +} + +-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (_state == 0b0111) { + if (_description) _description = [_description stringByAppendingString: string]; + else _description = string; + } +} + + +@end + + +static SoftwareList *LoadSoftwareList(NSURL *url, NSError **error) { + + NSXMLParser *p = [[NSXMLParser alloc] initWithContentsOfURL: url]; + SoftwareListDelegate *d = [SoftwareListDelegate new]; + + [p setDelegate: d]; + + BOOL ok = [p parse]; + if (!ok) { + if (error) *error = [p parserError]; + return nil; + } + return [d list]; + +} + + +NSArray *SoftwareListForMachine(NSString *machine) { + + static NSCache *cache; + + if (!cache) + cache = [NSCache new]; + + machine = InternString(machine); + NSArray *a = [cache objectForKey: machine]; + if (a) return a; + + NSBundle *bundle = [NSBundle mainBundle]; + NSURL *url= [bundle URLForResource: machine withExtension: @"plist"]; + + NSDictionary *d = [NSDictionary dictionaryWithContentsOfURL: url]; + if (!d) return nil; + NSArray *list = [d objectForKey: @"software"]; + + NSMutableArray *tmp = [NSMutableArray new]; + for (NSString *xml in list) { + SoftwareList *sw; + NSURL *url = SupportDirectory(); + url = [url URLByAppendingPathComponent: @"hash"]; + url = [url URLByAppendingPathComponent: xml]; + + + NSError *error = nil; + sw = LoadSoftwareList(url, &error); + if (error) { + NSLog(@"SoftwareListForMachine: %@ %@: %@", machine, xml, error); + continue; + } + + [tmp addObject: sw]; + } + +#if 0 + [tmp sortUsingComparator: ^NSComparisonResult(id a, id b){ + NSString *aa = [(Software *)a title]; + NSString *bb = [(Software *)b title]; + return [aa compare: bb]; + + }]; +#endif + + [cache setObject: tmp forKey: machine]; + return tmp; + +}