diff --git a/Frameworks/RSParser/Feeds/ParsedAttachment.swift b/Frameworks/RSParser/Feeds/ParsedAttachment.swift index bb836948e..7dd008d24 100644 --- a/Frameworks/RSParser/Feeds/ParsedAttachment.swift +++ b/Frameworks/RSParser/Feeds/ParsedAttachment.swift @@ -19,6 +19,10 @@ public struct ParsedAttachment: Hashable { init?(url: String, mimeType: String?, title: String?, sizeInBytes: Int?, durationInSeconds: Int?) { + if url.isEmpty { + return nil + } + self.url = url self.mimeType = mimeType self.title = title diff --git a/Frameworks/RSParser/Feeds/XML/RSParsedArticle.h b/Frameworks/RSParser/Feeds/XML/RSParsedArticle.h index bb11f4f64..442a47510 100755 --- a/Frameworks/RSParser/Feeds/XML/RSParsedArticle.h +++ b/Frameworks/RSParser/Feeds/XML/RSParsedArticle.h @@ -8,6 +8,7 @@ @import Foundation; +@class RSParsedEnclosure; @interface RSParsedArticle : NSObject @@ -22,6 +23,7 @@ @property (nonatomic, nullable) NSString *link; @property (nonatomic, nullable) NSString *permalink; @property (nonatomic, nullable) NSString *author; +@property (nonatomic, nullable) NSSet *enclosures; @property (nonatomic, nullable) NSDate *datePublished; @property (nonatomic, nullable) NSDate *dateModified; @property (nonatomic, nonnull) NSDate *dateParsed; diff --git a/Frameworks/RSParser/Feeds/XML/RSParsedEnclosure.h b/Frameworks/RSParser/Feeds/XML/RSParsedEnclosure.h new file mode 100644 index 000000000..b8f0e4cde --- /dev/null +++ b/Frameworks/RSParser/Feeds/XML/RSParsedEnclosure.h @@ -0,0 +1,21 @@ +// +// RSParsedEnclosure.h +// RSParser +// +// Created by Brent Simmons on 12/18/17. +// Copyright © 2017 Ranchero Software, LLC. All rights reserved. +// + +@import Foundation; + +NS_ASSUME_NONNULL_BEGIN + +@interface RSParsedEnclosure : NSObject + +@property (nonatomic) NSString *url; +@property (nonatomic) NSInteger length; +@property (nonatomic, nullable) NSString *mimeType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/RSParser/Feeds/XML/RSParsedEnclosure.m b/Frameworks/RSParser/Feeds/XML/RSParsedEnclosure.m new file mode 100644 index 000000000..f6f35da59 --- /dev/null +++ b/Frameworks/RSParser/Feeds/XML/RSParsedEnclosure.m @@ -0,0 +1,13 @@ +// +// RSParsedEnclosure.m +// RSParser +// +// Created by Brent Simmons on 12/18/17. +// Copyright © 2017 Ranchero Software, LLC. All rights reserved. +// + +#import "RSParsedEnclosure.h" + +@implementation RSParsedEnclosure + +@end diff --git a/Frameworks/RSParser/Feeds/XML/RSParsedFeedTransformer.swift b/Frameworks/RSParser/Feeds/XML/RSParsedFeedTransformer.swift index d44a84875..77d6d4098 100644 --- a/Frameworks/RSParser/Feeds/XML/RSParsedFeedTransformer.swift +++ b/Frameworks/RSParser/Feeds/XML/RSParsedFeedTransformer.swift @@ -41,8 +41,9 @@ private extension RSParsedFeedTransformer { let datePublished = parsedArticle.datePublished let dateModified = parsedArticle.dateModified let authors = parsedAuthors(parsedArticle.author) + let attachments = parsedAttachments(parsedArticle.enclosures) - return ParsedItem(syncServiceID: nil, uniqueID: uniqueID, feedURL: parsedArticle.feedURL, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: nil, attachments: nil) + return ParsedItem(syncServiceID: nil, uniqueID: uniqueID, feedURL: parsedArticle.feedURL, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: nil, attachments: attachments) } static func parsedAuthors(_ authorEmailAddress: String?) -> Set? { @@ -53,4 +54,19 @@ private extension RSParsedFeedTransformer { let author = ParsedAuthor(name: nil, url: nil, avatarURL: nil, emailAddress: authorEmailAddress) return Set([author]) } + + static func parsedAttachments(_ enclosures: Set?) -> Set? { + + guard let enclosures = enclosures, !enclosures.isEmpty else { + return nil + } + + let attachments = enclosures.flatMap { (enclosure) -> ParsedAttachment? in + + let sizeInBytes = enclosure.length > 0 ? enclosure.length : nil + return ParsedAttachment(url: enclosure.url, mimeType: enclosure.mimeType, title: nil, sizeInBytes: sizeInBytes, durationInSeconds: nil) + } + + return attachments.isEmpty ? nil : Set(attachments) + } } diff --git a/Frameworks/RSParser/Feeds/XML/RSRSSParser.m b/Frameworks/RSParser/Feeds/XML/RSRSSParser.m index 369652b54..0c6c11d0d 100755 --- a/Frameworks/RSParser/Feeds/XML/RSRSSParser.m +++ b/Frameworks/RSParser/Feeds/XML/RSRSSParser.m @@ -15,7 +15,7 @@ #import #import #import - +#import @interface RSRSSParser () @@ -159,6 +159,8 @@ static const NSInteger kTrueLength = 5; static const char *kUppercaseRDF = "RDF"; static const NSInteger kUppercaseRDFLength = 4; +static const char *kEnclosure = "enclosure"; +static const NSInteger kEnclosureLength = 10; #pragma mark - Parsing @@ -232,6 +234,29 @@ static const NSInteger kUppercaseRDFLength = 4; } } +- (void)addEnclosure { + + NSDictionary *attributes = self.currentAttributes; + NSString *url = attributes[kURLKey]; + if (!url || url.length < 1) { + return; + } + + RSParsedEnclosure *enclosure = [[RSParsedEnclosure alloc] init]; + enclosure.url = url; + + NSString *lengthString = attributes[kLengthKey]; + if (lengthString) { + enclosure.length = lengthString.integerValue; + } + + enclosure.mimeType = attributes[kTypeKey]; + + // The RSS spec specifies zero or one enclosures. + // However, the Media RSS namespace allows for more than one. + // We could add support for multiple enclosures at some time in the future. + self.currentArticle.enclosures = [NSSet setWithObject:enclosure]; +} - (NSString *)urlString:(NSString *)s { @@ -304,6 +329,9 @@ static const NSInteger kUppercaseRDFLength = 4; else if (RSSAXEqualTags(localName, kTitle, kTitleLength)) { self.currentArticle.title = [self currentStringWithHTMLEntitiesDecoded]; } + else if (RSSAXEqualTags(localName, kEnclosure, kEnclosureLength)) { + [self addEnclosure]; + } } @@ -327,7 +355,7 @@ static const NSInteger kUppercaseRDFLength = 4; } NSDictionary *xmlAttributes = nil; - if ((self.isRDF && RSSAXEqualTags(localName, kItem, kItemLength)) || RSSAXEqualTags(localName, kGuid, kGuidLength)) { + if ((self.isRDF && RSSAXEqualTags(localName, kItem, kItemLength)) || RSSAXEqualTags(localName, kGuid, kGuidLength) || RSSAXEqualTags(localName, kEnclosure, kEnclosureLength)) { xmlAttributes = [self.parser attributesDictionary:attributes numberOfAttributes:numberOfAttributes]; } if (self.currentAttributes != xmlAttributes) { diff --git a/Frameworks/RSParser/RSParser.h b/Frameworks/RSParser/RSParser.h index 0fbec1c5b..9059aa5e2 100644 --- a/Frameworks/RSParser/RSParser.h +++ b/Frameworks/RSParser/RSParser.h @@ -37,6 +37,7 @@ #import #import #import +#import // HTML diff --git a/Frameworks/RSParser/RSParser.xcodeproj/project.pbxproj b/Frameworks/RSParser/RSParser.xcodeproj/project.pbxproj index 623f113bd..e62c5de61 100644 --- a/Frameworks/RSParser/RSParser.xcodeproj/project.pbxproj +++ b/Frameworks/RSParser/RSParser.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 8401FF811FE862E70080F13F /* RSParsedEnclosure.h in Headers */ = {isa = PBXBuildFile; fileRef = 8401FF7F1FE862E70080F13F /* RSParsedEnclosure.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8401FF821FE862E70080F13F /* RSParsedEnclosure.m in Sources */ = {isa = PBXBuildFile; fileRef = 8401FF801FE862E70080F13F /* RSParsedEnclosure.m */; }; + 8401FF841FE87C2F0080F13F /* theomnishow.rss in Resources */ = {isa = PBXBuildFile; fileRef = 8401FF831FE87C2E0080F13F /* theomnishow.rss */; }; 840FDCB21F01FED00041F61B /* RSSParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840FDCB11F01FED00041F61B /* RSSParserTests.swift */; }; 840FDCB41F0217410041F61B /* JSONFeedParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840FDCB31F0217410041F61B /* JSONFeedParserTests.swift */; }; 840FDCB61F0217D20041F61B /* AtomParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840FDCB51F0217D20041F61B /* AtomParserTests.swift */; }; @@ -111,6 +114,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8401FF7F1FE862E70080F13F /* RSParsedEnclosure.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RSParsedEnclosure.h; sourceTree = ""; }; + 8401FF801FE862E70080F13F /* RSParsedEnclosure.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSParsedEnclosure.m; sourceTree = ""; }; + 8401FF831FE87C2E0080F13F /* theomnishow.rss */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = theomnishow.rss; sourceTree = ""; }; 840FDCB11F01FED00041F61B /* RSSParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSSParserTests.swift; sourceTree = ""; }; 840FDCB31F0217410041F61B /* JSONFeedParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFeedParserTests.swift; sourceTree = ""; }; 840FDCB51F0217D20041F61B /* AtomParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AtomParserTests.swift; sourceTree = ""; }; @@ -343,6 +349,8 @@ 84469D221EFA3134004A6B28 /* RSParsedArticle.m */, 84469D231EFA3134004A6B28 /* RSParsedFeed.h */, 84469D241EFA3134004A6B28 /* RSParsedFeed.m */, + 8401FF7F1FE862E70080F13F /* RSParsedEnclosure.h */, + 8401FF801FE862E70080F13F /* RSParsedEnclosure.m */, ); name = XML; path = Feeds/XML; @@ -381,6 +389,7 @@ 84628AAC1FCA10AE00566A9B /* allthis.atom */, 84566D931FD0ABFB00103322 /* allthis.json */, 84566D951FD1FC1800103322 /* allthis-partial.json */, + 8401FF831FE87C2E0080F13F /* theomnishow.rss */, 849A03CF1F0081EA00122600 /* Subs.opml */, ); path = Resources; @@ -454,6 +463,7 @@ 84469CFC1EFA3069004A6B28 /* RSSAXParser.h in Headers */, 845213281FCB4042003B6E93 /* RSHTMLTag.h in Headers */, 84E7E69F1F85780D0046719D /* ParserData.h in Headers */, + 8401FF811FE862E70080F13F /* RSParsedEnclosure.h in Headers */, 84469D071EFA307E004A6B28 /* RSHTMLLinkParser.h in Headers */, 84469D0D1EFA307E004A6B28 /* RSSAXHTMLParser.h in Headers */, 84469D2B1EFA3134004A6B28 /* RSParsedArticle.h in Headers */, @@ -561,6 +571,7 @@ 840FDCBA1F02186D0041F61B /* DaringFireball.json in Resources */, 849A03D21F0081EA00122600 /* EMarley.rss in Resources */, 849A03DA1F0081EA00122600 /* Subs.opml in Resources */, + 8401FF841FE87C2F0080F13F /* theomnishow.rss in Resources */, 849A03D61F0081EA00122600 /* manton.rss in Resources */, 849A03D11F0081EA00122600 /* DaringFireball.rss in Resources */, 84B19A771FDA438300458981 /* natasha.xml in Resources */, @@ -613,6 +624,7 @@ 84285AAA1F006456002E8708 /* RSParsedFeedTransformer.swift in Sources */, 84469D2E1EFA3134004A6B28 /* RSParsedFeed.m in Sources */, 84469CF81EFA3000004A6B28 /* RSOPMLParser.m in Sources */, + 8401FF821FE862E70080F13F /* RSParsedEnclosure.m in Sources */, 84469D401EFF29A9004A6B28 /* FeedParserError.swift in Sources */, 84469D321EFA31CF004A6B28 /* FeedParser.swift in Sources */, 84469D281EFA3134004A6B28 /* RSAtomParser.m in Sources */, diff --git a/Frameworks/RSParser/RSParserTests/RSSParserTests.swift b/Frameworks/RSParser/RSParserTests/RSSParserTests.swift index 42ba76cf5..497f3e425 100644 --- a/Frameworks/RSParser/RSParserTests/RSSParserTests.swift +++ b/Frameworks/RSParser/RSParserTests/RSSParserTests.swift @@ -54,4 +54,20 @@ class RSSParserTests: XCTestCase { XCTAssertEqual(parsedFeed.items.count, 10) } + func testTheOmniShowAttachments() { + + let d = parserData("theomnishow", "rss", "https://theomnishow.omnigroup.com/") + let parsedFeed = try! FeedParser.parse(d)! + + for article in parsedFeed.items { + XCTAssertNotNil(article.attachments) + XCTAssertEqual(article.attachments!.count, 1) + let attachment = Array(article.attachments!).first! + XCTAssertNotNil(attachment.mimeType) + XCTAssertNotNil(attachment.sizeInBytes) + XCTAssert(attachment.url.contains("cloudfront")) + XCTAssertGreaterThanOrEqual(attachment.sizeInBytes!, 22275279) + XCTAssertEqual(attachment.mimeType, "audio/mpeg") + } + } } diff --git a/Frameworks/RSParser/RSParserTests/Resources/theomnishow.rss b/Frameworks/RSParser/RSParserTests/Resources/theomnishow.rss new file mode 100644 index 000000000..a44b978db --- /dev/null +++ b/Frameworks/RSParser/RSParserTests/Resources/theomnishow.rss @@ -0,0 +1,135 @@ +The Omni Showhttps://theomnishow.omnigroup.com/en-us℗ & © 2017 The Omni GroupGet to know the people and stories behind Omni’s award-winning productivity apps for Mac and iOS.The Omni GroupGet to know the people and stories behind Omni’s award-winning productivity apps for Mac and iOS.Get to know the people and stories behind Omni’s award-winning productivity apps for Mac and iOS.The Omni Grouptheomnishow@omnigroup.comnoAndrea McVittie, User Experience DesignerThe Omni Grouphttps://theomnishow.omnigroup.com/episode/andrea-mcvittie-user-experience-designer/Andrea studied fine art at <a href="https://msu.edu/">Michigan State</a> and human-computer interaction at the <a href="https://www.umich.edu/">University of Michigan</a>. One day, while working at the school library, it was discovered that she could make websites — which launched her career as a UX designer, and since 2012 she’s been designing apps at Omni.https://theomnishow.omnigroup.com/episode/andrea-mcvittie-user-experience-designer/Wed, 06 Dec 2017 14:00:00 +000028:49Full transcript and show notes at <a href='https://theomnishow.omnigroup.com/episode/andrea-mcvittie-user-experience-designer/'>theomnishow.omnigroup.com</a> + +<p>Andrea studied fine art at <a href="https://msu.edu/">Michigan State</a> and human-computer interaction at the <a href="https://www.umich.edu/">University of Michigan</a>. One day, while working at the school library, it was discovered that she could make websites — which launched her career as a UX designer, and since 2012 she’s been designing apps at Omni.</p> + +<p>Andrea, it must be told, is firmly against stealing money from puppies — or maybe she’s against treating puppies as a particularly cuddly form of currency and then stealing <em>them</em>.</p> +<p>Nobody knows which. Don’t do either!</p> +<p>You can find Andrea <a href="https://twitter.com/amcvittie">@amcvittie</a> on Twitter. And you can find her work in every single Omni app.</p> +<p>Some other people, places, and things mentioned:</p> +<ul> +<li><a href="https://www.nngroup.com/articles/definition-user-experience/">User Experience (UX)</a></li> +<li><a href="https://usabilitygeek.com/ethics-in-user-experience-design/">User Experience and Ethics</a></li> +<li><a href="https://twitter.com/monteiro">Mike Monteiro</a></li> +<li><a href="https://www.omnigroup.com/blog/omnifocus-now-supports-end-to-end-encryption">OmniFocus Sync Encryption</a></li> +<li><a href="https://twitter.com/timothyekl">Tim Ekl</a></li> +<li><a href="https://www.omnigroup.com/omnigraffle">OmniGraffle</a></li> +<li><a href="https://www.apple.com/apple-pencil/">Apple Pencil</a></li> +<li><a href="https://www.omnigroup.com/blog/omni-roadmap-2017-q4-update">OmniFocus Tags</a></li> +<li><a href="http://vesperapp.co/">Vesper</a></li> +<li><a href="https://theomnishow.omnigroup.com/episode/curt-clifton-omnifocus-engineer">Curt Clifton</a></li> +<li><a href="https://twitter.com/jimcorreia">Jim Correia</a></li> +<li><a href="https://darkpatterns.org/">darkpatterns.org</a></li> +<li><a href="http://www.seattleartmuseum.org/">Seattle Art Museum</a></li> +<li><a href="https://twitter.com/OmniGroup/status/921132286527004672">Omni Slack Group</a></li> +<li><a href="http://www.rosemaryorchard.com/en">Rosemary Orchard</a></li> +<li><a href="https://support.omnigroup.com/">Omni Support</a></li> +<li><a href="https://twitter.com/KelStewart84/status/928377769561546752">Listener question from Twitter</a></li> +<li><a href="https://lib.msu.edu/">Michigan State Library</a></li> +<li><a href="https://twitter.com/Photoshop">Adobe Photoshop</a></li> +<li><a href="http://annarborchronicle.com/2012/05/03/in-the-archives-poison-pages/">Shadows from the Walls of Death</a></li> +<li><a href="https://www.si.umich.edu/">University of Michigan School of Information</a></li> +<li><a href="https://www.instagram.com/p/Baztg5FF7wJ/">Oliver, who’s a good dog</a></li> +</ul>noBrian Covey, Support ManagerThe Omni Grouphttps://theomnishow.omnigroup.com/episode/brian-covey-support-manager/Brian Covey studied computer science at <a href="http://www.evergreen.edu/">The Evergreen State College</a> — which your host also attended, but back before computers were a thing. After some normal young-person-bouncing-around a little, Brian came to shore at Omni in 2001, where he became the leader, dear and fearless, of Omni’s Support Humans.https://theomnishow.omnigroup.com/episode/brian-covey-support-manager/Wed, 22 Nov 2017 14:00:00 +000029:32Full transcript and show notes at <a href='https://theomnishow.omnigroup.com/episode/brian-covey-support-manager/'>theomnishow.omnigroup.com</a> + +<p>Brian Covey studied computer science at <a href="http://www.evergreen.edu/">The Evergreen State College</a> — which your host also attended, but back before computers were a thing. After some normal young-person-bouncing-around a little, Brian came to shore at Omni in 2001, where he became the leader, dear and fearless, of Omni’s Support Humans.</p> + +<p>You can find Brian <a href="https://twitter.com/hidvorak">@hidvorak</a> on Twitter. (You can contact Omni support at <a href="https://support.omnigroup.com/">support.omnigroup.com</a>.)</p> +<p>You can also find a <a href="https://www.omnigroup.com/blog/Yes_-_The_Omni_Video">video of Brian doing the worm</a> in an Omni commercial from 2006. Absolutely not to be missed!</p> +<p>Some other people, places, and things mentioned:</p> +<ul> +<li><a href="https://cherof.bandcamp.com/">The Music of Aaron Cherof</a> (Aaron works at Omni and does the music for this show)</li> +<li><a href="http://elevatesummit.co/">Elevate Support conference</a></li> +<li><a href="https://twitter.com/kcase">Ken Case</a></li> +<li><a href="https://twitter.com/chuckdude">Chuck Toporek</a></li> +<li><a href="https://www.omnigroup.com/more">OmniWeb</a></li> +<li><a href="https://www2.gov.bc.ca/">British Columbia</a></li> +<li><a href="https://en.wikipedia.org/wiki/Ninja">Ninjas</a></li> +<li><a href="http://starwars.wikia.com/wiki/Droid">Droids</a></li> +<li><a href="https://twitter.com/curtclifton">Curt Clifton</a></li> +<li><a href="https://en.wikipedia.org/wiki/Pair_programming">Pair programming</a></li> +<li><a href="https://www.omnigroup.com/omniplan">OmniPlan</a></li> +<li><a href="https://en.wikipedia.org/wiki/Babylon_5">Babylon 5</a></li> +<li><a href="https://www.seattle.gov/visiting-seattle">Seattle</a></li> +<li><a href="http://www.netzero.net/">NetZero</a></li> +<li><a href="https://en.wikipedia.org/wiki/MacOS">Mac OS X</a></li> +<li><a href="https://en.wikipedia.org/wiki/Boomerang">Boomerangs</a></li> +<li><a href="http://www.nirvana.com/">Nirvana</a></li> +<li><a href="https://www.youtube.com/watch?v=o_hngjZUnOw">K Dorm</a></li> +<li><a href="https://www.mopop.org/">Experience Music Project</a> (now MoPOP)</li> +<li><a href="https://en.wikipedia.org/wiki/Dave_Grohl">Dave Grohl</a></li> +<li><a href="https://en.wikipedia.org/wiki/Nomeansno">Nomeansno</a></li> +<li><a href="https://en.wikipedia.org/wiki/Operant_conditioning_chamber">Skinner Box</a></li> +<li><a href="https://en.wikipedia.org/wiki/The_Mythical_Man-Month">Mythical Man Month</a></li> +<li><a href="https://en.wikipedia.org/wiki/Spokane,_Washington">Spokane</a></li> +<li><a href="https://en.wikipedia.org/wiki/Anton%C3%ADn_Dvořák">Antonín Dvořák</a></li> +<li><a href="https://en.wikipedia.org/wiki/Dvorak_Simplified_Keyboard">Dvorak keyboard</a></li> +<li><a href="https://en.wikipedia.org/wiki/Repetitive_strain_injury">Repetitive Strain Injury (RSI)</a></li> +</ul>noCurt Clifton, OmniFocus EngineerThe Omni Grouphttps://theomnishow.omnigroup.com/episode/curt-clifton-omnifocus-engineer/Curt Clifton came to Omni while on sabbatical from his computer science professorship at <a href="http://www.rose-hulman.edu/">Rose-Hulman</a> — and never went back. He’s spent much of his six years at Omni on the <a href="https://www.omnigroup.com/omnifocus">OmniFocus</a> team, where he clicks checkboxes for a living.https://theomnishow.omnigroup.com/episode/curt-clifton-omnifocus-engineer/Mon, 06 Nov 2017 17:00:00 +000028:47Full transcript and show notes at <a href='https://theomnishow.omnigroup.com/episode/curt-clifton-omnifocus-engineer/'>theomnishow.omnigroup.com</a> + +<p>Curt Clifton came to Omni while on sabbatical from his computer science professorship at <a href="http://www.rose-hulman.edu/">Rose-Hulman</a> — and never went back. He’s spent much of his six years at Omni on the <a href="https://www.omnigroup.com/omnifocus">OmniFocus</a> team, where he clicks checkboxes for a living.</p> + +<p>Curt’s a fan of the local <a href="https://www.soundersfc.com/">association football team</a>. He also likes woodworking and distance-running, though he has yet to explore the synergistic possibilities of doing both at the same time.</p> +<p>You can find Curt <a href="https://twitter.com/curtclifton">@curtclifton</a> on Twitter and at <a href="http://curtclifton.net/">curtclifton.net</a>.</p> +<p>Some other people, places, and things mentioned:</p> +<ul> +<li><a href="https://developer.apple.com/ios/drag-and-drop/">iOS 11 drag and drop</a></li> +<li><a href="https://www.omnigroup.com/blog/omni-roadmap-2017-q4-update">OmniFocus 3: tags</a></li> +<li><a href="https://support.apple.com/en-us/HT206481">Files app</a></li> +<li><a href="https://developer.apple.com/wwdc/">World Wide Developer’s Conference (WWDC) (aka Dub Dub)</a></li> +<li><a href="https://en.wikipedia.org/wiki/Lake_Union">Lake Union</a></li> +<li><a href="https://arstechnica.com/gadgets/2013/09/ios-7-thoroughly-reviewed/">iOS 7</a></li> +<li><a href="https://www.apple.com/iphone-x/">The notch</a></li> +<li><a href="https://macosxautomation.com/applescript/">AppleScript</a></li> +<li><a href="https://www.omnigroup.com/omnioutliner">OmniOutliner</a></li> +<li><a href="http://ethanschoonover.com/">Ethan Schoonover</a></li> +<li><a href="http://shawnblanc.net/2014/03/history-of-omnifocus-for-mac/">Kinkless GTD</a></li> +<li><a href="http://gettingthingsdone.com/">Getting Things Done, by David Allen</a></li> +<li><a href="https://en.wikipedia.org/wiki/Indiana">Indiana</a></li> +<li><a href="https://discourse.omnigroup.com/">Omni Forums</a></li> +<li><a href="https://twitter.com/kcase">Ken Case</a></li> +<li><a href="https://twitter.com/tjw">Tim Wood</a></li> +<li><a href="https://en.wikipedia.org/wiki/Amish">The Amish</a></li> +<li><a href="https://twitter.com/vanillaice">Vanilla Ice</a></li> +<li><a href="http://www.imdb.com/title/tt0090555/">Crocodile Dundee</a></li> +<li><a href="https://en.wikipedia.org/wiki/Pennsylvania_Dutch_Country">Pennsylvania Dutch Country</a></li> +<li><a href="http://www.goodnplenty.com">Good ’N Plenty Restaurant</a></li> +<li><a href="https://en.wikipedia.org/wiki/Amana_Colonies">Amana Colonies</a></li> +<li><a href="https://twitter.com/timothyekl">Tim Ekl</a></li> +<li><a href="https://twitter.com/jimcorreia">Jim Correia</a></li> +<li><a href="https://www.pomona.edu/">Pomona College</a></li> +<li><a href="https://www.hmc.edu/">Harvey Mudd College</a></li> +<li><a href="https://twitter.com/reid_callan">Reid Callan</a></li> +<li><a href="https://twitter.com/tbunch">Tom Bunch</a></li> +<li><a href="https://www.mlb.com/cubs">Cubs</a></li> +<li><a href="https://www.mlssoccer.com/">Major League Soccer Cup</a></li> +<li><a href="http://www.baa.org/races/boston-marathon.aspx">Boston Marathon</a></li> +<li><a href="https://en.wikipedia.org/wiki/Battle_of_Marathon">Marathon</a></li> +<li><a href="https://support.omnigroup.com/">Support Humans</a></li> +</ul>noKristina Sontag, Software Test ManagerThe Omni Grouphttps://theomnishow.omnigroup.com/episode/kristina-sontag-software-test-manager/For our first episode we interview Kristina Sontag, who runs Omni’s test department — a group tireless, fearless, and strikingly effective in its hunt for bugs.https://theomnishow.omnigroup.com/episode/kristina-sontag-software-test-manager/Wed, 01 Nov 2017 16:00:00 +000026:18Full transcript and show notes at <a href='https://theomnishow.omnigroup.com/episode/kristina-sontag-software-test-manager/'>theomnishow.omnigroup.com</a> + +<p>For our first episode we interview Kristina Sontag, who runs Omni’s test department — a group tireless, fearless, and strikingly effective in its hunt for bugs.</p> + +<p>Kristina got her start playing games for a living at <a href="http://lucasfilm.com/games">LucasArts</a>. Between there and today she’s also worked on <a href="https://web.archive.org/web/19970105135152/http://www.edmark.com/">educational software</a>, gotten a pilot’s license, volunteered for <a href="http://appcamp4girls.com/">App Camp for Girls</a>, and helped organize the <a href="https://www.weareecs.com/">Emerald City Supporters</a>.</p> +<p>When she’s not doing everything else, she’s playing <a href="https://www.destinythegame.com/">Destiny</a>. She may even be thinking about Destiny at this very moment.</p> +<p>You can find Kristina on Twitter <a href="https://twitter.com/knsontag">@knsontag</a>.</p> +<p>Some other people and places mentioned:</p> +<ul> +<li><a href="https://developer.apple.com/wwdc/">WWDC</a></li> +<li><a href="https://twitter.com/macgenie">Jean McDonald</a></li> +<li><a href="https://twitter.com/emarley">Liz Marley</a></li> +<li><a href="https://twitter.com/uipoptart">Grey Osten</a></li> +<li><a href="https://itunes.apple.com/us/developer/app-camp-for-girls/id942207153">App Camp App</a></li> +<li><a href="https://en.wikipedia.org/wiki/Santa_Rosa,_California">Santa Rosa, CA</a></li> +<li><a href="http://www.ilm.com/">Industrial Light &amp; Magic (ILM)</a></li> +<li><a href="http://lucasfilm.com/skywalker-ranch-marin">Skywalker Ranch</a></li> +<li><a href="https://en.wikipedia.org/wiki/George_Lucas">George Lucas</a></li> +<li><a href="https://en.wikipedia.org/wiki/Ron_Gilbert">Ron Gilbert</a></li> +<li><a href="http://humongous.com/">Humongous Entertainment</a></li> +<li><a href="https://www.omnigroup.com/omnifocus">OmniFocus for iPhone</a></li> +<li><a href="https://twitter.com/hidvorak">Brian Covey</a></li> +<li><a href="https://twitter.com/kcase">Ken Case</a></li> +<li><a href="https://en.wikipedia.org/wiki/Floatplane">Float plane</a></li> +<li><a href="https://www.kenmoreair.com/">Kenmore Air</a></li> +<li><a href="https://www.soundersfc.com/">Seattle Sounders</a></li> +<li><a href="https://www.mlssoccer.com/">Major League Soccer (MLS)</a></li> +</ul>no \ No newline at end of file